Propagate volatile variables in AsNode previsit #2930
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
When a global variable is referenced many times throughout a variadic expression, e.g.
Notify(x); Set(y, x+1); Collect(c, x); // etc.
the code generator may chose to generate a variable lookup each time the variable is accessed in the expression. Variable lookups come as some runtime expense, and the value does not change at any point in the example expression. As such, the compiler employs a strategy termed in-code as "variable lifting". The compiled output of such an expression may instead create a local variable that caches the value of x, and the accesses may be translated to reference this cached value. This allows the expression to be run while only performing a lookup once, which improves runtime performance at scale.
This is implemented during binding. When a variadic node is visited and a global variable reference is encountered, a local cache variable will be generated to contain the value. The problem arises when the variable is altered within the variadic expression. For example, suppose that x is 10 when this expression is executed:
Notify(x); Set(x, 1); Set(y, x + 1);
If variable lifting is employed, the value of x, 10, will be cached and used in the Notify, which is correct, but also in Set(y, x + 1). This is incorrect, as semantically, x was set to one in the Set expression previous to Set(y, x + 1). x in this case constitutes a volatile variable, one whose features are different depending on the index of the variadic expression.
To account for this, when variadic expressions are bound, each semicolon separated sub-expression is visited. During visitation, the binder is meant to track Set invocations of any global variables. If a set is encountered, the set variable is propagated via a node map in TexlBinding.VolatileVariables, through which it propagates up the tree on ascension (PostVisit) and is remapped until the variadic parent is reencountered. When the next expression is to be bound, the volatile variables are propagated into it, and if any such variable is referenced by subsequent expressions, this node is flagged as "Unliftable".
The problem this PR solves is that when using a ForAll(variable As Test, ...), variable As Test is deemed unliftable, but it fails to propagate the volatile variables to its left child, variable. As a result, variable is never categorized as unliftable, and the code generator will apply the volatile variable cache lifting pattern, which will cause incorrect behavior at runtime.
DottedNameNode already accounts for this behavior, but we missed it when added the As keyword, which was implemented after the variable weight system was put into place.
Working, without As. The global variable is referenced lazily, after it is set, and passed into the ForAll lambda.
Not working, with As, and without my fix. The local1_0 is the cached value of the global variable. Since it is cached before it is accessed, the previous value is used.
Here is what the codegen looks like with this fix. Notice that local1_0 is gone and it now exactly matches the working code