summary refs log tree commit diff stats
path: root/compiler
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2015-12-26 00:42:30 +0100
committerAndreas Rumpf <rumpf_a@web.de>2015-12-26 00:42:30 +0100
commitc1627354d1fe5499c3d8b6886ce63de6e062a463 (patch)
treeabda7e9d5089d52b395fac747d26318f5fd8c04a /compiler
parent86bb9d8b4768b638ccbc603c26a4b5a70343ab3e (diff)
downloadNim-c1627354d1fe5499c3d8b6886ce63de6e062a463.tar.gz
first implementation of the new lambda-lifting pass; barely anything works
Diffstat (limited to 'compiler')
-rw-r--r--compiler/ast.nim20
-rw-r--r--compiler/ccgexprs.nim2
-rw-r--r--compiler/ccgstmts.nim2
-rw-r--r--compiler/cgen.nim2
-rw-r--r--compiler/docgen.nim2
-rw-r--r--compiler/jsgen.nim2
-rw-r--r--compiler/lambdalifting.nim1137
-rw-r--r--compiler/lookups.nim2
-rw-r--r--compiler/lowerings.nim2
-rw-r--r--compiler/semcall.nim4
-rw-r--r--compiler/semdata.nim2
-rw-r--r--compiler/semexprs.nim18
-rw-r--r--compiler/semgnrc.nim4
-rw-r--r--compiler/semstmts.nim23
-rw-r--r--compiler/semtempl.nim4
-rw-r--r--compiler/semtypes.nim2
-rw-r--r--compiler/sigmatch.nim2
-rw-r--r--compiler/transf.nim18
-rw-r--r--compiler/vmgen.nim4
19 files changed, 464 insertions, 788 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 8054e9248..f34358788 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -500,8 +500,7 @@ type
     skResult,             # special 'result' variable
     skProc,               # a proc
     skMethod,             # a method
-    skIterator,           # an inline iterator
-    skClosureIterator,    # a resumable closure iterator
+    skIterator,           # an iterator
     skConverter,          # a type converter
     skMacro,              # a macro
     skTemplate,           # a template; currently also misused for user-defined
@@ -518,7 +517,7 @@ type
   TSymKinds* = set[TSymKind]
 
 const
-  routineKinds* = {skProc, skMethod, skIterator, skClosureIterator,
+  routineKinds* = {skProc, skMethod, skIterator,
                    skConverter, skMacro, skTemplate}
   tfIncompleteStruct* = tfVarargs
   tfUncheckedArray* = tfVarargs
@@ -905,7 +904,7 @@ type
 # the poor naming choices in the standard library.
 
 const
-  OverloadableSyms* = {skProc, skMethod, skIterator, skClosureIterator,
+  OverloadableSyms* = {skProc, skMethod, skIterator,
     skConverter, skModule, skTemplate, skMacro}
 
   GenericTypes*: TTypeKinds = {tyGenericInvocation, tyGenericBody,
@@ -929,7 +928,7 @@ const
   NilableTypes*: TTypeKinds = {tyPointer, tyCString, tyRef, tyPtr, tySequence,
     tyProc, tyString, tyError}
   ExportableSymKinds* = {skVar, skConst, skProc, skMethod, skType,
-    skIterator, skClosureIterator,
+    skIterator,
     skMacro, skTemplate, skConverter, skEnumField, skLet, skStub, skAlias}
   PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16,
                                       nfDotSetter, nfDotField,
@@ -958,11 +957,9 @@ const
   nkStrKinds* = {nkStrLit..nkTripleStrLit}
 
   skLocalVars* = {skVar, skLet, skForVar, skParam, skResult}
-  skProcKinds* = {skProc, skTemplate, skMacro, skIterator, skClosureIterator,
+  skProcKinds* = {skProc, skTemplate, skMacro, skIterator,
                   skMethod, skConverter}
 
-  skIterators* = {skIterator, skClosureIterator}
-
 var ggDebug* {.deprecated.}: bool ## convenience switch for trying out things
 
 proc isCallExpr*(n: PNode): bool =
@@ -1558,12 +1555,13 @@ proc isGenericRoutine*(s: PSym): bool =
   else: discard
 
 proc skipGenericOwner*(s: PSym): PSym =
-  internalAssert s.kind in skProcKinds
   ## Generic instantiations are owned by their originating generic
   ## symbol. This proc skips such owners and goes straight to the owner
   ## of the generic itself (the module or the enclosing proc).
-  result = if sfFromGeneric in s.flags: s.owner.owner
-           else: s.owner
+  result = if s.kind in skProcKinds and sfFromGeneric in s.flags:
+             s.owner.owner
+           else:
+             s.owner
 
 proc originatingModule*(s: PSym): PSym =
   result = s.owner
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index d691b8ea2..0df7e22d3 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -1965,7 +1965,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
       else:
         genProc(p.module, sym)
       putLocIntoDest(p, d, sym.loc)
-    of skProc, skConverter, skIterators:
+    of skProc, skConverter, skIterator:
       if sfCompileTime in sym.flags:
         localError(n.info, "request to generate code for .compileTime proc: " &
            sym.name.s)
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim
index 73497bded..699361ef8 100644
--- a/compiler/ccgstmts.nim
+++ b/compiler/ccgstmts.nim
@@ -955,7 +955,7 @@ proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): Rope =
       res.add(t.sons[i].strVal)
     of nkSym:
       var sym = t.sons[i].sym
-      if sym.kind in {skProc, skIterator, skClosureIterator, skMethod}:
+      if sym.kind in {skProc, skIterator, skMethod}:
         var a: TLoc
         initLocExpr(p, t.sons[i], a)
         res.add($rdLoc(a))
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index f63134b66..db376821c 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -594,7 +594,7 @@ proc cgsym(m: BModule, name: string): Rope =
   var sym = magicsys.getCompilerProc(name)
   if sym != nil:
     case sym.kind
-    of skProc, skMethod, skConverter, skIterators: genProc(m, sym)
+    of skProc, skMethod, skConverter, skIterator: genProc(m, sym)
     of skVar, skResult, skLet: genVarPrototype(m, sym)
     of skType: discard getTypeDesc(m, sym.typ)
     else: internalError("cgsym: " & name & ": " & $sym.kind)
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index 8536cc619..952366f93 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -537,7 +537,7 @@ proc generateJson(d: PDoc, n: PNode, jArray: JsonNode = nil): JsonNode =
 proc genSection(d: PDoc, kind: TSymKind) =
   const sectionNames: array[skModule..skTemplate, string] = [
     "Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Methods",
-    "Iterators", "Iterators", "Converters", "Macros", "Templates"
+    "Iterators", "Converters", "Macros", "Templates"
   ]
   if d.section[kind] == nil: return
   var title = sectionNames[kind].rope
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index f8bf35ed6..2f6496b9b 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -931,7 +931,7 @@ proc isIndirect(v: PSym): bool =
   result = {sfAddrTaken, sfGlobal} * v.flags != {} and
     #(mapType(v.typ) != etyObject) and
     {sfImportc, sfVolatile, sfExportc} * v.flags == {} and
-    v.kind notin {skProc, skConverter, skMethod, skIterator, skClosureIterator,
+    v.kind notin {skProc, skConverter, skMethod, skIterator,
                   skConst, skTemp, skLet}
 
 proc genAddr(p: PProc, n: PNode, r: var TCompRes) =
diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim
index be1631af0..00849b47e 100644
--- a/compiler/lambdalifting.nim
+++ b/compiler/lambdalifting.nim
@@ -11,7 +11,7 @@
 
 import
   intsets, strutils, lists, options, ast, astalgo, trees, treetab, msgs, os,
-  idents, renderer, types, magicsys, rodread, lowerings
+  idents, renderer, types, magicsys, rodread, lowerings, tables
 
 discard """
   The basic approach is that captured vars need to be put on the heap and
@@ -113,44 +113,19 @@ discard """
 #   local storage requirements for efficiency. This means closure iterators
 #   have slightly different semantics from ordinary closures.
 
+# ---------------- essential helpers -------------------------------------
 
 const
   upName* = ":up" # field name for the 'up' reference
   paramName* = ":envP"
   envName* = ":env"
 
-type
-  POuterContext = ref TOuterContext
-
-  TIter = object
-    fn, closureParam, state, resultSym: PSym # most are only valid if
-                                             # fn.kind == skClosureIterator
-    obj: PType
-    isIterator: bool
-
-  PEnv = ref TEnv
-  TEnv {.final.} = object of RootObj
-    attachedNode, replacementNode: PNode
-    createdVar: PNode        # if != nil it is a used environment; for closure
-                             # iterators this can be 'envParam.env'
-    createdVarComesFromIter: bool
-    capturedVars: seq[PSym] # captured variables in this environment
-    up, next: PEnv          # outer scope and next to keep all in a list
-    upField: PSym        # if != nil the dependency to the outer scope is used
-    obj: PType
-    fn: PSym                # function that belongs to this scope;
-                            # if up.fn != fn then we cross function boundaries.
-                            # This is an important case to consider.
-    vars: IntSet           # variables belonging to this environment
-
-  TOuterContext = object
-    fn: PSym # may also be a module!
-    head: PEnv
-    capturedVars, processed: IntSet
-    localsToAccess: TIdNodeTable
-    lambdasToEnv: TIdTable # PSym->PEnv mapping
-
-proc getStateType(iter: PSym): PType =
+proc newCall(a: PSym, b: PNode): PNode =
+  result = newNodeI(nkCall, a.info)
+  result.add newSymNode(a)
+  result.add b
+
+proc createStateType(iter: PSym): PType =
   var n = newNodeI(nkRange, iter.info)
   addSon(n, newIntNode(nkIntLit, -1))
   addSon(n, newIntNode(nkIntLit, 0))
@@ -162,7 +137,7 @@ proc getStateType(iter: PSym): PType =
 
 proc createStateField(iter: PSym): PSym =
   result = newSym(skField, getIdent(":state"), iter, iter.info)
-  result.typ = getStateType(iter)
+  result.typ = createStateType(iter)
 
 proc createEnvObj(owner: PSym): PType =
   # YYY meh, just add the state field for every closure for now, it's too
@@ -170,7 +145,7 @@ proc createEnvObj(owner: PSym): PType =
   result = createObj(owner, owner.info)
   rawAddField(result, createStateField(owner))
 
-proc newIterResult(iter: PSym): PSym =
+proc getIterResult(iter: PSym): PSym =
   if resultPos < iter.ast.len:
     result = iter.ast.sons[resultPos].sym
   else:
@@ -194,9 +169,13 @@ proc addHiddenParam(routine: PSym, param: PSym) =
 proc getHiddenParam(routine: PSym): PSym =
   let params = routine.ast.sons[paramsPos]
   let hidden = lastSon(params)
-  internalAssert hidden.kind == nkSym and hidden.sym.kind == skParam
-  result = hidden.sym
-  assert sfFromGeneric in result.flags
+  if hidden.kind == nkSym and hidden.sym.kind == skParam:
+    result = hidden.sym
+    assert sfFromGeneric in result.flags
+  else:
+    # writeStackTrace()
+    localError(routine.info, "internal error: could not find env param " & routine.name.s)
+    result = routine
 
 proc getEnvParam*(routine: PSym): PSym =
   let params = routine.ast.sons[paramsPos]
@@ -205,141 +184,18 @@ proc getEnvParam*(routine: PSym): PSym =
     result = hidden.sym
     assert sfFromGeneric in result.flags
 
-proc initIter(iter: PSym; ptrType: PType = nil): TIter =
-  result.fn = iter
-  result.isIterator = ptrType != nil or iter.kind == skClosureIterator
-  #echo "fuck you ", ptrType != nil
-  if result.isIterator:
-    var cp = getEnvParam(iter)
-    if cp == nil:
-      result.obj = if ptrType != nil: ptrType.lastSon else: createEnvObj(iter)
-
-      cp = newSym(skParam, getIdent(paramName), iter, iter.info)
-      incl(cp.flags, sfFromGeneric)
-      if ptrType != nil:
-        cp.typ = ptrType
-      else:
-        cp.typ = newType(tyRef, iter)
-        rawAddSon(cp.typ, result.obj)
-      addHiddenParam(iter, cp)
-    else:
-      result.obj = cp.typ.sons[0]
-      assert result.obj.kind == tyObject
-    internalAssert result.obj.n.len > 0
-    result.state = result.obj.n[0].sym
-    result.closureParam = cp
-    if iter.typ.sons[0] != nil:
-      result.resultSym = newIterResult(iter)
-      #iter.ast.add(newSymNode(c.resultSym))
-
-proc newOuterContext(fn: PSym): POuterContext =
-  new(result)
-  result.fn = fn
-  result.capturedVars = initIntSet()
-  result.processed = initIntSet()
-  initIdNodeTable(result.localsToAccess)
-  initIdTable(result.lambdasToEnv)
-
-proc newEnv(o: POuterContext; up: PEnv, n: PNode; owner: PSym): PEnv =
-  new(result)
-  result.capturedVars = @[]
-  result.up = up
-  result.attachedNode = n
-  result.fn = owner
-  result.vars = initIntSet()
-  result.next = o.head
-  o.head = result
-  if owner.kind != skModule and (up == nil or up.fn != owner):
-    let param = getEnvParam(owner)
-    if param != nil:
-      result.obj = param.typ.sons[0]
-      assert result.obj.kind == tyObject
-  if result.obj.isNil:
-    result.obj = createEnvObj(owner)
-
-proc addCapturedVar(e: PEnv, v: PSym) =
-  for x in e.capturedVars:
-    if x == v: return
-  e.capturedVars.add(v)
-  addField(e.obj, v)
-
-proc newCall(a: PSym, b: PNode): PNode =
-  result = newNodeI(nkCall, a.info)
-  result.add newSymNode(a)
-  result.add b
-
-proc isInnerProc(s, outerProc: PSym): bool =
-  if s.kind in {skProc, skMethod, skConverter, skClosureIterator}:
-    var owner = s.skipGenericOwner
-    while true:
-      if owner.isNil: return false
-      if owner == outerProc: return true
-      owner = owner.owner
-  #s.typ.callConv == ccClosure
-
-proc addClosureParam(fn: PSym; e: PEnv) =
-  var cp = getEnvParam(fn)
-  if cp == nil:
-    cp = newSym(skParam, getIdent(paramName), fn, fn.info)
-    incl(cp.flags, sfFromGeneric)
-    cp.typ = newType(tyRef, fn)
-    rawAddSon(cp.typ, e.obj)
-    addHiddenParam(fn, cp)
-    #else:
-    #cp.typ.sons[0] = e.obj
-    #assert e.obj.kind == tyObject
+proc interestingVar(s: PSym): bool {.inline.} =
+  result = s.kind in {skVar, skLet, skTemp, skForVar, skParam, skResult} and
+    sfGlobal notin s.flags
 
 proc illegalCapture(s: PSym): bool {.inline.} =
   result = skipTypes(s.typ, abstractInst).kind in
                    {tyVar, tyOpenArray, tyVarargs} or
       s.kind == skResult
 
-proc interestingVar(s: PSym): bool {.inline.} =
-  result = s.kind in {skVar, skLet, skTemp, skForVar, skParam, skResult} and
-    sfGlobal notin s.flags
-
-proc nestedAccess(top: PEnv; local: PSym): PNode =
-  # Parts after the transformation are in []:
-  #
-  #  proc main =
-  #    var [:env.]foo = 23
-  #    proc outer(:paramO) =
-  #      [var :envO; createClosure(:envO); :envO.up = paramO]
-  #      proc inner(:paramI) =
-  #        echo [:paramI.up.]foo
-  #      inner([:envO])
-  #    outer([:env])
-  if not interestingVar(local) or top.fn == local.owner:
-    return nil
-  # check it's in fact a captured variable:
-  var it = top
-  while it != nil:
-    if it.vars.contains(local.id): break
-    it = it.up
-  if it == nil: return nil
-  let envParam = top.fn.getEnvParam
-  internalAssert(not envParam.isNil)
-  var access = newSymNode(envParam)
-  it = top.up
-  while it != nil:
-    if it.vars.contains(local.id):
-      access = indirectAccess(access, local, local.info)
-      return access
-    internalAssert it.upField != nil
-    access = indirectAccess(access, it.upField, local.info)
-    it = it.up
-  when false:
-    # Type based expression construction works too, but turned out to hide
-    # other bugs:
-    while true:
-      let obj = access.typ.sons[0]
-      let field = getFieldFromObj(obj, local)
-      if field != nil:
-        return rawIndirectAccess(access, field, local.info)
-      let upField = lookupInRecord(obj.n, getIdent(upName))
-      if upField == nil: break
-      access = rawIndirectAccess(access, upField, local.info)
-  return nil
+proc isInnerProc(s: PSym): bool =
+  if s.kind in {skProc, skMethod, skConverter, skIterator}:
+    result = s.skipGenericOwner.kind in routineKinds
 
 proc createUpField(obj, fieldType: PType): PSym =
   let pos = obj.n.len
@@ -350,98 +206,15 @@ proc createUpField(obj, fieldType: PType): PSym =
   #rawAddField(obj, result)
   addField(obj, result)
 
-proc captureVar(o: POuterContext; top: PEnv; local: PSym;
-                info: TLineInfo): bool =
-  # first check if we should be concerned at all:
-  var it = top
-  while it != nil:
-    if it.vars.contains(local.id): break
-    it = it.up
-  if it == nil: return false
-  # yes, so mark every 'up' pointer as taken:
-  if illegalCapture(local) or top.fn.typ.callConv notin {ccClosure, ccDefault}:
-    localError(info, errIllegalCaptureX, local.name.s)
-  it = top
-  while it != nil:
-    if it.vars.contains(local.id): break
-    # keep in mind that the first element of the chain belong to top.fn itself
-    # and these don't need any upFields
-    if it.upField == nil and it.up != nil and it.fn != top.fn:
-      it.upField = createUpField(it.obj, it.up.obj)
-
-    if it.fn != local.owner:
-      it.fn.typ.callConv = ccClosure
-      incl(it.fn.typ.flags, tfCapturesEnv)
-
-      var u = it.up
-      while u != nil and u.fn == it.fn: u = u.up
-      addClosureParam(it.fn, u)
-
-      if idTableGet(o.lambdasToEnv, it.fn) == nil:
-        if u != nil: idTablePut(o.lambdasToEnv, it.fn, u)
-
-    it = it.up
-  # don't do this: 'top' might not require a closure:
-  #if idTableGet(o.lambdasToEnv, it.fn) == nil:
-  #  idTablePut(o.lambdasToEnv, it.fn, top)
-
-  # mark as captured:
-  #if top.iter != nil:
-  #  if not containsOrIncl(o.capturedVars, local.id):
-  #    #addField(top.iter.obj, local)
-  #    addCapturedVar(it, local)
-  #else:
-  incl(o.capturedVars, local.id)
-  addCapturedVar(it, local)
-  result = true
-
-proc semCaptureSym*(s, owner: PSym) =
-  if interestingVar(s) and owner.id != s.owner.id and s.kind != skResult:
-    if owner.typ != nil and not isGenericRoutine(owner):
-      # XXX: is this really safe?
-      # if we capture a var from another generic routine,
-      # it won't be consider captured.
-      owner.typ.callConv = ccClosure
-    #echo "semCaptureSym ", owner.name.s, owner.id, " ", s.name.s, s.id
-    # since the analysis is not entirely correct, we don't set 'tfCapturesEnv'
-    # here
-
-proc gatherVars(o: POuterContext; e: PEnv; n: PNode): int =
-  # gather used vars for closure generation; returns number of captured vars
-  if n == nil: return 0
-  case n.kind
-  of nkSym:
-    var s = n.sym
-    if interestingVar(s) and e.fn != s.owner:
-      if captureVar(o, e, s, n.info): result = 1
-  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, nkClosure, nkProcDef,
-     nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef, nkTypeSection:
-    discard
-  else:
-    for k in countup(0, sonsLen(n) - 1):
-      result += gatherVars(o, e, n.sons[k])
-
-proc generateThunk(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):
-  if gCmd == cmdCompileToJS: return prc
-  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 transformOuterConv(n: PNode): PNode =
-  # numeric types need range checks:
-  var dest = skipTypes(n.typ, abstractVarRange)
-  var source = skipTypes(n.sons[1].typ, abstractVarRange)
-  if dest.kind == tyProc:
-    if dest.callConv == ccClosure and source.callConv == ccDefault:
-      result = generateThunk(n.sons[1], dest)
+proc newAsgnStmt(le, ri: PNode, info: TLineInfo): PNode =
+  # Bugfix: unfortunately we cannot use 'nkFastAsgn' here as that would
+  # mean to be able to capture string literals which have no GC header.
+  # However this can only happen if the capture happens through a parameter,
+  # which is however the only case when we generate an assignment in the first
+  # place.
+  result = newNodeI(nkAsgn, info, 2)
+  result.sons[0] = le
+  result.sons[1] = ri
 
 proc makeClosure(prc: PSym; env: PNode; info: TLineInfo): PNode =
   result = newNodeIT(nkClosure, info, prc.typ)
@@ -451,179 +224,191 @@ proc makeClosure(prc: PSym; env: PNode; info: TLineInfo): PNode =
   else:
     result.add(env)
 
-proc newClosureCreationVar(e: PEnv): PNode =
-  var v = newSym(skVar, getIdent(envName), e.fn, e.attachedNode.info)
-  incl(v.flags, sfShadowed)
-  v.typ = newType(tyRef, e.fn)
-  v.typ.rawAddSon(e.obj)
-  if e.fn.kind == skClosureIterator:
-    let it = initIter(e.fn)
-    addUniqueField(it.obj, v)
-    result = indirectAccess(newSymNode(it.closureParam), v, v.info)
-  else:
-    result = newSymNode(v)
+proc interestingIterVar(s: PSym): bool {.inline.} =
+  # XXX optimization: Only lift the variable if it lives across
+  # yield/return boundaries! This can potentially speed up
+  # closure iterators quite a bit.
+  result = s.kind in {skVar, skLet, skTemp, skForVar} and sfGlobal notin s.flags
 
-proc getClosureVar(e: PEnv): PNode =
-  if e.createdVar == nil:
-    result = newClosureCreationVar(e)
-    e.createdVar = result
-  else:
-    result = e.createdVar
-
-proc findEnv(o: POuterContext; s: PSym): PEnv =
-  var env = o.head
-  while env != nil:
-    if env.fn == s: break
-    env = env.next
-  internalAssert env != nil and env.up != nil
-  result = env.up
-  while result.fn == s: result = result.up
-
-proc transformInnerProc(o: POuterContext; e: PEnv, n: PNode): PNode =
+template isIterator(owner: PSym): bool =
+  owner.kind == skIterator and owner.typ.callConv == ccClosure
+
+proc liftIterSym(n: PNode; owner: PSym): PNode =
+  # transforms  (iter)  to  (let env = newClosure[iter](); (iter, env))
+  let iter = n.sym
+  assert iter.isIterator
+
+  result = newNodeIT(nkStmtListExpr, n.info, n.typ)
+
+  let hp = getHiddenParam(iter)
+  let env = newSym(skLet, iter.name, owner, n.info)
+  env.typ = hp.typ
+  env.flags = hp.flags
+  var v = newNodeI(nkVarSection, n.info)
+  addVar(v, newSymNode(env))
+  result.add(v)
+  # add 'new' statement:
+  let envAsNode = env.newSymNode
+  result.add newCall(getSysSym"internalNew", envAsNode)
+  result.add makeClosure(iter, envAsNode, n.info)
+
+# ------------------ new stuff -------------------------------------------
+
+proc createUpField(dest, dep: PSym) =
+  let obj = getHiddenParam(dest).typ.lastSon
+  let fieldType = getHiddenParam(dep).typ
+  let pos = obj.n.len
+  let result = newSym(skField, getIdent(upName), obj.owner, obj.owner.info)
+  result.typ = fieldType
+  result.position = pos
+  addField(obj, result)
+
+proc markAsClosure(owner: PSym; n: PNode) =
+  owner.typ.callConv = ccClosure
+  incl(owner.typ.flags, tfCapturesEnv)
+  let s = n.sym
+  if illegalCapture(s) or owner.typ.callConv notin {ccClosure, ccDefault}:
+    localError(n.info, errIllegalCaptureX, s.name.s)
+
+type
+  DetectionPass = object
+    processed, capturedVars: IntSet
+    ownerToType: Table[int, PType]
+    somethingToDo: bool
+
+proc initDetectionPass(fn: PSym): DetectionPass =
+  result.processed = initIntSet()
+  result.capturedVars = initIntSet()
+  result.ownerToType = initTable[int, PType]()
+  result.processed.incl(fn.id)
+
+discard """
+proc outer =
+  var a, b: int
+  proc innerA = use(a)
+  proc innerB = use(b); innerA()
+# --> innerA and innerB need to *share* the closure type!
+This is why need to store the 'ownerToType' table and use it
+during .closure'fication.
+"""
+
+proc getEnvTypeForOwner(c: var DetectionPass; owner: PSym): PType =
+  result = c.ownerToType.getOrDefault(owner.id)
+  if result.isNil:
+    result = newType(tyRef, owner)
+    let obj = createEnvObj(owner)
+    rawAddSon(result, obj)
+    c.ownerToType[owner.id] = result
+
+proc addClosureParam(c: var DetectionPass; fn, owner: PSym) =
+  var cp = getEnvParam(fn)
+  if cp == nil:
+    cp = newSym(skParam, getIdent(paramName), fn, fn.info)
+    incl(cp.flags, sfFromGeneric)
+    cp.typ = c.getEnvTypeForOwner(owner)
+    addHiddenParam(fn, cp)
+
+proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) =
   case n.kind
-  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: discard
   of nkSym:
     let s = n.sym
-    if s == e.fn:
-      # recursive calls go through (lambda, hiddenParam):
-      result = makeClosure(s, getEnvParam(s).newSymNode, n.info)
-    elif isInnerProc(s, o.fn) and s.typ.callConv == ccClosure:
-      # ugh: call to some other inner proc;
-      result = makeClosure(s, findEnv(o, s).getClosureVar, n.info)
-    else:
-      # captured symbol?
-      result = nestedAccess(e, n.sym)
-      #result = idNodeTableGet(i.localsToAccess, n.sym)
-    #of nkLambdaKinds, nkIteratorDef:
-    #  if n.typ != nil:
-    #    result = transformInnerProc(o, e, n.sons[namePos])
-    #of nkClosure:
-    #  let x = transformInnerProc(o, e, n.sons[0])
-    #  if x != nil: n.sons[0] = x
-  of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef,
-     nkLambdaKinds, nkIteratorDef, nkClosure:
-    # don't recurse here:
+    let innerProc = isInnerProc(s)
+    if innerProc:
+      if not c.processed.containsOrIncl(s.id):
+        detectCapturedVars(s.getBody, s, c)
+    let ow = s.skipGenericOwner
+    if ow == owner:
+      if owner.isIterator and interestingIterVar(s):
+        c.somethingToDo = true
+        if not c.capturedVars.containsOrIncl(s.id):
+          let obj = getHiddenParam(owner).typ.lastSon
+          addField(obj, s)
+      # but always return because the rest of the proc is only relevant when
+      # ow != owner:
+      return
+    # direct or indirect dependency:
+    if innerProc or interestingVar(s):
+      discard """
+        proc outer() =
+          var x: int
+          proc inner() =
+            proc innerInner() =
+              echo x
+            innerInner()
+          inner()
+
+        # inner() takes a closure too!
+      """
+
+      echo "came here for ", n.sym.name.s, " proc ", owner.name.s
+
+      # mark 'owner' as taking a closure:
+      c.somethingToDo = true
+      markAsClosure(owner, n)
+      addClosureParam(c, owner, ow)
+      # variable 's' is actually captured:
+      if interestingVar(s) and not c.capturedVars.containsOrIncl(s.id):
+        let obj = getHiddenParam(owner).typ.lastSon
+        addField(obj, s)
+      # create required upFields:
+      var w = ow
+      if false: # XXX rethink this w != owner:
+        var up = w
+        while w != nil and w.kind != skModule:
+          w = w.skipGenericOwner
+          markAsClosure(w, n)
+          discard """
+          proc outer =
+            var a, b: int
+            proc outerB =
+              proc innerA = use(a)
+              proc innerB = use(b); innerA()
+          # --> make outerB of calling convention .closure and
+          # give it the same env type that outer's env var gets:
+          """
+          addClosureParam(c, w, ow)
+          createUpField(w, up)
+          if w == owner: break
+          up = w
+  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, nkClosure,
+     nkTemplateDef, nkTypeSection:
     discard
-  else:
-    for j in countup(0, sonsLen(n) - 1):
-      let x = transformInnerProc(o, e, n.sons[j])
-      if x != nil: n.sons[j] = x
-
-proc closureCreationPoint(n: PNode): PNode =
-  if n.kind == nkStmtList and n.len >= 1 and n[0].kind == nkEmpty:
-    # we already have a free slot
-    result = n
-  else:
-    result = newNodeI(nkStmtList, n.info)
-    result.add(emptyNode)
-    result.add(n)
-  #result.flags.incl nfLL
-
-proc addParamsToEnv(fn: PSym; env: PEnv) =
-  let params = fn.typ.n
-  for i in 1.. <params.len:
-    if params.sons[i].kind != nkSym:
-      internalError(params.info, "liftLambdas: strange params")
-    let param = params.sons[i].sym
-    env.vars.incl(param.id)
-  # put the 'result' into the environment so it can be captured:
-  let ast = fn.ast
-  if resultPos < sonsLen(ast) and ast.sons[resultPos].kind == nkSym:
-    env.vars.incl(ast.sons[resultPos].sym.id)
-
-proc searchForInnerProcs(o: POuterContext, n: PNode, env: PEnv) =
-  if n == nil: return
-  case n.kind
-  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
-    discard
-  of nkSym:
-    let fn = n.sym
-    if isInnerProc(fn, o.fn) and not containsOrIncl(o.processed, fn.id):
-      let body = fn.getBody
-      if nfLL in body.flags: return
-
-      # handle deeply nested captures:
-      let ex = closureCreationPoint(body)
-      let envB = newEnv(o, env, ex, fn)
-      addParamsToEnv(fn, envB)
-      searchForInnerProcs(o, body, envB)
-      fn.ast.sons[bodyPos] = ex
-
-      let capturedCounter = gatherVars(o, envB, body)
-      # dummy closure param needed?
-      if capturedCounter == 0 and fn.typ.callConv == ccClosure:
-        #assert tfCapturesEnv notin n.sym.typ.flags
-        if idTableGet(o.lambdasToEnv, fn) == nil:
-          idTablePut(o.lambdasToEnv, fn, env)
-        addClosureParam(fn, env)
-
-      elif fn.getEnvParam != nil:
-        # only transform if it really needs a closure:
-        let ti = transformInnerProc(o, envB, body)
-        if ti != nil: fn.ast.sons[bodyPos] = ti
-  of nkLambdaKinds, nkIteratorDef:
-    if n.typ != nil:
-      searchForInnerProcs(o, n.sons[namePos], env)
-  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 loop bodies are of interest here as only they
-    # yield observable changes in semantics. For Zahary we also
-    # include ``nkBlock``. We don't do this for closure iterators because
-    # 'yield' can produce wrong code otherwise (XXX show example):
-    if env.fn.kind != skClosureIterator:
-      var body = n.len-1
-      for i in countup(0, body - 1): searchForInnerProcs(o, n.sons[i], env)
-      # special handling for the loop body:
-      let ex = closureCreationPoint(n.sons[body])
-      searchForInnerProcs(o, n.sons[body], newEnv(o, env, ex, env.fn))
-      n.sons[body] = ex
-    else:
-      for i in countup(0, sonsLen(n) - 1):
-        searchForInnerProcs(o, n.sons[i], env)
-  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: discard
-      elif it.kind == nkIdentDefs:
-        var L = sonsLen(it)
-        if it.sons[0].kind == nkSym:
-          # this can be false for recursive invocations that already
-          # transformed it into 'env.varName':
-          env.vars.incl(it.sons[0].sym.id)
-        searchForInnerProcs(o, it.sons[L-1], env)
-      elif it.kind == nkVarTuple:
-        var L = sonsLen(it)
-        for j in countup(0, L-3):
-          #echo "set: ", it.sons[j].sym.name.s, " ", o.currentBlock == nil
-          if it.sons[j].kind == nkSym:
-            env.vars.incl(it.sons[j].sym.id)
-        searchForInnerProcs(o, it.sons[L-1], env)
-      else:
-        internalError(it.info, "searchForInnerProcs")
-  of nkClosure:
-    searchForInnerProcs(o, n.sons[0], env)
-  of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef,
-     nkTypeSection:
-    # don't recurse here:
+  of nkProcDef, nkMethodDef, nkIteratorDef, nkConverterDef, nkMacroDef:
     discard
   else:
-    for i in countup(0, sonsLen(n) - 1):
-      searchForInnerProcs(o, n.sons[i], env)
+    for i in 0..<n.len:
+      detectCapturedVars(n[i], owner, c)
 
-proc newAsgnStmt(le, ri: PNode, info: TLineInfo): PNode =
-  # Bugfix: unfortunately we cannot use 'nkFastAsgn' here as that would
-  # mean to be able to capture string literals which have no GC header.
-  # However this can only happen if the capture happens through a parameter,
-  # which is however the only case when we generate an assignment in the first
-  # place.
-  result = newNodeI(nkAsgn, info, 2)
-  result.sons[0] = le
-  result.sons[1] = ri
+type
+  LiftingPass = object
+    processed: IntSet
+    envCreation: PNode
+    envVar: PNode
+
+proc initLiftingPass(fn: PSym): LiftingPass =
+  result.processed = initIntSet()
+  result.processed.incl(fn.id)
+
+proc accessViaEnvParam(n: PNode; owner: PSym): PNode =
+  let s = n.sym
+  # Type based expression construction for simplicity:
+  let envParam = getEnvParam(owner)
+  if not envParam.isNil:
+    var access = newSymNode(envParam)
+    while true:
+      let obj = access.typ.sons[0]
+      let field = getFieldFromObj(obj, s)
+      if field != nil:
+        return rawIndirectAccess(access, field, n.info)
+      let upField = lookupInRecord(obj.n, getIdent(upName))
+      if upField == nil: break
+      access = rawIndirectAccess(access, upField, n.info)
+  localError(n.info, "internal error: no environment parameter set")
+  result = n
 
-proc rawClosureCreation(o: POuterContext, scope: PEnv; env: PNode): PNode =
+proc rawClosureCreation(env: PNode; owner: PSym;
+                        d: DetectionPass;
+                        obj: PType): PNode =
   result = newNodeI(nkStmtList, env.info)
   if env.kind == nkSym:
     var v = newNodeI(nkVarSection, env.info)
@@ -631,75 +416,79 @@ proc rawClosureCreation(o: POuterContext, scope: PEnv; env: PNode): PNode =
     result.add(v)
   # add 'new' statement:
   result.add(newCall(getSysSym"internalNew", env))
-
-  # add assignment statements:
-  for local in scope.capturedVars:
-    let fieldAccess = indirectAccess(env, local, env.info)
-    if local.kind == skParam:
-      # maybe later: (sfByCopy in local.flags)
+  # add assignment statements for captured parameters:
+  for i in 1..<owner.typ.n.len:
+    let local = owner.typ.n[i].sym
+    if local.id in d.capturedVars:
+      let fieldAccess = indirectAccess(env, local, env.info)
       # add ``env.param = param``
       result.add(newAsgnStmt(fieldAccess, newSymNode(local), env.info))
-    # it can happen that we already captured 'local' in some other environment
-    # then we capture by copy for now. This is not entirely correct but better
-    # than nothing:
-    let existing = idNodeTableGet(o.localsToAccess, local)
-    if existing.isNil:
-      idNodeTablePut(o.localsToAccess, local, fieldAccess)
-    else:
-      result.add(newAsgnStmt(fieldAccess, existing, env.info))
-  if scope.upField != nil:
-    # "up" chain has been used:
-    if scope.up.fn != scope.fn:
-      # crosses function boundary:
-      result.add(newAsgnStmt(indirectAccess(env, scope.upField, env.info),
-                 newSymNode(getEnvParam(scope.fn)), env.info))
-    else:
-      result.add(newAsgnStmt(indirectAccess(env, scope.upField, env.info),
-                 getClosureVar(scope.up), env.info))
-
-proc generateClosureCreation(o: POuterContext, scope: PEnv): PNode =
-  var env = getClosureVar(scope)
-  result = rawClosureCreation(o, scope, env)
-
-proc generateIterClosureCreation(o: POuterContext; env: PEnv;
-                                 scope: PNode): PNode =
-  if env.createdVarComesFromIter or env.createdVar.isNil:
-    # we have to create a new closure:
-    result = newClosureCreationVar(env)
-    let cc = rawClosureCreation(o, env, result)
-    var insertPoint = scope.sons[0]
-    if insertPoint.kind == nkEmpty: scope.sons[0] = cc
+
+  let upField = lookupInRecord(obj.n, getIdent(upName))
+  if upField != nil:
+    let param = getHiddenParam(owner)
+    if upField.typ == param.typ:
+      result.add(newAsgnStmt(indirectAccess(env, upField, env.info),
+                 newSymNode(param), env.info))
     else:
-      assert cc.kind == nkStmtList and insertPoint.kind == nkStmtList
-      for x in cc: insertPoint.add(x)
-    if env.createdVar == nil: env.createdVar = result
+      localError(env.info, "internal error: cannot create up reference")
+
+proc newEnvVar(owner: PSym; typ: PType): PNode =
+  var v = newSym(skVar, getIdent(envName), owner, owner.info)
+  incl(v.flags, sfShadowed)
+  v.typ = typ
+  if owner.kind == skIterator and owner.typ.callConv == ccClosure:
+    let it = getHiddenParam(owner)
+    addUniqueField(it.typ.sons[0], v)
+    result = indirectAccess(newSymNode(it), v, v.info)
   else:
-    result = env.createdVar
-  env.createdVarComesFromIter = true
+    result = newSymNode(v)
 
-proc interestingIterVar(s: PSym): bool {.inline.} =
-  result = s.kind in {skVar, skLet, skTemp, skForVar} and sfGlobal notin s.flags
+proc accessViaEnvVar(n: PNode; owner: PSym; d: DetectionPass;
+                     c: var LiftingPass): PNode =
+  if c.envVar.isNil:
+    assert c.envCreation.isNil
+    let envVarType = d.ownerToType.getOrDefault(owner.id)
+    if envVarType.isNil:
+      localError n.info, "internal error: could not determine closure type"
+    c.envVar = newEnvVar(owner, envVarType)
+    c.envCreation = rawClosureCreation(c.envVar, owner, d,
+                                       envVarType.sons[0])
+  let access = c.envVar
+  let obj = access.typ.sons[0]
+  let field = getFieldFromObj(obj, n.sym)
+  if field != nil:
+    result = rawIndirectAccess(access, field, n.info)
+  else:
+    localError(n.info, "internal error: not part of closure object type")
+    result = n
+
+proc getStateField(owner: PSym): PSym =
+  getEnvParam(owner).typ.sons[0].n.sons[0].sym
 
-proc transformOuterProc(o: POuterContext, n: PNode, it: TIter): PNode
+proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass;
+                      c: var LiftingPass): PNode
 
-proc transformYield(c: POuterContext, n: PNode, it: TIter): PNode =
-  assert it.state != nil
-  assert it.state.typ != nil
-  assert it.state.typ.n != nil
-  inc it.state.typ.n.sons[1].intVal
-  let stateNo = it.state.typ.n.sons[1].intVal
+proc transformYield(n: PNode; owner: PSym; d: DetectionPass;
+                    c: var LiftingPass): PNode =
+  let state = getStateField(owner)
+  assert state != nil
+  assert state.typ != nil
+  assert state.typ.n != nil
+  inc state.typ.n.sons[1].intVal
+  let stateNo = state.typ.n.sons[1].intVal
 
   var stateAsgnStmt = newNodeI(nkAsgn, n.info)
-  stateAsgnStmt.add(rawIndirectAccess(newSymNode(it.closureParam),
-                    it.state, n.info))
+  stateAsgnStmt.add(rawIndirectAccess(newSymNode(getEnvParam(owner)),
+                    state, n.info))
   stateAsgnStmt.add(newIntTypeNode(nkIntLit, stateNo, getSysType(tyInt)))
 
   var retStmt = newNodeI(nkReturnStmt, n.info)
   if n.sons[0].kind != nkEmpty:
     var a = newNodeI(nkAsgn, n.sons[0].info)
-    var retVal = transformOuterProc(c, n.sons[0], it)
-    addSon(a, newSymNode(it.resultSym))
-    addSon(a, if retVal.isNil: n.sons[0] else: retVal)
+    var retVal = liftCapturedVars(n.sons[0], owner, d, c)
+    addSon(a, newSymNode(getIterResult(owner)))
+    addSon(a, retVal)
     retStmt.add(a)
   else:
     retStmt.add(emptyNode)
@@ -712,260 +501,143 @@ proc transformYield(c: POuterContext, n: PNode, it: TIter): PNode =
   result.add(retStmt)
   result.add(stateLabelStmt)
 
-proc transformReturn(c: POuterContext, n: PNode, it: TIter): PNode =
+proc transformReturn(n: PNode; owner: PSym; d: DetectionPass;
+                     c: var LiftingPass): PNode =
+  let state = getStateField(owner)
   result = newNodeI(nkStmtList, n.info)
   var stateAsgnStmt = newNodeI(nkAsgn, n.info)
-  stateAsgnStmt.add(rawIndirectAccess(newSymNode(it.closureParam), it.state,
-                    n.info))
+  stateAsgnStmt.add(rawIndirectAccess(newSymNode(getEnvParam(owner)),
+                    state, n.info))
   stateAsgnStmt.add(newIntTypeNode(nkIntLit, -1, getSysType(tyInt)))
   result.add(stateAsgnStmt)
   result.add(n)
 
-proc outerProcSons(o: POuterContext, n: PNode, it: TIter) =
-  for i in countup(0, sonsLen(n) - 1):
-    let x = transformOuterProc(o, n.sons[i], it)
-    if x != nil: n.sons[i] = x
-
-proc liftIterSym(n: PNode; owner: PSym): PNode =
-  # transforms  (iter)  to  (let env = newClosure[iter](); (iter, env))
-  let iter = n.sym
-  assert iter.kind == skClosureIterator
-
-  result = newNodeIT(nkStmtListExpr, n.info, n.typ)
-
-  let hp = getHiddenParam(iter)
-  let env = newSym(skLet, iter.name, owner, n.info)
-  env.typ = hp.typ
-  env.flags = hp.flags
-  var v = newNodeI(nkVarSection, n.info)
-  addVar(v, newSymNode(env))
-  result.add(v)
-  # add 'new' statement:
-  let envAsNode = env.newSymNode
-  result.add newCall(getSysSym"internalNew", envAsNode)
-  result.add makeClosure(iter, envAsNode, n.info)
-
-when false:
-  proc transformRemainingLocals(n: PNode; it: TIter): PNode =
-    assert it.fn.kind == skClosureIterator
-    result = n
-    case n.kind
-    of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: discard
-    of nkSym:
-      let local = n.sym
-      if interestingIterVar(local) and it.fn == local.owner:
-        addUniqueField(it.obj, local)
-        result = indirectAccess(newSymNode(it.closureParam), local, n.info)
-    else:
-      result = newNodeI(n.kind, n.info, n.len)
-      for i in 0.. <n.safeLen:
-        result.sons[i] = transformRemainingLocals(n.sons[i], it)
-
-template envActive(env): expr =
-  (env.capturedVars.len > 0 or env.upField != nil)
-
-# We have to split up environment creation in 2 steps:
-# 1. Generate it and store it in env.replacementNode
-# 2. Insert replacementNode into its forseen slot.
-# This split is necessary so that assignments belonging to closure
-# creation like 'env.param = param' are not transformed
-# into 'env.param = env.param'.
-proc createEnvironments(o: POuterContext) =
-  var env = o.head
-  while env != nil:
-    if envActive(env):
-      var scope = env.attachedNode
-      assert scope.kind == nkStmtList
-      if scope.sons[0].kind == nkEmpty:
-        # prepare for closure construction:
-        env.replacementNode = generateClosureCreation(o, env)
-    env = env.next
-
-proc finishEnvironments(o: POuterContext) =
-  var env = o.head
-  while env != nil:
-    if env.replacementNode != nil:
-      var scope = env.attachedNode
-      assert scope.kind == nkStmtList
-      if scope.sons[0].kind == nkEmpty:
-        # change the empty node to contain the closure construction:
-        scope.sons[0] = env.replacementNode
-        when false:
-          if env.fn.kind == skClosureIterator:
-            scope.sons[0] = transformRemainingLocals(env.replacementNode,
-                                                     initIter(env.fn))
-          else:
-            scope.sons[0] = env.replacementNode
-    env = env.next
-
-proc transformOuterProcBody(o: POuterContext, n: PNode; it: TIter): PNode =
-  if nfLL in n.flags:
-    result = nil
-  elif it.isIterator:
+proc wrapIterBody(n: PNode; owner: PSym): PNode =
+  if not owner.isIterator: return n
+  when false:
     # unfortunately control flow is still convoluted and we can end up
     # multiple times here for the very same iterator. We shield against this
     # with some rather primitive check for now:
     if n.kind == nkStmtList and n.len > 0:
-      if n.sons[0].kind == nkGotoState: return nil
+      if n.sons[0].kind == nkGotoState: return n
       if n.len > 1 and n[1].kind == nkStmtList and n[1].len > 0 and
           n[1][0].kind == nkGotoState:
-        return nil
-    result = newNodeI(nkStmtList, it.fn.info)
-    var gs = newNodeI(nkGotoState, it.fn.info)
-    assert it.closureParam != nil
-    assert it.state != nil
-    gs.add(rawIndirectAccess(newSymNode(it.closureParam), it.state, it.fn.info))
-    result.add(gs)
-    var state0 = newNodeI(nkState, it.fn.info)
-    state0.add(newIntNode(nkIntLit, 0))
-    result.add(state0)
-
-    let newBody = transformOuterProc(o, n, it)
-    if newBody != nil:
-      result.add(newBody)
-    else:
-      result.add(n)
-
-    var stateAsgnStmt = newNodeI(nkAsgn, it.fn.info)
-    stateAsgnStmt.add(rawIndirectAccess(newSymNode(it.closureParam),
-                      it.state, it.fn.info))
-    stateAsgnStmt.add(newIntTypeNode(nkIntLit, -1, getSysType(tyInt)))
-    result.add(stateAsgnStmt)
-    result.flags.incl nfLL
-  else:
-    result = transformOuterProc(o, n, it)
-    if result != nil: result.flags.incl nfLL
+        return n
+  let info = n.info
+  result = newNodeI(nkStmtList, info)
+  var gs = newNodeI(nkGotoState, info)
+  gs.add(rawIndirectAccess(newSymNode(owner.getHiddenParam), getStateField(owner), info))
+  result.add(gs)
+  var state0 = newNodeI(nkState, info)
+  state0.add(newIntNode(nkIntLit, 0))
+  result.add(state0)
+
+  result.add(n)
 
-proc transformOuterProc(o: POuterContext, n: PNode; it: TIter): PNode =
-  if n == nil or nfLL in n.flags: return nil
+  var stateAsgnStmt = newNodeI(nkAsgn, info)
+  stateAsgnStmt.add(rawIndirectAccess(newSymNode(owner.getHiddenParam),
+                    getStateField(owner), info))
+  stateAsgnStmt.add(newIntTypeNode(nkIntLit, -1, getSysType(tyInt)))
+  result.add(stateAsgnStmt)
+  result.flags.incl nfLL
+
+proc symToClosure(n: PNode; owner: PSym; c: LiftingPass): PNode =
+  let s = n.sym
+  # direct dependency, so use the outer's env variable:
+  if s.skipGenericOwner == owner:
+    result = makeClosure(s, c.envVar, n.info)
+  elif s == owner:
+    # recursive calls go through (lambda, hiddenParam):
+    let available = getHiddenParam(owner)
+    result = makeClosure(s, available.newSymNode, n.info)
+  else:
+    let available = getHiddenParam(owner)
+    let wanted = getHiddenParam(s).typ
+    # ugh: call through some other inner proc;
+    var access = newSymNode(available)
+    while true:
+      if access.typ == wanted:
+        return makeClosure(s, access, n.info)
+      let obj = access.typ.sons[0]
+      let upField = lookupInRecord(obj.n, getIdent(upName))
+      if upField == nil:
+        localError(n.info, "internal error: no environment found")
+        break
+      access = rawIndirectAccess(access, upField, n.info)
+
+proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass;
+                      c: var LiftingPass): PNode =
+  result = n
   case n.kind
-  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: discard
   of nkSym:
-    var local = n.sym
-
-    if isInnerProc(local, o.fn) and o.processed.contains(local.id):
-      o.processed.excl(local.id)
-      let body = local.getBody
-      let newBody = transformOuterProcBody(o, body, initIter(local))
-      if newBody != nil:
-        local.ast.sons[bodyPos] = newBody
-
-    if it.isIterator and interestingIterVar(local) and
-        it.fn == local.owner:
-      # every local goes through the closure:
-      #if not containsOrIncl(o.capturedVars, local.id):
-      #  addField(it.obj, local)
-      if contains(o.capturedVars, local.id):
-        # change 'local' to 'closure.local', unless it's a 'byCopy' variable:
-        # if sfByCopy notin local.flags:
-        result = idNodeTableGet(o.localsToAccess, local)
-        assert result != nil, "cannot find: " & local.name.s
-        return result
-      else:
-        addUniqueField(it.obj, local)
-        return indirectAccess(newSymNode(it.closureParam), local, n.info)
-
-    if local.kind == skClosureIterator:
-      # bug #3354; allow for
-      #iterator iter(): int {.closure.}=
-      #  s.add(iter)
-      #  yield 1
-
-      #if local == o.fn or local == it.fn:
-      #  message(n.info, errRecursiveDependencyX, local.name.s)
-
-      # consider: [i1, i2, i1]  Since we merged the iterator's closure
-      # with the captured owning variables, we need to generate the
-      # closure generation code again:
-      # XXX why doesn't this work?
-      var closure = PEnv(idTableGet(o.lambdasToEnv, local))
-      if closure.isNil:
-        return liftIterSym(n, o.fn)
-      else:
-        let createdVar = generateIterClosureCreation(o, closure,
-                                                     closure.attachedNode)
-        let lpt = getHiddenParam(local).typ
-        if lpt != createdVar.typ:
-          assert lpt.kind == tyRef and createdVar.typ.kind == tyRef
-          # fix bug 'tshallowcopy_closures' but report if this gets any weirder:
-          if createdVar.typ.sons[0].len == 1 and lpt.sons[0].len >= 1:
-            createdVar.typ = lpt
-            if createdVar.kind == nkSym: createdVar.sym.typ = lpt
-            closure.obj = lpt.sons[0]
-          else:
-            internalError(n.info, "environment computation failed")
-        return makeClosure(local, createdVar, n.info)
-
-    var closure = PEnv(idTableGet(o.lambdasToEnv, local))
-    if closure != nil:
-      # we need to replace the lambda with '(lambda, env)':
-      let a = closure.createdVar
-      if a != nil:
-        return makeClosure(local, a, n.info)
+    let s = n.sym
+    # if s.kind == skIterator and s.typ.callConv == ccClosure:
+    # consider: [i1, i2, i1]  Since we merged the iterator's closure
+    # with the captured owning variables, we need to generate the
+    # closure generation code again: XXX think about this more,
+    # closure iterators are really strange in this regard.
+    if isInnerProc(s) and s.typ.callConv == ccClosure:
+      if not c.processed.containsOrIncl(s.id):
+        let oldEnvVar = c.envVar
+        let oldEnvCreation = c.envCreation
+        c.envVar = nil
+        c.envCreation = nil
+        let body = wrapIterBody(liftCapturedVars(s.getBody, s, d, c), s)
+        if c.envCreation.isNil:
+          s.ast.sons[bodyPos] = body
+        else:
+          s.ast.sons[bodyPos] = newTree(nkStmtList, c.envCreation, body)
+          c.envCreation = nil
+        c.envVar = oldEnvVar
+        c.envCreation = oldEnvCreation
+      result = symToClosure(n, owner, c)
+    elif s.id in d.capturedVars:
+      if s.owner != owner:
+        result = accessViaEnvParam(n, owner)
+      elif owner.kind == skIterator and interestingIterVar(s):
+        result = accessViaEnvParam(n, owner)
       else:
-        # can happen for dummy closures:
-        var scope = closure.attachedNode
-        assert scope.kind == nkStmtList
-        if scope.sons[0].kind == nkEmpty:
-          # change the empty node to contain the closure construction:
-          scope.sons[0] = generateClosureCreation(o, closure)
-        let x = closure.createdVar
-        assert x != nil
-        return makeClosure(local, x, n.info)
-
-    if not contains(o.capturedVars, local.id): return
-    # change 'local' to 'closure.local', unless it's a 'byCopy' variable:
-    # if sfByCopy notin local.flags:
-    result = idNodeTableGet(o.localsToAccess, local)
-    assert result != nil, "cannot find: " & local.name.s
-    # else it is captured by copy and this means that 'outer' should continue
-    # to access the local as a local.
-  of nkLambdaKinds, nkIteratorDef:
-    if n.typ != nil:
-      result = transformOuterProc(o, n.sons[namePos], it)
-  of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef:
-    # don't recurse here:
+        result = accessViaEnvVar(n, owner, d, c)
+  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, nkClosure,
+     nkTemplateDef, nkTypeSection:
+    discard
+  of nkProcDef, nkMethodDef, nkIteratorDef, nkConverterDef, nkMacroDef:
     discard
-  of nkClosure:
-    if n.sons[0].kind == nkSym:
-      var local = n.sons[0].sym
-      if isInnerProc(local, o.fn) and o.processed.contains(local.id):
-        o.processed.excl(local.id)
-        let body = local.getBody
-        let newBody = transformOuterProcBody(o, body, initIter(local))
-        if newBody != nil:
-          local.ast.sons[bodyPos] = newBody
-    when false:
-      if n.sons[1].kind == nkSym:
-        var local = n.sons[1].sym
-        if it.isIterator and interestingIterVar(local) and
-            it.fn == local.owner:
-          # every local goes through the closure:
-          addUniqueField(it.obj, local)
-          n.sons[1] = indirectAccess(newSymNode(it.closureParam), local, n.info)
-  of nkHiddenStdConv, nkHiddenSubConv, nkConv:
-    let x = transformOuterProc(o, n.sons[1], it)
-    if x != nil: n.sons[1] = x
-    result = transformOuterConv(n)
-  of nkYieldStmt:
-    if it.isIterator: result = transformYield(o, n, it)
-    else: outerProcSons(o, n, it)
-  of nkReturnStmt:
-    if it.isIterator: result = transformReturn(o, n, it)
-    else: outerProcSons(o, n, it)
   else:
-    outerProcSons(o, n, it)
+    if owner.isIterator and n.kind == nkYieldStmt:
+      result = transformYield(n, owner, d, c)
+    elif owner.isIterator and n.kind == nkReturnStmt:
+      result = transformReturn(n, owner, d, c)
+    else:
+      for i in 0..<n.len:
+        n.sons[i] = liftCapturedVars(n[i], owner, d, c)
+
+# ------------------ old stuff -------------------------------------------
+
+proc semCaptureSym*(s, owner: PSym) =
+  if interestingVar(s) and owner.id != s.owner.id and s.kind != skResult:
+    if owner.typ != nil and not isGenericRoutine(owner):
+      # XXX: is this really safe?
+      # if we capture a var from another generic routine,
+      # it won't be consider captured.
+      owner.typ.callConv = ccClosure
+    #echo "semCaptureSym ", owner.name.s, owner.id, " ", s.name.s, s.id
+    # since the analysis is not entirely correct, we don't set 'tfCapturesEnv'
+    # here
 
 proc liftIterToProc*(fn: PSym; body: PNode; ptrType: PType): PNode =
-  var o = newOuterContext(fn)
-  let ex = closureCreationPoint(body)
-  let env = newEnv(o, nil, ex, fn)
-  addParamsToEnv(fn, env)
-  searchForInnerProcs(o, body, env)
-  createEnvironments(o)
-  let it = initIter(fn, ptrType)
-  result = transformOuterProcBody(o, body, it)
-  finishEnvironments(o)
+  var d = initDetectionPass(fn)
+  var c = initLiftingPass(fn)
+  # pretend 'fn' is a closure iterator for the analysis:
+  let oldKind = fn.kind
+  let oldCC = fn.typ.callConv
+  fn.kind = skIterator
+  fn.typ.callConv = ccClosure
+  d.ownerToType[fn.id] = ptrType
+  detectCapturedVars(body, fn, d)
+  result = wrapIterBody(liftCapturedVars(body, fn, d, c), fn)
+  fn.kind = oldKind
+  fn.typ.callConv = oldCC
 
 proc liftLambdas*(fn: PSym, body: PNode): PNode =
   # XXX gCmd == cmdCompileToJS does not suffice! The compiletime stuff needs
@@ -979,35 +651,26 @@ proc liftLambdas*(fn: PSym, body: PNode): PNode =
     # ignore forward declaration:
     result = body
   else:
-    #if fn.name.s == "sort":
-    #  echo rendertree(fn.ast, {renderIds})
-    var o = newOuterContext(fn)
-    let ex = closureCreationPoint(body)
-    let env = newEnv(o, nil, ex, fn)
-    addParamsToEnv(fn, env)
-    searchForInnerProcs(o, body, env)
-    createEnvironments(o)
-    if fn.kind == skClosureIterator:
-      result = transformOuterProcBody(o, body, initIter(fn))
+    var d = initDetectionPass(fn)
+    var c = initLiftingPass(fn)
+    detectCapturedVars(body, fn, d)
+    if d.somethingToDo:
+      var newBody = liftCapturedVars(body, fn, d, c)
+      if not c.envCreation.isNil:
+        newBody = newTree(nkStmtList, c.envCreation, newBody)
+        c.envCreation = nil
+      result = wrapIterBody(newBody, fn)
     else:
-      discard transformOuterProcBody(o, body, initIter(fn))
-      result = ex
-    finishEnvironments(o)
-    #if fn.name.s == "parseLong":
-    #  echo rendertree(result, {renderIds})
+      result = body
+    if fn.name.s == "outer":
+      echo renderTree(result, {renderIds})
 
 proc liftLambdasForTopLevel*(module: PSym, body: PNode): PNode =
   if body.kind == nkEmpty or gCmd == cmdCompileToJS:
     result = body
   else:
-    var o = newOuterContext(module)
-    let ex = closureCreationPoint(body)
-    let env = newEnv(o, nil, ex, module)
-    searchForInnerProcs(o, body, env)
-    createEnvironments(o)
-    discard transformOuterProc(o, body, initIter(module))
-    finishEnvironments(o)
-    result = ex
+    # XXX implement it properly
+    result = body
 
 # ------------------- iterator transformation --------------------------------
 
@@ -1049,10 +712,10 @@ proc liftForLoop*(body: PNode): PNode =
 
   # static binding?
   var env: PSym
-  if call[0].kind == nkSym and call[0].sym.kind == skClosureIterator:
+  if call[0].kind == nkSym and call[0].sym.isIterator:
     # createClosure()
     let iter = call[0].sym
-    assert iter.kind == skClosureIterator
+    assert iter.isIterator
     env = copySym(getHiddenParam(iter))
 
     var v = newNodeI(nkVarSection, body.info)
diff --git a/compiler/lookups.nim b/compiler/lookups.nim
index e88589c3e..a337bf0f3 100644
--- a/compiler/lookups.nim
+++ b/compiler/lookups.nim
@@ -133,7 +133,7 @@ type
 
 proc getSymRepr*(s: PSym): string =
   case s.kind
-  of skProc, skMethod, skConverter, skIterators: result = getProcHeader(s)
+  of skProc, skMethod, skConverter, skIterator: result = getProcHeader(s)
   else: result = s.name.s
 
 proc ensureNoMissingOrUnusedSymbols(scope: PScope) =
diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim
index 20800b809..6b60faba2 100644
--- a/compiler/lowerings.nim
+++ b/compiler/lowerings.nim
@@ -585,7 +585,7 @@ proc wrapProcForSpawn*(owner: PSym; spawnExpr: PNode; retType: PType;
     objType.addField(field)
     result.add newFastAsgnStmt(newDotExpr(scratchObj, field), n[0])
     fn = indirectAccess(castExpr, field, n.info)
-  elif fn.kind == nkSym and fn.sym.kind in {skClosureIterator, skIterator}:
+  elif fn.kind == nkSym and fn.sym.kind == skIterator:
     localError(n.info, "iterator in spawn environment is not allowed")
   elif fn.typ.callConv == ccClosure:
     localError(n.info, "closure in spawn environment is not allowed")
diff --git a/compiler/semcall.nim b/compiler/semcall.nim
index d8838e347..8445b24d9 100644
--- a/compiler/semcall.nim
+++ b/compiler/semcall.nim
@@ -75,7 +75,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
           errors.add(err)
     if z.state == csMatch:
       # little hack so that iterators are preferred over everything else:
-      if sym.kind in skIterators: inc(z.exactMatches, 200)
+      if sym.kind == skIterator: inc(z.exactMatches, 200)
       case best.state
       of csEmpty, csNoMatch: best = z
       of csMatch:
@@ -395,7 +395,7 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
     for i in countup(0, len(a)-1):
       var candidate = a.sons[i].sym
       if candidate.kind in {skProc, skMethod, skConverter,
-                            skIterator, skClosureIterator}:
+                            skIterator}:
         # it suffices that the candidate has the proper number of generic
         # type parameters:
         if safeLen(candidate.ast.sons[genericParamsPos]) == n.len-1:
diff --git a/compiler/semdata.nim b/compiler/semdata.nim
index 9b2f2e2ce..656bfc449 100644
--- a/compiler/semdata.nim
+++ b/compiler/semdata.nim
@@ -315,7 +315,7 @@ proc makeRangeType*(c: PContext; first, last: BiggestInt;
   addSonSkipIntLit(result, intType) # basetype of range
 
 proc markIndirect*(c: PContext, s: PSym) {.inline.} =
-  if s.kind in {skProc, skConverter, skMethod, skIterator, skClosureIterator}:
+  if s.kind in {skProc, skConverter, skMethod, skIterator}:
     incl(s.flags, sfAddrTaken)
     # XXX add to 'c' for global analysis
 
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 95a90463c..43c98859c 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -753,11 +753,11 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
                                      flags: TExprFlags): PNode =
   if flags*{efInTypeof, efWantIterator} != {}:
     # consider: 'for x in pReturningArray()' --> we don't want the restriction
-    # to 'skIterators' anymore; skIterators are preferred in sigmatch already
+    # to 'skIterator' anymore; skIterator is preferred in sigmatch already
     # for typeof support.
     # for ``type(countup(1,3))``, see ``tests/ttoseq``.
     result = semOverloadedCall(c, n, nOrig,
-      {skProc, skMethod, skConverter, skMacro, skTemplate}+skIterators)
+      {skProc, skMethod, skConverter, skMacro, skTemplate, skIterator})
   else:
     result = semOverloadedCall(c, n, nOrig,
       {skProc, skMethod, skConverter, skMacro, skTemplate})
@@ -770,7 +770,7 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
     case callee.kind
     of skMacro, skTemplate: discard
     else:
-      if callee.kind in skIterators and callee.id == c.p.owner.id:
+      if callee.kind == skIterator and callee.id == c.p.owner.id:
         localError(n.info, errRecursiveDependencyX, callee.name.s)
         # error correction, prevents endless for loop elimination in transf.
         # See bug #2051:
@@ -1201,7 +1201,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
     let s = if n.sons[0].kind == nkSym: n.sons[0].sym
             elif n[0].kind in nkSymChoices: n.sons[0][0].sym
             else: nil
-    if s != nil and s.kind in {skProc, skMethod, skConverter}+skIterators:
+    if s != nil and s.kind in {skProc, skMethod, skConverter, skIterator}:
       # type parameters: partial generic specialization
       n.sons[0] = semSymGenericInstantiation(c, n.sons[0], s)
       result = explicitGenericInstantiation(c, n, s)
@@ -1349,8 +1349,8 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
 proc semReturn(c: PContext, n: PNode): PNode =
   result = n
   checkSonsLen(n, 1)
-  if c.p.owner.kind in {skConverter, skMethod, skProc, skMacro,
-                        skClosureIterator}:
+  if c.p.owner.kind in {skConverter, skMethod, skProc, skMacro} or (
+     c.p.owner.kind == skIterator and c.p.owner.typ.callConv == ccClosure):
     if n.sons[0].kind != nkEmpty:
       # transform ``return expr`` to ``result = expr; return``
       if c.p.resultSym != nil:
@@ -1426,7 +1426,7 @@ proc semYieldVarResult(c: PContext, n: PNode, restype: PType) =
 proc semYield(c: PContext, n: PNode): PNode =
   result = n
   checkSonsLen(n, 1)
-  if c.p.owner == nil or c.p.owner.kind notin skIterators:
+  if c.p.owner == nil or c.p.owner.kind != skIterator:
     localError(n.info, errYieldNotAllowedHere)
   elif c.p.inTryStmt > 0 and c.p.owner.typ.callConv != ccInline:
     localError(n.info, errYieldNotAllowedInTryStmt)
@@ -2124,7 +2124,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
     var s = lookUp(c, n)
     if c.inTypeClass == 0: semCaptureSym(s, c.p.owner)
     result = semSym(c, n, s, flags)
-    if s.kind in {skProc, skMethod, skConverter}+skIterators:
+    if s.kind in {skProc, skMethod, skConverter, skIterator}:
       #performProcvarCheck(c, n, s)
       result = symChoice(c, n, s, scClosed)
       if result.kind == nkSym:
@@ -2212,7 +2212,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
           localError(n.info, errUseQualifier, s.name.s)
         elif s.magic == mNone: result = semDirectOp(c, n, flags)
         else: result = semMagic(c, n, s, flags)
-      of skProc, skMethod, skConverter, skIterators:
+      of skProc, skMethod, skConverter, skIterator:
         if s.magic == mNone: result = semDirectOp(c, n, flags)
         else: result = semMagic(c, n, s, flags)
       else:
diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim
index 620453277..6651de78e 100644
--- a/compiler/semgnrc.nim
+++ b/compiler/semgnrc.nim
@@ -58,7 +58,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
   of skUnknown:
     # Introduced in this pass! Leave it as an identifier.
     result = n
-  of skProc, skMethod, skIterators, skConverter, skModule:
+  of skProc, skMethod, skIterator, skConverter, skModule:
     result = symChoice(c, n, s, scOpen)
   of skTemplate:
     if macroToExpand(s):
@@ -226,7 +226,7 @@ proc semGenericStmt(c: PContext, n: PNode,
       of skUnknown, skParam:
         # Leave it as an identifier.
         discard
-      of skProc, skMethod, skIterators, skConverter, skModule:
+      of skProc, skMethod, skIterator, skConverter, skModule:
         result.sons[0] = symChoice(c, fn, s, scOption)
         # do not check of 's.magic==mRoof' here because it might be some
         # other '^' but after overload resolution the proper one:
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index e80f1cfda..f54f523ad 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -84,7 +84,7 @@ proc performProcvarCheck(c: PContext, n: PNode, s: PSym) =
 proc semProcvarCheck(c: PContext, n: PNode) =
   let n = n.skipConv
   if n.kind == nkSym and n.sym.kind in {skProc, skMethod, skConverter,
-                                        skIterator, skClosureIterator}:
+                                        skIterator}:
     performProcvarCheck(c, n, n.sym)
 
 proc semProc(c: PContext, n: PNode): PNode
@@ -598,7 +598,7 @@ proc semFor(c: PContext, n: PNode): PNode =
     # first class iterator:
     result = semForVars(c, n)
   elif not isCallExpr or call.sons[0].kind != nkSym or
-      call.sons[0].sym.kind notin skIterators:
+      call.sons[0].sym.kind != skIterator:
     if length == 3:
       n.sons[length-2] = implicitIterator(c, "items", n.sons[length-2])
     elif length == 4:
@@ -1143,13 +1143,15 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
   if tfTriggersCompileTime in s.typ.flags: incl(s.flags, sfCompileTime)
   if n.sons[patternPos].kind != nkEmpty:
     n.sons[patternPos] = semPattern(c, n.sons[patternPos])
-  if s.kind in skIterators:
+  if s.kind == skIterator:
     s.typ.flags.incl(tfIterator)
 
   var proto = searchForProc(c, oldScope, s)
   if proto == nil:
-    if s.kind == skClosureIterator: s.typ.callConv = ccClosure
-    else: s.typ.callConv = lastOptionEntry(c).defaultCC
+    if s.kind == skIterator and s.typ.callConv == ccClosure:
+      discard
+    else:
+      s.typ.callConv = lastOptionEntry(c).defaultCC
     # add it here, so that recursive procs are possible:
     if sfGenSym in s.flags: discard
     elif kind in OverloadableSyms:
@@ -1209,7 +1211,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
         n.sons[bodyPos] = transformBody(c.module, semBody, s)
       popProcCon(c)
     else:
-      if s.typ.sons[0] != nil and kind notin skIterators:
+      if s.typ.sons[0] != nil and kind != skIterator:
         addDecl(c, newSym(skUnknown, getIdent"result", nil, n.info))
       openScope(c)
       n.sons[bodyPos] = semGenericStmt(c, n.sons[bodyPos])
@@ -1230,7 +1232,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
   if n.sons[patternPos].kind != nkEmpty:
     c.patterns.add(s)
   if isAnon: result.typ = s.typ
-  if isTopLevel(c) and s.kind != skClosureIterator and
+  if isTopLevel(c) and s.kind != skIterator and
       s.typ.callConv == ccClosure:
     message(s.info, warnDeprecated, "top level '.closure' calling convention")
 
@@ -1240,15 +1242,12 @@ proc determineType(c: PContext, s: PSym) =
   discard semProcAux(c, s.ast, s.kind, {}, stepDetermineType)
 
 proc semIterator(c: PContext, n: PNode): PNode =
-  let kind = if hasPragma(n[pragmasPos], wClosure) or
-                n[namePos].kind == nkEmpty: skClosureIterator
-             else: skIterator
   # gensym'ed iterator?
   if n[namePos].kind == nkSym:
     # gensym'ed iterators might need to become closure iterators:
     n[namePos].sym.owner = getCurrOwner()
-    n[namePos].sym.kind = kind
-  result = semProcAux(c, n, kind, iteratorPragmas)
+    n[namePos].sym.kind = skIterator
+  result = semProcAux(c, n, skIterator, iteratorPragmas)
   var s = result.sons[namePos].sym
   var t = s.typ
   if t.sons[0] == nil and s.typ.callConv != ccClosure:
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index 2dda8276d..b19ffd77f 100644
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -456,9 +456,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
   of nkMethodDef:
     result = semRoutineInTemplBody(c, n, skMethod)
   of nkIteratorDef:
-    let kind = if hasPragma(n[pragmasPos], wClosure): skClosureIterator
-               else: skIterator
-    result = semRoutineInTemplBody(c, n, kind)
+    result = semRoutineInTemplBody(c, n, skIterator)
   of nkTemplateDef:
     result = semRoutineInTemplBody(c, n, skTemplate)
   of nkMacroDef:
diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim
index 65edb756f..d27702e8c 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -1296,7 +1296,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
       child.flags.incl tfIterator
       result.addSonSkipIntLit(child)
     else:
-      result = semProcTypeWithScope(c, n, prev, skClosureIterator)
+      result = semProcTypeWithScope(c, n, prev, skIterator)
       result.flags.incl(tfIterator)
       if n.lastSon.kind == nkPragma and hasPragma(n.lastSon, wInline):
         result.callConv = ccInline
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index aee42e021..784294839 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -1442,7 +1442,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
     z.calleeSym = m.calleeSym
     var best = -1
     for i in countup(0, sonsLen(arg) - 1):
-      if arg.sons[i].sym.kind in {skProc, skMethod, skConverter}+skIterators:
+      if arg.sons[i].sym.kind in {skProc, skMethod, skConverter, skIterator}:
         copyCandidate(z, m)
         z.callee = arg.sons[i].typ
         z.calleeSym = arg.sons[i].sym
diff --git a/compiler/transf.nim b/compiler/transf.nim
index 3a5ff982e..98aafaaff 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -348,6 +348,20 @@ proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode =
       # addr ( deref ( x )) --> x
       result = PTransNode(n.sons[0].sons[0])
 
+proc generateThunk(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):
+  if gCmd == cmdCompileToJS: return prc
+  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:
   var dest = skipTypes(n.typ, abstractVarRange)
@@ -428,6 +442,10 @@ proc transformConv(c: PTransf, n: PNode): PTransNode =
   of tyGenericParam, tyOrdinal:
     result = transform(c, n.sons[1])
     # happens sometimes for generated assignments, etc.
+  of tyProc:
+    result = transformSons(c, n)
+    if dest.callConv == ccClosure and source.callConv == ccDefault:
+      result = generateThunk(result[1].PNode, dest).PTransNode
   else:
     result = transformSons(c, n)
 
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index 2a16406e7..75c1378e5 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -1209,7 +1209,7 @@ proc checkCanEval(c: PCtx; n: PNode) =
       not s.isOwnedBy(c.prc.sym) and s.owner != c.module and c.mode != emRepl:
     cannotEval(n)
   elif s.kind in {skProc, skConverter, skMethod,
-                  skIterator, skClosureIterator} and sfForward in s.flags:
+                  skIterator} and sfForward in s.flags:
     cannotEval(n)
 
 proc isTemp(c: PCtx; dest: TDest): bool =
@@ -1638,7 +1638,7 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) =
     case s.kind
     of skVar, skForVar, skTemp, skLet, skParam, skResult:
       genRdVar(c, n, dest, flags)
-    of skProc, skConverter, skMacro, skTemplate, skMethod, skIterators:
+    of skProc, skConverter, skMacro, skTemplate, skMethod, skIterator:
       # 'skTemplate' is only allowed for 'getAst' support:
       if procIsCallback(c, s): discard
       elif sfImportc in s.flags: c.importcSym(n.info, s)