top level closures should work; transf is not a pass anymore; next steps for first class iterator support

This commit is contained in:
Araq 2012-08-13 17:07:49 +02:00
commit 244c14db0b
17 changed files with 293 additions and 229 deletions

View file

@ -340,7 +340,8 @@ type
# because for instantiations of objects, structural # because for instantiations of objects, structural
# type equality has to be used # type equality has to be used
tfAll, # type class requires all constraints to be met (default) tfAll, # type class requires all constraints to be met (default)
tfAny # type class requires any constraint to be met tfAny, # type class requires any constraint to be met
tfCapturesEnv # whether proc really captures some environment
TTypeFlags* = set[TTypeFlag] TTypeFlags* = set[TTypeFlag]
@ -867,6 +868,7 @@ proc newNode*(kind: TNodeKind, info: TLineInfo, sons: TNodeSeq = @[],
result = newNode(kind) result = newNode(kind)
result.info = info result.info = info
result.typ = typ result.typ = typ
# XXX use shallowCopy here for ownership transfer:
result.sons = sons result.sons = sons
proc newNodeIT(kind: TNodeKind, info: TLineInfo, typ: PType): PNode = proc newNodeIT(kind: TNodeKind, info: TLineInfo, typ: PType): PNode =

View file

@ -93,10 +93,9 @@ template preserveBreakIdx(body: stmt): stmt =
p.breakIdx = oldBreakIdx p.breakIdx = oldBreakIdx
proc genState(p: BProc, n: PNode) = proc genState(p: BProc, n: PNode) =
internalAssert n.len == 2 and n.sons[0].kind == nkIntLit internalAssert n.len == 1 and n.sons[0].kind == nkIntLit
let idx = n.sons[0].intVal let idx = n.sons[0].intVal
lineCg(p, cpsStmts, "STATE$1: ;$n", [idx.toRope]) lineCg(p, cpsStmts, "STATE$1: ;$n", [idx.toRope])
genStmts(p, n.sons[1])
proc genGotoState(p: BProc, n: PNode) = proc genGotoState(p: BProc, n: PNode) =
# we resist the temptation to translate it into duff's device as it later # we resist the temptation to translate it into duff's device as it later

View file

@ -627,7 +627,7 @@ proc deinitFrame(p: BProc): PRope =
result = ropecg(p.module, "\t#popFrame();$n") result = ropecg(p.module, "\t#popFrame();$n")
proc closureSetup(p: BProc, prc: PSym) = proc closureSetup(p: BProc, prc: PSym) =
if prc.typ.callConv != ccClosure: return if tfCapturesEnv notin prc.typ.flags: return
# prc.ast[paramsPos].last contains the type we're after: # prc.ast[paramsPos].last contains the type we're after:
var ls = lastSon(prc.ast[paramsPos]) var ls = lastSon(prc.ast[paramsPos])
if ls.kind != nkSym: if ls.kind != nkSym:

View file

@ -1366,7 +1366,8 @@ proc evalAux(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode =
inc(gNestedEvals) inc(gNestedEvals)
proc tryEval(c: PEvalContext, n: PNode): PNode = proc tryEval(c: PEvalContext, n: PNode): PNode =
var n = transform(c.module, n) #internalAssert nfTransf in n.flags
var n = transformExpr(c.module, n)
gWhileCounter = evalMaxIterations gWhileCounter = evalMaxIterations
gNestedEvals = evalMaxRecDepth gNestedEvals = evalMaxRecDepth
result = evalAux(c, n, {}) result = evalAux(c, n, {})

View file

@ -211,6 +211,7 @@ proc addHiddenParam(routine: PSym, param: PSym) =
var params = routine.ast.sons[paramsPos] var params = routine.ast.sons[paramsPos]
param.position = params.len param.position = params.len
addSon(params, newSymNode(param)) addSon(params, newSymNode(param))
incl(routine.typ.flags, tfCapturesEnv)
#echo "produced environment: ", param.id, " for ", routine.name.s #echo "produced environment: ", param.id, " for ", routine.name.s
proc isInnerProc(s, outerProc: PSym): bool {.inline.} = proc isInnerProc(s, outerProc: PSym): bool {.inline.} =
@ -234,12 +235,22 @@ proc dummyClosureParam(o: POuterContext, i: PInnerContext) =
IdTablePut(o.lambdasToEnv, i.fn, e) IdTablePut(o.lambdasToEnv, i.fn, e)
if i.closureParam == nil: addClosureParam(i, e) if i.closureParam == nil: addClosureParam(i, e)
proc illegalCapture(s: PSym): bool {.inline.} =
result = skipTypes(s.typ, abstractInst).kind in {tyVar, tyOpenArray} or
s.kind == skResult
proc captureVar(o: POuterContext, i: PInnerContext, local: PSym, proc captureVar(o: POuterContext, i: PInnerContext, local: PSym,
info: TLineInfo) = info: TLineInfo) =
# for inlined variables the owner is still wrong, so it can happen that it's # for inlined variables the owner is still wrong, so it can happen that it's
# not a captured variable at all ... *sigh* # not a captured variable at all ... *sigh*
var it = PEnv(IdTableGet(o.localsToEnv, local)) var it = PEnv(IdTableGet(o.localsToEnv, local))
if it == nil: return if it == nil: return
if illegalCapture(local) or o.fn.id != local.owner.id:
# Currently captures are restricted to a single level of nesting:
LocalError(info, errIllegalCaptureX, local.name.s)
i.fn.typ.callConv = ccClosure
incl(i.fn.typ.flags, tfCapturesEnv)
# we need to remember which inner most closure belongs to this lambda: # we need to remember which inner most closure belongs to this lambda:
var e = o.currentEnv var e = o.currentEnv
@ -269,12 +280,13 @@ proc interestingVar(s: PSym): bool {.inline.} =
proc gatherVars(o: POuterContext, i: PInnerContext, n: PNode) = proc gatherVars(o: POuterContext, i: PInnerContext, n: PNode) =
# gather used vars for closure generation # gather used vars for closure generation
if n == nil: return
case n.kind case n.kind
of nkSym: of nkSym:
var s = n.sym var s = n.sym
if interestingVar(s) and i.fn.id != s.owner.id: if interestingVar(s) and i.fn.id != s.owner.id:
captureVar(o, i, s, n.info) captureVar(o, i, s, n.info)
elif isInnerProc(s, o.fn) and s.typ.callConv == ccClosure and s != i.fn: elif isInnerProc(s, o.fn) and tfCapturesEnv in s.typ.flags and s != i.fn:
# call to some other inner proc; we need to track the dependencies for # call to some other inner proc; we need to track the dependencies for
# this: # this:
let env = PEnv(IdTableGet(o.lambdasToEnv, i.fn)) let env = PEnv(IdTableGet(o.lambdasToEnv, i.fn))
@ -287,6 +299,28 @@ proc gatherVars(o: POuterContext, i: PInnerContext, n: PNode) =
for k in countup(0, sonsLen(n) - 1): for k in countup(0, sonsLen(n) - 1):
gatherVars(o, i, n.sons[k]) gatherVars(o, i, 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 == cmdCompileToEcmaScript: 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 makeClosure(prc, env: PSym, info: TLineInfo): PNode = proc makeClosure(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))
@ -339,6 +373,7 @@ proc searchForInnerProcs(o: POuterContext, n: PNode) =
gatherVars(o, inner, body) gatherVars(o, inner, body)
# dummy closure param needed? # dummy closure param needed?
if inner.closureParam == nil and n.sym.typ.callConv == ccClosure: if inner.closureParam == nil and n.sym.typ.callConv == ccClosure:
assert tfCapturesEnv notin n.sym.typ.flags
dummyClosureParam(o, inner) dummyClosureParam(o, inner)
# only transform if it really needs a closure: # only transform if it really needs a closure:
if inner.closureParam != nil: if inner.closureParam != nil:
@ -437,7 +472,7 @@ proc generateClosureCreation(o: POuterContext, scope: PEnv): PNode =
newSymNode(getClosureVar(o, e)))) newSymNode(getClosureVar(o, e))))
proc transformOuterProc(o: POuterContext, n: PNode): PNode = proc transformOuterProc(o: POuterContext, n: PNode): PNode =
# XXX I with I knew where these 'nil' nodes come from: 'array[.. |X]' # XXX I wish I knew where these 'nil' nodes come from: 'array[.. |X]'
if n == nil: return nil if n == nil: return nil
case n.kind case n.kind
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
@ -481,17 +516,20 @@ proc transformOuterProc(o: POuterContext, n: PNode): PNode =
nkIteratorDef: nkIteratorDef:
# don't recurse here: # don't recurse here:
nil nil
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
let x = transformOuterProc(o, n.sons[1])
if x != nil: n.sons[1] = x
result = transformOuterConv(n)
else: else:
for i in countup(0, sonsLen(n) - 1): for i in countup(0, sonsLen(n) - 1):
let x = transformOuterProc(o, n.sons[i]) let x = transformOuterProc(o, n.sons[i])
if x != nil: n.sons[i] = x if x != nil: n.sons[i] = x
proc liftLambdas*(fn: PSym, body: PNode): PNode = proc liftLambdas*(fn: PSym, body: PNode): PNode =
if body.kind == nkEmpty: if body.kind == nkEmpty or gCmd == cmdCompileToEcmaScript:
# ignore forward declaration: # ignore forward declaration:
result = body result = body
elif (fn.typ == nil or fn.typ.callConv != ccClosure) and elif not containsNode(body, procDefs):
not containsNode(body, procDefs):
# fast path: no inner procs, so no closure needed: # fast path: no inner procs, so no closure needed:
result = body result = body
else: else:
@ -506,40 +544,144 @@ proc liftLambdas*(fn: PSym, body: PNode): PNode =
let param = params.sons[i].sym let param = params.sons[i].sym
IdTablePut(o.localsToEnv, param, o.currentEnv) IdTablePut(o.localsToEnv, param, o.currentEnv)
searchForInnerProcs(o, body) searchForInnerProcs(o, body)
let a = transformOuterProc(o, body) discard transformOuterProc(o, body)
result = ex result = ex
# XXX should 's' be replaced by a tuple ('s', env)?
proc liftLambdas*(n: PNode): PNode = proc liftLambdasForTopLevel*(module: PSym, body: PNode): PNode =
assert n.kind in procDefs if body.kind == nkEmpty or gCmd == cmdCompileToEcmaScript:
var s = n.sons[namePos].sym
if gCmd == cmdCompileToEcmaScript: return s.getBody
result = liftLambdas(s, s.getBody)
proc transformIterator*(fn: PSym, body: PNode): PNode =
if body.kind == nkEmpty:
# ignore forward declaration:
result = body result = body
# it(a, b) --> (it(a, b), createClosure()) else:
# it(a, b) --> ? var o = newOuterContext(module)
discard """ let ex = closureCreationPoint(body)
let c = chain(f, g) o.currentEnv = newEnv(module, nil, ex)
searchForInnerProcs(o, body)
for x in c: echo x discard transformOuterProc(o, body)
result = ex
# ------------------- iterator transformation --------------------------------
discard """
iterator chain[S, T](a, b: *S->T, args: *S): T = iterator chain[S, T](a, b: *S->T, args: *S): T =
for x in a(args): yield x for x in a(args): yield x
for x in b(args): yield x for x in b(args): yield x
let c = chain(f, g)
for x in c: echo x
# translated to: # translated to:
let c = chain( (f, newClosure(f)), (g, newClosure(g)), newClosure(chain)) let c = chain( (f, newClosure(f)), (g, newClosure(g)), newClosure(chain))
""" """
type
TIterContext {.final, pure.} = object
iter, closureParam, state, resultSym: PSym
capturedVars: TIntSet
tup: PType
proc newIterResult(iter: PSym): PSym =
result = newSym(skResult, getIdent":result", iter)
result.info = iter.info
result.typ = iter.typ.sons[0]
incl(result.flags, sfUsed)
proc transfIterBody(c: var TIterContext, n: PNode): PNode =
# gather used vars for closure generation
if n == nil: return nil
case n.kind
of nkSym:
var s = n.sym
if interestingVar(s) and c.iter.id == s.owner.id:
if not containsOrIncl(c.capturedVars, s.id): addField(c.tup, s)
result = indirectAccess(newSymNode(c.closureParam), s, n.info)
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
of nkYieldStmt:
inc c.state.typ.n.sons[1].intVal
let stateNo = c.state.typ.n.sons[1].intVal
var stateAsgnStmt = newNodeI(nkAsgn, n.info)
stateAsgnStmt.add(indirectAccess(newSymNode(c.closureParam),c.state,n.info))
stateAsgnStmt.add(newIntNode(nkIntLit, stateNo))
var retStmt = newNodeI(nkReturnStmt, n.info)
if n.sons[0].kind != nkEmpty:
var a = newNodeI(nkAsgn, n.sons[0].info)
addSon(a, newSymNode(c.resultSym))
addSon(a, n.sons[0])
retStmt.add(a)
else:
retStmt.add(emptyNode)
var stateLabelStmt = newNodeI(nkState, n.info)
stateLabelStmt.add(newIntNode(nkIntLit, stateNo-1))
result = newNodeI(nkStmtList, n.info)
result.add(stateAsgnStmt)
result.add(retStmt)
result.add(stateLabelStmt)
else:
for i in countup(0, sonsLen(n)-1):
let x = transfIterBody(c, n.sons[i])
if x != nil: n.sons[i] = x
proc getStateType(iter: PSym): PType =
var n = newNodeI(nkRange, iter.info)
addSon(n, newIntNode(nkIntLit, -1))
addSon(n, newIntNode(nkIntLit, 0))
result = newType(tyRange, iter)
result.n = n
rawAddSon(result, getSysType(tyInt))
proc liftIterator*(iter: PSym, body: PNode): PNode =
var c: TIterContext
c.iter = iter
c.capturedVars = initIntSet()
c.tup = newType(tyTuple, iter)
c.tup.n = newNodeI(nkRecList, iter.info)
var cp = newSym(skParam, getIdent(paramname), iter)
cp.info = iter.info
incl(cp.flags, sfFromGeneric)
cp.typ = newType(tyRef, iter)
rawAddSon(cp.typ, c.tup)
c.closureParam = cp
addHiddenParam(iter, cp)
c.state = newSym(skField, getIdent(":state"), iter)
c.state.typ = getStateType(iter)
addField(c.tup, c.state)
if iter.typ.sons[0] != nil:
c.resultSym = newIterResult(iter)
iter.ast.add(newSymNode(c.resultSym))
result = newNodeI(nkStmtList, iter.info)
var gs = newNodeI(nkGotoState, iter.info)
gs.add(indirectAccess(newSymNode(c.closureParam), c.state, iter.info))
result.add(gs)
var state0 = newNodeI(nkState, iter.info)
state0.add(newIntNode(nkIntLit, 0))
result.add(state0)
let newBody = transfIterBody(c, body)
if newBody != nil:
result.add(newBody)
else:
result.add(body)
var state1 = newNodeI(nkState, iter.info)
state1.add(newIntNode(nkIntLit, -1))
result.add(state1)
proc transformForLoop*(iter: PSym, body: PNode): PNode =
discard """
for i in foo(): nil
Is transformed to:
cl = createClosure()
while true:
let i = foo(cl)
if cl.state == -1: break
"""

View file

@ -15,7 +15,7 @@ import
os, lists, condsyms, rodread, rodwrite, ropes, trees, os, lists, condsyms, rodread, rodwrite, ropes, trees,
wordrecg, sem, semdata, idents, passes, docgen, extccomp, wordrecg, sem, semdata, idents, passes, docgen, extccomp,
cgen, ecmasgen, cgen, ecmasgen,
platform, nimconf, importer, passaux, depends, transf, evals, types, idgen, platform, nimconf, importer, passaux, depends, evals, types, idgen,
tables, docgen2 tables, docgen2
const const
@ -90,7 +90,6 @@ proc CompileProject(projectFile = gProjectFull) =
proc semanticPasses = proc semanticPasses =
registerPass(verbosePass()) registerPass(verbosePass())
registerPass(sem.semPass()) registerPass(sem.semPass())
registerPass(transf.transfPass())
proc CommandGenDepend = proc CommandGenDepend =
semanticPasses() semanticPasses()

View file

@ -25,7 +25,8 @@ type
errRecursiveDependencyX, errOnOrOffExpected, errNoneSpeedOrSizeExpected, errRecursiveDependencyX, errOnOrOffExpected, errNoneSpeedOrSizeExpected,
errInvalidPragma, errUnknownPragma, errInvalidDirectiveX, errInvalidPragma, errUnknownPragma, errInvalidDirectiveX,
errAtPopWithoutPush, errEmptyAsm, errInvalidIndentation, errAtPopWithoutPush, errEmptyAsm, errInvalidIndentation,
errExceptionExpected, errExceptionAlreadyHandled, errYieldNotAllowedHere, errExceptionExpected, errExceptionAlreadyHandled,
errYieldNotAllowedHere, errYieldNotAllowedInTryStmt,
errInvalidNumberOfYieldExpr, errCannotReturnExpr, errAttemptToRedefine, errInvalidNumberOfYieldExpr, errCannotReturnExpr, errAttemptToRedefine,
errStmtInvalidAfterReturn, errStmtExpected, errInvalidLabel, errStmtInvalidAfterReturn, errStmtExpected, errInvalidLabel,
errInvalidCmdLineOption, errCmdLineArgExpected, errCmdLineNoArgExpected, errInvalidCmdLineOption, errCmdLineArgExpected, errCmdLineNoArgExpected,
@ -149,7 +150,8 @@ const
errInvalidIndentation: "invalid indentation", errInvalidIndentation: "invalid indentation",
errExceptionExpected: "exception expected", errExceptionExpected: "exception expected",
errExceptionAlreadyHandled: "exception already handled", errExceptionAlreadyHandled: "exception already handled",
errYieldNotAllowedHere: "\'yield\' only allowed in a loop of an iterator", errYieldNotAllowedHere: "'yield' only allowed in an iterator",
errYieldNotAllowedInTryStmt: "'yield' cannot be used within 'try' in a non-inlined iterator",
errInvalidNumberOfYieldExpr: "invalid number of \'yield\' expresions", errInvalidNumberOfYieldExpr: "invalid number of \'yield\' expresions",
errCannotReturnExpr: "current routine cannot return an expression", errCannotReturnExpr: "current routine cannot return an expression",
errAttemptToRedefine: "redefinition of \'$1\'", errAttemptToRedefine: "redefinition of \'$1\'",

View file

@ -14,7 +14,7 @@ import
wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math, wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math,
magicsys, parser, nversion, nimsets, semfold, importer, magicsys, parser, nversion, nimsets, semfold, importer,
procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch, procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch,
semthreads, intsets, transf, evals, idgen, aliases semthreads, intsets, transf, evals, idgen, aliases, cgmeth
proc semPass*(): TPass proc semPass*(): TPass
# implementation # implementation
@ -182,6 +182,7 @@ proc myOpen(module: PSym, filename: string): PPassContext =
proc myOpenCached(module: PSym, filename: string, proc myOpenCached(module: PSym, filename: string,
rd: PRodReader): PPassContext = rd: PRodReader): PPassContext =
result = myOpen(module, filename) result = myOpen(module, filename)
for m in items(rd.methods): methodDef(m, true)
proc SemStmtAndGenerateGenerics(c: PContext, n: PNode): PNode = proc SemStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
result = semStmt(c, n) result = semStmt(c, n)
@ -193,6 +194,7 @@ proc SemStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
# a generic has been added to `a`: # a generic has been added to `a`:
if result.kind != nkEmpty: addSon(a, result) if result.kind != nkEmpty: addSon(a, result)
result = a result = a
result = transformStmt(c.module, result)
proc RecoverContext(c: PContext) = proc RecoverContext(c: PContext) =
# clean up in case of a semantic error: We clean up the stacks, etc. This is # clean up in case of a semantic error: We clean up the stacks, etc. This is

View file

@ -31,6 +31,8 @@ type
resultSym*: PSym # the result symbol (if we are in a proc) resultSym*: PSym # the result symbol (if we are in a proc)
nestedLoopCounter*: int # whether we are in a loop or not nestedLoopCounter*: int # whether we are in a loop or not
nestedBlockCounter*: int # whether we are in a block or not nestedBlockCounter*: int # whether we are in a block or not
InTryStmt*: int # whether we are in a try statement; works also
# in standalone ``except`` and ``finally``
next*: PProcCon # used for stacking procedure contexts next*: PProcCon # used for stacking procedure contexts
TInstantiatedSymbol* {.final.} = object TInstantiatedSymbol* {.final.} = object
@ -85,36 +87,6 @@ proc newGenericsCache*(): PGenericsCache =
initIdTable(result.InstTypes) initIdTable(result.InstTypes)
result.generics = @[] result.generics = @[]
proc tempContext*(c: PContext): PContext =
## generates a temporary context so that side-effects can be rolled-back;
## necessary for ``system.compiles``.
new(result)
result.module = c.module
result.p = c.p
# don't use the old cache:
result.generics = newGenericsCache()
result.friendModule = c.friendModule
result.InstCounter = c.InstCounter
result.threadEntries = @[]
# hrm, 'tab' is expensive to copy ... so we don't. We open a new scope
# instead to be able to undo scope changes. Not entirely correct for
# explicit 'global' vars though:
#shallowCopy(result.tab, c.tab)
assign(result.AmbiguousSymbols, c.AmbiguousSymbols)
result.InGenericContext = c.InGenericContext
result.InUnrolledContext = c.InUnrolledContext
result.InCompilesContext = c.InCompilesContext
result.converters = c.converters
result.semConstExpr = c.semConstExpr
result.semExpr = c.semExpr
result.semConstBoolExpr = c.semConstBoolExpr
assign(result.includedFiles, c.includedFiles)
result.filename = c.filename
#shallowCopy(result.userPragmas, c.userPragmas)
# XXX mark it as read-only:
result.evalContext = c.evalContext
proc newContext*(module: PSym, nimfile: string): PContext proc newContext*(module: PSym, nimfile: string): PContext
proc lastOptionEntry*(c: PContext): POptionEntry proc lastOptionEntry*(c: PContext): POptionEntry

View file

@ -65,10 +65,6 @@ proc inlineConst(n: PNode, s: PSym): PNode {.inline.} =
result.typ = s.typ result.typ = s.typ
result.info = n.info result.info = n.info
proc illegalCapture(s: PSym): bool {.inline.} =
result = skipTypes(s.typ, abstractInst).kind in {tyVar, tyOpenArray} or
s.kind == skResult
proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
case s.kind case s.kind
of skProc, skMethod, skIterator, skConverter: of skProc, skMethod, skIterator, skConverter:
@ -111,13 +107,11 @@ proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
incl(c.p.owner.flags, sfSideEffect) incl(c.p.owner.flags, sfSideEffect)
elif s.kind == skParam and s.typ.kind == tyExpr: elif s.kind == skParam and s.typ.kind == tyExpr:
return s.typ.n return s.typ.n
elif s.owner != c.p.owner and
c.p.owner.typ != nil and not IsGenericRoutine(s.owner):
c.p.owner.typ.callConv = ccClosure
if illegalCapture(s) or c.p.next.owner != s.owner:
# Currently captures are restricted to a single level of nesting:
LocalError(n.info, errIllegalCaptureX, s.name.s)
result = newSymNode(s, n.info) result = newSymNode(s, n.info)
# We cannot check for access to outer vars for example because it's still
# not sure the symbol really ends up being used:
# var len = 0 # but won't be called
# genericThatUsesLen(x) # marked as taking a closure?
of skGenericParam: of skGenericParam:
if s.ast != nil: result = semExpr(c, s.ast) if s.ast != nil: result = semExpr(c, s.ast)
else: else:
@ -1179,7 +1173,7 @@ proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode =
let oldInGenericContext = c.InGenericContext let oldInGenericContext = c.InGenericContext
let oldInUnrolledContext = c.InUnrolledContext let oldInUnrolledContext = c.InUnrolledContext
let oldProcCon = c.p
c.generics = newGenericsCache() c.generics = newGenericsCache()
try: try:
discard semExpr(c, n.sons[1]) discard semExpr(c, n.sons[1])
@ -1190,6 +1184,7 @@ proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode =
c.generics = oldGenerics c.generics = oldGenerics
c.InGenericContext = oldInGenericContext c.InGenericContext = oldInGenericContext
c.InUnrolledContext = oldInUnrolledContext c.InUnrolledContext = oldInUnrolledContext
c.p = oldProcCon
msgs.setInfoContextLen(oldContextLen) msgs.setInfoContextLen(oldContextLen)
setlen(gOwners, oldOwnerLen) setlen(gOwners, oldOwnerLen)
while c.tab.tos > oldTos: rawCloseScope(c.tab) while c.tab.tos > oldTos: rawCloseScope(c.tab)

View file

@ -79,8 +79,7 @@ proc instantiateBody(c: PContext, n: PNode, result: PSym) =
addResult(c, result.typ.sons[0], n.info, result.kind) addResult(c, result.typ.sons[0], n.info, result.kind)
addResultNode(c, n) addResultNode(c, n)
var b = semStmtScope(c, n.sons[bodyPos]) var b = semStmtScope(c, n.sons[bodyPos])
# XXX Bad hack for tests/titer2 and tests/tactiontable n.sons[bodyPos] = transformBody(c.module, b, result)
n.sons[bodyPos] = transform(c.module, b)
#echo "code instantiated ", result.name.s #echo "code instantiated ", result.name.s
excl(result.flags, sfForward) excl(result.flags, sfForward)
popProcCon(c) popProcCon(c)

View file

@ -198,15 +198,17 @@ proc SemYieldVarResult(c: PContext, n: PNode, restype: PType) =
localError(n.sons[0].info, errXExpected, "tuple constructor") localError(n.sons[0].info, errXExpected, "tuple constructor")
else: nil else: nil
proc SemYield(c: PContext, n: PNode): PNode = proc SemYield(c: PContext, n: PNode): PNode =
result = n result = n
checkSonsLen(n, 1) checkSonsLen(n, 1)
if c.p.owner == nil or c.p.owner.kind != skIterator: if c.p.owner == nil or c.p.owner.kind != skIterator:
LocalError(n.info, errYieldNotAllowedHere) LocalError(n.info, errYieldNotAllowedHere)
elif c.p.inTryStmt > 0 and c.p.owner.typ.callConv != ccInline:
LocalError(n.info, errYieldNotAllowedInTryStmt)
elif n.sons[0].kind != nkEmpty: elif n.sons[0].kind != nkEmpty:
n.sons[0] = SemExprWithType(c, n.sons[0]) # check for type compatibility: n.sons[0] = SemExprWithType(c, n.sons[0]) # check for type compatibility:
var restype = c.p.owner.typ.sons[0] var restype = c.p.owner.typ.sons[0]
if restype != nil: if restype != nil:
n.sons[0] = fitNode(c, restype, n.sons[0]) n.sons[0] = fitNode(c, restype, n.sons[0])
if n.sons[0].typ == nil: InternalError(n.info, "semYield") if n.sons[0].typ == nil: InternalError(n.info, "semYield")
SemYieldVarResult(c, n, restype) SemYieldVarResult(c, n, restype)
@ -486,6 +488,7 @@ proc semRaise(c: PContext, n: PNode): PNode =
proc semTry(c: PContext, n: PNode): PNode = proc semTry(c: PContext, n: PNode): PNode =
result = n result = n
inc c.p.inTryStmt
checkMinSonsLen(n, 2) checkMinSonsLen(n, 2)
n.sons[0] = semStmtScope(c, n.sons[0]) n.sons[0] = semStmtScope(c, n.sons[0])
var check = initIntSet() var check = initIntSet()
@ -511,6 +514,7 @@ proc semTry(c: PContext, n: PNode): PNode =
illFormedAst(n) illFormedAst(n)
# last child of an nkExcept/nkFinally branch is a statement: # last child of an nkExcept/nkFinally branch is a statement:
a.sons[length - 1] = semStmtScope(c, a.sons[length - 1]) a.sons[length - 1] = semStmtScope(c, a.sons[length - 1])
dec c.p.inTryStmt
proc addGenericParamListToScope(c: PContext, n: PNode) = proc addGenericParamListToScope(c: PContext, n: PNode) =
if n.kind != nkGenericParams: if n.kind != nkGenericParams:
@ -714,20 +718,33 @@ proc semLambda(c: PContext, n: PNode): PNode =
LocalError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s) LocalError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s)
pushProcCon(c, s) pushProcCon(c, s)
addResult(c, s.typ.sons[0], n.info, skProc) addResult(c, s.typ.sons[0], n.info, skProc)
n.sons[bodyPos] = semStmtScope(c, n.sons[bodyPos]) let semBody = semStmtScope(c, n.sons[bodyPos])
n.sons[bodyPos] = transformBody(c.module, semBody, s)
addResultNode(c, n) addResultNode(c, n)
popProcCon(c) popProcCon(c)
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
proc instantiateDestructor*(c: PContext, typ: PType): bool proc instantiateDestructor*(c: PContext, typ: PType): bool
proc doDestructorStuff(c: PContext, s: PSym, n: PNode) =
let t = s.typ.sons[1].skipTypes({tyVar})
t.destructor = s
# automatically insert calls to base classes' destructors
if n.sons[bodyPos].kind != nkEmpty:
for i in countup(0, t.sonsLen - 1):
# when inheriting directly from object
# there will be a single nil son
if t.sons[i] == nil: continue
if instantiateDestructor(c, t.sons[i]):
n.sons[bodyPos].addSon(newNode(nkCall, t.sym.info, @[
useSym(t.sons[i].destructor),
n.sons[paramsPos][1][0]]))
proc semProcAux(c: PContext, n: PNode, kind: TSymKind, proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
validPragmas: TSpecialWords): PNode = validPragmas: TSpecialWords): PNode =
result = semProcAnnotation(c, n) result = semProcAnnotation(c, n)
@ -754,7 +771,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
n.sons[genericParamsPos] = gp n.sons[genericParamsPos] = gp
# check for semantics again: # check for semantics again:
# semParamList(c, n.sons[ParamsPos], nil, s) # semParamList(c, n.sons[ParamsPos], nil, s)
else: else:
s.typ = newTypeS(tyProc, c) s.typ = newTypeS(tyProc, c)
rawAddSon(s.typ, nil) rawAddSon(s.typ, nil)
var proto = SearchForProc(c, s, c.tab.tos-2) # -2 because we have a scope var proto = SearchForProc(c, s, c.tab.tos-2) # -2 because we have a scope
@ -791,19 +808,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
popOwner() popOwner()
pushOwner(s) pushOwner(s)
s.options = gOptions s.options = gOptions
if sfDestructor in s.flags: if sfDestructor in s.flags: doDestructorStuff(c, s, n)
let t = s.typ.sons[1].skipTypes({tyVar})
t.destructor = s
# automatically insert calls to base classes' destructors
if n.sons[bodyPos].kind != nkEmpty:
for i in countup(0, t.sonsLen - 1):
# when inheriting directly from object
# there will be a single nil son
if t.sons[i] == nil: continue
if instantiateDestructor(c, t.sons[i]):
n.sons[bodyPos].addSon(newNode(nkCall, t.sym.info, @[
useSym(t.sons[i].destructor),
n.sons[paramsPos][1][0]]))
if n.sons[bodyPos].kind != nkEmpty: if n.sons[bodyPos].kind != nkEmpty:
# for DLL generation it is annoying to check for sfImportc! # for DLL generation it is annoying to check for sfImportc!
if sfBorrow in s.flags: if sfBorrow in s.flags:
@ -815,7 +820,10 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
addResult(c, s.typ.sons[0], n.info, kind) addResult(c, s.typ.sons[0], n.info, kind)
if sfImportc notin s.flags: if sfImportc notin s.flags:
# no semantic checking for importc: # no semantic checking for importc:
n.sons[bodyPos] = semStmtScope(c, n.sons[bodyPos]) let semBody = semStmtScope(c, n.sons[bodyPos])
# unfortunately we cannot skip this step when in 'system.compiles'
# context as it may even be evaluated in 'system.compiles':
n.sons[bodyPos] = transformBody(c.module, semBody, s)
if s.typ.sons[0] != nil and kind != skIterator: addResultNode(c, n) if s.typ.sons[0] != nil and kind != skIterator: addResultNode(c, n)
popProcCon(c) popProcCon(c)
else: else:
@ -827,14 +835,12 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
if sfImportc in s.flags: if sfImportc in s.flags:
# so we just ignore the body after semantic checking for importc: # so we just ignore the body after semantic checking for importc:
n.sons[bodyPos] = ast.emptyNode n.sons[bodyPos] = ast.emptyNode
else: else:
if proto != nil: LocalError(n.info, errImplOfXexpected, proto.name.s) if proto != nil: LocalError(n.info, errImplOfXexpected, proto.name.s)
if {sfImportc, sfBorrow} * s.flags == {} and s.magic == mNone: if {sfImportc, sfBorrow} * s.flags == {} and s.magic == mNone:
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()
@ -845,7 +851,10 @@ proc semIterator(c: PContext, n: PNode): PNode =
if t.sons[0] == nil: if t.sons[0] == nil:
LocalError(n.info, errXNeedsReturnType, "iterator") LocalError(n.info, errXNeedsReturnType, "iterator")
# iterators are either 'inline' or 'closure': # iterators are either 'inline' or 'closure':
if s.typ.callConv != ccInline: s.typ.callConv = ccClosure if s.typ.callConv != ccInline:
s.typ.callConv = ccClosure
# and they always at least use the 'env' for the state field:
incl(s.typ.flags, tfCapturesEnv)
if n.sons[bodyPos].kind == nkEmpty and s.magic == mNone: if n.sons[bodyPos].kind == nkEmpty and s.magic == mNone:
LocalError(n.info, errImplOfXexpected, s.name.s) LocalError(n.info, errImplOfXexpected, s.name.s)

View file

@ -25,7 +25,6 @@ import
const const
genPrefix* = ":tmp" # prefix for generated names genPrefix* = ":tmp" # prefix for generated names
proc transfPass*(): TPass
# implementation # implementation
type type
@ -109,54 +108,6 @@ proc transformSons(c: PTransf, n: PNode): PTransNode =
for i in countup(0, sonsLen(n)-1): for i in countup(0, sonsLen(n)-1):
result[i] = transform(c, n.sons[i]) result[i] = transform(c, n.sons[i])
# Transforming iterators into non-inlined versions is pretty hard, but
# unavoidable for not bloating the code too much. If we had direct access to
# the program counter, things'd be much easier.
# ::
#
# iterator items(a: string): char =
# var i = 0
# while i < length(a):
# yield a[i]
# inc(i)
#
# for ch in items("hello world"): # `ch` is an iteration variable
# echo(ch)
#
# Should be transformed into::
#
# type
# TItemsClosure = record
# i: int
# state: int
# proc items(a: string, c: var TItemsClosure): char =
# case c.state
# of 0: goto L0 # very difficult without goto!
# of 1: goto L1 # can be implemented by GCC's computed gotos
#
# block L0:
# c.i = 0
# while c.i < length(a):
# c.state = 1
# return a[i]
# block L1: inc(c.i)
#
# More efficient, but not implementable::
#
# type
# TItemsClosure = record
# i: int
# pc: pointer
#
# proc items(a: string, c: var TItemsClosure): char =
# goto c.pc
# c.i = 0
# while c.i < length(a):
# c.pc = label1
# return a[i]
# label1: inc(c.i)
#
proc newAsgnStmt(c: PTransf, le: PNode, ri: PTransNode): PTransNode = proc newAsgnStmt(c: PTransf, le: PNode, ri: PTransNode): PTransNode =
result = newTransNode(nkFastAsgn, PNode(ri).info, 2) result = newTransNode(nkFastAsgn, PNode(ri).info, 2)
result[0] = PTransNode(le) result[0] = PTransNode(le)
@ -178,15 +129,6 @@ proc transformSymAux(c: PTransf, n: PNode): PNode =
if result != nil: return if result != nil: return
tc = tc.next tc = tc.next
result = b result = b
when false:
case b.sym.kind
of skConst, skEnumField:
if sfFakeConst notin b.sym.flags:
if skipTypes(b.sym.typ, abstractInst).kind notin ConstantDataTypes:
result = getConstExpr(c.module, b)
if result == nil: InternalError(b.info, "transformSym: const")
else:
nil
proc transformSym(c: PTransf, n: PNode): PTransNode = proc transformSym(c: PTransf, n: PNode): PTransNode =
result = PTransNode(transformSymAux(c, n)) result = PTransNode(transformSymAux(c, n))
@ -378,20 +320,6 @@ proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode =
if n.sons[0].kind == a or n.sons[0].kind == b: if n.sons[0].kind == a or n.sons[0].kind == b:
# addr ( deref ( x )) --> x # addr ( deref ( x )) --> x
result = PTransNode(n.sons[0].sons[0]) result = PTransNode(n.sons[0].sons[0])
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):
if gCmd == cmdCompileToEcmaScript: 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 = proc transformConv(c: PTransf, n: PNode): PTransNode =
# numeric types need range checks: # numeric types need range checks:
@ -466,12 +394,6 @@ proc transformConv(c: PTransf, n: PNode): PTransNode =
result[0] = transform(c, n.sons[1]) result[0] = transform(c, n.sons[1])
else: else:
result = transform(c, n.sons[1]) result = transform(c, n.sons[1])
of tyProc:
if dest.callConv == ccClosure and source.callConv == ccDefault:
let x = transform(c, n.sons[1]).pnode
result = generateThunk(c, x, dest).ptransnode
else:
result = transformSons(c, n)
of tyGenericParam, tyOrdinal, tyTypeClass: of tyGenericParam, tyOrdinal, tyTypeClass:
result = transform(c, n.sons[1]) result = transform(c, n.sons[1])
# happens sometimes for generated assignments, etc. # happens sometimes for generated assignments, etc.
@ -657,21 +579,23 @@ proc transform(c: PTransf, n: PNode): PTransNode =
result = PTransNode(n) result = PTransNode(n)
of nkBracketExpr: result = transformArrayAccess(c, n) of nkBracketExpr: result = transformArrayAccess(c, n)
of procDefs: of procDefs:
if n.sons[genericParamsPos].kind == nkEmpty: when false:
var s = n.sons[namePos].sym if n.sons[genericParamsPos].kind == nkEmpty:
n.sons[bodyPos] = PNode(transform(c, s.getBody)) var s = n.sons[namePos].sym
if s.ast.sons[bodyPos] != n.sons[bodyPos]: n.sons[bodyPos] = PNode(transform(c, s.getBody))
# somehow this can happen ... :-/ if s.ast.sons[bodyPos] != n.sons[bodyPos]:
s.ast.sons[bodyPos] = n.sons[bodyPos] # somehow this can happen ... :-/
n.sons[bodyPos] = liftLambdas(n) s.ast.sons[bodyPos] = n.sons[bodyPos]
if n.kind == nkMethodDef: methodDef(s, false) #n.sons[bodyPos] = liftLambdas(s, n)
#if n.kind == nkMethodDef: methodDef(s, false)
result = PTransNode(n) result = PTransNode(n)
of nkMacroDef: of nkMacroDef:
# XXX no proper closure support yet: # XXX no proper closure support yet:
if n.sons[genericParamsPos].kind == nkEmpty: when false:
var s = n.sons[namePos].sym if n.sons[genericParamsPos].kind == nkEmpty:
n.sons[bodyPos] = PNode(transform(c, s.getBody)) var s = n.sons[namePos].sym
if n.kind == nkMethodDef: methodDef(s, false) n.sons[bodyPos] = PNode(transform(c, s.getBody))
if n.kind == nkMethodDef: methodDef(s, false)
result = PTransNode(n) result = PTransNode(n)
of nkForStmt: of nkForStmt:
inc c.inLoop inc c.inLoop
@ -735,38 +659,57 @@ proc transform(c: PTransf, n: PNode): PTransNode =
if cnst != nil and not dontInlineConstant(n, cnst): if cnst != nil and not dontInlineConstant(n, cnst):
result = PTransNode(cnst) # do not miss an optimization result = PTransNode(cnst) # do not miss an optimization
proc processTransf(context: PPassContext, n: PNode): PNode = proc processTransf(c: PTransf, n: PNode): PNode =
# Note: For interactive mode we cannot call 'passes.skipCodegen' and skip # Note: For interactive mode we cannot call 'passes.skipCodegen' and skip
# this step! We have to rely that the semantic pass transforms too errornous # this step! We have to rely that the semantic pass transforms too errornous
# nodes into an empty node. # nodes into an empty node.
if passes.skipCodegen(n) or context.fromCache or nfTransf in n.flags: return n if passes.skipCodegen(n) or c.fromCache or nfTransf in n.flags: return n
var c = PTransf(context)
pushTransCon(c, newTransCon(getCurrOwner(c))) pushTransCon(c, newTransCon(getCurrOwner(c)))
result = PNode(transform(c, n)) result = PNode(transform(c, n))
popTransCon(c) popTransCon(c)
incl(result.flags, nfTransf) incl(result.flags, nfTransf)
proc openTransf(module: PSym, filename: string): PPassContext = proc openTransf(module: PSym, filename: string): PTransf =
var n: PTransf new(result)
new(n) result.contSyms = @[]
n.contSyms = @[] result.breakSyms = @[]
n.breakSyms = @[] result.module = module
n.module = module
result = n
proc openTransfCached(module: PSym, filename: string, when false:
rd: PRodReader): PPassContext = proc openTransfCached(module: PSym, filename: string,
result = openTransf(module, filename) rd: PRodReader): PPassContext =
for m in items(rd.methods): methodDef(m, true) result = openTransf(module, filename)
for m in items(rd.methods): methodDef(m, true)
proc transfPass(): TPass = proc transfPass(): TPass =
initPass(result) initPass(result)
result.open = openTransf result.open = openTransf
result.openCached = openTransfCached result.openCached = openTransfCached
result.process = processTransf result.process = processTransf
result.close = processTransf # we need to process generics too! result.close = processTransf # we need to process generics too!
proc transform*(module: PSym, n: PNode): PNode = proc transformBody*(module: PSym, n: PNode, prc: PSym): PNode =
if nfTransf in n.flags or prc.kind in {skTemplate, skMacro}:
result = n
else:
var c = openTransf(module, "")
result = processTransf(c, n)
if prc.kind != skMacro:
# XXX no closures yet for macros:
result = liftLambdas(prc, result)
if prc.kind == skMethod: methodDef(prc, false)
incl(result.flags, nfTransf)
proc transformStmt*(module: PSym, n: PNode): PNode =
if nfTransf in n.flags:
result = n
else:
var c = openTransf(module, "")
result = processTransf(c, n)
result = liftLambdasForTopLevel(module, result)
incl(result.flags, nfTransf)
proc transformExpr*(module: PSym, n: PNode): PNode =
if nfTransf in n.flags: if nfTransf in n.flags:
result = n result = n
else: else:

View file

@ -455,7 +455,7 @@ proc getFloat*(x: TAny): float =
proc getFloat32*(x: TAny): float32 = proc getFloat32*(x: TAny): float32 =
## retrieve the float32 value out of `x`. `x` needs to represent an float32. ## retrieve the float32 value out of `x`. `x` needs to represent an float32.
assert skipRange(x.rawtype).kind == tyFloat64 assert skipRange(x.rawtype).kind == tyFloat32
result = cast[ptr float32](x.value)[] result = cast[ptr float32](x.value)[]
proc getFloat64*(x: TAny): float64 = proc getFloat64*(x: TAny): float64 =

View file

@ -1,3 +1 @@
import utemplates import utemplates, uclosures
# uclosures

View file

@ -4,7 +4,8 @@ test "loop variables are captured by copy":
var funcs: seq[proc (): int {.closure.}] = @[] var funcs: seq[proc (): int {.closure.}] = @[]
for i in 0..10: for i in 0..10:
funcs.add do -> int: return i * i let ii = i
funcs.add do -> int: return ii * ii
check funcs[0]() == 0 check funcs[0]() == 0
check funcs[3]() == 9 check funcs[3]() == 9

View file

@ -1,14 +1,14 @@
version 0.9.0 version 0.9.0
============= =============
- closure: implement closure support for procs capturing nested - implement "closure tuple consists of a single 'ref'" optimization
module vars - implement for loop transformation for first class iterators
- implicit deref for parameter matching
- implicit deref for parameter matching; implement ``varargs[T, `$`]``
- optimize genericAssign in the code generator - optimize genericAssign in the code generator
- the lookup rules for generics really are too permissive
- fix remaining closure bugs: - fix remaining closure bugs:
- fix evals.nim with closures - fix evals.nim with closures
- implement "closure tuple consists of a single 'ref'" optimization
Bugs Bugs