summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xcompiler/ast.nim3
-rwxr-xr-xcompiler/astalgo.nim4
-rw-r--r--compiler/lambdalifting.nim570
-rwxr-xr-xcompiler/transf.nim37
-rwxr-xr-xtests/run/tclosure.nim12
5 files changed, 411 insertions, 215 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index d9ec70450..834f5efb5 100755
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -257,6 +257,9 @@ const
   sfShadowed* = sfInnerProc
     # a variable that was shadowed in some inner scope
 
+  sfByCopy* = sfBorrow
+    # a variable is to be captured by value in a closure
+
 const
   # getting ready for the future expr/stmt merge
   nkWhen* = nkWhenStmt
diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim
index 861642594..7b5c5be25 100755
--- a/compiler/astalgo.nim
+++ b/compiler/astalgo.nim
@@ -849,6 +849,10 @@ proc IdNodeTablePut(t: var TIdNodeTable, key: PIdObj, val: PNode) =
     IdNodeTableRawInsert(t.data, key, val)
     inc(t.counter)
 
+iterator pairs*(t: TIdNodeTable): tuple[key: PIdObj, val: PNode] =
+  for i in 0 .. high(t.data):
+    if not isNil(t.data[i].key): yield (t.data[i].key, t.data[i].val)
+
 proc initIITable(x: var TIITable) = 
   x.counter = 0
   newSeq(x.data, startSize)
diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim
index c62c212fb..7a21428a8 100644
--- a/compiler/lambdalifting.nim
+++ b/compiler/lambdalifting.nim
@@ -10,27 +10,157 @@
 # This include file implements lambda lifting for the transformator.
 # included from transf.nim
 
+import 
+  intsets, strutils, lists, options, ast, astalgo, trees, treetab, msgs, os, 
+  idents, renderer, types, magicsys, rodread
+
+discard """
+  The basic approach is that captured vars need to be put on the heap and
+  that the calling chain needs to be explicitely modelled. Things to consider:
+  
+  proc a =
+    var v = 0
+    proc b =
+      var w = 2
+      
+      for x in 0..3:
+        proc c = capture v, w, x
+        c()
+    b()
+    
+    for x in 0..4:
+      proc d = capture x
+      d()
+  
+  Needs to be translated into:
+    
+  proc a =
+    var cl: *
+    new cl
+    cl.v = 0
+    
+    proc b(cl) =
+      var bcl: *
+      new bcl
+      bcl.w = 2
+      bcl.up = cl
+      
+      for x in 0..3:
+        var bcl2: *
+        new bcl2
+        bcl2.up = bcl
+        bcl2.x = x
+      
+        proc c(cl) = capture cl.up.up.v, cl.up.w, cl.x
+        c(bcl2)
+      
+      c(bcl)
+    
+    b(cl)
+    
+    for x in 0..4:
+      var acl2: *
+      new acl2
+      acl2.x = x
+      proc d(cl) = capture cl.x
+      d(acl2)
+    
+  Closures as interfaces:
+  
+  proc outer: T =
+    var captureMe: TObject # value type required for efficiency
+    proc getter(): int = result = captureMe.x
+    proc setter(x: int) = captureMe.x = x
+    
+    result = (getter, setter)
+    
+  Is translated to:
+  
+  proc outer: T =
+    var cl: *
+    new cl
+    
+    proc getter(cl): int = result = cl.captureMe.x
+    proc setter(cl: *, x: int) = cl.captureMe.x = x
+    
+    result = ((cl, getter), (cl, setter))
+    
+    
+  For 'byref' capture, the outer proc needs to access the captured var through
+  the indirection too. For 'bycopy' capture, the outer proc accesses the var
+  not through the indirection.
+    
+  Possible optimizations: 
+  
+  1) If the closure contains a single 'ref' and this
+  reference is not re-assigned (check ``sfAddrTaken`` flag) make this the
+  closure. This is an important optimization if closures are used as 
+  interfaces.
+  2) If the closure does not escape, put it onto the stack, not on the heap.
+  3) Dataflow analysis would help to eliminate the 'up' indirections.
+  4) If the captured var is not actually used in the outer proc (common?),
+  put it into an inner proc.
+
+"""
+
 const
-  declarativeDefs = {nkProcDef, nkMethodDef, nkIteratorDef, nkConverterDef}
-  procDefs = nkLambdaKinds + declarativeDefs
+  declarativeDefs* = {nkProcDef, nkMethodDef, nkIteratorDef, nkConverterDef}
+  procDefs* = nkLambdaKinds + declarativeDefs
+  upName* = ":up" # field name for the 'up' reference
+  envName* = ":env"
 
 type
-  TCapture = seq[PSym]
-  
   TLLShared {.final.} = object
-    transformedInnerProcs: TIntSet
-    c: PTransf
+    upField: PSym
   
-  TLLContext {.final.} = object
-    outerProc, innerProc: PSym
-    mapping: TIdNodeTable     # mapping from symbols to nodes
-    shared: ref TLLShared
+  PInnerContext = ref TInnerContext
+  POuterContext = ref TOuterContext
+  PLLShared = ref TLLShared
+  PBlock = ref TBlock
   
-  PLLContext = ref TLLContext
+  TBlock {.final.} = object
+    body: PNode
+    closure: PSym
+    used: bool
+  
+  TInnerContext {.final.} = object
+    fn: PSym
+    closureParam: PSym
+    localsToAccess: TIdNodeTable
+    up: POuterContext         # used for chaining
+    levelsUp: int             # counts how many "up levels" are accessed
+    tup: PType
+    
+  TOuterContext {.final.} = object
+    fn: PSym
+    currentBlock: PNode
+    capturedVars: TIntSet
+    localsToEnclosingScope: TIdNodeTable
+    localsToAccess: TIdNodeTable
+    lambdasToEnclosingScope: TIdNodeTable
+  
+    shared: PLLShared
+    up: POuterContext
 
-proc indirectAccess(a, b: PSym, info: TLineInfo): PNode = 
+proc newOuterContext(fn: PSym, shared: PLLShared, 
+                     up: POuterContext = nil): POuterContext =
+  new(result)
+  result.fn = fn
+  result.shared = shared
+  result.capturedVars = initIntSet()
+  initIdNodeTable(result.localsToAccess)
+  initIdNodeTable(result.localsToEnclosingScope)
+  initIdNodeTable(result.lambdasToEnclosingScope)
+  
+proc newInnerContext(fn: PSym, outer: POuterContext): PInnerContext =
+  new(result)
+  result.up = outer
+  result.fn = fn
+  initIdNodeTable(result.localsToAccess)
+  
+proc indirectAccess(a: PNode, b: PSym, info: TLineInfo): PNode = 
   # returns a[].b as a node
-  let x = newSymNode(a)
+  let x = a
   var deref = newNodeI(nkHiddenDeref, info)
   deref.typ = x.typ.sons[0]
   
@@ -41,54 +171,20 @@ proc indirectAccess(a, b: PSym, info: TLineInfo): PNode =
   addSon(result, newSymNode(field))
   result.typ = field.typ
 
-proc Capture(cap: var TCapture, s: PSym) = 
-  for x in cap:
-    if x.name.id == s.name.id: return
-  cap.add(s)
+proc indirectAccess(a, b: PSym, info: TLineInfo): PNode =
+  result = indirectAccess(newSymNode(a), b, info)
 
-proc captureToTuple(cap: TCapture, owner: PSym): PType =
-  result = newType(tyTuple, owner)
-  result.n = newNodeI(nkRecList, owner.info)
-  for s in cap:
-    var field = newSym(skField, s.name, s.owner)
-    
-    let typ = s.typ
-    field.typ = typ
-    field.position = sonsLen(result)
-    
-    addSon(result.n, newSymNode(field))
-    addSon(result, typ)
-
-proc interestingVar(s: PSym): bool {.inline.} =
-  result = s.kind in {skVar, skLet, skTemp, skForVar, skParam, skResult} and
-    sfGlobal notin s.flags
-
-proc gatherVars(c: PLLContext, n: PNode, cap: var TCapture) = 
-  # gather used vars for closure generation into 'cap'
-  case n.kind
-  of nkSym:
-    var s = n.sym
-    if interestingVar(s) and c.innerProc.id != s.owner.id:
-      # we need to compute the path here:
-      var 
-      
-      #echo "captured: ", s.name.s
-      Capture(cap, s)
-  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
-  else:
-    for i in countup(0, sonsLen(n) - 1): 
-      gatherVars(c, n.sons[i], cap)
+proc newCall(a, b: PSym): PNode =
+  result = newNodeI(nkCall, a.info)
+  result.add newSymNode(a)
+  result.add newSymNode(b)
 
-proc replaceVars(c: PTransf, n: PNode, outerProc, env: PSym) = 
-  for i in countup(0, safeLen(n) - 1):
-    let a = n.sons[i]
-    if a.kind == nkSym:
-      let s = a.sym
-      if interestingVar(s) and outerProc == s.owner:
-        # access through the closure param:
-        n.sons[i] = indirectAccess(env, s, n.info)
-    else:
-      replaceVars(c, a, outerProc, env)
+proc addField(tup: PType, s: PSym) =
+  var field = newSym(skField, s.name, s.owner)
+  field.typ = s.typ
+  field.position = sonsLen(tup)
+  addSon(tup.n, newSymNode(field))
+  addSon(tup, s.typ)
 
 proc addHiddenParam(routine: PSym, param: PSym) =
   var params = routine.ast.sons[paramsPos]
@@ -97,174 +193,248 @@ proc addHiddenParam(routine: PSym, param: PSym) =
   #echo "produced environment: ", param.id, " for ", routine.name.s
 
 proc isInnerProc(s, outerProc: PSym): bool {.inline.} =
-  result = s.kind in {skProc, skMacro, skIterator, skMethod, skConverter} and
+  result = s.kind in {skProc, skIterator, skMethod, skConverter} and
     s.owner == outerProc and not isGenericRoutine(s)
   #s.typ.callConv == ccClosure
 
-proc searchForInnerProcs(c: PTransf, n: PNode, outerProc: PSym,
-                         cap: var TCapture) =
+proc captureVar(o: POuterContext, i: PInnerContext, local: PSym,
+                info: TLineInfo) =
+  discard """
+    Consider:
+      var x = 0
+      var y = 2
+      capture x, y
+      
+      block:
+        var z = 3
+        capture z
+      
+    We need to merge x, y into a closure, but not z! 
+  """
+  # we need to remember which outer closure belongs to this lambda; we also
+  # use this check to prevent multiple runs over the same inner proc:
+  if IdNodeTableGet(o.lambdasToEnclosingScope, i.fn) != nil: return
+  IdNodeTablePut(o.lambdasToEnclosingScope, i.fn, o.currentBlock)
+
+  if IdNodeTableGet(i.localsToAccess, local) != nil: return
+  if i.closureParam == nil:
+    var cp = newSym(skParam, getIdent(upname), i.fn)
+    cp.info = i.fn.info
+    incl(cp.flags, sfFromGeneric)
+    i.tup = newType(tyTuple, i.fn)
+    i.tup.n = newNodeI(nkRecList, i.fn.info)
+    cp.typ = i.tup
+    i.closureParam = cp
+  addField(i.tup, local)
+  var it = i.up
+  var access = newSymNode(i.closureParam)
+  var levelsUp = 0
+  while it.fn.id != local.owner.id:
+    access = indirectAccess(access, o.shared.upField, info)
+    it = it.up
+    assert it != nil
+    inc levelsUp
+  i.levelsUp = max(i.levelsUp, levelsUp)
+  access = indirectAccess(access, local, info)
+  IdNodeTablePut(i.localsToAccess, local, access)
+  incl(o.capturedVars, local.id)
+
+proc interestingVar(s: PSym): bool {.inline.} =
+  result = s.kind in {skVar, skLet, skTemp, skForVar, skParam, skResult} and
+    sfGlobal notin s.flags
+
+proc gatherVars(o: POuterContext, i: PInnerContext, n: PNode) = 
+  # gather used vars for closure generation
   case n.kind
   of nkSym:
-    if isInnerProc(n.sym, outerProc):
-      gatherVars(c, n.sym.getBody, outerProc, cap)
+    var s = n.sym
+    if interestingVar(s) and i.fn.id != s.owner.id:
+      captureVar(o, i, s, n.info)
+      #echo "captured: ", s.name.s
   of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
   else:
-    for i in 0.. <len(n):
-      searchForInnerProcs(c, n.sons[i], outerProc, cap)
-  
-proc makeClosure(c: PTransf, prc, env: PSym, info: TLineInfo): PNode =
+    for k in countup(0, sonsLen(n) - 1): 
+      gatherVars(o, i, n.sons[k])
+
+proc makeClosure(prc, env: PSym, info: TLineInfo): PNode =
   result = newNodeIT(nkClosure, info, prc.typ)
   result.add(newSymNode(prc))
   if env == nil:
     result.add(newNodeIT(nkNilLit, info, getSysType(tyNil)))
   else:
     result.add(newSymNode(env))
-  
-proc transformInnerProcs(c: PTransf, n: PNode, outerProc, env: PSym) =
+
+proc transformInnerProc(o: POuterContext, i: PInnerContext, n: PNode): PNode =
   case n.kind
+  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
   of nkSym:
-    let innerProc = n.sym
-    if isInnerProc(innerProc, outerProc) and not 
-        containsOrIncl(c.transformedInnerProcs, innerProc.id):
-      if env == nil:
-        innerProc.ast.sons[bodyPos] = transform(c, innerProc.getBody).pnode
-      else:
-        # inner proc could capture outer vars:
-        var param = newTemp(c, env.typ, n.info)
-        param.kind = skParam
-        
-        # recursive calls go through (f, hiddenParam):
-        IdNodeTablePut(c.transCon.mapping, innerProc, 
-                       makeClosure(c, innerProc, param, n.info))
-        # access all non-local vars through the 'env' param:
-        replaceVars(c, innerProc.getBody, outerProc, param)
+    if n.sym == i.fn: 
+      # recursive calls go through (lambda, hiddenParam):
+      assert i.closureParam != nil
+      result = makeClosure(n.sym, i.closureParam, n.info)
+    else:
+      # captured symbol?
+      result = IdNodeTableGet(i.localsToAccess, n.sym)
+  of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef,
+     nkIteratorDef, nkLambdaKinds:
+    # don't recurse here:
+    nil
+  else:
+    for j in countup(0, sonsLen(n) - 1):
+      let x = transformInnerProc(o, i, n.sons[j])
+      if x != nil: n.sons[j] = x
 
-        innerProc.ast.sons[bodyPos] = transform(c, innerProc.getBody).pnode
-        addHiddenParam(innerProc, param)
-        
-        # 'anon' should be replaced by '(anon, env)' in the outer proc:
-        IdNodeTablePut(c.transCon.mapping, innerProc, 
-                       makeClosure(c, innerProc, env, n.info))
-  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
+proc searchForInnerProcs(o: POuterContext, n: PNode) =
+  case n.kind
+  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: 
+    nil
+  of nkSym:
+    if isInnerProc(n.sym, o.fn):
+      var inner = newInnerContext(n.sym, o)
+      let body = n.sym.getBody
+      gatherVars(o, inner, body)
+      let ti = transformInnerProc(o, inner, body)
+      if ti != nil: n.sym.ast.sons[bodyPos] = ti
+  of nkWhileStmt, nkForStmt, nkParForStmt, nkBlockStmt:
+    # some nodes open a new scope, so they are candidates for the insertion
+    # of closure creation; however for simplicity we merge closures between
+    # branches, in fact, only loops bodies are of interest here as only they 
+    # yield observable changes in semantics. For Zahary we also
+    # include ``nkBlock``.
+    var body = n.len-1
+    for i in countup(0, body - 1): searchForInnerProcs(o, n.sons[i])
+    # special handling for the loop body:
+    let oldBlock = o.currentBlock
+    var ex = newNodeI(nkStmtList, n.info)
+    ex.add(emptyNode)
+    ex.add(n.sons[body])
+    o.currentBlock = ex
+    searchForInnerProcs(o, n.sons[body])
+    n.sons[body] = ex
+    o.currentBlock = oldBlock
+  of nkVarSection, nkLetSection:
+    # we need to compute a mapping var->declaredBlock. Note: The definition
+    # counts, not the block where it is captured!
+    for i in countup(0, sonsLen(n) - 1):
+      var it = n.sons[i]
+      if it.kind == nkCommentStmt: nil
+      elif it.kind == nkIdentDefs:
+        if it.sons[0].kind != nkSym: InternalError(it.info, "transformOuter")
+        IdNodeTablePut(o.localsToEnclosingScope, it.sons[0].sym, o.currentBlock)
+      elif it.kind == nkVarTuple:
+        var L = sonsLen(it)
+        for j in countup(0, L-3):
+          IdNodeTablePut(o.localsToEnclosingScope, it.sons[j].sym, 
+                         o.currentBlock)
+      else:
+        InternalError(it.info, "transformOuter")
+  of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef, 
+     nkIteratorDef, nkLambdaKinds: 
+    # don't recurse here:
+    # XXX recurse here and setup 'up' pointers
+    nil
   else:
-    for i in 0.. <len(n):
-      transformInnerProcs(c, n.sons[i], outerProc, env)
-  
-template checkInvariant(n: PNode, s: PSym) =
-  when false:
-    if s.ast != n:
-      echo renderTree(s.ast)
-      echo " -------------- "
-      echo n.renderTree
-    assert s.ast == n
+    for i in countup(0, sonsLen(n) - 1):
+      searchForInnerProcs(o, n.sons[i])
 
-proc newCall(a, b: PSym): PNode =
-  result = newNodeI(nkCall, a.info)
-  result.add newSymNode(a)
-  result.add newSymNode(b)
+proc newAsgnStmt(le, ri: PNode): PNode = 
+  result = newNodeI(nkFastAsgn, ri.info)
+  result.add(le)
+  result.add(ri)
+
+proc addVar*(father, v: PNode) = 
+  var vpart = newNodeI(nkIdentDefs, v.info)
+  addSon(vpart, v)
+  addSon(vpart, ast.emptyNode)
+  addSon(vpart, ast.emptyNode)
+  addSon(father, vpart)
 
-proc createEnvStmt(c: PTransf, varList: TCapture, env: PSym): PTransNode =
-  # 'varlist' can contain parameters or variables. We don't eliminate yet
-  # local vars that end up in an environment. This could even be a for loop
-  # var!
-  result = newTransNode(nkStmtList, env.info, 0)
+proc generateClosureCreation(o: POuterContext, scope: PNode): PNode =
+  # add assignment if its a parameter that has been captured:
+  var env = newSym(skParam, getIdent(envName), o.fn)
+  env.info = scope.info
+  env.typ = newType(tyTuple, o.fn)
+  env.typ.n = newNodeI(nkRecList, scope.info)
+
+  result = newNodeI(nkStmtList, env.info)
   var v = newNodeI(nkVarSection, env.info)
   addVar(v, newSymNode(env))
-  result.add(v.ptransNode)
+  result.add(v)
   # add 'new' statement:
-  result.add(newCall(getSysSym"internalNew", env).ptransnode)
+  result.add(newCall(getSysSym"internalNew", env))
   
   # add assignment statements:
-  for v in varList:
-    let fieldAccess = indirectAccess(env, v, env.info)
-    if v.kind == skParam:
-      # add ``env.param = param``
-      result.add(newAsgnStmt(c, fieldAccess, newSymNode(v).ptransNode))
-    IdNodeTablePut(c.transCon.mapping, v, fieldAccess)
-  
-proc transformProcFin(c: PTransf, n: PNode, s: PSym): PTransNode =
-  if n.kind in nkLambdaKinds:
-    # for lambdas we transformed 'n.sons[bodyPos]', but not 'ast.n[bodyPos]'!
-    s.ast.sons[bodyPos] = n.sons[bodyPos]
-  else:
-    assert s.ast == n
-  
-  if n.kind == nkMethodDef: methodDef(s, false)
-  # should 's' be replaced by a tuple ('s', env)?
-  var tc = c.transCon
-  var repl: PNode = nil
-  while tc != nil:
-    repl = IdNodeTableGet(tc.mapping, s)
-    if repl != nil: break
-    tc = tc.next
-  if repl != nil:
-    result = PTransNode(repl)
-  else:
-    result = PTransNode(n)
+  for v, scope2 in pairs(o.localsToEnclosingScope):
+    if scope2 == scope:
+      let local = PSym(v)
+      addField(env.typ, local)
+      let fieldAccess = indirectAccess(env, local, env.info)
+      if sfByCopy in local.flags or local.kind == skParam:
+        # add ``env.param = param``
+        result.add(newAsgnStmt(fieldAccess, newSymNode(local)))
+      IdNodeTablePut(o.localsToAccess, local, fieldAccess)
+  # XXX add support for 'up' references!
 
-proc transformProc(c: PTransf, n: PNode): PTransNode =
-  # don't process generics:
-  if n.sons[genericParamsPos].kind != nkEmpty:
-    return PTransNode(n)
+proc transformOuterProc(o: POuterContext, n: PNode): PNode =
+  case n.kind
+  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
+  of nkSym:
+    var local = n.sym
+    var envBlock = IdNodeTableGet(o.lambdasToEnclosingScope, local)
+    if envBlock != nil:
+      # we need to replace the lambda with '(lambda, env)': 
+      let a = envBlock.sons[0]
+      assert a.kind == nkStmtList
+      assert a.sons[0].kind == nkVarSection
+      assert a.sons[0].sons[0].kind == nkIdentDefs
+      var env = a.sons[0].sons[0].sons[0].sym
+      return makeClosure(local, env, n.info)
   
-  var s = n.sons[namePos].sym
-  var body = s.getBody
-  if body.kind == nkEmpty or n.sons[bodyPos].kind == nkEmpty or
-     containsOrIncl(c.transformedInnerProcs, s.id):
-    return PTransNode(n)
+    if not o.capturedVars.contains(local.id): return
+    var scope = IdNodeTableGet(o.localsToEnclosingScope, local)
+    if scope == nil: return
     
-  checkInvariant(n, s)
-  
-  if not containsNode(body, procDefs) and s.typ.callConv != ccClosure:
-    # fast path: no inner procs, so no closure needed:
-    n.sons[bodyPos] = PNode(transform(c, body))
-    checkInvariant(n, s)
-    return transformProcFin(c, n, s)
-
-  # create environment:
-  var cap: TCapture = @[]
-  searchForInnerProcs(c, body, s, cap)
-
-  var envType = newType(tyRef, s)
-  addSon(envType, captureToTuple(cap, s))
-  if s.typ.callConv == ccClosure:
-    addHiddenParam(s, newTemp(c, envType, n.info))
-    IdNodeTablePut(c.transCon.mapping, s, 
-                   makeClosure(c, s, nil, n.info))
-  
-  if cap.len == 0:
-    # fast path: no captured variables, so no closure needed:
-    transformInnerProcs(c, body, s, nil)
-    n.sons[bodyPos] = PNode(transform(c, body))
-    return transformProcFin(c, n, s)
-  
-  # Currently we always do a heap allocation. A simple escape analysis
-  # could turn the closure into a stack allocation. Later versions might 
-  # implement that. This would require backend changes too though.
-  var envSym = newTemp(c, envType, s.info)
-  
-  var newBody = createEnvStmt(c, cap, envSym)
-  # modify any local proc to gain a new parameter; this also creates the
-  # mapping entries that turn (localProc) into (localProc, env):
-  transformInnerProcs(c, body, s, envSym)
-
-  # now we can transform 'body' as all rewriting entries have been created:
-  newBody.add(transform(c, body))
-  n.sons[bodyPos] = newBody.pnode
-  result = transformProcFin(c, n, s)
-  checkInvariant(n, s)
+    assert scope.kind == nkStmtList
+    if scope.sons[0].kind == nkEmpty:
+      # change the empty node to contain the closure construction; we need to
+      # gather all variables here that belong to the closure which is a bit
+      # expensive:
+      scope.sons[0] = generateClosureCreation(o, scope)
+    
+    # change 'local' to 'closure.local', unless it's a 'byCopy' variable:
+    if sfByCopy notin local.flags:
+      result = IdNodeTableGet(o.localsToAccess, local)
+      assert result != nil
+    # else it is captured by copy and this means that 'outer' should continue
+    # to access the local as a local.
+  of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef, 
+     nkIteratorDef, nkLambdaKinds: 
+    # don't recurse here:
+    nil
+  else:
+    for i in countup(0, sonsLen(n) - 1):
+      let x = transformOuterProc(o, n.sons[i])
+      if x != nil: n.sons[i] = x
 
-proc generateThunk(c: PTransf, prc: PNode, dest: PType): PNode =
-  ## Converts 'prc' into '(thunk, nil)' so that it's compatible with
-  ## a closure.
-  
-  # we cannot generate a proper thunk here for GC-safety reasons (see internal
-  # documentation):
-  result = newNodeIT(nkClosure, prc.info, dest)
-  var conv = newNodeIT(nkHiddenStdConv, prc.info, dest)
-  conv.add(emptyNode)
-  conv.add(prc)
-  result.add(conv)
-  result.add(newNodeIT(nkNilLit, prc.info, getSysType(tyNil)))
+proc liftLambdas(fn: PSym, shared: PLLShared, body: PNode): PNode =
+  if body.kind == nkEmpty:
+    # ignore forward declaration:
+    result = body
+  elif not containsNode(body, procDefs) and fn.typ.callConv != ccClosure:
+    # fast path: no inner procs, so no closure needed:
+    result = body
+  else:
+    var o = newOuterContext(fn, shared)
+    searchForInnerProcs(o, body)
+    result = transformOuterProc(o, body)
+    if result == nil: result = body
   
+# XXX should 's' be replaced by a tuple ('s', env)?
 
+proc liftLambdas*(n: PNode): PNode =
+  assert n.kind in procDefs
+  var s = n.sons[namePos].sym
+  var shared: ref TLLShared
+  new shared
+  shared.upField = newSym(skField, upName.getIdent, s)
+  result = liftLambdas(s, shared, s.getBody)
diff --git a/compiler/transf.nim b/compiler/transf.nim
index c05627039..b10310e14 100755
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -19,7 +19,8 @@
 
 import 
   intsets, strutils, lists, options, ast, astalgo, trees, treetab, msgs, os, 
-  idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread
+  idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread,
+  lambdalifting
 
 const 
   genPrefix* = ":tmp"         # prefix for generated names
@@ -193,7 +194,7 @@ proc transformSym(c: PTransf, n: PNode): PTransNode =
 
 proc transformVarSection(c: PTransf, v: PNode): PTransNode =
   result = newTransNode(v)
-  for i in countup(0, sonsLen(v)-1): 
+  for i in countup(0, sonsLen(v)-1):
     var it = v.sons[i]
     if it.kind == nkCommentStmt: 
       result[i] = PTransNode(it)
@@ -349,13 +350,6 @@ proc transformYield(c: PTransf, n: PNode): PTransNode =
     # we need to introduce new local variables:
     add(result, introduceNewLocalVars(c, c.transCon.forLoopBody.pnode))
 
-proc addVar(father, v: PNode) = 
-  var vpart = newNodeI(nkIdentDefs, v.info)
-  addSon(vpart, v)
-  addSon(vpart, ast.emptyNode)
-  addSon(vpart, ast.emptyNode)
-  addSon(father, vpart)
-
 proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode =
   result = transformSons(c, n)
   var n = result.pnode
@@ -377,7 +371,18 @@ proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode =
       # addr ( deref ( x )) --> x
       result = PTransNode(n.sons[0].sons[0])
 
-include lambdalifting
+proc generateThunk(c: PTransf, prc: PNode, dest: PType): PNode =
+  ## Converts 'prc' into '(thunk, nil)' so that it's compatible with
+  ## a closure.
+  
+  # we cannot generate a proper thunk here for GC-safety reasons (see internal
+  # documentation):
+  result = newNodeIT(nkClosure, prc.info, dest)
+  var conv = newNodeIT(nkHiddenStdConv, prc.info, dest)
+  conv.add(emptyNode)
+  conv.add(prc)
+  result.add(conv)
+  result.add(newNodeIT(nkNilLit, prc.info, getSysType(tyNil)))
   
 proc transformConv(c: PTransf, n: PNode): PTransNode = 
   # numeric types need range checks:
@@ -638,7 +643,14 @@ proc transform(c: PTransf, n: PNode): PTransNode =
     # nothing to be done for leaves:
     result = PTransNode(n)
   of nkBracketExpr: result = transformArrayAccess(c, n)
-  of procDefs, nkMacroDef:
+  of procDefs:
+    if n.sons[genericParamsPos].kind == nkEmpty:
+      var s = n.sons[namePos].sym
+      n.sons[bodyPos] = PNode(transform(c, s.getBody))
+      n.sons[bodyPos] = liftLambdas(n)
+      if n.kind == nkMethodDef: methodDef(s, false)
+    result = PTransNode(n)
+  of nkMacroDef:
     # XXX no proper closure support yet:
     if n.sons[genericParamsPos].kind == nkEmpty:
       var s = n.sons[namePos].sym
@@ -737,10 +749,9 @@ proc transfPass(): TPass =
   result.close = processTransf # we need to process generics too!
   
 proc transform*(module: PSym, n: PNode): PNode =
-  if nfTransf in n.flags: 
+  if nfTransf in n.flags:
     result = n
   else:
     var c = openTransf(module, "")
     result = processTransf(c, n)
     incl(result.flags, nfTransf)
-
diff --git a/tests/run/tclosure.nim b/tests/run/tclosure.nim
index 372e296d0..d9e7b8ee4 100755
--- a/tests/run/tclosure.nim
+++ b/tests/run/tclosure.nim
@@ -34,6 +34,14 @@ myData.each do (x: int):
 
 #OUT 2 4 6 8 10
 
-
-
+type
+  ITest = tuple[
+    setter: proc(v: Int),
+    getter: proc(): int]
+
+proc getInterf(): ITest =
+  var shared: int
+  
+  return (setter: proc (x) = shared = x,
+          getter: proc (): int = return shared)