-
Notifications
You must be signed in to change notification settings - Fork 0
Type and Dataflow analysis
The optimizer can perform type and dataflow analysis.
These optimizations are controlled by the options trustDeclaredTypes, propagateTypes and sizeBasedPRE.
-
trustDeclaredTypes - If this option is set, then any declared types will be used to augment the analysis. This can enable more precise optimizations - but can produce incorrect results if your type declarations are inaccurate.
-
propagateTypes - If this option is set, then the compiler will perform data flow analysis on the types. Without this, local variables won't get type information.
-
sizeBasedPRE - If this option is set, then the compiler will perform data flow analysis to find values that are used repeatedly within a function, and put the value into a local variable to save code size.
Given:
function foo(x as Number) as Number {
return x * 2;
}
function bar() as Number {
return foo(1) + 3 + 2;
}
If the option is enabled, then the compiler knows that foo returns a number, and can re-order bar's return expression to foo(1) + 5. When its disabled, foo might return a string, so it can't optimize the result (if foo returned "foo", the result would be "foo32", rather than "foo5" in this case).
Given:
var x = 0;
if (x != 0) {
System.println("X is not zero");
}
When propagateTypes is enabled, the compiler knows that the x in if (x != 0) is zero, and can optimize the test away. When it's disabled, it knows nothing about the condition, and can't optimize it away.
Note that in this context, types can include what would normally be thought of as values. The type 0 is just a more specialized instance of Number; just as Array<Number> is a more specialized instance of Array.
A downside of propagateTypes is that it can increase code size. For example:
var x = 0;
foo(x, x, x);
will be optimized to
foo(0, 0, 0);
but each of those zero's takes 5 bytes to produce, while each x only takes 2. The assignment to x also takes 2 bytes, so the original code takes 5 (for the zero) + 2 (assignment to x) + 2*3 = 13 bytes (plus the size of the call), while the modified code uses 5*3 = 15 bytes.
And this is where sizeBasedPRE comes in.
Regardless of how the code was originally written, when this option is enabled the compiler will take
foo(0, 0, 0)
and produce
var tmp=0;
foo(tmp, tmp, tmp)
Similarly, it will take multiple uses of a module or class scope variable, and put the value in a local:
var mGlobal as Number = 0;
function foo() as Number {
System.println(mGlobal);
return mGlobal + 1;
}
var mGlobal as Number = 0;
function foo() as Number {
var pre_mGlobal;
pre_mGlobal = mGlobal;
System.println(pre_mGlobal);
return pre_mGlobal + 1;
}
In some cases, Garmin's type checker will report an error if a local variable with a known value is used in place of a literal with the known value.
eg
function foo(x as [Number, Boolean]) as Void {
var a = x[0] + 1; // Ok, Garmin's type checker knows that x[0] contains a Number
var b = 0;
var c = x[b] + 1; // Not Ok, Garmin's type checker thinks that x[b] is either a Number or a Boolean
}
Unfortunately, sizeBasedPRE can introduce this pattern. This is really a Garmin problem, and one way to deal with it is probably to disable Garmin's type checker on the optimized code. If you're using the post build optimizer, however, it also does sizeBasedPRE for literals, so another workaround is to turn off sizedBasedPRE for literals in the source to source optimizer.
That's the purpose of the preSkipLiterals flag.