After messing with SIMD stuff for a little, I think I now understand why
the industry went with xyzw instead of the mathematical wxyz. Anyway, this
will make for less pain in the future (assuming I got everything).
I've decided that setting pr.max_edicts and pr.zone_size as part of the
local progs initialization rather than in PR_LoadProgsFile makes more
sense. For one, it is unlikely for the limits to change every time progs is
reloaded. Also, they seem to be a property of the VM rather than the progs.
However, there is nothing stopping the caller from updating max_edicts and
zone_size every call.
While scan-build wasn't what I was looking for, it has proven useful
anyway: many of the sizeof errors were just noise, but a few were actual
bugs (allocating too much or too little memory).
This fixes a segfault when optimizing the empty-body test. The label was
getting moved, but the statement block to which it pointed was not updated
and thus it pointed to dead data.
Saw a discussion of such in #qc and that gcc implemented it. I realized it
would be pretty easy to detect and very useful (I've made such mistakes at
times).
It is now in its own file and uses table lookups to check for valid type
and operator combinations, and also the resulting type of the expression.
This probably breaks multiple function calls in the one expression.
This is a bit of a workaround to ensure the operands have their types
setup correctly. Really, binary_expr needs to handle expression types
properly.
This fixes the bogus error for comparing the result of pointer subtraction
with an integer.
Currently, they can represent either vectors or quaternions, and the
quaternions can be in either [s, v] form or [w, x, y, z] form.
Many things will not actual work yet as the vector expression needs to be
converted into the appropriate form for assigning the elements to the
components of the "vector" type.
It's sometimes more useful to have direct access to each individual
component of the imaginary part of the quaternion, and then for
consistency, alias w and s.
This is a nice feature found in fteqcc (also a bit of a challenge from
Spike). Getting bison to accept the new expression required rewriting the
state expression grammar, so this is mostly for the state expression. A
test to ensure the state expression doesn't break is included.
This goes towards complementing the "if not" logic extension. I need to
check if fteqcc supports "not" with "while" (the version I have access to
at the moment does not), and also whether it would be good to support
"not" with "for", and if so, what form the syntax should take.
It is syntactic sugar for if (!(foo)), but is useful for avoiding
inconsistencies between such things as if (string) and if (!string), even
though qcc can't parse if not (string). It also makes for easier to read
code when the logic in the condition is complex.
It turns out this is required for compatibility with qcc (and C, really).
Once string to boolean conversions are sorted out completely (not that
simple as qcc is inconsistent with if (string) vs if (!string)), Qgets can
be implemented :)
It looks like I had forgotten that the compare function is supposed to
return true/false (unlike memcmp's sorting ability). Also, avoid the
pointers in the value struct as they can change without notice.
Using enums in switches now works nicely, including warnings for unused
enum values.
Either I had gotten confused while writing the code and mixed up line and
offset, or I had changed offset to line at one stage but missed a place.
This fixes the segfault when compiling chewed-alias.r and return-ivar.r
Rather than prefixing free_ to the supplied name, suffix _freelist to the
supplied name. The biggest advantage of this is it allows the free-list to
be a structure member. It also cleans up the name-space a little.
type_obj_class is no longer a class, so its ivars are not stored in
type_obj_class.t.class->ivars but rather type_obj_class.t.symtab.
This fixes the segfault Spirit and Randy were experiencing.
In passing, correct the unneeded emission of meta class ivars for non-root
classes. This should make for much smaller progs that use classes.
MOVEP's opc itself is always known and used, whether it's a constant
pointer or variable doesn't matter. This fixes the lost pointer calculation
for va_list.list[j] = object_from_plist (item);
Dead nodes are those that generate unused values (unassigned leaf nodes,
expressions or destinationless move(p) nodes). The revoval is done by the
flow analysis code (via the dags code) so that any pre and post removal
flow analysis and manipulation may be done (eg, available expressions).
assign_expr mangles the destination expression for dereferenced
assignments into something that is invalid as an lvalue, so simply use
new_binary_expr with the same opcode.
It turns out expression trees are (mostly?) valid DAGs, so all edges being
constrained works, though the graphs get a little tall (but easier to read).
This fixes the infinite loop in if ((x = self.heat && x))
Really, I think I need to revisit the whole expression tree code. It's
proving to be rather fragile.
The source of the assignment is used as the value to test, and the
assignment itself is inserted into the boolean expressions's block. This
fixes the inernal error for "if ((x = 0))".
Normally, it will happen only as a follow-on error, but I can think of a
way to force it without other errors, so treating it as an internal error
is a bit harsh.
But reset current_symtab to its prior value when done. This fixes a
segfault caused by initializing the class system while parsing a struct
(eg, one of the members is of type id).
The keywords table was rather awkward to edit (and sometimes confusing).
Worse, because the hash table used to look up the keywords was initialized
only once, changing modes in the same execution of qfcc would not work
properly as keywords would not be added or removed as appropriate.
Now there are four categories of keywords:
o "core" Always available. They form the core of QuakeC except for two
extensions.
o "@" In extended and advanced modes, the preceeding @ is optional,
but tranditional mode requires the keywords to be preceeded by
an @. They are the C keywords that QuakeC did not use, but can
be implemented in v6 progs under certain circumstances.
o "QF" These keywords require the QuakeForge VM to be usable.
o "Obj" These keywords form Ruamoko/Objective-QuakeC and require both
advanced mode and the QuakeForge VM.
This fixes the segfault/null pointer access in sendv.r. While I wanted to
use the edge setting code to set the live bit, I didn't expect it to be
this easy. def_visit_all is proving to be worth every bit it consumes :)
If the final block ends in a conditional statement, appending return to the
block will hide the conditional statement from the flow analyzer. This may
cause the conditional statement's destination node be become unreachable
according to the analyzer and thus eliminated. The label for the branch
then loses its target sblock and thus the code generator will produce a
zero-distance jump resulting in an infinite loop.
Thus, if the final block ends in a conditional statement (or, for
completeness, a call statement), append a new empty block before adding the
return statement.
If MOVEP's destination is variable, then the actual destination isn't (at
this stage) knowable, so it can't be attached to the dagnode and thus must
be a child.
Getting the operands directly from the statement was missing the
destination operand of movep when movep's op_c was a constant pointer and
thus the flowvar wasn't being counted/created early enough. This led to a
segfault in the set code when attempting to add -1 to the set.
It turns out the recent dead-block code "broke" vector component access
from objects. The breakage is really highlighting a problem with temporary
operands and aliasing. The problem was hiding behind a basic-block split
that the recent dead-block work mended and thus exposed the bug.
type_id is implemented as a pointer to "struct obj_object" (ie, not really
a class), so the correct check is to ensure the type is:
1 a pointer
2 to a struct
3 using the same symbol table as type_obj_object
Empty structs are now (correctly) invalid. The hack of using an empty
struct to represent a handle returned from a builtin has been unnecessary
since opaque structs were implemented: now a pointer to an opaque struct
can be used. This is mostly safe as handles are aways negative and thus
attempting to dereference such a pointer should result in a VM error. It
will be even safer once const is implemented and the pointers can be made
constant (eg, typedef struct handle * const handle;)
void foo (int); is fine for a prototype (or, presumably, a qc function
variable), but not for an actual function body. This fixes the segmentation
fault when the parameter name is omitted.
This is needed to allow compile-time protocol conformance checks, though
nothing along those lines has been implemented yet.
id has been changed from TYPE to OBJECT, required to allow id <proto> to be
parsed. OBJECT uses symbol, allowing id to be redefined once suitable work
has been done on the parser.
It uses the new block merge code. Now forgotten return statements are
detected properly (naive dead block removal) and all unreachable code is
eliminated (flow analysis unreachable node removal).
This reverts commit 83ead0842f.
Note: does not compile.
It turns out basic dead block removal is needed for the "control reaches
end of non-void function" warning to work correctly.
Empty sblocks are removed (unless it's the only sblock), and blocks that
are split unnecessarily are merged.
This mostly fixes bogus "no return" warnings.
Unreachable nodes will cause the first elements of the array to remain
unwritten by df_search. This fixes the segfaults caused by unreachable
nodes (the reason they were an internal error before).
The current implementation probably needs more work, but for the case where
I needed it, it does the job.
grid.r💯 vector size = {range, range, 0};
0115 store.f range, size
0116 store.f range, [$2ac]
0117 store.f .zero, [$2ad]
After all that effort getting the class def initialized early enough for
type encodings to work, it proved to be a problem: just including a header
with an interface in it would cause linker errors if there was no
implementation available (even if the class is never used).
qfcc now does local common subexpression elimination. It seems to work, but
is optional (default off): use -O to enable. Also, uninitialized variable
detection is finally back :)
The progs engine now has very basic valgrind-like functionality for
checking pointer accesses. Enable with pr_boundscheck 2
Temps aren't supported yet :P
The alias defs themselves aren't killed (still want any assignments to
occur) but rather, their nodes are. Also, edges to the alias defs' nodes
are added to the assigning node. Fixes structlive.r :)
I got fed up with using "int" types, but the members being "integer"
(hold-over from before the int rename).
Also, correct the names of those types and @va_list (error reporting was
chopping off part of the name).
MOVE (static move) and MOVEP to a pointer constant know exactly where their
data is going, so treat them similarly to assignments: save their
distination operands (the addressed def for MOVEP) and mark them as
defined.
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.