mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-12-02 17:12:55 +00:00
55ecf94f2d
Return statements never flow to the next block (or any other block, for that matter), so drawing arrows leaving them not only messes up dot's graphs, but is quite missleading.
253 lines
6.3 KiB
C
253 lines
6.3 KiB
C
/*
|
|
dot_flow.c
|
|
|
|
"emit" flow graphs to dot (graphvis).
|
|
|
|
Copyright (C) 2011 Bill Currie <bill@taniwha.org>
|
|
|
|
Author: Bill Currie <bill@taniwha.org>
|
|
Date: 2011/01/21
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
|
|
#include <QF/dstring.h>
|
|
#include <QF/quakeio.h>
|
|
#include <QF/va.h>
|
|
|
|
#include "expr.h"
|
|
#include "statements.h"
|
|
#include "symtab.h"
|
|
#include "type.h"
|
|
|
|
static const char *
|
|
quote_string (const char *str)
|
|
{
|
|
static dstring_t *q;
|
|
char c[2] = {0, 0};
|
|
|
|
if (!str)
|
|
return "(null)";
|
|
if (!q)
|
|
q = dstring_new ();
|
|
dstring_clearstr (q);
|
|
while ((c[0] = *str++)) {
|
|
switch (c[0]) {
|
|
case '\n':
|
|
dstring_appendstr (q, "\\\\n");
|
|
break;
|
|
case '<':
|
|
dstring_appendstr (q, "<");
|
|
break;
|
|
case '>':
|
|
dstring_appendstr (q, ">");
|
|
break;
|
|
case '&':
|
|
dstring_appendstr (q, "&");
|
|
break;
|
|
case '"':
|
|
dstring_appendstr (q, """);
|
|
break;
|
|
default:
|
|
dstring_appendstr (q, c);
|
|
break;
|
|
}
|
|
}
|
|
return q->str;
|
|
}
|
|
|
|
static const char *
|
|
get_operand (operand_t *op)
|
|
{
|
|
type_t *type;
|
|
|
|
if (!op)
|
|
return "";
|
|
switch (op->op_type) {
|
|
case op_symbol:
|
|
return op->o.symbol->name;
|
|
case op_value:
|
|
switch (op->o.value->type) {
|
|
case ev_string:
|
|
return quote_string (op->o.value->v.string_val);
|
|
case ev_float:
|
|
return va ("%g", op->o.value->v.float_val);
|
|
case ev_vector:
|
|
return va ("'%g %g %g'",
|
|
op->o.value->v.vector_val[0],
|
|
op->o.value->v.vector_val[1],
|
|
op->o.value->v.vector_val[2]);
|
|
case ev_quat:
|
|
return va ("'%g %g %g %g'",
|
|
op->o.value->v.quaternion_val[0],
|
|
op->o.value->v.quaternion_val[1],
|
|
op->o.value->v.quaternion_val[2],
|
|
op->o.value->v.quaternion_val[3]);
|
|
case ev_pointer:
|
|
return va ("ptr %d", op->o.value->v.pointer.val);
|
|
case ev_field:
|
|
return va ("field %d", op->o.value->v.pointer.val);
|
|
case ev_entity:
|
|
return va ("ent %d", op->o.value->v.integer_val);
|
|
case ev_func:
|
|
return va ("func %d", op->o.value->v.integer_val);
|
|
case ev_integer:
|
|
return va ("int %d", op->o.value->v.integer_val);
|
|
case ev_uinteger:
|
|
return va ("uint %u", op->o.value->v.uinteger_val);
|
|
case ev_short:
|
|
return va ("short %d", op->o.value->v.short_val);
|
|
case ev_void:
|
|
return "(void)";
|
|
case ev_invalid:
|
|
return "(invalid)";
|
|
case ev_type_count:
|
|
return "(type_count)";
|
|
}
|
|
break;
|
|
case op_label:
|
|
return op->o.label->name;
|
|
case op_temp:
|
|
return va ("tmp %p", op);
|
|
case op_pointer:
|
|
type = op->o.pointer->type;
|
|
if (op->o.pointer->def)
|
|
return va ("(%s)[%d]<%s>",
|
|
type ? pr_type_name[type->type] : "???",
|
|
op->o.pointer->val, op->o.pointer->def->name);
|
|
else
|
|
return va ("(%s)[%d]",
|
|
type ? pr_type_name[type->type] : "???",
|
|
op->o.pointer->val);
|
|
case op_alias:
|
|
return get_operand (op->o.alias);//FIXME better output
|
|
}
|
|
return ("??");
|
|
}
|
|
|
|
static void
|
|
flow_statement (dstring_t *dstr, statement_t *s)
|
|
{
|
|
dasprintf (dstr, " <tr>");
|
|
dasprintf (dstr, "<td>%s</td>", quote_string (s->opcode));
|
|
dasprintf (dstr, "<td>%s</td>", get_operand (s->opa));
|
|
dasprintf (dstr, "<td>%s</td>", get_operand (s->opb));
|
|
dasprintf (dstr, "<td>%s</td>", get_operand (s->opc));
|
|
dasprintf (dstr, "</tr>\n");
|
|
}
|
|
|
|
static int
|
|
is_goto (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
return !strcmp (s->opcode, "<GOTO>");
|
|
}
|
|
|
|
static int
|
|
is_return (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
return !strncmp (s->opcode, "<RETURN", 7);
|
|
}
|
|
|
|
static sblock_t *
|
|
get_target (statement_t *s)
|
|
{
|
|
if (!s)
|
|
return 0;
|
|
if (!strncmp (s->opcode, "<IF", 3))
|
|
return s->opb->o.label->dest;
|
|
if (!strcmp (s->opcode, "<GOTO>"))
|
|
return s->opa->o.label->dest;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
flow_sblock (dstring_t *dstr, sblock_t *sblock, int blockno)
|
|
{
|
|
statement_t *s;
|
|
sblock_t *target;
|
|
ex_label_t *l;
|
|
|
|
dasprintf (dstr, " sb_%p [shape=none,label=<\n", sblock);
|
|
dasprintf (dstr, " <table border=\"0\" cellborder=\"1\" "
|
|
"cellspacing=\"0\">\n");
|
|
dasprintf (dstr, " <tr>\n");
|
|
dasprintf (dstr, " <td>%p(%d)</td>\n", sblock, blockno);
|
|
dasprintf (dstr, " <td height=\"0\" colspan=\"2\" port=\"s\">\n");
|
|
for (l = sblock->labels; l; l = l->next)
|
|
dasprintf (dstr, " %s(%d)\n", l->name, l->used);
|
|
dasprintf (dstr, " </td>\n");
|
|
dasprintf (dstr, " <td></td>\n");
|
|
dasprintf (dstr, " </tr>\n");
|
|
for (s = sblock->statements; s; s = s->next)
|
|
flow_statement (dstr, s);
|
|
dasprintf (dstr, " <tr>\n");
|
|
dasprintf (dstr, " <td></td>\n");
|
|
dasprintf (dstr, " <td height=\"0\" colspan=\"2\" "
|
|
"port=\"e\"></td>\n");
|
|
dasprintf (dstr, " <td></td>\n");
|
|
dasprintf (dstr, " </tr>\n");
|
|
dasprintf (dstr, " </table>>];\n");
|
|
if (sblock->next && !is_goto ((statement_t *) sblock->tail)
|
|
&& !is_return ((statement_t *) sblock->tail))
|
|
dasprintf (dstr, " sb_%p:e -> sb_%p:s;\n", sblock, sblock->next);
|
|
if ((target = get_target ((statement_t *) sblock->tail)))
|
|
dasprintf (dstr, " sb_%p:e -> sb_%p:s [label=\"%s\"];\n", sblock,
|
|
target, ((statement_t *) sblock->tail)->opcode);
|
|
dasprintf (dstr, "\n");
|
|
}
|
|
|
|
void
|
|
print_flow (sblock_t *sblock, const char *filename)
|
|
{
|
|
int i;
|
|
dstring_t *dstr = dstring_newstr();
|
|
|
|
dasprintf (dstr, "digraph flow_%p {\n", sblock);
|
|
dasprintf (dstr, " layout=dot; rankdir=TB;\n");
|
|
for (i = 0; sblock; sblock = sblock->next, i++)
|
|
flow_sblock (dstr, sblock, i);
|
|
dasprintf (dstr, "}\n");
|
|
|
|
if (filename) {
|
|
QFile *file;
|
|
|
|
file = Qopen (filename, "wt");
|
|
Qwrite (file, dstr->str, dstr->size - 1);
|
|
Qclose (file);
|
|
} else {
|
|
fputs (dstr->str, stdout);
|
|
}
|
|
dstring_delete (dstr);
|
|
}
|