further steps for closure support

This commit is contained in:
Araq 2012-02-08 09:10:59 +01:00
commit 2c4a1dbc0c
7 changed files with 101 additions and 57 deletions

View file

@ -578,6 +578,7 @@ 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 env = lastSon(prc.ast[paramsPos]).sym
#echo "created environment: ", env.id, " for ", prc.name.s
assignLocalVar(p, env) assignLocalVar(p, env)
# generate cast assignment: # generate cast assignment:
appcg(p, cpsStmts, "$1 = ($2) ClEnv;$n", rdLoc(env.loc), appcg(p, cpsStmts, "$1 = ($2) ClEnv;$n", rdLoc(env.loc),

View file

@ -10,8 +10,9 @@
# This include file implements lambda lifting for the transformator. # This include file implements lambda lifting for the transformator.
const const
procDefs = {nkLambda, nkProcDef, nkMethodDef, nkIteratorDef, nkMacroDef, declarativeDefs = {nkProcDef, nkMethodDef, nkIteratorDef, nkMacroDef,
nkConverterDef} nkConverterDef}
procDefs = {nkLambda} + declarativeDefs
proc indirectAccess(a, b: PSym, info: TLineInfo): PNode = proc indirectAccess(a, b: PSym, info: TLineInfo): PNode =
# returns a[].b as a node # returns a[].b as a node
@ -47,17 +48,16 @@ proc captureToTuple(cap: TCapture, owner: PSym): PType =
addSon(result.n, newSymNode(field)) addSon(result.n, newSymNode(field))
addSon(result, typ) 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: PTransf, n: PNode, outerProc: PSym, cap: var TCapture) = proc gatherVars(c: PTransf, n: PNode, outerProc: PSym, cap: var TCapture) =
# gather used vars for closure generation into 'cap' # gather used vars for closure generation into 'cap'
case n.kind case n.kind
of nkSym: of nkSym:
var s = n.sym var s = n.sym
var found = false if interestingVar(s) and outerProc.id == s.owner.id:
case s.kind
of skVar, skLet: found = sfGlobal notin s.flags
of skTemp, skForVar, skParam, skResult: found = true
else: nil
if found and outerProc.id == s.owner.id:
#echo "captured: ", s.name.s #echo "captured: ", s.name.s
Capture(cap, s) Capture(cap, s)
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
@ -70,37 +70,34 @@ proc replaceVars(c: PTransf, n: PNode, outerProc, env: PSym) =
let a = n.sons[i] let a = n.sons[i]
if a.kind == nkSym: if a.kind == nkSym:
let s = a.sym let s = a.sym
var found = false if interestingVar(s) and outerProc == s.owner:
case s.kind
of skVar, skLet: found = sfGlobal notin s.flags
of skTemp, skForVar, skParam, skResult: found = true
else: nil
if found and outerProc.id == s.owner.id:
# access through the closure param: # access through the closure param:
n.sons[i] = indirectAccess(env, s, n.info) n.sons[i] = indirectAccess(env, s, n.info)
else: else:
replaceVars(c, a, outerProc, env) replaceVars(c, a, outerProc, env)
proc addFormalParam(routine: PType, param: PSym) = proc addHiddenParam(routine: PSym, param: PSym) =
addSon(routine, param.typ) var params = routine.ast.sons[paramsPos]
addSon(routine.n, newSymNode(param)) let L = params.len-1
if L >= 0:
proc addFormalParam(routine: PSym, param: PSym) = # update if we already added a hidden parameter:
#addFormalParam(routine.typ, param) if params.sons[L].kind == nkSym and params.sons[L].sym.kind == skTemp:
addSon(routine.ast.sons[paramsPos], newSymNode(param)) params.sons[L].sym = param
return
addSon(params, newSymNode(param))
#echo "produced environment: ", param.id, " for ", routine.name.s
proc isInnerProc(s, outerProc: PSym): bool {.inline.} = proc isInnerProc(s, outerProc: PSym): bool {.inline.} =
result = s.kind in {skProc, skMacro, skIterator, skMethod, skConverter} and result = s.kind in {skProc, skMacro, skIterator, skMethod, skConverter} and
s.owner.id == outerProc.id and not isGenericRoutine(s) and s.owner == outerProc and not isGenericRoutine(s)
s.typ.callConv == ccClosure #s.typ.callConv == ccClosure
proc searchForInnerProcs(c: PTransf, n: PNode, outerProc: PSym, proc searchForInnerProcs(c: PTransf, n: PNode, outerProc: PSym,
cap: var TCapture) = cap: var TCapture) =
case n.kind case n.kind
of nkSym: of nkSym:
let s = n.sym if isInnerProc(n.sym, outerProc):
if isInnerProc(s, outerProc): gatherVars(c, n.sym.getBody, outerProc, cap)
gatherVars(c, s.getBody, outerProc, cap)
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
else: else:
for i in 0.. <len(n): for i in 0.. <len(n):
@ -109,30 +106,48 @@ proc searchForInnerProcs(c: PTransf, n: PNode, outerProc: PSym,
proc makeClosure(c: PTransf, prc, env: PSym, info: TLineInfo): PNode = proc makeClosure(c: PTransf, prc, env: PSym, info: TLineInfo): PNode =
result = newNodeIT(nkClosure, info, prc.typ) result = newNodeIT(nkClosure, info, prc.typ)
result.add(newSymNode(prc)) result.add(newSymNode(prc))
if env == nil:
result.add(newNodeIT(nkNilLit, info, getSysType(tyNil)))
else:
result.add(newSymNode(env)) result.add(newSymNode(env))
proc transformInnerProcs(c: PTransf, n: PNode, outerProc, env: PSym) = proc transformInnerProcs(c: PTransf, n: PNode, outerProc, env: PSym) =
case n.kind case n.kind
of nkSym: of nkSym:
let innerProc = n.sym let innerProc = n.sym
if isInnerProc(innerProc, outerProc): 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: # inner proc could capture outer vars:
var param = newTemp(c, env.typ, n.info) var param = newTemp(c, env.typ, n.info)
param.kind = skParam
addFormalParam(innerProc, param) # recursive calls go through (f, hiddenParam):
# 'anon' should be replaced by '(anon, env)': 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)
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, IdNodeTablePut(c.transCon.mapping, innerProc,
makeClosure(c, innerProc, env, n.info)) makeClosure(c, innerProc, env, n.info))
# access all non-local vars through the 'env' param:
var body = innerProc.getBody
# XXX does not work with recursion!
replaceVars(c, body, outerProc, param)
innerProc.ast.sons[bodyPos] = body
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
else: else:
for i in 0.. <len(n): for i in 0.. <len(n):
transformInnerProcs(c, n.sons[i], outerProc, env) 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
proc newCall(a, b: PSym): PNode = proc newCall(a, b: PSym): PNode =
result = newNodeI(nkCall, a.info) result = newNodeI(nkCall, a.info)
result.add newSymNode(a) result.add newSymNode(a)
@ -158,10 +173,13 @@ proc createEnvStmt(c: PTransf, varList: TCapture, env: PSym): PTransNode =
IdNodeTablePut(c.transCon.mapping, v, fieldAccess) IdNodeTablePut(c.transCon.mapping, v, fieldAccess)
proc transformProcFin(c: PTransf, n: PNode, s: PSym): PTransNode = proc transformProcFin(c: PTransf, n: PNode, s: PSym): PTransNode =
# to be safe: XXX this a mystery how it could ever happen that: s.ast != n. if n.kind == nkLambda:
# for lambdas we transformed 'n.sons[bodyPos]', but not 'ast.n[bodyPos]'!
s.ast.sons[bodyPos] = n.sons[bodyPos] s.ast.sons[bodyPos] = n.sons[bodyPos]
if n.kind == nkMethodDef: methodDef(s, false) else:
assert s.ast == n
if n.kind == nkMethodDef: methodDef(s, false)
# should 's' be replaced by a tuple ('s', env)? # should 's' be replaced by a tuple ('s', env)?
var tc = c.transCon var tc = c.transCon
var repl: PNode = nil var repl: PNode = nil
@ -181,25 +199,34 @@ proc transformProc(c: PTransf, n: PNode): PTransNode =
var s = n.sons[namePos].sym var s = n.sons[namePos].sym
var body = s.getBody var body = s.getBody
if body.kind == nkEmpty: if body.kind == nkEmpty or n.sons[bodyPos].kind == nkEmpty or
containsOrIncl(c.transformedInnerProcs, s.id):
return PTransNode(n) return PTransNode(n)
if not containsNode(body, procDefs): checkInvariant(n, s)
if not containsNode(body, procDefs) and s.typ.callConv != ccClosure:
# fast path: no inner procs, so no closure needed: # fast path: no inner procs, so no closure needed:
n.sons[bodyPos] = PNode(transform(c, body)) n.sons[bodyPos] = PNode(transform(c, body))
checkInvariant(n, s)
return transformProcFin(c, n, s) return transformProcFin(c, n, s)
# create environment: # create environment:
var cap: TCapture = @[] var cap: TCapture = @[]
searchForInnerProcs(c, body, s, cap) searchForInnerProcs(c, body, s, cap)
if cap.len == 0:
# fast path: no captured variables, so no closure needed:
n.sons[bodyPos] = PNode(transform(c, body))
return transformProcFin(c, n, s)
var envType = newType(tyRef, s) var envType = newType(tyRef, s)
addSon(envType, captureToTuple(cap, 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 # Currently we always do a heap allocation. A simple escape analysis
# could turn the closure into a stack allocation. Later versions might # could turn the closure into a stack allocation. Later versions might
@ -211,11 +238,11 @@ proc transformProc(c: PTransf, n: PNode): PTransNode =
# mapping entries that turn (localProc) into (localProc, env): # mapping entries that turn (localProc) into (localProc, env):
transformInnerProcs(c, body, s, envSym) transformInnerProcs(c, body, s, envSym)
# now we can transform 'body' as all rewriting entries have been created. # now we can transform 'body' as all rewriting entries have been created:
# Careful this transforms the inner procs too!
newBody.add(transform(c, body)) newBody.add(transform(c, body))
n.sons[bodyPos] = newBody.pnode n.sons[bodyPos] = newBody.pnode
result = transformProcFin(c, n, s) result = transformProcFin(c, n, s)
checkInvariant(n, s)
proc generateThunk(c: PTransf, prc: PNode, dest: PType): PNode = proc generateThunk(c: PTransf, prc: PNode, dest: PType): PNode =
## Converts 'prc' into '(thunk, nil)' so that it's compatible with ## Converts 'prc' into '(thunk, nil)' so that it's compatible with

View file

@ -94,6 +94,7 @@ type
errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate, errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate,
errXhasSideEffects, errIteratorExpected, errLetNeedsInit, errXhasSideEffects, errIteratorExpected, errLetNeedsInit,
errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX, errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX,
errXCannotBeClosure,
errUser, errUser,
warnCannotOpenFile, warnCannotOpenFile,
warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit,
@ -326,6 +327,7 @@ const
errThreadvarCannotInit: "a thread var cannot be initialized explicitly", errThreadvarCannotInit: "a thread var cannot be initialized explicitly",
errWrongSymbolX: "usage of \'$1\' is a user-defined error", errWrongSymbolX: "usage of \'$1\' is a user-defined error",
errIllegalCaptureX: "illegal capture '$1'", errIllegalCaptureX: "illegal capture '$1'",
errXCannotBeClosure: "'$1' cannot have 'closure' calling convention",
errUser: "$1", errUser: "$1",
warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]", warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]",
warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]", warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]",

View file

@ -92,7 +92,8 @@ proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
# if a proc accesses a global variable, it is not side effect free: # if a proc accesses a global variable, it is not side effect free:
if sfGlobal in s.flags: if sfGlobal in s.flags:
incl(c.p.owner.flags, sfSideEffect) incl(c.p.owner.flags, sfSideEffect)
elif s.owner != c.p.owner and s.owner.kind != skModule: elif s.owner != c.p.owner and s.owner.kind != skModule and
c.p.owner.typ != nil and not IsGenericRoutine(s.owner):
c.p.owner.typ.callConv = ccClosure c.p.owner.typ.callConv = ccClosure
if illegalCapture(s) or c.p.next.owner != s.owner: if illegalCapture(s) or c.p.next.owner != s.owner:
# Currently captures are restricted to a single level of nesting: # Currently captures are restricted to a single level of nesting:

View file

@ -658,6 +658,8 @@ proc semLambda(c: PContext, n: PNode): PNode =
else: else:
LocalError(n.info, errImplOfXexpected, s.name.s) LocalError(n.info, errImplOfXexpected, s.name.s)
sideEffectsCheck(c, s) sideEffectsCheck(c, s)
if s.typ.callConv == ccClosure and s.owner.kind == skModule:
localError(s.info, errXCannotBeClosure, s.name.s)
closeScope(c.tab) # close scope for parameters closeScope(c.tab) # close scope for parameters
popOwner() popOwner()
result.typ = s.typ result.typ = s.typ
@ -754,6 +756,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
incl(s.flags, sfForward) incl(s.flags, sfForward)
elif sfBorrow in s.flags: semBorrow(c, n, s) elif sfBorrow in s.flags: semBorrow(c, n, s)
sideEffectsCheck(c, s) sideEffectsCheck(c, s)
if s.typ.callConv == ccClosure and s.owner.kind == skModule:
localError(s.info, errXCannotBeClosure, s.name.s)
closeScope(c.tab) # close scope for parameters closeScope(c.tab) # close scope for parameters
popOwner() popOwner()

View file

@ -45,9 +45,9 @@ type
module: PSym module: PSym
transCon: PTransCon # top of a TransCon stack transCon: PTransCon # top of a TransCon stack
inlining: int # > 0 if we are in inlining context (copy vars) inlining: int # > 0 if we are in inlining context (copy vars)
nestedProcs: int # > 0 if we are in a nested proc
blocksyms: seq[PSym] blocksyms: seq[PSym]
procToEnv: TIdTable # mapping from a proc to its generated explicit transformedInnerProcs: TIntSet
# 'env' var (for closure generation)
PTransf = ref TTransfContext PTransf = ref TTransfContext
proc newTransNode(a: PNode): PTransNode {.inline.} = proc newTransNode(a: PNode): PTransNode {.inline.} =
@ -616,7 +616,16 @@ proc transform(c: PTransf, n: PNode): PTransNode =
# nothing to be done for leaves: # nothing to be done for leaves:
result = PTransNode(n) result = PTransNode(n)
of nkBracketExpr: result = transformArrayAccess(c, n) of nkBracketExpr: result = transformArrayAccess(c, n)
of procDefs: result = transformProc(c, n) of procDefs:
if c.nestedProcs == 0:
inc c.nestedProcs
result = transformProc(c, n)
dec c.nestedProcs
else:
result = PTransNode(n)
if n.sons[namePos].kind == nkSym:
let x = transformSym(c, n.sons[namePos])
if x.pnode.kind == nkClosure: result = x
of nkForStmt: result = transformFor(c, n) of nkForStmt: result = transformFor(c, n)
of nkCaseStmt: result = transformCase(c, n) of nkCaseStmt: result = transformCase(c, n)
of nkContinueStmt: of nkContinueStmt:
@ -680,7 +689,7 @@ proc openTransf(module: PSym, filename: string): PPassContext =
new(n) new(n)
n.blocksyms = @[] n.blocksyms = @[]
n.module = module n.module = module
initIdTable(n.procToEnv) n.transformedInnerProcs = initIntSet()
result = n result = n
proc openTransfCached(module: PSym, filename: string, proc openTransfCached(module: PSym, filename: string,

View file

@ -2,9 +2,9 @@ version 0.8.14
============== ==============
- implement closures - implement closures
- test evals.nim with closures - fix evals.nim with closures
- deactivate lambda lifting for JS backend - deactivate lambda lifting for JS backend
- Test capture of for loop vars; test generics; test recursion - Test capture of for loop vars; test generics;
- test constant closures - test constant closures
- 'closureEnv' magic for easy interfacing with C - 'closureEnv' magic for easy interfacing with C