class
compiler::ClosureVars
sys::Obj compiler::CompilerSupport compiler::CompilerStep compiler::ClosureVars
ClosureVars is used to process closure variables which have been enclosed from their parent scope:
ResolveExpr ----------- ResolveExpr we detected variables used from parent scope and created shadow variables in the closure's scope with a reference via MethodVar.shadows
. Also during this step we note any variables which are reassigned making them non-final (according to Java final variable semantics).
Process Method -------------- First we walk all types looking for methods which use closure variables:
- For each one walk thru its variables to see if any variables enclosed are non-final (reassigned at some point). These variables as hoisted onto the heap with wrappers:
class Wrapper$T { new make(T v) { val=v } T val }
- If no wrapped variables, then we can leave a cvars method alone - everything stays the same. If however we do have wrapped variables, then we need to walk the expr tree of the method replacing all access of the variable with its wrapper access:
x := 3 => x := Wrapper$Int(3) x = x + 1 => x.val = x.val + 1
- If any params were wrapped, we generated a new local variable in
wrapNonFinalVars
. During the expr tree walk we replaced all references to the param to its new wrapped local. To finish processing the method we insert a bit of code in the beginning of the method to initialize the local.
Process Closure --------------- After we have walked all methods using closure variables (which might include closure doCall methods themselves), then we walk all the closures.
- For each shadowed variables we need:
- Define field on the closure to store variable
- Pass variable to closure constructor at substitution site
- Add variable to as closure constructor param
- Assign param to field in constructor If the variable has been wrapped we are doing this for the wrapped variable (we don't unwrap it).
- If any of the closures shadowed variables are wrapped, then we do a expr tree walk of doCall - the exact same thing as step 2 of the processMethod stage.
Slots
-
static private FieldDef addToClosure(ClosureExpr closure, Str name, Expr subtituteArg)
Common code between addVarToClosure and makeOuterThisField. Return storage field for closure variable.
-
private Void addVarToClosure(ClosureExpr closure, MethodVar var, Str name)
For each variable enclosed by the closure:
- Add field on the closure to store variable
- Add param to as closure constructor
- Pass variable to closure constructor at substitution site
- Assign param to field in constructor
- Initialize variable in doCall from field
-
static private FieldExpr fieldExpr(Loc loc, Expr target, CField field)
-
private Stmt[]? fixLocalDef(LocalDefStmt stmt)
If a local variable has been hoisted onto the heap with a wrapper, then generate wrapper initialization:
// original code local := 3 // becomes local := Wrap$Int(3)
-
private Void fixWrappedParams(MethodDef method)
After we have walked the expr tree, we go back and initialize the wrapper for any wrapped params used inside closures:
Void foo(Int x) { x$wrapper := Wrap$Int(x) ...
-
private Expr fixWrappedVar(LocalVarExpr local)
If we are accessing a wrapped variable, then add indirection to access it from Wrapper.val field.
- genWrapperSource
-
static CField genWrapper(CompilerSupport cs, CType ctype)
Given a variable type, generate a wrapper class of the format:
class Wrap$ctype[$n] { CType val }
Wrappers are used to manage variables on the heap so that they can be shared between methods and closures. We generate one wrapper class per variable type per pod with potentially a non-nullable and nullable variant ($n suffix).
Eventually we'd probably like to share wrappers for common types like Int, Str, Obj, etc.
Return the val field of the wrapper.
-
private Expr initWrapper(Loc loc, MethodVar var, Expr init)
Generate the expression: var := Wrapper(init)
- makeSource
-
new make(Compiler compiler)
- makeOuterThisFieldSource
-
static CField makeOuterThisField(ClosureExpr closure)
This method is called by ClosureExpr to auto-generate the implicit outer "this" field in the Closure's implementation class:
- Add $this field to closure's anonymous class
- Add $this param to closure's make constructor
- Pass this to closure constructor at substitute site
- Set field from param in constructor
-
private Void processClosure(ClosureExpr closure)
Walk each closure:
- Find all the shadowed variables
- Call addVarToClosure for each shadowed variable
- If needed do expr tree walk
- runSource
-
override Void run()
Overrides compiler::CompilerStep.run
Doc inherited from compiler::CompilerStep.run
Run the step
-
static private Int syntheticFieldFlags := ...
- visitExprSource
-
override Expr visitExpr(Expr expr)
Overrides compiler::Visitor.visitExpr
Doc inherited from compiler::Visitor.visitExpr
Call to visit an expression. Return expr or a new expression if doing a replacement for the expression in the abstract syntax tree.
- visitStmtSource
-
override Stmt[]? visitStmt(Stmt stmt)
Overrides compiler::Visitor.visitStmt
Doc inherited from compiler::Visitor.visitStmt
Callback when visiting a stmt. Return a list to replace the statement with new statements, or return null to keep existing statement.
-
private Void walkMethod(MethodDef method)
Walk the method body:
- Create wrapper for each local var definition which requries it
- Add unwrap val access for each use of a wrapped local variable
- If using a wrapped param, then replace with wrapped local
-
private Bool wrapNonFinalVars(MethodDef m)
Wrap each non-final variable which is reassigned and used inside a closure. By wrapping it we hoist it into the heap so that it may be shared b/w method and closure(s). Return true if we wrapped any vars.