c5d804a62baa96cf3c2f265360b4ea699a56fcb7
[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   /* print table head (register classes) */
163   fprintf(dmp->f, "\nREG PRESSURE:\n");
164   fprintf(dmp->f, "%12s", "Block Nr");
165   for (rp_entry = pset_first(b_entry->reg_pressure);
166        rp_entry;
167            rp_entry = pset_next(b_entry->reg_pressure))
168   {
169     fprintf(dmp->f, "%15s", get_id_str(rp_entry->id_name));
170   }
171   fprintf(dmp->f, "\n");
172
173   /* print the reg pressure for all blocks and register classes */
174   for (/* b_entry is already initialized */ ;
175        b_entry;
176            b_entry = pset_next(entry->be_block_hash))
177   {
178         fprintf(dmp->f, "BLK   %6ld", b_entry->block_nr);
179         for (rp_entry = pset_first(b_entry->reg_pressure);
180              rp_entry;
181                  rp_entry = pset_next(b_entry->reg_pressure))
182         {
183       fprintf(dmp->f, "%15d", rp_entry->pressure);
184         }
185     fprintf(dmp->f, "\n");
186   }
187 }
188
189 /** prints a distribution entry */
190 void dump_block_sched_ready_distrib(const distrib_entry_t *entry, void *env) {
191   FILE *dmp_f = env;
192   fprintf(dmp_f, "%12d", entry->cnt.cnt[0]);
193 }
194
195 /**
196  * dumps the distribution of the amount of ready nodes for each block
197  */
198 static void simple_dump_be_block_sched_ready(dumper_t *dmp, graph_entry_t *entry) {
199   be_block_entry_t *b_entry = pset_first(entry->be_block_hash);
200   counter_t         cnt_0;
201   int               i;
202
203   /* return if no reg pressure information available */
204   if (! b_entry)
205           return;
206
207   cnt_clr(&cnt_0);
208
209   fprintf(dmp->f, "\nSCHEDULING: NUMBER OF READY NODES\n");
210   fprintf(dmp->f, "%12s %12s %12s %12s %12s %12s %12s\n", "Block Nr", "1 node", "2 nodes", "3 nodes", "4 nodes", "5 or more", "mean value");
211   for (/* b_entry is already initialized */ ;
212        b_entry;
213            b_entry = pset_next(entry->be_block_hash))
214   {
215     /* this ensures that all keys from 1 to 5 are in the table*/
216         for (i = 1; i < 6; i++)
217             stat_add_int_distrib_tbl(b_entry->sched_ready, i, &cnt_0);
218
219     fprintf(dmp->f, "BLK   %6ld", b_entry->block_nr);
220     stat_iterate_distrib_tbl(b_entry->sched_ready, dump_block_sched_ready_distrib, dmp->f);
221     fprintf(dmp->f, "%12.2lf", stat_calc_mean_distrib_tbl(b_entry->sched_ready));
222         fprintf(dmp->f, "\n");
223   }
224 }
225
226 /**
227  * dumps the number of real_function_call optimization
228  */
229 static void simple_dump_real_func_calls(dumper_t *dmp, counter_t *cnt)
230 {
231   if (! dmp->f)
232     return;
233
234   if (! cnt_eq(cnt, 0)) {
235     fprintf(dmp->f, "\nReal Function Calls optimized:\n");
236     fprintf(dmp->f, "%-16s %8u\n",
237       "Call", cnt->cnt[0]);
238   }
239 }
240
241 /**
242  * dumps the number of tail_recursion optimization
243  */
244 static void simple_dump_tail_recursion(dumper_t *dmp, unsigned num_tail_recursion)
245 {
246   if (! dmp->f)
247     return;
248
249   if (num_tail_recursion > 0) {
250     fprintf(dmp->f, "\nTail recursion optimized:\n");
251     fprintf(dmp->f, "%-16s %8u\n", "Call", num_tail_recursion);
252   }
253 }
254
255 /**
256  * dumps the edges count
257  */
258 static void simple_dump_edges(dumper_t *dmp, counter_t *cnt)
259 {
260   if (! dmp->f)
261     return;
262
263   fprintf(dmp->f, "%-16s %8d\n", "Edges", cnt->cnt[0]);
264 }
265
266 /**
267  * dumps the IRG
268  */
269 static void simple_dump_graph(dumper_t *dmp, graph_entry_t *entry)
270 {
271   int i, dump_opts = 1;
272   block_entry_t *b_entry;
273   extbb_entry_t *eb_entry;
274
275   if (! dmp->f)
276     return;
277
278   if (entry->irg) {
279     ir_graph *const_irg = get_const_code_irg();
280
281     if (entry->irg == const_irg) {
282       fprintf(dmp->f, "\nConst code Irg %p", (void *)entry->irg);
283     }
284     else {
285       if (entry->ent)
286         fprintf(dmp->f, "\nEntity %s, Irg %p", get_entity_ld_name(entry->ent), (void *)entry->irg);
287       else
288         fprintf(dmp->f, "\nIrg %p", (void *)entry->irg);
289     }
290
291     fprintf(dmp->f, " %swalked %u over blocks %u:\n"
292                 " was inlined               : %u\n"
293                 " got inlined               : %u\n"
294                 " strength red              : %u\n"
295                 " leaf function             : %s\n"
296                 " calls only leaf functions : %s\n"
297                 " recursive                 : %s\n"
298                 " chain call                : %s\n"
299         " calls                     : %u\n"
300         " indirect calls            : %u\n",
301         entry->is_deleted ? "DELETED " : "",
302         entry->cnt_walked.cnt[0], entry->cnt_walked_blocks.cnt[0],
303         entry->cnt_was_inlined.cnt[0],
304         entry->cnt_got_inlined.cnt[0],
305                 entry->cnt_strength_red.cnt[0],
306                 entry->is_leaf ? "YES" : "NO",
307                 entry->is_leaf_call == LCS_NON_LEAF_CALL ? "NO" : (entry->is_leaf_call == LCS_LEAF_CALL ? "Yes" : "Maybe"),
308                 entry->is_recursive ? "YES" : "NO",
309                 entry->is_chain_call ? "YES" : "NO",
310         entry->cnt_all_calls.cnt[0],
311         entry->cnt_indirect_calls.cnt[0]
312         );
313
314     for (i = 0; i < sizeof(entry->cnt_if_conv)/sizeof(entry->cnt_if_conv[0]); ++i) {
315       fprintf(dmp->f, " %s : %u\n", if_conv_names[i], entry->cnt_if_conv[i].cnt[0]);
316     }
317
318   }
319   else {
320     fprintf(dmp->f, "\nGlobals counts:\n");
321     fprintf(dmp->f, "--------------\n");
322     dump_opts = 0;
323   }
324
325   simple_dump_opcode_hash(dmp, entry->opcode_hash);
326   simple_dump_edges(dmp, &entry->cnt_edges);
327
328   /* effects of optimizations */
329   if (dump_opts) {
330     int i;
331
332     simple_dump_real_func_calls(dmp, &entry->cnt_real_func_call);
333     simple_dump_tail_recursion(dmp, entry->num_tail_recursion);
334
335     for (i = 0; i < sizeof(entry->opt_hash)/sizeof(entry->opt_hash[0]); ++i) {
336       simple_dump_opt_hash(dmp, entry->opt_hash[i], i);
337     }
338
339     /* dump block info */
340     fprintf(dmp->f, "\n%12s %12s %12s %12s %12s %12s %12s\n", "Block Nr", "Nodes", "intern E", "incoming E", "outgoing E", "Phi", "quot");
341     for (b_entry = pset_first(entry->block_hash);
342                b_entry;
343                b_entry = pset_next(entry->block_hash)) {
344       fprintf(dmp->f, "BLK   %6ld %12u %12u %12u %12u %12u %4.8f\n",
345               b_entry->block_nr,
346               b_entry->cnt_nodes.cnt[0],
347               b_entry->cnt_edges.cnt[0],
348               b_entry->cnt_in_edges.cnt[0],
349               b_entry->cnt_out_edges.cnt[0],
350         b_entry->cnt_phi_data.cnt[0],
351               (double)b_entry->cnt_edges.cnt[0] / (double)b_entry->cnt_nodes.cnt[0]
352       );
353     }
354
355     /* dump block reg pressure */
356         simple_dump_be_block_reg_pressure(dmp, entry);
357
358     /* dump block ready nodes distribution */
359         simple_dump_be_block_sched_ready(dmp, entry);
360
361     if (dmp->status->stat_options & FIRMSTAT_COUNT_EXTBB) {
362       /* dump extended block info */
363       fprintf(dmp->f, "\n%12s %12s %12s %12s %12s %12s %12s\n", "Extbb Nr", "Nodes", "intern E", "incoming E", "outgoing E", "Phi", "quot");
364       for (eb_entry = pset_first(entry->extbb_hash);
365            eb_entry;
366            eb_entry = pset_next(entry->extbb_hash)) {
367         fprintf(dmp->f, "ExtBB %6ld %12u %12u %12u %12u %12u %4.8f\n",
368           eb_entry->block_nr,
369           eb_entry->cnt_nodes.cnt[0],
370           eb_entry->cnt_edges.cnt[0],
371           eb_entry->cnt_in_edges.cnt[0],
372           eb_entry->cnt_out_edges.cnt[0],
373           eb_entry->cnt_phi_data.cnt[0],
374           (double)eb_entry->cnt_edges.cnt[0] / (double)eb_entry->cnt_nodes.cnt[0]
375         );
376       }
377     }
378   }
379 }
380
381 /**
382  * dumps the IRG
383  */
384 static void simple_dump_const_tbl(dumper_t *dmp, const constant_info_t *tbl)
385 {
386   int i;
387   counter_t sum;
388
389   if (! dmp->f)
390     return;
391
392   cnt_clr(&sum);
393
394   fprintf(dmp->f, "\nConstant Information:\n");
395   fprintf(dmp->f, "---------------------\n");
396
397   fprintf(dmp->f, "\nBit usage for integer constants\n");
398   fprintf(dmp->f, "-------------------------------\n");
399
400   for (i = 0; i < ARR_SIZE(tbl->int_bits_count); ++i) {
401     fprintf(dmp->f, "%5d %12u\n", i + 1, tbl->int_bits_count[i].cnt[0]);
402     cnt_add(&sum, &tbl->int_bits_count[i]);
403   }
404   fprintf(dmp->f, "-------------------------------\n");
405
406   fprintf(dmp->f, "\nFloating point constants classification\n");
407   fprintf(dmp->f, "--------------------------------------\n");
408   for (i = 0; i < ARR_SIZE(tbl->floats); ++i) {
409     fprintf(dmp->f, "%-10s %12u\n", stat_fc_name(i), tbl->floats[i].cnt[0]);
410     cnt_add(&sum, &tbl->floats[i]);
411   }
412   fprintf(dmp->f, "--------------------------------------\n");
413
414   fprintf(dmp->f, "other %12u\n", tbl->others.cnt[0]);
415   cnt_add(&sum, &tbl->others);
416   fprintf(dmp->f, "-------------------------------\n");
417
418   fprintf(dmp->f, "sum   %12u\n", sum.cnt[0]);
419 }
420
421 /**
422  * initialize the simple dumper
423  */
424 static void simple_init(dumper_t *dmp, const char *name)
425 {
426   char fname[2048];
427
428   snprintf(fname, sizeof(fname), "%s.txt", name);
429   dmp->f = fopen(fname, "w");
430   if (! dmp->f) {
431     perror(fname);
432   }
433 }
434
435 /**
436  * finishes the simple dumper
437  */
438 static void simple_finish(dumper_t *dmp)
439 {
440   if (dmp->f)
441     fclose(dmp->f);
442   dmp->f = NULL;
443 }
444
445 /**
446  * the simple human readable dumper
447  */
448 const dumper_t simple_dumper = {
449   simple_dump_graph,
450   simple_dump_const_tbl,
451   simple_init,
452   simple_finish,
453   NULL,
454   NULL,
455   NULL,
456 };
457
458 /* ---------------------------------------------------------------------- */
459
460 /**
461  * count the nodes as needed:
462  *
463  * 1 normal (data) Phi's
464  * 2 memory Phi's
465  * 3 Proj
466  * 0 all other nodes
467  */
468 static void csv_count_nodes(dumper_t *dmp, graph_entry_t *graph, counter_t cnt[])
469 {
470   node_entry_t *entry;
471   int i;
472
473   for (i = 0; i < 4; ++i)
474     cnt_clr(&cnt[i]);
475
476   for (entry = pset_first(graph->opcode_hash); entry; entry = pset_next(graph->opcode_hash)) {
477     if (entry->op == op_Phi) {
478       /* normal Phi */
479       cnt_add(&cnt[1], &entry->cnt_alive);
480     }
481     else if (entry->op == dmp->status->op_PhiM) {
482       /* memory Phi */
483       cnt_add(&cnt[2], &entry->cnt_alive);
484     }
485     else if (entry->op == op_Proj) {
486       /* Proj */
487       cnt_add(&cnt[3], &entry->cnt_alive);
488     }
489     else {
490       /* all other nodes */
491       cnt_add(&cnt[0], &entry->cnt_alive);
492     }
493   }
494 }
495
496 /**
497  * dumps the IRG
498  */
499 static void csv_dump_graph(dumper_t *dmp, graph_entry_t *entry)
500 {
501   const char *name;
502   counter_t cnt[4];
503
504   if (! dmp->f)
505     return;
506
507   if (entry->irg && !entry->is_deleted) {
508     ir_graph *const_irg = get_const_code_irg();
509
510     if (entry->irg == const_irg) {
511       name = "<Const code Irg>";
512       return;
513     }
514     else {
515       if (entry->ent)
516         name = get_entity_name(entry->ent);
517       else
518         name = "<UNKNOWN IRG>";
519     }
520
521     csv_count_nodes(dmp, entry, cnt);
522
523     fprintf(dmp->f, "%-40s, %p, %d, %d, %d, %d\n",
524         name,
525         (void *)entry->irg,
526         cnt[0].cnt[0],
527         cnt[1].cnt[0],
528         cnt[2].cnt[0],
529         cnt[3].cnt[0]
530     );
531   }
532 }
533
534 /**
535  * dumps the IRG
536  */
537 static void csv_dump_const_tbl(dumper_t *dmp, const constant_info_t *tbl)
538 {
539   /* FIXME: NYI */
540 }
541
542 /**
543  * initialize the simple dumper
544  */
545 static void csv_init(dumper_t *dmp, const char *name)
546 {
547   char fname[2048];
548
549   snprintf(fname, sizeof(fname), "%s.csv", name);
550   dmp->f = fopen(fname, "a");
551   if (! dmp->f) {
552     perror(fname);
553   }
554 }
555
556 /**
557  * finishes the simple dumper
558  */
559 static void csv_finish(dumper_t *dmp)
560 {
561   if (dmp->f)
562     fclose(dmp->f);
563   dmp->f = NULL;
564 }
565
566 /**
567  * the simple human readable dumper
568  */
569 const dumper_t csv_dumper = {
570   csv_dump_graph,
571   csv_dump_const_tbl,
572   csv_init,
573   csv_finish,
574   NULL,
575   NULL,
576   NULL,
577 };