bugfix: stack traces; first class iterators almost working

This commit is contained in:
Araq 2012-11-15 01:27:25 +01:00
commit 814fcb2639
13 changed files with 186 additions and 51 deletions

View file

@ -205,7 +205,8 @@ type
nkReturnToken, # token used for interpretation nkReturnToken, # token used for interpretation
nkClosure, # (prc, env)-pair (internally used for code gen) nkClosure, # (prc, env)-pair (internally used for code gen)
nkGotoState, # used for the state machine (for iterators) nkGotoState, # used for the state machine (for iterators)
nkState # give a label to a code section (for iterators) nkState, # give a label to a code section (for iterators)
nkBreakState # special break statement for easier code generation
TNodeKinds* = set[TNodeKind] TNodeKinds* = set[TNodeKind]
type type

View file

@ -1678,7 +1678,7 @@ proc expr(p: BProc, e: PNode, d: var TLoc) =
else: else:
genProc(p.module, sym) genProc(p.module, sym)
putLocIntoDest(p, d, sym.loc) putLocIntoDest(p, d, sym.loc)
of skProc, skConverter: of skProc, skConverter, skIterator:
genProc(p.module, sym) genProc(p.module, sym)
if sym.loc.r == nil or sym.loc.t == nil: if sym.loc.r == nil or sym.loc.t == nil:
InternalError(e.info, "expr: proc not init " & sym.name.s) InternalError(e.info, "expr: proc not init " & sym.name.s)

View file

@ -106,10 +106,17 @@ proc genGotoState(p: BProc, n: PNode) =
var a: TLoc var a: TLoc
initLocExpr(p, n.sons[0], a) initLocExpr(p, n.sons[0], a)
lineF(p, cpsStmts, "switch ($1) {$n", [rdLoc(a)]) lineF(p, cpsStmts, "switch ($1) {$n", [rdLoc(a)])
p.BeforeRetNeeded = true
lineF(p, cpsStmts, "case -1: goto BeforeRet;$n", [])
for i in 0 .. lastOrd(n.sons[0].typ): for i in 0 .. lastOrd(n.sons[0].typ):
lineF(p, cpsStmts, "case $1: goto STATE$1;$n", [toRope(i)]) lineF(p, cpsStmts, "case $1: goto STATE$1;$n", [toRope(i)])
lineF(p, cpsStmts, "}$n", []) lineF(p, cpsStmts, "}$n", [])
proc genBreakState(p: BProc, n: PNode) =
var a: TLoc
initLocExpr(p, n.sons[0], a)
lineF(p, cpsStmts, "if (($1) < 0) break;$n", [rdLoc(a)])
proc genSingleVar(p: BProc, a: PNode) = proc genSingleVar(p: BProc, a: PNode) =
var v = a.sons[0].sym var v = a.sons[0].sym
if sfCompileTime in v.flags: return if sfCompileTime in v.flags: return
@ -713,7 +720,7 @@ proc genAsmOrEmitStmt(p: BProc, t: PNode): PRope =
app(result, t.sons[i].strVal) app(result, t.sons[i].strVal)
of nkSym: of nkSym:
var sym = t.sons[i].sym var sym = t.sons[i].sym
if sym.kind in {skProc, skMethod}: if sym.kind in {skProc, skIterator, skMethod}:
var a: TLoc var a: TLoc
initLocExpr(p, t.sons[i], a) initLocExpr(p, t.sons[i], a)
app(result, rdLoc(a)) app(result, rdLoc(a))
@ -884,5 +891,6 @@ proc genStmts(p: BProc, t: PNode) =
of nkParForStmt: genParForStmt(p, t) of nkParForStmt: genParForStmt(p, t)
of nkState: genState(p, t) of nkState: genState(p, t)
of nkGotoState: genGotoState(p, t) of nkGotoState: genGotoState(p, t)
of nkBreakState: genBreakState(p, t)
else: internalError(t.info, "genStmts(" & $t.kind & ')') else: internalError(t.info, "genStmts(" & $t.kind & ')')

View file

@ -69,7 +69,7 @@ proc mangleName(s: PSym): PRope =
if result == nil: if result == nil:
if gCmd == cmdCompileToLLVM: if gCmd == cmdCompileToLLVM:
case s.kind case s.kind
of skProc, skMethod, skConverter, skConst: of skProc, skMethod, skConverter, skConst, skIterator:
result = toRope("@") result = toRope("@")
of skVar, skForVar, skResult, skLet: of skVar, skForVar, skResult, skLet:
if sfGlobal in s.flags: result = toRope("@") if sfGlobal in s.flags: result = toRope("@")

View file

@ -589,7 +589,7 @@ proc cgsym(m: BModule, name: string): PRope =
var sym = magicsys.getCompilerProc(name) var sym = magicsys.getCompilerProc(name)
if sym != nil: if sym != nil:
case sym.kind case sym.kind
of skProc, skMethod, skConverter: genProc(m, sym) of skProc, skMethod, skConverter, skIterator: genProc(m, sym)
of skVar, skResult, skLet: genVarPrototype(m, sym) of skVar, skResult, skLet: genVarPrototype(m, sym)
of skType: discard getTypeDesc(m, sym.typ) of skType: discard getTypeDesc(m, sym.typ)
else: InternalError("cgsym: " & name) else: InternalError("cgsym: " & name)

View file

@ -214,8 +214,14 @@ proc addHiddenParam(routine: PSym, param: PSym) =
incl(routine.typ.flags, tfCapturesEnv) incl(routine.typ.flags, tfCapturesEnv)
#echo "produced environment: ", param.id, " for ", routine.name.s #echo "produced environment: ", param.id, " for ", routine.name.s
proc getHiddenParam(routine: PSym): PSym =
let params = routine.ast.sons[paramsPos]
let hidden = lastSon(params)
assert hidden.kind == nkSym
result = hidden.sym
proc isInnerProc(s, outerProc: PSym): bool {.inline.} = proc isInnerProc(s, outerProc: PSym): bool {.inline.} =
result = s.kind in {skProc, skIterator, skMethod, skConverter} and result = s.kind in {skProc, skMethod, skConverter} and
s.owner == outerProc and not isGenericRoutine(s) s.owner == outerProc and not isGenericRoutine(s)
#s.typ.callConv == ccClosure #s.typ.callConv == ccClosure
@ -481,7 +487,6 @@ 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 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
@ -593,13 +598,16 @@ proc newIterResult(iter: PSym): PSym =
result.typ = iter.typ.sons[0] result.typ = iter.typ.sons[0]
incl(result.flags, sfUsed) incl(result.flags, sfUsed)
proc interestingIterVar(s: PSym): bool {.inline.} =
result = s.kind in {skVar, skLet, skTemp, skForVar} and sfGlobal notin s.flags
proc transfIterBody(c: var TIterContext, n: PNode): PNode = proc transfIterBody(c: var TIterContext, n: PNode): PNode =
# gather used vars for closure generation # gather used vars for closure generation
if n == nil: return nil if n == nil: return nil
case n.kind case n.kind
of nkSym: of nkSym:
var s = n.sym var s = n.sym
if interestingVar(s) and c.iter.id == s.owner.id: if interestingIterVar(s) and c.iter.id == s.owner.id:
if not containsOrIncl(c.capturedVars, s.id): addField(c.tup, s) if not containsOrIncl(c.capturedVars, s.id): addField(c.tup, s)
result = indirectAccess(newSymNode(c.closureParam), s, n.info) result = indirectAccess(newSymNode(c.closureParam), s, n.info)
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
@ -609,19 +617,20 @@ proc transfIterBody(c: var TIterContext, n: PNode): PNode =
var stateAsgnStmt = newNodeI(nkAsgn, n.info) var stateAsgnStmt = newNodeI(nkAsgn, n.info)
stateAsgnStmt.add(indirectAccess(newSymNode(c.closureParam),c.state,n.info)) stateAsgnStmt.add(indirectAccess(newSymNode(c.closureParam),c.state,n.info))
stateAsgnStmt.add(newIntNode(nkIntLit, stateNo)) stateAsgnStmt.add(newIntTypeNode(nkIntLit, stateNo, getSysType(tyInt)))
var retStmt = newNodeI(nkReturnStmt, n.info) var retStmt = newNodeI(nkReturnStmt, n.info)
if n.sons[0].kind != nkEmpty: if n.sons[0].kind != nkEmpty:
var a = newNodeI(nkAsgn, n.sons[0].info) var a = newNodeI(nkAsgn, n.sons[0].info)
var retVal = transfIterBody(c, n.sons[0])
addSon(a, newSymNode(c.resultSym)) addSon(a, newSymNode(c.resultSym))
addSon(a, n.sons[0]) addSon(a, if retVal.isNil: n.sons[0] else: retVal)
retStmt.add(a) retStmt.add(a)
else: else:
retStmt.add(emptyNode) retStmt.add(emptyNode)
var stateLabelStmt = newNodeI(nkState, n.info) var stateLabelStmt = newNodeI(nkState, n.info)
stateLabelStmt.add(newIntNode(nkIntLit, stateNo-1)) stateLabelStmt.add(newIntTypeNode(nkIntLit, stateNo, getSysType(tyInt)))
result = newNodeI(nkStmtList, n.info) result = newNodeI(nkStmtList, n.info)
result.add(stateAsgnStmt) result.add(stateAsgnStmt)
@ -676,23 +685,82 @@ proc liftIterator*(iter: PSym, body: PNode): PNode =
result.add(newBody) result.add(newBody)
else: else:
result.add(body) result.add(body)
var state1 = newNodeI(nkState, iter.info)
state1.add(newIntNode(nkIntLit, -1))
result.add(state1)
proc transformForLoop*(iter: PSym, body: PNode): PNode = var stateAsgnStmt = newNodeI(nkAsgn, iter.info)
stateAsgnStmt.add(indirectAccess(newSymNode(c.closureParam),
c.state,iter.info))
stateAsgnStmt.add(newIntTypeNode(nkIntLit, -1, getSysType(tyInt)))
result.add(stateAsgnStmt)
proc liftForLoop*(body: PNode): PNode =
# BIG problem ahead: the iterator could be invoked indirectly, but then
# we don't know what environment to create here:
#
# iterator count(): int =
# yield 0
#
# iterator count2(): int =
# var x = 3
# yield x
# inc x
# yield x
#
# proc invoke(iter: iterator(): int) =
# for x in iter(): echo x
#
# --> When to create the closure? --> for the (count) occurence!
discard """ discard """
for i in foo(): nil for i in foo(): ...
Is transformed to: Is transformed to:
cl = createClosure() cl = createClosure()
while true: while true:
let i = foo(cl) let i = foo(cl)
if cl.state == -1: break nkBreakState(cl.state)
""" ...
InternalAssert body.kind == nkForStmt """
# gather vars in a tuple:
var L = body.len var L = body.len
InternalAssert body.kind == nkForStmt and body[L-2].kind in nkCallKinds
var call = body[L-2]
result = newNodeI(nkStmtList, body.info)
# static binding?
var env: PSym
if call[0].kind == nkSym and call[0].sym.kind == skIterator:
# createClose()
let iter = call[0].sym
assert iter.kind == skIterator
env = copySym(getHiddenParam(iter))
var v = newNodeI(nkVarSection, body.info)
addVar(v, newSymNode(env))
result.add(v)
# add 'new' statement:
result.add(newCall(getSysSym"internalNew", env))
var loopBody = newNodeI(nkStmtList, body.info, 3)
var whileLoop = newNodeI(nkWhileStmt, body.info, 2)
whileLoop.sons[0] = newIntTypeNode(nkIntLit, 1, getSysType(tyBool))
whileLoop.sons[1] = loopBody
result.add whileLoop
# setup loopBody:
# gather vars in a tuple:
var v2 = newNodeI(nkLetSection, body.info)
var vpart = newNodeI(if L == 3: nkIdentDefs else: nkVarTuple, body.info)
for i in 0 .. L-3: addSon(vpart, body[i])
addSon(vpart, ast.emptyNode) # no explicit type
if not env.isnil:
call.sons[0] = makeClosure(call.sons[0].sym, env, body.info)
addSon(vpart, call)
addSon(v2, vpart)
loopBody.sons[0] = v2
var bs = newNodeI(nkBreakState, body.info)
bs.addSon(indirectAccess(env,
newSym(skField, getIdent(":state"), env, env.info), body.info))
loopBody.sons[1] = bs
loopBody.sons[2] = body[L-1]

View file

@ -817,11 +817,16 @@ proc semIterator(c: PContext, n: PNode): PNode =
var t = s.typ var t = s.typ
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'; for backwards compatibility,
if s.typ.callConv != ccInline: # we require first class iterators to be marked with 'closure' explicitly
s.typ.callConv = ccClosure # -- at least for 0.9.2.
# and they always at least use the 'env' for the state field: if s.typ.callConv == ccClosure:
incl(s.typ.flags, tfCapturesEnv) incl(s.typ.flags, tfCapturesEnv)
when false:
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

@ -415,17 +415,23 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
# generate access statements for the parameters (unless they are constant) # generate access statements for the parameters (unless they are constant)
# put mapping from formal parameters to actual parameters # put mapping from formal parameters to actual parameters
if n.kind != nkForStmt: InternalError(n.info, "transformFor") if n.kind != nkForStmt: InternalError(n.info, "transformFor")
var length = sonsLen(n)
var call = n.sons[length - 2]
if call.kind notin nkCallKinds or call.sons[0].kind != nkSym or
call.sons[0].typ.callConv == ccClosure or
call.sons[0].sym.kind != skIterator:
n.sons[length-1] = transformLoopBody(c, n.sons[length-1]).pnode
return lambdalifting.liftForLoop(n).ptransNode
#InternalError(call.info, "transformFor")
#echo "transforming: ", renderTree(n) #echo "transforming: ", renderTree(n)
result = newTransNode(nkStmtList, n.info, 0) result = newTransNode(nkStmtList, n.info, 0)
var length = sonsLen(n)
var loopBody = transformLoopBody(c, n.sons[length-1]) var loopBody = transformLoopBody(c, n.sons[length-1])
var v = newNodeI(nkVarSection, n.info) var v = newNodeI(nkVarSection, n.info)
for i in countup(0, length - 3): for i in countup(0, length - 3):
addVar(v, copyTree(n.sons[i])) # declare new vars addVar(v, copyTree(n.sons[i])) # declare new vars
add(result, v.ptransNode) add(result, v.ptransNode)
var call = n.sons[length - 2]
if call.kind notin nkCallKinds or call.sons[0].kind != nkSym:
InternalError(call.info, "transformFor")
# Bugfix: inlined locals belong to the invoking routine, not to the invoked # Bugfix: inlined locals belong to the invoking routine, not to the invoked
# iterator! # iterator!
@ -697,6 +703,8 @@ proc transformBody*(module: PSym, n: PNode, prc: PSym): PNode =
if prc.kind != skMacro: if prc.kind != skMacro:
# XXX no closures yet for macros: # XXX no closures yet for macros:
result = liftLambdas(prc, result) result = liftLambdas(prc, result)
if prc.kind == skIterator and prc.typ.callConv == ccClosure:
result = lambdalifting.liftIterator(prc, result)
incl(result.flags, nfTransf) incl(result.flags, nfTransf)
proc transformStmt*(module: PSym, n: PNode): PNode = proc transformStmt*(module: PSym, n: PNode): PNode =

View file

@ -675,21 +675,27 @@ proc dbgWriteStackTrace(f: PFrame) =
i = 0 i = 0
total = 0 total = 0
tempFrames: array [0..127, PFrame] tempFrames: array [0..127, PFrame]
while it != nil and i <= high(tempFrames)-(firstCalls-1): # setup long head:
# the (-1) is for a nil entry that marks where the '...' should occur while it != nil and i <= high(tempFrames)-firstCalls:
tempFrames[i] = it tempFrames[i] = it
inc(i) inc(i)
inc(total) inc(total)
it = it.prev it = it.prev
# go up the stack to count 'total':
var b = it var b = it
while it != nil: while it != nil:
inc(total) inc(total)
it = it.prev it = it.prev
for j in 1..total-i-(firstCalls-1): var skipped = 0
if b != nil: b = b.prev if total > len(tempFrames):
if total != i: # skip N
skipped = total-i-firstCalls+1
for j in 1..skipped:
if b != nil: b = b.prev
# create '...' entry:
tempFrames[i] = nil tempFrames[i] = nil
inc(i) inc(i)
# setup short tail:
while b != nil and i <= high(tempFrames): while b != nil and i <= high(tempFrames):
tempFrames[i] = b tempFrames[i] = b
inc(i) inc(i)
@ -697,7 +703,7 @@ proc dbgWriteStackTrace(f: PFrame) =
for j in countdown(i-1, 0): for j in countdown(i-1, 0):
if tempFrames[j] == nil: if tempFrames[j] == nil:
write(stdout, "(") write(stdout, "(")
write(stdout, (total-i-1)) write(stdout, skipped)
write(stdout, " calls omitted) ...") write(stdout, " calls omitted) ...")
else: else:
write(stdout, tempFrames[j].filename) write(stdout, tempFrames[j].filename)

View file

@ -135,21 +135,27 @@ proc auxWriteStackTrace(f: PFrame, s: var string) =
it = f it = f
i = 0 i = 0
total = 0 total = 0
while it != nil and i <= high(tempFrames)-(firstCalls-1): # setup long head:
# the (-1) is for a nil entry that marks where the '...' should occur while it != nil and i <= high(tempFrames)-firstCalls:
tempFrames[i] = it tempFrames[i] = it
inc(i) inc(i)
inc(total) inc(total)
it = it.prev it = it.prev
# go up the stack to count 'total':
var b = it var b = it
while it != nil: while it != nil:
inc(total) inc(total)
it = it.prev it = it.prev
for j in 1..total-i-(firstCalls-1): var skipped = 0
if b != nil: b = b.prev if total > len(tempFrames):
if total != i: # skip N
skipped = total-i-firstCalls+1
for j in 1..skipped:
if b != nil: b = b.prev
# create '...' entry:
tempFrames[i] = nil tempFrames[i] = nil
inc(i) inc(i)
# setup short tail:
while b != nil and i <= high(tempFrames): while b != nil and i <= high(tempFrames):
tempFrames[i] = b tempFrames[i] = b
inc(i) inc(i)
@ -157,7 +163,7 @@ proc auxWriteStackTrace(f: PFrame, s: var string) =
for j in countdown(i-1, 0): for j in countdown(i-1, 0):
if tempFrames[j] == nil: if tempFrames[j] == nil:
add(s, "(") add(s, "(")
add(s, $(total-i-1)) add(s, $skipped)
add(s, " calls omitted) ...") add(s, " calls omitted) ...")
else: else:
var oldLen = s.len var oldLen = s.len

33
tests/run/titer8.nim Normal file
View file

@ -0,0 +1,33 @@
discard """
output: '''tada
ta da'''
"""
# Test first class iterator:
import strutils
iterator tokenize2(s: string, seps: set[char] = Whitespace): tuple[
token: string, isSep: bool] {.closure.} =
var i = 0
while i < s.len:
var j = i
if s[j] in seps:
while j < s.len and s[j] in seps: inc(j)
if j > i:
yield (substr(s, i, j-1), true)
else:
while j < s.len and s[j] notin seps: inc(j)
if j > i:
yield (substr(s, i, j-1), false)
i = j
for word, isSep in tokenize2("ta da", whiteSpace):
if not isSep:
stdout.write(word)
echo ""
proc inProc() =
for word, isSep in tokenize2("ta da", whiteSpace):
stdout.write(word)
inProc()

View file

@ -1,8 +1,8 @@
version 0.9.2 version 0.9.2
============= =============
- implement the effect system - test&finish first class iterators
- implement for loop transformation for first class iterators - fix closure bug finally
- overloading based on ASTs: 'constraint' should not be in PType but for the - overloading based on ASTs: 'constraint' should not be in PType but for the
parameter *symbol* parameter *symbol*
@ -11,8 +11,9 @@ version 0.9.2
- ``hoist`` pragma for loop hoisting: can be easily done with - ``hoist`` pragma for loop hoisting: can be easily done with
AST overloading + global AST overloading + global
- implement the compiler as a service - improve the compiler as a service
- ``=`` should be overloadable; requires specialization for ``=`` - ``=`` should be overloadable; requires specialization for ``=``
- implement constructors and non-nil types
- make 'bind' default for templates and introduce 'mixin'; - make 'bind' default for templates and introduce 'mixin';
special rule for ``[]=`` special rule for ``[]=``
- implicit deref for parameter matching; overloading based on 'var T' - implicit deref for parameter matching; overloading based on 'var T'
@ -45,6 +46,7 @@ version 0.9.XX
echo a echo a
echo b) echo b)
- implement read/write tracking in the effect system
- implement the "snoopResult" pragma; no, make a strutils with string append - implement the "snoopResult" pragma; no, make a strutils with string append
semantics instead ... semantics instead ...
- implement "closure tuple consists of a single 'ref'" optimization - implement "closure tuple consists of a single 'ref'" optimization

View file

@ -103,11 +103,9 @@ Roadmap to 1.0
Version 0.9.2 Version 0.9.2
* overloading based on ASTs (like already possible for term rewriting macros) * overloading based on ASTs (like already possible for term rewriting macros)
* better interaction between macros, templates and overloading * better interaction between macros, templates and overloading
* the effect system will be extended
* the symbol binding rules for generics and templates may change again * the symbol binding rules for generics and templates may change again
Version 0.9.x Version 0.9.x
* first class iterators
* message passing performance will be greatly improved * message passing performance will be greatly improved
* the syntactic distinction between statements and expressions will be * the syntactic distinction between statements and expressions will be
removed removed