fixed indents
[libfirm] / ir / stat / stat_dmp.c
1 /*
2  * Project:     libFIRM
3  * File name:   ir/ir/stat_dmp.c
4  * Purpose:     Statistics for Firm.
5  * Author:      Michael Beck
6  * Created:
7  * CVS-ID:      $Id$
8  * Copyright:   (c) 2004 Universität Karlsruhe
9  * Licence:     This file protected by GPL -  GNU GENERAL PUBLIC LICENSE.
10  */
11 #ifdef HAVE_CONFIG_H
12 # include "config.h"
13 #endif
14
15 #include "stat_dmp.h"
16 #include "irhooks.h"
17
18 /**
19  * names of the optimizations
20  */
21 static const struct {
22   hook_opt_kind kind;
23   const char    *name;
24 } opt_names[] = {
25   { HOOK_OPT_DEAD_BLOCK,   "dead block elimination" },
26   { HOOK_OPT_STG,          "straightening optimization" },
27   { HOOK_OPT_IFSIM,        "if simplification" },
28   { HOOK_OPT_CONST_EVAL,   "constant evaluation" },
29   { HOOK_OPT_ALGSIM,       "algebraic simplification" },
30   { HOOK_OPT_PHI,          "Phi optmization" },
31   { HOOK_OPT_SYNC,         "Sync optmization" },
32   { HOOK_OPT_WAW,          "Write-After-Write optimization" },
33   { HOOK_OPT_WAR,          "Write-After-Read optimization" },
34   { HOOK_OPT_RAW,          "Read-After-Write optimization" },
35   { HOOK_OPT_RAR,          "Read-After-Read optimization" },
36   { HOOK_OPT_RC,           "Read-a-Const optimization" },
37   { HOOK_OPT_TUPLE,        "Tuple optimization" },
38   { HOOK_OPT_ID,           "ID optimization" },
39   { HOOK_OPT_CSE,          "Common subexpression elimination" },
40   { HOOK_OPT_STRENGTH_RED, "Strength reduction" },
41   { HOOK_OPT_ARCH_DEP,     "Architecture dependant optimization" },
42   { HOOK_OPT_REASSOC,      "Reassociation optimization" },
43   { HOOK_OPT_POLY_CALL,    "Polymorphic call optimization" },
44   { HOOK_OPT_IF_CONV,      "an if conversion was tried" },
45   { HOOK_OPT_FUNC_CALL,    "Real function call optimization" },
46   { HOOK_OPT_CONFIRM,      "Confirm-based optimization: replacement" },
47   { HOOK_OPT_CONFIRM_C,    "Confirm-based optimization: replaced by const" },
48   { HOOK_OPT_CONFIRM_E,    "Confirm-based optimization: evaluated" },
49   { HOOK_OPT_EXC_REM,      "a exception edge was removed due to a Confirmation prove" },
50   { HOOK_LOWERED,          "Lowered" },
51   { HOOK_BACKEND,          "Backend transformation" },
52   { FS_OPT_NEUTRAL_0,      "algebraic simplification: a op 0 = 0 op a = a" },
53   { FS_OPT_NEUTRAL_1,      "algebraic simplification: a op 1 = 1 op a = a" },
54   { FS_OPT_ADD_A_A,        "algebraic simplification: a + a = a * 2" },
55   { FS_OPT_ADD_A_MINUS_B,  "algebraic simplification: a + -b = a - b" },
56   { FS_OPT_ADD_SUB,        "algebraic simplification: (a + x) - x = (a - x) + x = a" },
57   { FS_OPT_ADD_MUL_A_X_A,  "algebraic simplification: a * x + a = a * (x + 1)" },
58   { FS_OPT_SUB_0_A,        "algebraic simplification: 0 - a = -a" },
59   { FS_OPT_SUB_MUL_A_X_A,  "algebraic simplification: a * x - a = a * (x - 1)" },
60   { FS_OPT_MUL_MINUS_1,    "algebraic simplification: a * -1 = -a" },
61   { FS_OPT_OR,             "algebraic simplification: a | a = a | 0 = 0 | a = a" },
62   { FS_OPT_AND,            "algebraic simplification: a & 0b1...1 = 0b1...1 & a =  a & a = a" },
63   { FS_OPT_EOR_A_A,        "algebraic simplification: a ^ a = 0" },
64   { FS_OPT_EOR_TO_NOT_BOOL,"algebraic simplification: bool ^ 1 = !bool" },
65   { FS_OPT_EOR_TO_NOT,     "algebraic simplification: x ^ 0b1..1 = ~x" },
66   { FS_OPT_NOT_CMP,        "algebraic simplification: !(a cmp b) = a !cmp b" },
67   { FS_OPT_OR_SHFT_TO_ROT, "algebraic simplification: (x << c) | (x >> (bits - c)) == Rot(x, c)" },
68   { FS_OPT_REASSOC_SHIFT,  "algebraic simplification: (x SHF c1) SHF c2 = x SHF (c1+c2)" },
69   { FS_OPT_CONV,           "algebraic simplification: Conv could be removed" },
70   { FS_OPT_CAST,           "algebraic simplification: a Cast could be removed" },
71   { FS_OPT_MIN_MAX_EQ,     "algebraic simplification: Min(a,a) = Max(a,a) = a" },
72   { FS_OPT_MUX_C,          "algebraic simplification: Mux(C, f, t) = C ? t : f" },
73   { FS_OPT_MUX_EQ,         "algebraic simplification: Mux(v, x, x) = x" },
74   { FS_OPT_MUX_TRANSFORM,  "algebraic simplification: Mux(a, b, c) = b OR Mux(a,b, c) = c" },
75   { FS_OPT_MUX_TO_MIN,     "algebraic simplification: Mux(a < b, a, b) = Min(a,b)" },
76   { FS_OPT_MUX_TO_MAX,     "algebraic simplification: Mux(a > b, a, b) = Max(a,b)" },
77   { FS_OPT_MUX_TO_ABS,     "algebraic simplification: Mux(a > b, a, b) = Abs(a,b)" },
78   { FS_OPT_MUX_TO_SHR,     "algebraic simplification: Mux(a > b, a, b) = a >> b" },
79   { FS_BE_IA32_LEA,        "ia32 Backend transformation: Lea was created" },
80   { FS_BE_IA32_LOAD_LEA,   "ia32 Backend transformation: Load merged with a Lea" },
81   { FS_BE_IA32_STORE_LEA,  "ia32 Backend transformation: Store merged with a Lea" },
82   { FS_BE_IA32_AM_S,       "ia32 Backend transformation: Source address mode node created" },
83   { FS_BE_IA32_AM_D,       "ia32 Backend transformation: Destination address mode node created" },
84   { FS_BE_IA32_CJMP,       "ia32 Backend transformation: CJmp created to save a cmp/test" },
85   { FS_BE_IA32_2ADDRCPY,   "ia32 Backend transformation: Copy created due to 2-Addresscode constraints" },
86   { FS_BE_IA32_SPILL2ST,   "ia32 Backend transformation: Created Store for a Spill" },
87   { FS_BE_IA32_RELOAD2LD,  "ia32 Backend transformation: Created Load for a Reload" },
88   { FS_BE_IA32_SUB2NEGADD, "ia32 Backend transformation: Created Neg-Add for a Sub due to 2-Addresscode constraints" },
89   { FS_BE_IA32_LEA2ADD,    "ia32 Backend transformation: Transformed Lea back into Add" },
90 };
91
92 static const char *if_conv_names[IF_RESULT_LAST] = {
93         "if conv done             ",
94         "if conv side effect      ",
95         "if conv Phi node found   ",
96         "if conv to deep DAG's    ",
97         "if conv bad control flow ",
98         "if conv denied by arch   ",
99 };
100
101 /**
102  * dumps a opcode hash into human readable form
103  */
104 static void simple_dump_opcode_hash(dumper_t *dmp, pset *set)
105 {
106         node_entry_t *entry;
107         counter_t f_alive;
108         counter_t f_new_node;
109         counter_t f_Id;
110
111         cnt_clr(&f_alive);
112         cnt_clr(&f_new_node);
113         cnt_clr(&f_Id);
114
115         fprintf(dmp->f, "%-16s %-8s %-8s %-8s\n", "Opcode", "alive", "created", "->Id");
116         for (entry = pset_first(set); entry; entry = pset_next(set)) {
117                 fprintf(dmp->f, "%-16s %8u %8u %8u\n",
118                         get_id_str(entry->op->name), entry->cnt_alive.cnt[0], entry->new_node.cnt[0], entry->into_Id.cnt[0]);
119
120                 cnt_add(&f_alive,    &entry->cnt_alive);
121                 cnt_add(&f_new_node, &entry->new_node);
122                 cnt_add(&f_Id,       &entry->into_Id);
123         }
124         fprintf(dmp->f, "-------------------------------------------\n");
125         fprintf(dmp->f, "%-16s %8u %8u %8u\n", "Sum",
126                 f_alive.cnt[0],
127                 f_new_node.cnt[0],
128                 f_Id.cnt[0]);
129 }
130
131 /**
132  * dumps an optimization hash into human readable form
133  */
134 static void simple_dump_opt_hash(dumper_t *dmp, pset *set, int index)
135 {
136         opt_entry_t *entry = pset_first(set);
137
138         assert(index < ARR_SIZE(opt_names) && "index out of range");
139         assert(opt_names[index].kind == index && "opt_names broken");
140         if (entry) {
141                 fprintf(dmp->f, "\n%s:\n", opt_names[index].name);
142                 fprintf(dmp->f, "%-16s %-8s\n", "Opcode", "deref");
143
144                 for (; entry; entry = pset_next(set)) {
145                         fprintf(dmp->f, "%-16s %8u\n",
146                                 get_id_str(entry->op->name), entry->count.cnt[0]);
147                 }
148         }
149 }
150
151 /**
152  * dumps the register pressure for each block and for each register class
153  */
154 static void simple_dump_be_block_reg_pressure(dumper_t *dmp, graph_entry_t *entry) {
155         be_block_entry_t     *b_entry = pset_first(entry->be_block_hash);
156         reg_pressure_entry_t *rp_entry;
157
158         /* return if no reg pressure information available */
159         if (! b_entry)
160                 return;
161
162         fprintf(dmp->f, "\nREG PRESSURE:\n");
163         fprintf(dmp->f, "%12s", "Block Nr");
164
165         /* print table head (register class names) */
166         for (rp_entry = pset_first(b_entry->reg_pressure);
167              rp_entry;
168              rp_entry = pset_next(b_entry->reg_pressure))
169         {
170                 fprintf(dmp->f, "%15s", get_id_str(rp_entry->id_name));
171         }
172         fprintf(dmp->f, "\n");
173
174         /* print the reg pressure for all blocks and register classes */
175         for (/* b_entry is already initialized */ ;
176          b_entry;
177              b_entry = pset_next(entry->be_block_hash))
178         {
179                 fprintf(dmp->f, "BLK   %6ld", b_entry->block_nr);
180
181                 for (rp_entry = pset_first(b_entry->reg_pressure);
182                      rp_entry;
183                      rp_entry = pset_next(b_entry->reg_pressure))
184                 {
185                         fprintf(dmp->f, "%15d", rp_entry->pressure);
186                 }
187                 fprintf(dmp->f, "\n");
188         }
189 }
190
191 /** prints a distribution entry */
192 void dump_block_sched_ready_distrib(const distrib_entry_t *entry, void *env) {
193         FILE *dmp_f = env;
194         fprintf(dmp_f, "%12d", entry->cnt.cnt[0]);
195 }
196
197 /**
198  * dumps the distribution of the amount of ready nodes for each block
199  */
200 static void simple_dump_be_block_sched_ready(dumper_t *dmp, graph_entry_t *entry) {
201         be_block_entry_t *b_entry = pset_first(entry->be_block_hash);
202         counter_t         cnt_0;
203         int               i;
204
205         /* return if no reg pressure information available */
206         if (! b_entry)
207                 return;
208
209         cnt_clr(&cnt_0);
210
211         fprintf(dmp->f, "\nSCHEDULING: NUMBER OF READY NODES\n");
212         fprintf(dmp->f, "%12s %12s %12s %12s %12s %12s %12s\n",
213                 "Block Nr", "1 node", "2 nodes", "3 nodes", "4 nodes", "5 or more", "AVERAGE");
214
215         for (/* b_entry is already initialized */ ;
216              b_entry;
217              b_entry = pset_next(entry->be_block_hash))
218         {
219                 /* this ensures that all keys from 1 to 5 are in the table*/
220                 for (i = 1; i < 6; i++)
221                         stat_add_int_distrib_tbl(b_entry->sched_ready, i, &cnt_0);
222
223                 fprintf(dmp->f, "BLK   %6ld", b_entry->block_nr);
224                 stat_iterate_distrib_tbl(b_entry->sched_ready, dump_block_sched_ready_distrib, dmp->f);
225                 fprintf(dmp->f, "%12.2lf", stat_calc_avg_distrib_tbl(b_entry->sched_ready));
226                 fprintf(dmp->f, "\n");
227         }
228 }
229
230 /**
231  * dumps the number of real_function_call optimization
232  */
233 static void simple_dump_real_func_calls(dumper_t *dmp, counter_t *cnt)
234 {
235         if (! dmp->f)
236                 return;
237
238         if (! cnt_eq(cnt, 0)) {
239                 fprintf(dmp->f, "\nReal Function Calls optimized:\n");
240                 fprintf(dmp->f, "%-16s %8u\n", "Call", cnt->cnt[0]);
241         }
242 }
243
244 /**
245  * dumps the number of tail_recursion optimization
246  */
247 static void simple_dump_tail_recursion(dumper_t *dmp, unsigned num_tail_recursion)
248 {
249         if (! dmp->f)
250                 return;
251
252         if (num_tail_recursion > 0) {
253                 fprintf(dmp->f, "\nTail recursion optimized:\n");
254                 fprintf(dmp->f, "%-16s %8u\n", "Call", num_tail_recursion);
255         }
256 }
257
258 /**
259  * dumps the edges count
260  */
261 static void simple_dump_edges(dumper_t *dmp, counter_t *cnt)
262 {
263         if (! dmp->f)
264                 return;
265
266         fprintf(dmp->f, "%-16s %8d\n", "Edges", cnt->cnt[0]);
267 }
268
269 /**
270  * dumps the IRG
271  */
272 static void simple_dump_graph(dumper_t *dmp, graph_entry_t *entry)
273 {
274         int i, dump_opts = 1;
275         block_entry_t *b_entry;
276         extbb_entry_t *eb_entry;
277
278         if (! dmp->f)
279                 return;
280
281         if (entry->irg) {
282                 ir_graph *const_irg = get_const_code_irg();
283
284                 if (entry->irg == const_irg) {
285                         fprintf(dmp->f, "\nConst code Irg %p", (void *)entry->irg);
286                 }
287                 else {
288                         if (entry->ent)
289                                 fprintf(dmp->f, "\nEntity %s, Irg %p", get_entity_ld_name(entry->ent), (void *)entry->irg);
290                         else
291                                 fprintf(dmp->f, "\nIrg %p", (void *)entry->irg);
292                 }
293
294                 fprintf(dmp->f, " %swalked %u over blocks %u:\n"
295                         " was inlined               : %u\n"
296                         " got inlined               : %u\n"
297                         " strength red              : %u\n"
298                         " leaf function             : %s\n"
299                         " calls only leaf functions : %s\n"
300                         " recursive                 : %s\n"
301                         " chain call                : %s\n"
302                         " calls                     : %u\n"
303                         " indirect calls            : %u\n",
304                         entry->is_deleted ? "DELETED " : "",
305                         entry->cnt_walked.cnt[0], entry->cnt_walked_blocks.cnt[0],
306                         entry->cnt_was_inlined.cnt[0],
307                         entry->cnt_got_inlined.cnt[0],
308                         entry->cnt_strength_red.cnt[0],
309                         entry->is_leaf ? "YES" : "NO",
310                         entry->is_leaf_call == LCS_NON_LEAF_CALL ? "NO" : (entry->is_leaf_call == LCS_LEAF_CALL ? "Yes" : "Maybe"),
311                         entry->is_recursive ? "YES" : "NO",
312                         entry->is_chain_call ? "YES" : "NO",
313                         entry->cnt_all_calls.cnt[0],
314                         entry->cnt_indirect_calls.cnt[0]
315                 );
316
317                 for (i = 0; i < sizeof(entry->cnt_if_conv)/sizeof(entry->cnt_if_conv[0]); ++i) {
318                         fprintf(dmp->f, " %s : %u\n", if_conv_names[i], entry->cnt_if_conv[i].cnt[0]);
319                 }
320
321         }
322         else {
323                 fprintf(dmp->f, "\nGlobals counts:\n");
324                 fprintf(dmp->f, "--------------\n");
325                 dump_opts = 0;
326         }
327
328         simple_dump_opcode_hash(dmp, entry->opcode_hash);
329         simple_dump_edges(dmp, &entry->cnt_edges);
330
331         /* effects of optimizations */
332         if (dump_opts) {
333                 int i;
334
335                 simple_dump_real_func_calls(dmp, &entry->cnt_real_func_call);
336                 simple_dump_tail_recursion(dmp, entry->num_tail_recursion);
337
338                 for (i = 0; i < sizeof(entry->opt_hash)/sizeof(entry->opt_hash[0]); ++i) {
339                         simple_dump_opt_hash(dmp, entry->opt_hash[i], i);
340                 }
341
342                 /* dump block info */
343                 fprintf(dmp->f, "\n%12s %12s %12s %12s %12s %12s %12s\n", "Block Nr", "Nodes", "intern E", "incoming E", "outgoing E", "Phi", "quot");
344                 for (b_entry = pset_first(entry->block_hash);
345                      b_entry;
346                      b_entry = pset_next(entry->block_hash))
347                 {
348                         fprintf(dmp->f, "BLK   %6ld %12u %12u %12u %12u %12u %4.8f\n",
349                                 b_entry->block_nr,
350                                 b_entry->cnt_nodes.cnt[0],
351                                 b_entry->cnt_edges.cnt[0],
352                                 b_entry->cnt_in_edges.cnt[0],
353                                 b_entry->cnt_out_edges.cnt[0],
354                                 b_entry->cnt_phi_data.cnt[0],
355                                 (double)b_entry->cnt_edges.cnt[0] / (double)b_entry->cnt_nodes.cnt[0]
356                         );
357                 }
358
359                 /* dump block reg pressure */
360                 simple_dump_be_block_reg_pressure(dmp, entry);
361
362                 /* dump block ready nodes distribution */
363                 simple_dump_be_block_sched_ready(dmp, entry);
364
365                 if (dmp->status->stat_options & FIRMSTAT_COUNT_EXTBB) {
366                         /* dump extended block info */
367                         fprintf(dmp->f, "\n%12s %12s %12s %12s %12s %12s %12s\n", "Extbb Nr", "Nodes", "intern E", "incoming E", "outgoing E", "Phi", "quot");
368                         for (eb_entry = pset_first(entry->extbb_hash);
369                              eb_entry;
370                              eb_entry = pset_next(entry->extbb_hash))
371                         {
372                                 fprintf(dmp->f, "ExtBB %6ld %12u %12u %12u %12u %12u %4.8f\n",
373                                         eb_entry->block_nr,
374                                         eb_entry->cnt_nodes.cnt[0],
375                                         eb_entry->cnt_edges.cnt[0],
376                                         eb_entry->cnt_in_edges.cnt[0],
377                                         eb_entry->cnt_out_edges.cnt[0],
378                                         eb_entry->cnt_phi_data.cnt[0],
379                                         (double)eb_entry->cnt_edges.cnt[0] / (double)eb_entry->cnt_nodes.cnt[0]
380                                 );
381                         }
382                 }
383         }
384 }
385
386 /**
387  * dumps the IRG
388  */
389 static void simple_dump_const_tbl(dumper_t *dmp, const constant_info_t *tbl)
390 {
391         int i;
392         counter_t sum;
393
394         if (! dmp->f)
395                 return;
396
397         cnt_clr(&sum);
398
399         fprintf(dmp->f, "\nConstant Information:\n");
400         fprintf(dmp->f, "---------------------\n");
401
402         fprintf(dmp->f, "\nBit usage for integer constants\n");
403         fprintf(dmp->f, "-------------------------------\n");
404
405         for (i = 0; i < ARR_SIZE(tbl->int_bits_count); ++i) {
406                 fprintf(dmp->f, "%5d %12u\n", i + 1, tbl->int_bits_count[i].cnt[0]);
407                 cnt_add(&sum, &tbl->int_bits_count[i]);
408         }
409         fprintf(dmp->f, "-------------------------------\n");
410
411         fprintf(dmp->f, "\nFloating point constants classification\n");
412         fprintf(dmp->f, "--------------------------------------\n");
413         for (i = 0; i < ARR_SIZE(tbl->floats); ++i) {
414                 fprintf(dmp->f, "%-10s %12u\n", stat_fc_name(i), tbl->floats[i].cnt[0]);
415                 cnt_add(&sum, &tbl->floats[i]);
416         }
417         fprintf(dmp->f, "--------------------------------------\n");
418
419         fprintf(dmp->f, "other %12u\n", tbl->others.cnt[0]);
420         cnt_add(&sum, &tbl->others);
421         fprintf(dmp->f, "-------------------------------\n");
422
423         fprintf(dmp->f, "sum   %12u\n", sum.cnt[0]);
424 }
425
426 /**
427  * initialize the simple dumper
428  */
429 static void simple_init(dumper_t *dmp, const char *name)
430 {
431         char fname[2048];
432
433         snprintf(fname, sizeof(fname), "%s.txt", name);
434         dmp->f = fopen(fname, "w");
435         if (! dmp->f) {
436                 perror(fname);
437         }
438 }
439
440 /**
441  * finishes the simple dumper
442  */
443 static void simple_finish(dumper_t *dmp)
444 {
445         if (dmp->f)
446                 fclose(dmp->f);
447         dmp->f = NULL;
448 }
449
450 /**
451  * the simple human readable dumper
452  */
453 const dumper_t simple_dumper = {
454         simple_dump_graph,
455         simple_dump_const_tbl,
456         simple_init,
457         simple_finish,
458         NULL,
459         NULL,
460         NULL,
461 };
462
463 /* ---------------------------------------------------------------------- */
464
465 /**
466  * count the nodes as needed:
467  *
468  * 1 normal (data) Phi's
469  * 2 memory Phi's
470  * 3 Proj
471  * 0 all other nodes
472  */
473 static void csv_count_nodes(dumper_t *dmp, graph_entry_t *graph, counter_t cnt[])
474 {
475         node_entry_t *entry;
476         int i;
477
478         for (i = 0; i < 4; ++i)
479                 cnt_clr(&cnt[i]);
480
481         for (entry = pset_first(graph->opcode_hash); entry; entry = pset_next(graph->opcode_hash)) {
482                 if (entry->op == op_Phi) {
483                         /* normal Phi */
484                         cnt_add(&cnt[1], &entry->cnt_alive);
485                 }
486                 else if (entry->op == dmp->status->op_PhiM) {
487                         /* memory Phi */
488                         cnt_add(&cnt[2], &entry->cnt_alive);
489                 }
490                 else if (entry->op == op_Proj) {
491                         /* Proj */
492                         cnt_add(&cnt[3], &entry->cnt_alive);
493                 }
494                 else {
495                         /* all other nodes */
496                         cnt_add(&cnt[0], &entry->cnt_alive);
497                 }
498         }
499 }
500
501 /**
502  * dumps the IRG
503  */
504 static void csv_dump_graph(dumper_t *dmp, graph_entry_t *entry)
505 {
506         const char *name;
507         counter_t cnt[4];
508
509         if (! dmp->f)
510                 return;
511
512         if (entry->irg && !entry->is_deleted) {
513                 ir_graph *const_irg = get_const_code_irg();
514
515                 if (entry->irg == const_irg) {
516                         name = "<Const code Irg>";
517                         return;
518                 }
519                 else {
520                         if (entry->ent)
521                                 name = get_entity_name(entry->ent);
522                         else
523                                 name = "<UNKNOWN IRG>";
524                 }
525
526                 csv_count_nodes(dmp, entry, cnt);
527
528                 fprintf(dmp->f, "%-40s, %p, %d, %d, %d, %d\n",
529                         name,
530                         (void *)entry->irg,
531                         cnt[0].cnt[0],
532                         cnt[1].cnt[0],
533                         cnt[2].cnt[0],
534                         cnt[3].cnt[0]
535                 );
536         }
537 }
538
539 /**
540  * dumps the IRG
541  */
542 static void csv_dump_const_tbl(dumper_t *dmp, const constant_info_t *tbl)
543 {
544         /* FIXME: NYI */
545 }
546
547 /**
548  * initialize the simple dumper
549  */
550 static void csv_init(dumper_t *dmp, const char *name)
551 {
552         char fname[2048];
553
554         snprintf(fname, sizeof(fname), "%s.csv", name);
555         dmp->f = fopen(fname, "a");
556         if (! dmp->f) {
557                 perror(fname);
558         }
559 }
560
561 /**
562  * finishes the simple dumper
563  */
564 static void csv_finish(dumper_t *dmp)
565 {
566         if (dmp->f)
567                 fclose(dmp->f);
568         dmp->f = NULL;
569 }
570
571 /**
572  * the simple human readable dumper
573  */
574 const dumper_t csv_dumper = {
575         csv_dump_graph,
576         csv_dump_const_tbl,
577         csv_init,
578         csv_finish,
579         NULL,
580         NULL,
581         NULL,
582 };