1 //
2 // Copyright (c) 2006, Brian Frank and Andy Frank
3 // Licensed under the Academic Free License version 3.0
4 //
5 // History:
6 // 5 Mar 06 Brian Frank Creation
7 // 4 Oct 06 Brian Frank Port from Java to Fan
8 //
9
10 **
11 ** ClosureVars (cvars) is used to pull local variables used by closures
12 ** into an auto-generated anonymous class so that they can be
13 ** used outside and inside the closure as well as have a lifetime
14 ** once the method returns:
15 ** 1) scan for methods with have locals used by their closures
16 ** 3) define the cvars class
17 ** 4) remove method vars which are stored in cvars
18 ** 5) walk the method body
19 ** a) remap local var access to cvars field access
20 ** b) accumulate all the ClosureExprs
21 ** 6) walk accumlated ClosureExprs in method body
22 ** a) add $cvars field to closure implementation class
23 ** b) add $cvars parameter to implementation class constructor
24 ** c) pass $cvars arg from method body to implementation constructor
25 ** d) walk implementation class code and remap local var access
26 ** 7) decide if closure is thread-safe or not and mark isConst
27 **
28 ** Note: this same process is used to process nested closure doCall methods
29 ** too; but they do things a bit differently since they always share the
30 ** outmost method's cvars.
31 **
32 class ClosureVars : CompilerStep
33 {
34
35 //////////////////////////////////////////////////////////////////////////
36 // Constructor
37 //////////////////////////////////////////////////////////////////////////
38
39 new make(Compiler compiler)
40 : super(compiler)
41 {
42 closures = ClosureExpr[,]
43 }
44
45 //////////////////////////////////////////////////////////////////////////
46 // Run
47 //////////////////////////////////////////////////////////////////////////
48
49 override Void run()
50 {
51 types.each |TypeDef t| { scanType(t) }
52 }
53
54 private Void scanType(TypeDef t)
55 {
56 t.methodDefs.each |MethodDef m|
57 {
58 if (m.needsCvars) process(m)
59 }
60 }
61
62 //////////////////////////////////////////////////////////////////////////
63 // Process
64 //////////////////////////////////////////////////////////////////////////
65
66 private Void process(MethodDef method)
67 {
68 this.method = method
69 this.location = method.location
70 this.inClosure = method.parentDef.isClosure && method.parentDef.closure.doCall === method
71 this.cvars = null
72 this.cvarsCtor = null
73 this.cvarsLocal = null
74 this.closures.clear
75
76 defineCvarsClass
77 reorderVars
78 insertCvarsInit
79 remapVarsInMethod
80 remapVarsInClosures
81 }
82
83 //////////////////////////////////////////////////////////////////////////
84 // Define Cvars Class
85 //////////////////////////////////////////////////////////////////////////
86
87 **
88 ** Walk the current method and create the cvars class, a default
89 ** constructor, and a field for every local variable used inside
90 ** closures. If this is a closure itself, then we just reuse
91 ** the cvar class of the outer most method.
92 **
93 private Void defineCvarsClass()
94 {
95 // if in a closure body, then reuse the enclosing
96 // method's cvar class (which should have already
97 // been defined)
98 if (inClosure)
99 {
100 closure := method.parentDef.closure
101 name := toCvarsTypeName(closure.enclosingType, closure.enclosingMethod)
102 cvars = (TypeDef)compiler.pod.resolveType(name, true)
103 }
104
105 // define the Cvars class and generate no arg constructor
106 else
107 {
108 // define type def
109 name := toCvarsTypeName(method.parentDef, method)
110 cvars = TypeDef.make(ns, location, method.parentDef.unit, name)
111 cvars.flags = FConst.Internal | FConst.Synthetic
112 cvars.base = ns.objType
113 addTypeDef(cvars)
114
115 // generate no arg constructor
116 cvarsCtor = DefaultCtor.addDefaultCtor(cvars, FConst.Internal | FConst.Synthetic)
117 }
118
119 // generate the fields used to store each local
120 method.vars.each |MethodVar var, Int index|
121 {
122 if (var.usedInClosure)
123 {
124 f := FieldDef.make(location, cvars)
125 f.name = "${var.name}\$$index"
126 f.fieldType = var.ctype
127 f.flags = syntheticFieldFlags
128 cvars.addSlot(f)
129 var.cvarsField = f
130 }
131 }
132 }
133
134 private static Str toCvarsTypeName(TypeDef t, MethodDef m)
135 {
136 if (m.isGetter)
137 return "${t.name}\$${m.name}\$GetCvars"
138 else if (m.isSetter)
139 return "${t.name}\$${m.name}\$SetCvars"
140 else
141 return "${t.name}\$${m.name}\$Cvars"
142 }
143
144 //////////////////////////////////////////////////////////////////////////
145 // Reorder Vars
146 //////////////////////////////////////////////////////////////////////////
147
148 **
149 ** Once all the variables of a method body have been processed
150 ** into cvars fields, this method strips out any non-parameter
151 ** locals and optimally reorders them. We return the local
152 ** variable to use for the cvar reference itself.
153 **
154 private Void reorderVars()
155 {
156 // remove any non-parameter, locally defined variables
157 // from the list which are to moved into the cvars class
158 method.vars = method.vars.exclude |MethodVar v->Bool|
159 {
160 return !v.isParam && v.usedInClosure
161 }
162
163 // if in a closure, then the $cvars local variable was
164 // created previously by remapVarInClosure() while processing
165 // the enclosing method, so just look it up
166 if (inClosure)
167 {
168 cvarsLocal = method.vars[method.params.size]
169 if (cvarsLocal.name != "\$cvars")
170 throw err("Internal error", method.location)
171 }
172
173 // now insert the cvars (right after params so that we can
174 // use optimized register access such as ILOAD_2 for Java)
175 else
176 {
177 cvarsLocal = MethodVar.make(-1, cvars, "\$cvars")
178 method.vars.insert(method.params.size, cvarsLocal)
179 }
180
181 // re-index the registers
182 reg := method.isStatic ? 0 : 1
183 method.vars.each |MethodVar v| { v.register = reg++ }
184 }
185
186 //////////////////////////////////////////////////////////////////////////
187 // Insert Cvars Initialization
188 //////////////////////////////////////////////////////////////////////////
189
190 private Void insertCvarsInit()
191 {
192 // method(Foo x)
193 // {
194 // $cvars := $Cvars.make()
195 // $cvars.x = x // for all params
196 // ...
197 // }
198
199 // if not in closure then generate "$cvars = $Cvars.make()"
200 // constructor call; if in closure, then we've already
201 // generated "$cvars = this.$cvars" in remapVarInClosure()
202 // while processing the enclosng method
203 if (!inClosure)
204 {
205 local := LocalVarExpr.make(location, cvarsLocal)
206 local.ctype = cvars
207
208 ctorCall := CallExpr.makeWithMethod(location, null, cvarsCtor)
209
210 assign := BinaryExpr.makeAssign(local, ctorCall)
211
212 method.code.stmts.insert(0, assign.toStmt)
213 }
214
215 // init any params we are going to remap to cvars
216 method.vars.each |MethodVar var|
217 {
218 if (!var.isParam || var.cvarsField == null) return
219
220 lhs := fieldExpr(location, LocalVarExpr.make(location, cvarsLocal), var.cvarsField)
221
222 rhs := LocalVarExpr.make(location, var)
223 rhs.noRemapToCvars = true // don't want to replace this access with cvars field
224
225 assign := BinaryExpr.makeAssign(lhs, rhs)
226 method.code.stmts.insert(1, assign.toStmt)
227 }
228 }
229
230 //////////////////////////////////////////////////////////////////////////
231 // Remap Vars in Method
232 //////////////////////////////////////////////////////////////////////////
233
234 private Void remapVarsInMethod()
235 {
236 method.code.walkExpr |Expr expr->Expr|
237 {
238 switch (expr.id)
239 {
240 case ExprId.localVar: return remapLocalVar((LocalVarExpr)expr)
241 case ExprId.closure: closures.add((ClosureExpr)expr)
242 }
243 return expr
244 }
245 }
246
247 Expr remapLocalVar(LocalVarExpr local)
248 {
249 // x -> $cvars.x
250 if (local.var.cvarsField == null) return local
251 if (local.noRemapToCvars) return local
252 loc := local.location
253 return fieldExpr(loc, LocalVarExpr.make(loc, cvarsLocal), local.var.cvarsField)
254 }
255
256 //////////////////////////////////////////////////////////////////////////
257 // Remap Vars in Closures
258 //////////////////////////////////////////////////////////////////////////
259
260 private Void remapVarsInClosures()
261 {
262 // closures now contains all the ClosureExpr we found inside
263 // the current method body, now we need to walk them; this is
264 // also where we change isConst() to return false because
265 // capturing locals into a cvars is not thread safe
266 closures.each |ClosureExpr c|
267 {
268 remapVarInClosure(c)
269 markMutable(c)
270 }
271 }
272
273 private Void remapVarInClosure(ClosureExpr closure)
274 {
275 doCall := closure.cls.methodDef("doCall")
276
277
278 // walk closure implementation looking for cvarss
279 MethodVar cvarsLocal := null
280 doCall.code.walkExpr |Expr expr->Expr|
281 {
282 // if we've encountered a nested closure which uses cvars,
283 // then this closure must pass the cvars thru - we will
284 // process this closure fully in process() eventually because
285 // it should have been marked needsCvars in ResolveExpr
286 if (expr is ClosureExpr)
287 {
288 nested := (ClosureExpr)expr
289 if (nested.usesCvars && cvarsLocal == null)
290 cvarsLocal = MethodVar.make(-1, cvars, "\$cvars")
291 return expr
292 }
293
294 // check if it a local from my outer scope
295 local := expr as LocalVarExpr
296 if (local == null ||
297 local.var == null ||
298 local.var.cvarsField == null) return expr
299
300 // if I haven't yet allocated my own local to access
301 // the whole cvars instance, let's do that now
302 if (cvarsLocal == null)
303 cvarsLocal = MethodVar.make(-1, cvars, "\$cvars")
304
305 // replace "x" with "$cvars.x"
306 loc := local.location
307 return fieldExpr(loc, LocalVarExpr.make(loc, cvarsLocal), local.var.cvarsField)
308 }
309
310 // if no expressions within the closure use cvars,
311 // then our work here is done
312 if (cvarsLocal == null) return
313
314 // add cvars field to closure implementation class
315 loc := closure.location
316 field := FieldDef.make(loc, closure.cls)
317 field.name = "\$cvars"
318 field.fieldType = TypeRef.make(loc, cvars)
319 field.flags = syntheticFieldFlags
320 closure.cls.addSlot(field)
321
322 // add parameter to closure implementation constructor
323 ctor := closure.cls.methodDef("make")
324 param := ParamDef.make(loc, cvars, "\$cvars")
325 paramVar := MethodVar.makeForParam(ctor.params.size+1, param)
326 ctor.params.add(param)
327 ctor.vars.add(paramVar)
328
329 // set field in constructor
330 assign := BinaryExpr.makeAssign(
331 fieldExpr(loc, ThisExpr.make(loc), field),
332 LocalVarExpr.make(loc, paramVar))
333 ctor.code.stmts.insert(0, assign.toStmt)
334
335 // pass cvars instance to closure class constructor
336 closure.substitute.args.add(LocalVarExpr.make(loc, this.cvarsLocal))
337
338 // add local variable $cvars into doCall
339 cvarsLoad := BinaryExpr.makeAssign(
340 LocalVarExpr.make(loc, cvarsLocal),
341 fieldExpr(loc, ThisExpr.make(loc), field))
342 doCall.vars.insert(doCall.params.size, cvarsLocal)
343 doCall.vars.each |MethodVar v, Int i| { v.register = i+1 }
344 doCall.code.stmts.insert(0, cvarsLoad.toStmt)
345 }
346
347 //////////////////////////////////////////////////////////////////////////
348 // Outer This Field
349 //////////////////////////////////////////////////////////////////////////
350
351 **
352 ** This method is called by ClosureExpr to auto-generate the
353 ** implicit outer "this" field in the Closure's implementation
354 ** class:
355 ** - add $this field to closure's anonymous class
356 ** - add $this param to closure's make constructor
357 ** - set field from param in constructor
358 ** - update substitute to make sure this is passed to ctor
359 **
360 static CField makeOuterThisField(ClosureExpr closure)
361 {
362 loc := closure.location
363 thisType := closure.enclosingType
364 implType := closure.cls
365
366 // define outer this as "$this"
367 field := FieldDef.make(loc, implType)
368 field.name = "\$this"
369 field.flags = syntheticFieldFlags
370 field.fieldType = thisType
371 implType.addSlot(field)
372
373 // pass this to subtitute closure constructor - if this is a nested
374 // closure, then we have to get $this from it's own $this field
375 if (closure.enclosingClosure != null)
376 {
377 outerThis := closure.enclosingClosure.outerThisField
378 closure.substitute.args.add(fieldExpr(loc, ThisExpr.make(loc), outerThis))
379 }
380 else
381 {
382 // outer most closure just uses this
383 closure.substitute.args.add(ThisExpr.make(loc))
384 }
385
386 // add parameter to constructor
387 ctor := implType.methodDef("make")
388 param := ParamDef.make(loc, thisType, "\$this")
389 var := MethodVar.makeForParam(ctor.params.size+1, param)
390 ctor.params.add(param)
391 ctor.vars.add(var)
392
393 // set field in constructor
394 assign := BinaryExpr.makeAssign(fieldExpr(loc, ThisExpr.make(loc), field), LocalVarExpr.make(loc, var))
395 ctor.code.stmts.insert(0, assign.toStmt)
396
397 // we can longer assume this closure is thread safe
398 markMutable(closure)
399
400 return field
401 }
402
403 //////////////////////////////////////////////////////////////////////////
404 // Utils
405 //////////////////////////////////////////////////////////////////////////
406
407 private static Void markMutable(ClosureExpr c)
408 {
409 // if the closure captures any state, then we change the is
410 // isImmutable() method added in InitClosures to return false
411 ns := c.cls.ns
412 falseLiteral := LiteralExpr.make(c.location, ExprId.falseLiteral, ns.boolType, false)
413 c.cls.methodDef("isImmutable").code.stmts.first->expr = falseLiteral
414 }
415
416 private static FieldExpr fieldExpr(Location loc, Expr target, CField field)
417 {
418 // need to make sure all the synthetic field access is direct
419 fexpr := FieldExpr.make(loc, target, field)
420 fexpr.useAccessor = false
421 return fexpr
422 }
423
424 //////////////////////////////////////////////////////////////////////////
425 // Fields
426 //////////////////////////////////////////////////////////////////////////
427
428 private const static Int syntheticFieldFlags:= FConst.Internal | FConst.Storage | FConst.Synthetic
429
430 private MethodDef method // current method being processed
431 private Location location // method.location
432 private Bool inClosure // is method itself a closure doCall body
433 private TypeDef cvars // cvars class implementation
434 private MethodDef cvarsCtor // constructor for cvars class
435 private MethodVar cvarsLocal // local var referencing cvars in method body
436 private ClosureExpr[] closures // acc for closures found in method body
437
438 }