somewhat working closures
This commit is contained in:
parent
98458a3076
commit
f191059e56
8 changed files with 95 additions and 33 deletions
|
|
@ -571,7 +571,10 @@ proc deinitFrame(p: BProc): PRope =
|
||||||
proc closureSetup(p: BProc, prc: PSym) =
|
proc closureSetup(p: BProc, prc: PSym) =
|
||||||
if prc.typ.callConv != ccClosure: return
|
if prc.typ.callConv != ccClosure: return
|
||||||
# prc.ast[paramsPos].last contains the type we're after:
|
# 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
|
#echo "created environment: ", env.id, " for ", prc.name.s
|
||||||
assignLocalVar(p, env)
|
assignLocalVar(p, env)
|
||||||
# generate cast assignment:
|
# generate cast assignment:
|
||||||
|
|
|
||||||
|
|
@ -501,6 +501,7 @@ proc evalSym(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode =
|
||||||
result = evalGlobalVar(c, s, flags)
|
result = evalGlobalVar(c, s, flags)
|
||||||
of skParam:
|
of skParam:
|
||||||
# XXX what about LValue?
|
# XXX what about LValue?
|
||||||
|
if s.position + 1 <% c.tos.params.len:
|
||||||
result = c.tos.params[s.position + 1]
|
result = c.tos.params[s.position + 1]
|
||||||
of skConst: result = s.ast
|
of skConst: result = s.ast
|
||||||
of skEnumField: result = newIntNodeT(s.position, n)
|
of skEnumField: result = newIntNodeT(s.position, n)
|
||||||
|
|
|
||||||
|
|
@ -218,16 +218,7 @@ proc isInnerProc(s, outerProc: PSym): bool {.inline.} =
|
||||||
s.owner == outerProc and not isGenericRoutine(s)
|
s.owner == outerProc and not isGenericRoutine(s)
|
||||||
#s.typ.callConv == ccClosure
|
#s.typ.callConv == ccClosure
|
||||||
|
|
||||||
proc captureVar(o: POuterContext, i: PInnerContext, local: PSym,
|
proc addClosureParam(i: PInnerContext, e: PEnv) =
|
||||||
info: TLineInfo) =
|
|
||||||
# we need to remember which inner most closure belongs to this lambda:
|
|
||||||
var e = o.currentEnv
|
|
||||||
if IdTableGet(o.lambdasToEnv, i.fn) == nil:
|
|
||||||
IdTablePut(o.lambdasToEnv, i.fn, e)
|
|
||||||
|
|
||||||
# variable already captured:
|
|
||||||
if IdNodeTableGet(i.localsToAccess, local) != nil: return
|
|
||||||
if i.closureParam == nil:
|
|
||||||
var cp = newSym(skParam, getIdent(paramname), i.fn)
|
var cp = newSym(skParam, getIdent(paramname), i.fn)
|
||||||
cp.info = i.fn.info
|
cp.info = i.fn.info
|
||||||
incl(cp.flags, sfFromGeneric)
|
incl(cp.flags, sfFromGeneric)
|
||||||
|
|
@ -235,11 +226,32 @@ proc captureVar(o: POuterContext, i: PInnerContext, local: PSym,
|
||||||
addSon(cp.typ, e.tup)
|
addSon(cp.typ, e.tup)
|
||||||
i.closureParam = cp
|
i.closureParam = cp
|
||||||
addHiddenParam(i.fn, i.closureParam)
|
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:
|
||||||
|
IdTablePut(o.lambdasToEnv, i.fn, e)
|
||||||
|
|
||||||
|
# variable already captured:
|
||||||
|
if IdNodeTableGet(i.localsToAccess, local) != nil: return
|
||||||
|
if i.closureParam == nil: addClosureParam(i, e)
|
||||||
|
|
||||||
# check which environment `local` belongs to:
|
# check which environment `local` belongs to:
|
||||||
var access = newSymNode(i.closureParam)
|
var access = newSymNode(i.closureParam)
|
||||||
var it = PEnv(IdTableGet(o.localsToEnv, local))
|
|
||||||
assert it != nil
|
|
||||||
addCapturedVar(it, local)
|
addCapturedVar(it, local)
|
||||||
if it == e:
|
if it == e:
|
||||||
# common case: local directly in current environment:
|
# common case: local directly in current environment:
|
||||||
|
|
@ -325,6 +337,9 @@ proc searchForInnerProcs(o: POuterContext, n: PNode) =
|
||||||
var inner = newInnerContext(n.sym)
|
var inner = newInnerContext(n.sym)
|
||||||
let body = n.sym.getBody
|
let body = n.sym.getBody
|
||||||
gatherVars(o, inner, body)
|
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)
|
let ti = transformInnerProc(o, inner, body)
|
||||||
if ti != nil: n.sym.ast.sons[bodyPos] = ti
|
if ti != nil: n.sym.ast.sons[bodyPos] = ti
|
||||||
of nkLambdaKinds:
|
of nkLambdaKinds:
|
||||||
|
|
@ -425,8 +440,18 @@ proc transformOuterProc(o: POuterContext, n: PNode): PNode =
|
||||||
if closure != nil:
|
if closure != nil:
|
||||||
# we need to replace the lambda with '(lambda, env)':
|
# we need to replace the lambda with '(lambda, env)':
|
||||||
let a = closure.closure
|
let a = closure.closure
|
||||||
assert a != nil
|
if a != nil:
|
||||||
return makeClosure(local, a, n.info)
|
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
|
if not contains(o.capturedVars, local.id): return
|
||||||
var env = PEnv(IdTableGet(o.localsToEnv, local))
|
var env = PEnv(IdTableGet(o.localsToEnv, local))
|
||||||
|
|
@ -474,5 +499,6 @@ proc liftLambdas(fn: PSym, body: PNode): PNode =
|
||||||
|
|
||||||
proc liftLambdas*(n: PNode): PNode =
|
proc liftLambdas*(n: PNode): PNode =
|
||||||
assert n.kind in procDefs
|
assert n.kind in procDefs
|
||||||
|
if gCmd == cmdCompileToEcmaScript: return n
|
||||||
var s = n.sons[namePos].sym
|
var s = n.sons[namePos].sym
|
||||||
result = liftLambdas(s, s.getBody)
|
result = liftLambdas(s, s.getBody)
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,8 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
|
||||||
c.friendModule = getModule(fn)
|
c.friendModule = getModule(fn)
|
||||||
result = copySym(fn, false)
|
result = copySym(fn, false)
|
||||||
incl(result.flags, sfFromGeneric)
|
incl(result.flags, sfFromGeneric)
|
||||||
|
# keep the owner if it's an inner proc (for proper closure transformations):
|
||||||
|
if fn.owner.kind == skModule:
|
||||||
result.owner = getCurrOwner().owner
|
result.owner = getCurrOwner().owner
|
||||||
# careful! we copy the whole AST including the possibly nil body!
|
# careful! we copy the whole AST including the possibly nil body!
|
||||||
var n = copyTree(fn.ast)
|
var n = copyTree(fn.ast)
|
||||||
|
|
|
||||||
|
|
@ -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
|
# we cannot generate a proper thunk here for GC-safety reasons (see internal
|
||||||
# documentation):
|
# documentation):
|
||||||
|
if gCmd == cmdCompileToEcmaScript: return prc
|
||||||
result = newNodeIT(nkClosure, prc.info, dest)
|
result = newNodeIT(nkClosure, prc.info, dest)
|
||||||
var conv = newNodeIT(nkHiddenStdConv, prc.info, dest)
|
var conv = newNodeIT(nkHiddenStdConv, prc.info, dest)
|
||||||
conv.add(emptyNode)
|
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:
|
if call.kind notin nkCallKinds or call.sons[0].kind != nkSym:
|
||||||
InternalError(call.info, "transformFor")
|
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.forStmt = n
|
||||||
newC.forLoopBody = loopBody
|
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)
|
# generate access statements for the parameters (unless they are constant)
|
||||||
pushTransCon(c, newC)
|
pushTransCon(c, newC)
|
||||||
for i in countup(1, sonsLen(call) - 1):
|
for i in countup(1, sonsLen(call) - 1):
|
||||||
var arg = transform(c, call.sons[i]).pnode
|
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)
|
case putArgInto(arg, formal.typ)
|
||||||
of paDirectMapping:
|
of paDirectMapping:
|
||||||
IdNodeTablePut(newC.mapping, formal, arg)
|
IdNodeTablePut(newC.mapping, formal, arg)
|
||||||
|
|
@ -528,7 +532,7 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
|
||||||
assert(skipTypes(formal.typ, abstractInst).kind == tyVar)
|
assert(skipTypes(formal.typ, abstractInst).kind == tyVar)
|
||||||
IdNodeTablePut(newC.mapping, formal, arg)
|
IdNodeTablePut(newC.mapping, formal, arg)
|
||||||
# XXX BUG still not correct if the arg has a side effect!
|
# XXX BUG still not correct if the arg has a side effect!
|
||||||
var body = newC.owner.getBody
|
var body = iter.getBody
|
||||||
pushInfoContext(n.info)
|
pushInfoContext(n.info)
|
||||||
inc(c.inlining)
|
inc(c.inlining)
|
||||||
add(result, transform(c, body))
|
add(result, transform(c, body))
|
||||||
|
|
@ -647,6 +651,9 @@ proc transform(c: PTransf, n: PNode): PTransNode =
|
||||||
if n.sons[genericParamsPos].kind == nkEmpty:
|
if n.sons[genericParamsPos].kind == nkEmpty:
|
||||||
var s = n.sons[namePos].sym
|
var s = n.sons[namePos].sym
|
||||||
n.sons[bodyPos] = PNode(transform(c, s.getBody))
|
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)
|
n.sons[bodyPos] = liftLambdas(n)
|
||||||
if n.kind == nkMethodDef: methodDef(s, false)
|
if n.kind == nkMethodDef: methodDef(s, false)
|
||||||
result = PTransNode(n)
|
result = PTransNode(n)
|
||||||
|
|
|
||||||
7
tests/reject/tinvalidclosure.nim
Normal file
7
tests/reject/tinvalidclosure.nim
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
discard """
|
||||||
|
line: 6
|
||||||
|
errormsg: "'ugh' cannot have 'closure' calling convention"
|
||||||
|
"""
|
||||||
|
|
||||||
|
proc ugh[T](x: T) {.closure.} =
|
||||||
|
echo "ugha"
|
||||||
|
|
@ -1,20 +1,30 @@
|
||||||
discard """
|
discard """
|
||||||
output: '''1
|
output: '''0
|
||||||
|
11
|
||||||
|
1
|
||||||
|
11
|
||||||
2
|
2
|
||||||
|
11
|
||||||
3
|
3
|
||||||
|
11
|
||||||
4
|
4
|
||||||
|
11
|
||||||
5
|
5
|
||||||
|
11
|
||||||
6
|
6
|
||||||
|
11
|
||||||
7
|
7
|
||||||
|
11
|
||||||
8
|
8
|
||||||
|
11
|
||||||
9
|
9
|
||||||
10
|
|
||||||
11
|
|
||||||
11
|
11
|
||||||
py
|
py
|
||||||
py
|
py
|
||||||
py
|
py
|
||||||
py'''
|
py
|
||||||
|
px
|
||||||
|
6'''
|
||||||
"""
|
"""
|
||||||
|
|
||||||
when true:
|
when true:
|
||||||
|
|
@ -34,7 +44,7 @@ when true:
|
||||||
|
|
||||||
ax()
|
ax()
|
||||||
|
|
||||||
when false:
|
when true:
|
||||||
proc accumulator(start: int): (proc(): int {.closure.}) =
|
proc accumulator(start: int): (proc(): int {.closure.}) =
|
||||||
var x = start-1
|
var x = start-1
|
||||||
#let dummy = proc =
|
#let dummy = proc =
|
||||||
|
|
@ -62,8 +72,14 @@ when false:
|
||||||
outer()
|
outer()
|
||||||
|
|
||||||
|
|
||||||
when false:
|
when true:
|
||||||
proc outer =
|
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.} =
|
proc px() {.closure.} =
|
||||||
echo "px"
|
echo "px"
|
||||||
|
|
||||||
|
|
@ -77,6 +93,8 @@ when false:
|
||||||
}
|
}
|
||||||
mapping[0][1]()
|
mapping[0][1]()
|
||||||
|
|
||||||
|
echo fac(3)
|
||||||
|
|
||||||
outer()
|
|
||||||
|
outer2()
|
||||||
|
|
||||||
|
|
|
||||||
8
todo.txt
8
todo.txt
|
|
@ -8,12 +8,9 @@ version 0.9.0
|
||||||
- ``=`` should be overloadable; requires specialization for ``=``
|
- ``=`` should be overloadable; requires specialization for ``=``
|
||||||
- fix remaining generics bugs
|
- fix remaining generics bugs
|
||||||
- fix remaining closure bugs:
|
- fix remaining closure bugs:
|
||||||
|
- make toplevel but in a scope vars local; make procs there inner procs
|
||||||
- fix evals.nim with closures
|
- fix evals.nim with closures
|
||||||
- deactivate lambda lifting for JS backend
|
- test sequence of closures; especially that the GC does not leak for those!
|
||||||
- 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
|
|
||||||
- implement proper coroutines
|
- implement proper coroutines
|
||||||
|
|
||||||
- document 'do' notation
|
- document 'do' notation
|
||||||
|
|
@ -129,6 +126,7 @@ Low priority
|
||||||
- activate more thread tests
|
- activate more thread tests
|
||||||
- implement ``--script:sh|bat`` command line option; think about script
|
- implement ``--script:sh|bat`` command line option; think about script
|
||||||
generation
|
generation
|
||||||
|
- implement closures that support nesting of *procs* > 1
|
||||||
|
|
||||||
|
|
||||||
Further optimization ideas
|
Further optimization ideas
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue