This annoyance and discrepancy with LSO was finally fixed by Linden Lab. The change has prompted some modifications to the test suite to accommodate for the new results. A further improvement has been to make these tests more friendly to be run in SL, making it easier to verify the results.
Fixes#17.
Reported by SaladDais@users.noreply.github.com - thanks!
They are transformed by the scanner to the identifier `inline`, which is how the parser identifies it. This solves the comment problem, but it results in a funny side effect. Now, in inline mode, /*pragma inline*/ will always be the identifier `inline`, therefore this is valid:
integer /*pragma inline*/ = 5;
llOwnerSay((string)inline); // will say 5
Not overly elegant, but better than making up a specific token or declaring comments as tokens or the like.
It's unlikely to hit this one (requires more than 10,000 identifiers to trigger) but it's a bug nevertheless.
While touching the file, try to explain the situation better in the first comment.
1. When the last statement of a function is a RETURN statement which is syntactically required, it could still be deleted.
2. The child of a RETURN statement could be removed if the statement was not executed.
This commit fixes both issues.
Bug report and test case provided by @Tonaie. Fixes#14.
E.g. llSubStringIndex(...) > -1 was converted into !~llSubStringIndex(...) which is incorrect. We even had a test case for it... with a wrong expected response file.
Bug report and test case by Sinha Hynes (thanks!)
vector * quaternion: Simplified by precalculating the repeated products and removing math.fsum.
quaternion * quaternion: Add more F32's to get a result closer to SL's (still not there, but we're definitively closer now).
We left out quaternion/quaternion in the tests, and the q*q test was not general enough (had many zeros). Remedied that.
The algorithm for adding parentheses around unary operators was not working properly. It converted a * (-b) * c into a * -b * c, which LSL handles as a * -(b * c).
Fix and add test cases for that. One of the test cases shows an example where the difference matters: 0 * (-1e20) * 1e20 should result in 0.0, but if wrongly parenthesized, it gives NaN, because 1e20*1e20 gives infinity due to float overflow, and minus infinity times 0 is indeterminate.
The addition of parentheses has been improved, but it still does not eliminate every redundant parenthesis.
Also fix the horrendous typo of using "operands" where it should be "operators".
In the same places as state changes are allowed, i.e. in places where a parent of the AST node is a WHILE/DO/FOR or an IF without ELSE, it's allowed to use return statements with expressions that return void, e.g. llDie(), provided the function itself is declared as returning void.
The construction, when found, is rewritten to '{<void expression>; return;}' because the optimizer is not designed to deal with these monsters.
We've renamed the variable SuspiciousStSw to PruneBug, because it's used for both purposes now, though a better name might have been PruneBugPendingChecks, because these are only errors if the IF has an ELSE. We've also added the exception to raise as part of the data stored in the list.
Per report by Tonaie Resident.
Revert "Add support for C11-style _Pragma operator on processpre".
Revert "Add unit test for the _Pragma operator".
This reverts commits 31fcb331c7 and e261ac2121.
This should rather be the job of the preprocessor, which should generate #pragma lines. gcc does that.
A minor difference is that strings and whitespace are parsed according to LSL rules, not to C rules, since this processing is performed in the lexer.
This could be fixed, but is it worth the trouble?
This is a first try at redundant jump removal (jumps that target the very next instruction). It's too basic in several ways.
- The statement is replaced by a ';' instead of removed.
- If the jump was the only statement in an if, when the if becomes empty, it's not folded.
- Jumps that are last in the 'then' branch of if+else are not visible. This would need either to track multiple last statements, or to have some means to anticipate what the next statement is at every statement. A Control Flow Graph would help a lot.
- When a label is immediately followed by a jump, all jumps to that label should target the destination of that jump if it's in scope. Added to TODO.
- It misses some optimizations when not expanding WHILE and FOR into IF/JUMP.
Moving everything to an earlier stage would help with some of these, especially with ';' and 'if' folding. Unconditionally expanding WHILE and FOR would also help.
SymbolReplacedOrDeleted had an "emergency fix" that disabled several kinds of substitutions, because they generated code that didn't compile. The cause was actually elsewhere.
The actual problem was the marking of function parameters as being written to by function calls. This is true in a sense, but there's a big scope change that totally destroys the possibility of substituting identifiers, for example.
We were not removing the function parameters, anyway, therefore that code has just been disabled.
Note that removal of function parameters may be impossible if one parameter has side effects. Consider this:
f(string x, integer y, string z)
{
llOwnerSay(x + z);
}
integer n = 2;
default{state_entry(){
f("a" + (string)n, n=llSetRegionPos(<100,100,100>), "c" + (string)n);
}}
Even worse if the expression for the x argument has side effects too and x and y need to be performed in the right order.
Fortunately, such case is highly unlikely. But if we ever implement removal of function parameters, that's an additional difficulty to take care of.
This allows optimizing, for example:
integer a = 1;
integer b = a;
llOwnerSay((string)b);
which wasn't done before. This case is prone to happen with inlined functions, e.g. using the result of an inlined function as a parameter to another.
It was an awful name but we couldn't think of anything better. That's what we have come up with as a substitute, which is not entirely accurate but it is MUCH more descriptive of what it actually does.
It's set in a variable local to that module. There's currently no way to enable it except by editing the code, but since it's mostly for internal purposes, it's OK like that.
Instead of a tree of symbol tables, we keep a running stack of active symbol tables while parsing. The only case in which this causes problems is forward reference resolution for jump labels, which is solved by storing a copy of the stack at the point the jump was found.
Since we need to add variables, we need to know which scope to add them to. Add this information to the {} node, which is what creates a new scope.
An alternative would be to scan for any variable or label declaration within the braces and use that or create a new one if none, which is more expensive and may waste symbol tables.
Example: the optimization of if(i < 2 || i) was suboptimal, because FoldTree was done before FoldCond. In a first stage, the || was removed: if(!!(i < 2 | i)); when folded, the inner ! was optimized first and the result was if(!(1 < i & (!i))), which FoldCond wasn't smart enough to handle.
The new optimization takes over from there, and converts if(!(!a & b)) with b boolean, in any order, to if(a | !b). When applied to the above expression, it gets folded to if(i < 2 | i) as expected, which is optimal.
Also, new function: IsAndBool (see docstring), used on b in the above example.
Add blank lines between functions, between functions and states, between variables and functions or states, between states, and between events.
Or more concisely: add blank lines between events and between all elements at the global level except between variables (that actually describes the algorithm).
Some test cases expected no newlines; fix them.
Rather than using a hardcoded table of list-to-type extraction function, add a 'ListTo' attribute to the function data. No error is raised if more than one function exists to convert to the same type.
This change is of questionable usefulness, but it should soothe those allergic to magic names/numbers. I cringed a bit myself.
While on it, change the syntax error that was raised when the corresponding conversion function did not exist, to a tailor-made error.
After more thought, we believe that transforming llDumpList2String into a sum of strings is a gain even if there is a list constructor containing function calls as first parameter. The rationale is explained in the comment.
In particular, variables and fields. When we implement an analysis of functions contained in subexpressions, we can optimize more expressions, so add that as a TODO.
Float addition is commutative. Swap the constant to the left side if it's not there. This is a "cheap" version of a bigger change that is planned, to minimize stack usage as much as possible on savepoints.
Float addition is not associative, therefore we don't optimize e.g. 1 + (2 + float).
Integer addition is commutative and associative. If there's a constant, we swap it to the left side. If there's a chained summation of the form const + (const + expression), we apply associativity to turn it into (const + const) + expression and reoptimize.
This doesn't cover all possible cases. Expressions of the form (const + expr) + (const + expr) are not optimized. We need to flatten sums if we want to do the right thing here, but that's not yet implemented.
Get rid of some older code and TODOs that are no longer needed.
The missing bit was to mark labels are SEF when they are not referenced. Label references are now counted at parse time, enabling us to do this.
Also, make FoldStmt clearer when the node is an expression.