From 616a52efb570691ed835c25bc4e276238943043f Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Fri, 21 Jan 2022 16:19:04 +0900 Subject: [PATCH] [qfcc] Implement flow analysis for Ruamoko calls Thanks to the use/def/kill lists attached to statements for pseudo-ops, it turned out to be a lot easier to implement flow analysis (and thus dags processing) than I expected. I suspect I should go back and make the old call code use them too, and probably several other places, as that will greatly simplify the edge setting. --- tools/qfcc/include/statements.h | 6 +++--- tools/qfcc/source/dags.c | 14 +++++++++++--- tools/qfcc/source/flow.c | 7 ++++++- tools/qfcc/source/statements.c | 15 +++++++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/tools/qfcc/include/statements.h b/tools/qfcc/include/statements.h index 622d7c377..3bee3de33 100644 --- a/tools/qfcc/include/statements.h +++ b/tools/qfcc/include/statements.h @@ -113,9 +113,9 @@ typedef struct statement_s { operand_t *opc; struct expr_s *expr; ///< source expression for this statement int number; ///< number of this statement in function - operand_t *use; ///< list of pseudo operands used - operand_t *def; ///< list of pseudo operands defined - operand_t *kill; ///< list of pseudo operands killed + operand_t *use; ///< list of auxiliary operands used + operand_t *def; ///< list of auxiliary operands defined + operand_t *kill; ///< list of auxiliary operands killed } statement_t; typedef struct sblock_s { diff --git a/tools/qfcc/source/dags.c b/tools/qfcc/source/dags.c index d4a7b4fbe..a128422cc 100644 --- a/tools/qfcc/source/dags.c +++ b/tools/qfcc/source/dags.c @@ -406,7 +406,7 @@ dag_find_node (def_t *def, void *_daglabel) } static void -dagnode_set_edges (dag_t *dag, dagnode_t *n) +dagnode_set_edges (dag_t *dag, dagnode_t *n, statement_t *s) { int i; @@ -447,13 +447,21 @@ dagnode_set_edges (dag_t *dag, dagnode_t *n) set_add (n->edges, child->number); } } + for (operand_t *use = s->use; use; use = use->next) { + if (use->op_type == op_pseudo) { + continue; + } + daglabel_t *label = operand_label (dag, use); + label->live = 1; + set_add (n->edges, label->dagnode->number); + } if (n->type == st_func) { const char *num_params = 0; int first_param = 0; flowvar_t **flowvars = dag->flownode->graph->func->vars; if (!strcmp (n->label->opcode, "call")) { - internal_error (0, "not implemented"); + // nothing to do } else if (!strncmp (n->label->opcode, "rcall", 5)) { num_params = n->label->opcode + 6; first_param = 2; @@ -864,7 +872,7 @@ dag_create (flownode_t *flownode) n->type = s->type; n->label = op; dagnode_add_children (dag, n, operands, children); - dagnode_set_edges (dag, n); + dagnode_set_edges (dag, n, s); dagnode_set_reachable (dag, n); } } diff --git a/tools/qfcc/source/flow.c b/tools/qfcc/source/flow.c index 8bcd4bbeb..8db692667 100644 --- a/tools/qfcc/source/flow.c +++ b/tools/qfcc/source/flow.c @@ -1278,7 +1278,12 @@ flow_analyze_statement (statement_t *s, set_t *use, set_t *def, set_t *kill, } } if (strcmp (s->opcode, "call") == 0) { - internal_error (s->expr, "not implemented"); + // call uses opc to specify the destination of the return value + // parameter usage is taken care of by the statement's use + // list + flow_add_op_var (def, s->opc, 0); + // don't want old argument processing + calln = -1; } else if (strncmp (s->opcode, "call", 4) == 0) { start = 0; calln = s->opcode[5] - '0'; diff --git a/tools/qfcc/source/statements.c b/tools/qfcc/source/statements.c index fc5823395..b47dc4a50 100644 --- a/tools/qfcc/source/statements.c +++ b/tools/qfcc/source/statements.c @@ -1091,6 +1091,8 @@ expr_call (sblock_t *sblock, expr_t *call, operand_t **op) defspace_t *arg_space = current_func->arguments; expr_t *func = call->e.branch.target; expr_t *args = call->e.branch.args; + operand_t *use = 0; + operand_t *kill = 0; defspace_reset (arg_space); @@ -1112,6 +1114,17 @@ expr_call (sblock_t *sblock, expr_t *call, operand_t **op) expr_t *assign = assign_expr (new_def_expr (def), a); expr_file_line (assign, call); sblock = statement_slist (sblock, assign); + + // The call both uses and kills the arguments: use is obvious, but kill + // is because the callee has direct access to them and might modify + // them + // need two ops for the one def because there's two lists + operand_t *u = def_operand (def, arg_type, call); + operand_t *k = def_operand (def, arg_type, call); + u->next = use; + use = u; + k->next = kill; + kill = k; } statement_t *s = new_statement (st_func, "call", call); sblock = statement_subexpr (sblock, func, &s->opa); @@ -1123,6 +1136,8 @@ expr_call (sblock_t *sblock, expr_t *call, operand_t **op) } s->opc = *op; } + s->use = use; + s->kill = kill; sblock_add_statement (sblock, s); sblock->next = new_sblock (); return sblock->next;