The goto for the default expression is the source of the mis-counted label
users: the label was being counted by the goto, but the goto was never
being inserted into the code (only v6 progs or "difficult" types insert the
goto).
Such nodes are unreachable code (ie, dead blocks), but the dead block
removal code failed to remove them (current known cause: miscounted label
userrs). As such blocks cause problems for data flow analysis, ignoring
them is not a good idea. Thus make them an internal error.
vectors, quaternions and structs are a little tricky. I need to think about
how to get them working, but I also want qfcc to get through as much code
as possible.
The evil comment is not just "pragmas are bad, ok?", but switching between
advanced, extended and tradtitional modes when compiling truly is evil and
not guaranteed to work. However, I needed it to make building test cases
easier (it's mostly ok to go from advanced to extended or tradtional, but
going the other way will probably cause all sorts of fun).
In the process, opcode_init now copies the opcode table data rather than
modifying it.
It really should be impossible, but I'm not sure where the bug is yet
(though there are uninitialized variables that are false positives that
most definitely are initialized, might be related)
Pointing to aliases of the var causes all sorts of problems, but this time
it was causing the uninitialized variable detector to miss certain
parameters.
It is necessary to know if a def is a function parameter so it can be
treated as initialized by the flow analyzer. The support for the flag in
object files is, at this stage, purely for debugging purposes.
The structvar2 = structvar1 is implemented as a move expresion, which
address_expr didn't like. Return the address of the source. For indirect
move expressions, this is just the source expression itself.
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.
Accessing the final statement of an sblock via tail doesn't work in an
empty sblock because tail points to sblock->statements and thus the cast is
invalid. This bug has be lurking for a long time, but for some reason the
cse stuff tickled it (thankfully!!!).
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.
With temp types changing and temps being reused within the one instruction,
the def type is no longer usable for selecting the opcode. However, the
operand types are stable and more correct.
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.
Using "=" was rather confusing, so changing it to "<CONV>" seems to be a
good idea. As the string is used only for selecting opcodes at compile
time, only qfcc is affected.
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).
An instruction that both reads and writes the same variable will read the
variable before writing to it, so the instruction uses the variable rather
than defines it (for live-variable purposes).