diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2015-12-26 00:42:30 +0100 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2015-12-26 00:42:30 +0100 |
commit | c1627354d1fe5499c3d8b6886ce63de6e062a463 (patch) | |
tree | abda7e9d5089d52b395fac747d26318f5fd8c04a /compiler | |
parent | 86bb9d8b4768b638ccbc603c26a4b5a70343ab3e (diff) | |
download | Nim-c1627354d1fe5499c3d8b6886ce63de6e062a463.tar.gz |
first implementation of the new lambda-lifting pass; barely anything works
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/ast.nim | 20 | ||||
-rw-r--r-- | compiler/ccgexprs.nim | 2 | ||||
-rw-r--r-- | compiler/ccgstmts.nim | 2 | ||||
-rw-r--r-- | compiler/cgen.nim | 2 | ||||
-rw-r--r-- | compiler/docgen.nim | 2 | ||||
-rw-r--r-- | compiler/jsgen.nim | 2 | ||||
-rw-r--r-- | compiler/lambdalifting.nim | 1137 | ||||
-rw-r--r-- | compiler/lookups.nim | 2 | ||||
-rw-r--r-- | compiler/lowerings.nim | 2 | ||||
-rw-r--r-- | compiler/semcall.nim | 4 | ||||
-rw-r--r-- | compiler/semdata.nim | 2 | ||||
-rw-r--r-- | compiler/semexprs.nim | 18 | ||||
-rw-r--r-- | compiler/semgnrc.nim | 4 | ||||
-rw-r--r-- | compiler/semstmts.nim | 23 | ||||
-rw-r--r-- | compiler/semtempl.nim | 4 | ||||
-rw-r--r-- | compiler/semtypes.nim | 2 | ||||
-rw-r--r-- | compiler/sigmatch.nim | 2 | ||||
-rw-r--r-- | compiler/transf.nim | 18 | ||||
-rw-r--r-- | compiler/vmgen.nim | 4 |
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) |