Constant/label nodes should never be killed because they can (in theory)
never change. While constants /can/ change in the Quake VM, it's not worth
worrying about as there would be much more important things to worry about
(like 2+2 not giving 4).
Due to the hoops one would have to jump through, it is assumed that a
pointer or an offset from that pointer will never overwrite the pointer.
Having the source operand of a pointer assignment available to later
instrctions can make for more efficient code as the value does not need to
be dereferenced later. For this purpose, pointer dereference dag nodes now
store the source operand as their value, and dagnode_match will match x=a.b
with *(a+b)=y so long as both a and b are the same in both nodes. x and y
are irrelevant to the match. The resulting code will be the equivalent of:
*(a+b) = y;
x = y;
.return and .param_N are not classed as global variables for data flow
analysis. .return is taken care of by return statements, and .param_N by
call statements.
With this, the menus work up to attempting to load the menu plist.
Something is corrupting zmalloc's blocks.
Function calls need to ensure .param_N actually get assigned, and so the
params must be seen as live by the dead variable removal code. However, it
is undesirable to modify the live vars data of the flow node, so make a
local copy.
The main void defs are .return and .param_N. If the source operand is void,
use the destination operand's type to alias the source operand rather than
the source operand's type to alias the destination's operand (the usual
case).
The dags code isn't the only place that creates temporary variables, so
count them as they go into a statement rather than when they're created.
This fixes the temp underflows.
Nicely, the need for dag_gencode to recurse seems to have been removed.
At least for a simple case, correct code is generated :)
switch.r:49: case 1: *to = *from++;
003b loadbi.i *(from + 0), .tmp10
003c add.i from, .imm, from
003d storep.i .tmp10, *to
A node that writes to a var must be evaluated after any node that reads
that var, so for any node reading var, add that node to the edges of the
node currently associated with the var (unless the node is a child of the
node reading the var).
It doesn't make any difference yet, but that's because I need to add extra
edges indicating iter-node dependencies. However, the sort does seem to
work for its limited input.
Not adding them while creating the dag completely broke the dag as
node(deadvar) always returned null. Code quality is back to where it was
before the dags rewrite.
While things are quite broken now (very incorrect code is being generated),
the dag is much easier to work with. The dag is now stored in an array of
nodes (the children pointers are still used for dagnode operands), and sets
are used for marking node parents, attached identifiers and (when done,
extra edges).
Instead of storing the generating statement in the dagnode, the generating
expression is stored in the daglabel. The daglabel's expression pointer is
updated each time the label is attached to a node. Now I know why debugging
optimized code can be... interesting.
It now seems to generate correct code for each node. However, node order is
still incorrect in places (foo++ is being generated as ++foo). quattest.r
actually executes and produces the right output :)
flow_analyze_statement uses the statement type to quickly determin which
operands are inputs and which are outputs. It takes (optional) sets for
used variables, defined variables and killed variables (only partially
working, but I don't actually use kill sets yet). It also takes an optional
array for storing the operands: index 0 is the output, 1-3 are the inputs.
flow_analyze_statement clears any given sets on entry.
Live variable analysis now uses the sets rather than individual vars. Much
cleaner code :).
Dags are completely broken.
The types are expression, assignment, pointer assignment (ie, write to a
dereferenced pointer), move (special case of pointer assignment), state,
function call/return, and flow control. With this classification, it will
be easier (less code:) to determine which operands are inputs and which are
outputs.
Surprisingly, I don't yet have to "throw one out", but things are still
problematic: rcall1 is getting two arguments, goto and return get lost,
rcall2 got an old temp rather than the value it was supposed to, but
progress :)
This allows temporary variables that are used in multiple nodes to remain
in the dag, but also will allow more freedom when generating code from the
dag.
The root nodes of the dag need to be evaluated in execution order as some
roots may depend on the results of earlier roots (but then, this might also
be related to the problem of function calls not specifying all of their
parameters to the dag).
First, it turns out using daglabels wasn't such a workable plan (due to
labels being flushed every sblock). Instead, flowvars are used. Each actual
variable (whether normal or temp) has a pointer to the flowvar attached to
that variable.
For each variable, the statements that use or define the variable are
recorded in the appropriate set attached to each (flow)variable.
Dot interprets escape sequences in non-html string, and needs the quotes to
be escaped, so quote the result of operand_string. Unfortunately,
operand_string uses quote_string and quote_string returns a static pointer,
so some hoop-jumping is necessary.