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