The live var flow analysis doesn't check for aliases. Rather than changing
it to check for aliases (which might break uninitialized var analysis, as
it uses "use" from the live var analysis), make dag_remove_dead_vars do the
check. Fixes the misplaced text in the menus.
Nifty: if you pass a struct via reference to a function, and a field of
that struct may be both set and not set (eg, set only in an if statement),
gcc will report that field assuming that fields that are never set will be
set by the function (my interpretation).
* taniwha ponders the flow analysis for that
Nifty: if you pass a struct via reference to a function, and a field of
that struct may be both set and not set (eg, set only in an if statement),
gcc will report that field assuming that fields that are never set will be
set by the function (my interpretation).
* taniwha ponders the flow analysis for that
At the statement level, all pointer types are the same, so just return the
op obtained from the sub-expression when the low-level type of the alias
expression matches the low-level type of the type of type sub-expression
operand.
With this, the alias of a value code can be removed (I always thought it
was wrong), which is what broke calling obj_msgSend_super (type &.super
param lost the &).
Now I have to deal with pointer values in the optimizer :/
When an alais def (or aliased def) is used, any overlapping aliases that
have previously been assigned need to be marked as live, and edges to the
aliases added to the new node. However, when assigned to, live-forcing
needs to be turned off.
This fixes the lost assignments to .super.
This fixes the bogus temps for "*to = *from++;", but qfcc ices due to the
operand types being lost. It seems alias operands need to be resurrected,
if only for code output by dags.
I forgot to add func->num_statements :P. Fixes the weirdness where only
some alias temps were being (bogusly) detected as uninitialized. Now they
all are.
When the naive uninitialized variable detection finds a node with possible
uses of uninitialized variables, the statements in the node are scanned one
at a time checking each usage and removing uninitialized definitions as
appropriate. vectest.r now compiles without warnings. As an added bonus,
accurate line number information is reported for uninitialized variables.
Unfortunately, there is still a problem with uninitialized temps in
switch.r, but that might just be poor handling of temp op aliases.
Only definitions for the def used in the current statement (whether an
alias or not) are suitable for killing. Doing otherwise defeats the purpose
of this work :P
Fixes the false negatives found in a modified quattest.r (commented out the
"tq.s = 0;" line).
Nicely, the use sets from live_variable analysis can be used too, though
there are some problems with the naive implementation. For:
vector foo (float x, float y, float z)
{
vector v;
v.x = x;
v.y = y;
v.z = z;
return v;
}
qfcc thinks v is uninitialized, but if "if (x) return nil;" (or any other
basic-block splitter) is put just before the return v; qfcc correctly
detects that v is initialized. The reason is that the inits are in the same
basic block as the return, and thus aren't affecting the reaching
definitions, which are stored per-block.
The naive implementation should be good for a fast-cull before doing a
per-statement check.
The exit dummy block is setup to provide dummy uses of global variables to
the live variable analysis doesn't miss global variables. Much cleaner than
the previous code :) There may be some issues with aliases, though.
The entry dummy block is setup to provide dummy definitions of local
variables so the reaching definitions analysis can be used to detect
uninitialized variables (not implemented yet). Fake statement numbers
(func->num_statements + X) are used to represent the definitions. Local
variables (ie, not temp ops) use their offsets (ie, the offset range they
cover) for X. Temp ops use their flowvar number + the size of the
function's defspace for X. flow_kill_aliases() should take care of temp op
aliasing, while the use of the actual offsets spanned by the variable's def
should take care of any wild aliasing so structures and unions should
become a non-issue.
The dummy nodes are for detectining uninitialized variables (entry dummy)
and making globals live at function exit (exit dummy). The reaching defs
and live vars code currently seg because neither node has had its sets
initialized.
Fixed aliases are those that will never change through the life of the
code. They are generated from structure accesses and thus what they alias
is always known.
Also move the ALLOC/FREE macros from qfcc.h to QF/alloc.h (needed to for
set.c).
Both modules are more generally useful than just for qfcc (eg, set
builtins for ruamoko).
Set of everything is implemented by inverting the meaning of bits in the
bitmap: 1 becomes non-member, 0 member. This means that set_size and
set_first/set_next become inverted and represent non-members as counting
members becomes impossible :)
Aliasing the jump table to an integer broke statement_get_targetlist with
the new alias def handling, and was really wrong anyway. I probably did
that due to being fed up with things and wanting to get qfcc working again
rather than spending time getting jumpb right.
With the need to handle aliasing in the optimizer, it has become apparent
that having the flow data attached to symbols is not nearly as useful as
having it attached to defs (which are views of the actual variables).
This also involves a bit of a cleanup of operand types: op_pointer and
op_alias are gone (this seems to greatly simplify the optimizer)
There is a bit of a problem with enums in switch statements, but this might
actually be a sign that something is not quite right in the switch code
(other than enums not being recognized as ints for jump table
optimization).
Turns out there was only one place to fix (for qc, anyway: I don't have
tests for qp yet). func-static now passes :)
Hmm, how to test for static var naming... (not implemented yet)
Simply "backed" and "virutal". Backed spaces have memory allocated to them
while virtual spaces do not. Virtual spaces are intended for local
variables and entity fields.
It turns out they're not getting allocated properly (they're put in the
function's locals defspace rather than near data), but fixing it proved a
little more problematic than expected, so the test is marked as XFAIL for
now to remind me to fix it.
With this, alias defs become singletons based on the def they alias and the
type and offset of the alias. Thus, the removal of the free_def call in
emit.c.
alias_def now always creates an offset def (though the usual case has an
offset of 0). The if the alias escapes the bounds of the base def, an
internal error will be generated.
It really doesn't seem wise to allow the compiler to do so as it would
overwrite unrelated defs. The only time such a thing is valid is the return
statement (silly vm design), and that's read-only.
Also remove the extern for current_storage as it belongs in shared.h.
I'm not satisfied with the documentation for initialize_def, but it will do
for now. I probably have to rewrite the thing as it's a bit of a beast.
With the intoduction of the statement type enum came a prefix clash. As
"st" makes sense for "statement type", I decided that "storage class"
should be "sc". Although there haven't been any problems as of yet, I
decided it would be a good idea to clean up the clash now. It also helps
avoid confusion (I was a bit surprised after working with st_assign etc to
be reminded of st_extern etc).
qfcc isn't meant to be long running, so I'm not super worried about memory
usage, but definitely lost memory blocks when compiling just a single
function seems a tad sloppy.
It doesn't quite work yet, but...
It has proven necessary to know what type .return has at any point in the
function. The segfault in ctf is caused by the return statement added to
the end of the void function messing with the expr pointer stored in the
daglabel for .return. While this is actually by design (though the
statement really should have a valid expr pointer rather than), it actually
highlights a bigger problem: there's no stable knowledge of the current
type of .return. This is not a problem in expression statements as the
dagnodes for expression statements store the desired types of all operands.
However, when assigning from .return to attached variables in a leaf node,
the type of .return is not stored anywhere but the expression last
accessing .return.
Now information like dags or live variables are dumped separately, and the
live variable information replaces the flow node in the diagram (like dags
have recently).
They really should have been in statements.[ch] in the first place
(actually, they sort of were: is_goto etc, so some redundant code has been
removed, too).
Modifying the existing alias chain proved to be a bad idea (in retrospect,
I should have known better:P). Instead, just walk down any existing alias
chain to the root operand and build a new alias from that.
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.
After running across a question about lists of animation frames and states,
I decided giving qfcc the ability to generate such lists might be a nice
distraction from the optimizer :) Works for both progs.src and separate
compilation. No frame file is generated if no macros have been created.
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.
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).
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.
The flow graph nodes are now properly separated from the graph, and edge
information is stored in the graph struct. This actually made for much
cleaner code (partly thanks to the use of sets and set iterators).
Flow graph reduction has been (temporarily) ripped out as the entire
approach was wrong. There was also a bug in that I didn't really understand
the dragon book about selecting nodes and thus messed things up. The
depth-first search tree "fixed" the problem, but was really the wrong
solution (sledge hammer :P).
Also, now that I understand that dot's directed graphs must be acyclic, I
now have much better control over the graphs (back edges need to be
flipped).
It turns out dot does not like cyclic graphs (thus some of the weird
layouts), but fixing it by flipping back-edges requires proper recording of
edge info (I guess that's what T is for in the dragon book).
The reduction is performed itteratively until the graph is irreducible, but
such that each reduction wraps the previous graph. Unfortunately, due
depth-first searching not being implemented, graphs that should be reduced
(ie, those with natural loops).
set_first() now returns a pointer to a setstate_t struct that holds the
state necessary for scanning a set. set_next() will automatically delete
the state block when the end of the set is reached. set_delstate() is also
provided to allow early termination of the scan.
They're now dot_sblock.c and print_sblock. The new names both better
reflect their purpose and free up "flow" for outputting the real flow
analysis graphs.
Much of the data recently added to sblock_t has been moved to flownode_t.
No graph reduction is carried out yet, but the initial (innermost level)
graph has been built.