diff options
author | Araq <rumpf_a@web.de> | 2012-06-19 22:37:00 +0200 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2012-06-19 22:37:00 +0200 |
commit | f191059e56ccf8accf872f4fb10986418dac45e2 (patch) | |
tree | aafd9edc964744fd82b832660f2bcf36967cd309 | |
parent | 98458a3076c5d4fc2942cbd3e260999d1adcfe9a (diff) | |
download | Nim-f191059e56ccf8accf872f4fb10986418dac45e2.tar.gz |
somewhat working closures
-rwxr-xr-x | compiler/cgen.nim | 5 | ||||
-rwxr-xr-x | compiler/evals.nim | 3 | ||||
-rw-r--r-- | compiler/lambdalifting.nim | 52 | ||||
-rwxr-xr-x | compiler/seminst.nim | 4 | ||||
-rwxr-xr-x | compiler/transf.nim | 15 | ||||
-rw-r--r-- | tests/reject/tinvalidclosure.nim | 7 | ||||
-rw-r--r-- | tests/run/tclosure2.nim | 34 | ||||
-rwxr-xr-x | todo.txt | 8 |
8 files changed, 95 insertions, 33 deletions
diff --git a/compiler/cgen.nim b/compiler/cgen.nim index e8073b555..73bd79ce8 100755 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -571,7 +571,10 @@ proc deinitFrame(p: BProc): PRope = proc closureSetup(p: BProc, prc: PSym) = if prc.typ.callConv != ccClosure: return # prc.ast[paramsPos].last contains the type we're after: - var env = lastSon(prc.ast[paramsPos]).sym + var ls = lastSon(prc.ast[paramsPos]) + if ls.kind != nkSym: + InternalError(prc.info, "closure generation failed") + var env = ls.sym #echo "created environment: ", env.id, " for ", prc.name.s assignLocalVar(p, env) # generate cast assignment: diff --git a/compiler/evals.nim b/compiler/evals.nim index 5c77a4d94..eb193cb9e 100755 --- a/compiler/evals.nim +++ b/compiler/evals.nim @@ -501,7 +501,8 @@ proc evalSym(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = result = evalGlobalVar(c, s, flags) of skParam: # XXX what about LValue? - result = c.tos.params[s.position + 1] + if s.position + 1 <% c.tos.params.len: + result = c.tos.params[s.position + 1] of skConst: result = s.ast of skEnumField: result = newIntNodeT(s.position, n) else: result = nil diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index 2b0aba147..219fead2c 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -218,8 +218,29 @@ proc isInnerProc(s, outerProc: PSym): bool {.inline.} = s.owner == outerProc and not isGenericRoutine(s) #s.typ.callConv == ccClosure +proc addClosureParam(i: PInnerContext, e: PEnv) = + var cp = newSym(skParam, getIdent(paramname), i.fn) + cp.info = i.fn.info + incl(cp.flags, sfFromGeneric) + cp.typ = newType(tyRef, i.fn) + addSon(cp.typ, e.tup) + i.closureParam = cp + addHiddenParam(i.fn, i.closureParam) + #echo "closure param added for ", i.fn.name.s, " ", i.fn.id + +proc dummyClosureParam(o: POuterContext, i: PInnerContext) = + var e = o.currentEnv + if IdTableGet(o.lambdasToEnv, i.fn) == nil: + IdTablePut(o.lambdasToEnv, i.fn, e) + if i.closureParam == nil: addClosureParam(i, e) + proc captureVar(o: POuterContext, i: PInnerContext, local: PSym, info: TLineInfo) = + # for inlined variables the owner is still wrong, so it can happen that it's + # not a captured variable at all ... *sigh* + var it = PEnv(IdTableGet(o.localsToEnv, local)) + if it == nil: return + # we need to remember which inner most closure belongs to this lambda: var e = o.currentEnv if IdTableGet(o.lambdasToEnv, i.fn) == nil: @@ -227,19 +248,10 @@ proc captureVar(o: POuterContext, i: PInnerContext, local: PSym, # variable already captured: if IdNodeTableGet(i.localsToAccess, local) != nil: return - if i.closureParam == nil: - var cp = newSym(skParam, getIdent(paramname), i.fn) - cp.info = i.fn.info - incl(cp.flags, sfFromGeneric) - cp.typ = newType(tyRef, i.fn) - addSon(cp.typ, e.tup) - i.closureParam = cp - addHiddenParam(i.fn, i.closureParam) + if i.closureParam == nil: addClosureParam(i, e) # check which environment `local` belongs to: var access = newSymNode(i.closureParam) - var it = PEnv(IdTableGet(o.localsToEnv, local)) - assert it != nil addCapturedVar(it, local) if it == e: # common case: local directly in current environment: @@ -325,6 +337,9 @@ proc searchForInnerProcs(o: POuterContext, n: PNode) = var inner = newInnerContext(n.sym) let body = n.sym.getBody gatherVars(o, inner, body) + # dummy closure param needed? + if inner.closureParam == nil and n.sym.typ.callConv == ccClosure: + dummyClosureParam(o, inner) let ti = transformInnerProc(o, inner, body) if ti != nil: n.sym.ast.sons[bodyPos] = ti of nkLambdaKinds: @@ -425,9 +440,19 @@ proc transformOuterProc(o: POuterContext, n: PNode): PNode = if closure != nil: # we need to replace the lambda with '(lambda, env)': let a = closure.closure - assert a != nil - return makeClosure(local, a, n.info) - + if a != nil: + return makeClosure(local, a, n.info) + 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.closure + assert x != nil + return makeClosure(local, x, n.info) + if not contains(o.capturedVars, local.id): return var env = PEnv(IdTableGet(o.localsToEnv, local)) if env == nil: return @@ -474,5 +499,6 @@ proc liftLambdas(fn: PSym, body: PNode): PNode = proc liftLambdas*(n: PNode): PNode = assert n.kind in procDefs + if gCmd == cmdCompileToEcmaScript: return n var s = n.sons[namePos].sym result = liftLambdas(s, s.getBody) diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 85c68923c..f44b80223 100755 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -145,7 +145,9 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, c.friendModule = getModule(fn) result = copySym(fn, false) incl(result.flags, sfFromGeneric) - result.owner = getCurrOwner().owner + # keep the owner if it's an inner proc (for proper closure transformations): + if fn.owner.kind == skModule: + result.owner = getCurrOwner().owner # careful! we copy the whole AST including the possibly nil body! var n = copyTree(fn.ast) result.ast = n diff --git a/compiler/transf.nim b/compiler/transf.nim index b10310e14..c9e226c87 100755 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -377,6 +377,7 @@ proc generateThunk(c: PTransf, prc: PNode, dest: PType): PNode = # we cannot generate a proper thunk here for GC-safety reasons (see internal # documentation): + if gCmd == cmdCompileToEcmaScript: return prc result = newNodeIT(nkClosure, prc.info, dest) var conv = newNodeIT(nkHiddenStdConv, prc.info, dest) conv.add(emptyNode) @@ -506,15 +507,18 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = if call.kind notin nkCallKinds or call.sons[0].kind != nkSym: InternalError(call.info, "transformFor") - var newC = newTransCon(call.sons[0].sym) + # Bugfix: inlined locals belong to the invoking routine, not to the invoked + # iterator! + let iter = call.sons[0].sym + var newC = newTransCon(getCurrOwner(c)) newC.forStmt = n newC.forLoopBody = loopBody - if newC.owner.kind != skIterator: InternalError(call.info, "transformFor") + if iter.kind != skIterator: InternalError(call.info, "transformFor") # generate access statements for the parameters (unless they are constant) pushTransCon(c, newC) for i in countup(1, sonsLen(call) - 1): var arg = transform(c, call.sons[i]).pnode - var formal = skipTypes(newC.owner.typ, abstractInst).n.sons[i].sym + var formal = skipTypes(iter.typ, abstractInst).n.sons[i].sym case putArgInto(arg, formal.typ) of paDirectMapping: IdNodeTablePut(newC.mapping, formal, arg) @@ -528,7 +532,7 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = assert(skipTypes(formal.typ, abstractInst).kind == tyVar) IdNodeTablePut(newC.mapping, formal, arg) # XXX BUG still not correct if the arg has a side effect! - var body = newC.owner.getBody + var body = iter.getBody pushInfoContext(n.info) inc(c.inlining) add(result, transform(c, body)) @@ -647,6 +651,9 @@ proc transform(c: PTransf, n: PNode): PTransNode = if n.sons[genericParamsPos].kind == nkEmpty: var s = n.sons[namePos].sym n.sons[bodyPos] = PNode(transform(c, s.getBody)) + if s.ast.sons[bodyPos] != n.sons[bodyPos]: + # somehow this can happen ... :-/ + s.ast.sons[bodyPos] = n.sons[bodyPos] n.sons[bodyPos] = liftLambdas(n) if n.kind == nkMethodDef: methodDef(s, false) result = PTransNode(n) diff --git a/tests/reject/tinvalidclosure.nim b/tests/reject/tinvalidclosure.nim new file mode 100644 index 000000000..f5ac3a5f0 --- /dev/null +++ b/tests/reject/tinvalidclosure.nim @@ -0,0 +1,7 @@ +discard """ + line: 6 + errormsg: "'ugh' cannot have 'closure' calling convention" +""" + +proc ugh[T](x: T) {.closure.} = + echo "ugha" diff --git a/tests/run/tclosure2.nim b/tests/run/tclosure2.nim index 5a1cb8075..113c98250 100644 --- a/tests/run/tclosure2.nim +++ b/tests/run/tclosure2.nim @@ -1,20 +1,30 @@ discard """ - output: '''1 + output: '''0 +11 +1 +11 2 +11 3 +11 4 +11 5 +11 6 +11 7 +11 8 -9 -10 11 +9 11 py py py -py''' +py +px +6''' """ when true: @@ -34,7 +44,7 @@ when true: ax() -when false: +when true: proc accumulator(start: int): (proc(): int {.closure.}) = var x = start-1 #let dummy = proc = @@ -62,8 +72,14 @@ when false: outer() -when false: - proc outer = +when true: + proc outer2 = + var errorValue = 3 + proc fac[T](n: T): T = + if n < 0: result = errorValue + elif n <= 1: result = 1 + else: result = n * fac(n-1) + proc px() {.closure.} = echo "px" @@ -76,7 +92,9 @@ when false: "xyz": py } mapping[0][1]() + + echo fac(3) - outer() + outer2() diff --git a/todo.txt b/todo.txt index aa914bf84..6bbfe0289 100755 --- a/todo.txt +++ b/todo.txt @@ -8,12 +8,9 @@ version 0.9.0 - ``=`` should be overloadable; requires specialization for ``=`` - fix remaining generics bugs - fix remaining closure bugs: + - make toplevel but in a scope vars local; make procs there inner procs - fix evals.nim with closures - - deactivate lambda lifting for JS backend - - Test capture of for loop vars; test generics; - - test constant closures - - implement closures that support nesting of blocks > 1 - - implement closures that support nesting of *procs* > 1 + - test sequence of closures; especially that the GC does not leak for those! - implement proper coroutines - document 'do' notation @@ -129,6 +126,7 @@ Low priority - activate more thread tests - implement ``--script:sh|bat`` command line option; think about script generation +- implement closures that support nesting of *procs* > 1 Further optimization ideas |