Fan

 

class

compiler::ClosureVars

sys::Obj
  compiler::CompilerSupport
    compiler::CompilerStep
      compiler::ClosureVars
//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   5 Mar 06  Brian Frank  Creation
//   4 Oct 06  Brian Frank  Port from Java to Fan
//

**
** ClosureVars (cvars) is used to pull local variables used by closures
** into an auto-generated anonymous class so that they can be
** used outside and inside the closure as well as have a lifetime
** once the method returns:
**   1) scan for methods with have locals used by their closures
**   3) define the cvars class
**   4) remove method vars which are stored in cvars
**   5) walk the method body
**      a) remap local var access to cvars field access
**      b) accumulate all the ClosureExprs
**   6) walk accumlated ClosureExprs in method body
**      a) add $cvars field to closure implementation class
**      b) add $cvars parameter to implementation class constructor
**      c) pass $cvars arg from method body to implementation constructor
**      d) walk implementation class code and remap local var access
**   7) decide if closure is thread-safe or not and mark isConst
**
** Note: this same process is used to process nested closure doCall methods
**   too; but they do things a bit differently since they always share the
**   outmost method's cvars.
**
class ClosureVars : CompilerStep
{

//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////

  new make(Compiler compiler)
    : super(compiler)
  {
    closures = ClosureExpr[,]
  }

//////////////////////////////////////////////////////////////////////////
// Run
//////////////////////////////////////////////////////////////////////////

  override Void run()
  {
    types.each |TypeDef t| { scanType(t) }
  }

  private Void scanType(TypeDef t)
  {
    t.methodDefs.each |MethodDef m|
    {
      if (m.needsCvars) process(m)
    }
  }

//////////////////////////////////////////////////////////////////////////
// Process
//////////////////////////////////////////////////////////////////////////

  private Void process(MethodDef method)
  {
    this.method     = method
    this.location   = method.location
    this.inClosure  = method.parentDef.isClosure && method.parentDef.closure.doCall === method
    this.cvars      = null
    this.cvarsCtor  = null
    this.cvarsLocal = null
    this.closures.clear

    defineCvarsClass
    reorderVars
    insertCvarsInit
    remapVarsInMethod
    remapVarsInClosures
  }

//////////////////////////////////////////////////////////////////////////
// Define Cvars Class
//////////////////////////////////////////////////////////////////////////

  **
  ** Walk the current method and create the cvars class, a default
  ** constructor, and a field for every local variable used inside
  ** closures.  If this is a closure itself, then we just reuse
  ** the cvar class of the outer most method.
  **
  private Void defineCvarsClass()
  {
    // if in a closure body, then reuse the enclosing
    // method's cvar class (which should have already
    // been defined)
    if (inClosure)
    {
      closure := method.parentDef.closure
      name := toCvarsTypeName(closure.enclosingType, closure.enclosingSlot)
      cvars = (TypeDef)pod.resolveType(name, true)
    }

    // define the Cvars class and generate no arg constructor
    else
    {
      // define type def
      name := toCvarsTypeName(method.parentDef, method)
      cvars = TypeDef(ns, location, method.parentDef.unit, name)
      cvars.flags = FConst.Internal | FConst.Synthetic
      cvars.base  = ns.objType
      addTypeDef(cvars)

      // generate no arg constructor
      cvarsCtor = DefaultCtor.addDefaultCtor(cvars, FConst.Internal | FConst.Synthetic)
    }

    // generate the fields used to store each local
    method.vars.each |MethodVar var|
    {
      if (var.usedInClosure)
      {
        f := FieldDef(location, cvars)
        f.name      = "${var.name}\$${cvars.slots.size}"
        f.fieldType = var.ctype
        f.flags     = syntheticFieldFlags
        cvars.addSlot(f)
        var.cvarsField = f
      }
    }
  }

  private static Str toCvarsTypeName(TypeDef t, SlotDef s)
  {
    m := s as MethodDef
    if (m != null)
    {
      if (m.isGetter) return "${t.name}\$${s.name}\$GetCvars"
      if (m.isSetter) return "${t.name}\$${s.name}\$SetCvars"
    }
    return "${t.name}\$${s.name}\$Cvars"
  }

//////////////////////////////////////////////////////////////////////////
// Reorder Vars
//////////////////////////////////////////////////////////////////////////

  **
  ** Once all the variables of a method body have been processed
  ** into cvars fields, this method strips out any non-parameter
  ** locals and optimally reorders them.  We return the local
  ** variable to use for the cvar reference itself.
  **
  private Void reorderVars()
  {
    // remove any non-parameter, locally defined variables
    // from the list which are to moved into the cvars class
    method.vars = method.vars.exclude |MethodVar v->Bool|
    {
      return !v.isParam && v.usedInClosure && !v.isCatchVar
    }

    // if in a closure, then the $cvars local variable was
    // created previously by remapVarInClosure() while processing
    // the enclosing method, so just look it up
    if (inClosure)
    {
      cvarsLocal = method.vars[method.params.size]
      if (cvarsLocal.name != "\$cvars")
        throw err("Internal error", method.location)
    }

    // now insert the cvars (right after params so that we can
    // use optimized register access such as ILOAD_2 for Java)
    else
    {
      cvarsLocal = MethodVar(-1, cvars, "\$cvars")
      method.vars.insert(method.params.size, cvarsLocal)
    }

    // re-index the registers
    reg := method.isStatic ? 0 : 1
    method.vars.each |MethodVar v| { v.register = reg++ }
  }

//////////////////////////////////////////////////////////////////////////
// Insert Cvars Initialization
//////////////////////////////////////////////////////////////////////////

  private Void insertCvarsInit()
  {
    //  method(Foo x)
    //  {
    //    $cvars := $Cvars.make()
    //    $cvars.x = x // for all params
    //    ...
    //  }

    // if not in closure then generate "$cvars = $Cvars.make()"
    // constructor call; if in closure, then we've already
    // generated "$cvars = this.$cvars" in remapVarInClosure()
    // while processing the enclosng method
    if (!inClosure)
    {
      local := LocalVarExpr(location, cvarsLocal)
      local.ctype = cvars

      ctorCall := CallExpr.makeWithMethod(location, null, cvarsCtor)

      assign := BinaryExpr.makeAssign(local, ctorCall)

      method.code.stmts.insert(0, assign.toStmt)
    }

    // init any params we are going to remap to cvars
    method.vars.each |MethodVar var|
    {
      if (!var.isParam || var.cvarsField == null) return

      lhs := fieldExpr(location, LocalVarExpr(location, cvarsLocal), var.cvarsField)

      rhs := LocalVarExpr(location, var)
      rhs.noRemapToCvars = true // don't want to replace this access with cvars field

      assign := BinaryExpr.makeAssign(lhs, rhs)
      method.code.stmts.insert(1, assign.toStmt)
    }
  }

//////////////////////////////////////////////////////////////////////////
// Remap Vars in Method
//////////////////////////////////////////////////////////////////////////

  private Void remapVarsInMethod()
  {
    method.code.walkExpr |Expr expr->Expr|
    {
      switch (expr.id)
      {
        case ExprId.localVar: return remapLocalVar((LocalVarExpr)expr)
        case ExprId.closure:  closures.add((ClosureExpr)expr)
      }
      return expr
    }
  }

  Expr remapLocalVar(LocalVarExpr local)
  {
    // x -> $cvars.x
    if (local.var.cvarsField == null) return local
    if (local.noRemapToCvars) return local
    loc := local.location
    return fieldExpr(loc, LocalVarExpr(loc, cvarsLocal), local.var.cvarsField)
  }

//////////////////////////////////////////////////////////////////////////
// Remap Vars in Closures
//////////////////////////////////////////////////////////////////////////

  private Void remapVarsInClosures()
  {
    // closures now contains all the ClosureExpr we found inside
    // the current method body, now we need to walk them; this is
    // also where we change isImmutable() to return false because
    // capturing locals into a cvars is not thread safe
    closures.each |ClosureExpr c|
    {
      if (remapVarInClosure(c))
        markMutable(c)
    }
  }

  **
  ** Remap local variables to cvars.  Return false if no
  ** locals are captured in the given closure.
  **
  private Bool remapVarInClosure(ClosureExpr closure)
  {
    doCall := closure.cls.methodDef("doCall")

    // walk closure implementation looking for cvars
    MethodVar? cvarsLocal := null
    doCall.code.walkExpr |Expr expr->Expr|
    {
      // if we've encountered a nested closure which uses cvars,
      // then this closure must pass the cvars thru - we will
      // process this closure fully in process() eventually because
      // it should have been marked needsCvars in ResolveExpr
      if (expr is ClosureExpr)
      {
        nested := (ClosureExpr)expr
        if (nested.usesCvars && cvarsLocal == null)
          cvarsLocal = MethodVar(-1, cvars, "\$cvars")
        return expr
      }

      // check if it a local from my outer scope
      local := expr as LocalVarExpr
      if (local == null ||
          local.var == null ||
          local.var.cvarsField == null) return expr

      // if I haven't yet allocated my own local to access
      // the whole cvars instance, let's do that now
      if (cvarsLocal == null)
        cvarsLocal = MethodVar(-1, cvars, "\$cvars")

      // replace "x" with "$cvars.x"
      loc := local.location
      return fieldExpr(loc, LocalVarExpr(loc, cvarsLocal), local.var.cvarsField)
    }

    // if no expressions within the closure use cvars,
    // then our work here is done
    if (cvarsLocal == null) return false

    // add cvars field to closure implementation class
    loc := closure.location
    field := FieldDef(loc, closure.cls)
    field.name      = "\$cvars"
    field.fieldType = TypeRef(loc, cvars)
    field.flags     = syntheticFieldFlags
    closure.cls.addSlot(field)

    // add parameter to closure implementation constructor
    ctor := closure.cls.methodDef("make")
    param := ParamDef(loc, cvars, "\$cvars")
    paramVar := MethodVar.makeForParam(ctor.params.size+1, param, param.paramType)
    ctor.params.add(param)
    ctor.vars.add(paramVar)

    // set field in constructor
    assign := BinaryExpr.makeAssign(
      fieldExpr(loc, ThisExpr(loc), field),
      LocalVarExpr(loc, paramVar))
    ctor.code.stmts.insert(0, assign.toStmt)

    // pass cvars instance to closure class constructor
    closure.substitute.args.add(LocalVarExpr(loc, this.cvarsLocal))

    // add local variable $cvars into doCall
    cvarsLoad := BinaryExpr.makeAssign(
      LocalVarExpr(loc, cvarsLocal),
      fieldExpr(loc, ThisExpr(loc), field))
    doCall.vars.insert(doCall.params.size, cvarsLocal)
    doCall.vars.each |MethodVar v, Int i| { v.register = i+1 }
    doCall.code.stmts.insert(0, cvarsLoad.toStmt)
    return true
  }

//////////////////////////////////////////////////////////////////////////
// Outer This Field
//////////////////////////////////////////////////////////////////////////

  **
  ** 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
  **   - set field from param in constructor
  **   - update substitute to make sure this is passed to ctor
  **
  static CField makeOuterThisField(ClosureExpr closure)
  {
    loc      := closure.location
    thisType := closure.enclosingType
    implType := closure.cls

    // define outer this as "$this"
    field := FieldDef(loc, implType)
    field.name  = "\$this"
    field.flags = syntheticFieldFlags
    field.fieldType = thisType
    implType.addSlot(field)

    // pass this to subtitute closure constructor - if this is a nested
    // closure, then we have to get $this from it's own $this field
    if (closure.enclosingClosure != null)
    {
      outerThis := closure.enclosingClosure.outerThisField
      closure.substitute.args.add(fieldExpr(loc, ThisExpr(loc), outerThis))
    }
    else
    {
      // outer most closure just uses this
      closure.substitute.args.add(ThisExpr(loc))
    }

    // add parameter to constructor
    ctor  := implType.methodDef("make")
    param := ParamDef(loc, thisType, "\$this")
    var   := MethodVar.makeForParam(ctor.params.size+1, param, param.paramType)
    ctor.params.add(param)
    ctor.vars.add(var)

    // set field in constructor
    assign := BinaryExpr.makeAssign(fieldExpr(loc, ThisExpr(loc), field), LocalVarExpr(loc, var))
    ctor.code.stmts.insert(0, assign.toStmt)

    // we can longer assume this closure is thread safe
    markMutable(closure)

    return field
  }

//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////

  private static Void markMutable(ClosureExpr c)
  {
    // if the closure captures any state, then we change the is
    // isImmutable() method added in InitClosures to return false
    ns := c.cls.ns
    falseLiteral := LiteralExpr(c.location, ExprId.falseLiteral, ns.boolType, false)
    c.cls.methodDef("isImmutable").code.stmts.first->expr = falseLiteral
  }

  private static FieldExpr fieldExpr(Location loc, Expr target, CField field)
  {
    // need to make sure all the synthetic field access is direct
    fexpr := FieldExpr(loc, target, field)
    fexpr.useAccessor = false
    return fexpr
  }

//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////

  private const static Int syntheticFieldFlags:= FConst.Internal | FConst.Storage | FConst.Synthetic

  private MethodDef? method         // current method being processed
  private Location? location        // method.location
  private Bool inClosure            // is method itself a closure doCall body
  private TypeDef? cvars            // cvars class implementation
  private MethodDef? cvarsCtor      // constructor for cvars class
  private MethodVar? cvarsLocal     // local var referencing cvars in method body
  private ClosureExpr[] closures    // acc for closures found in method body

}