logo

class

compiler::ClosureVars

sys::Obj
  compiler::CompilerSupport
    compiler::CompilerStep
      compiler::ClosureVars
   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  }