further steps to closure support

This commit is contained in:
Araq 2012-02-06 00:19:56 +01:00
commit 632aece191
20 changed files with 379 additions and 138 deletions

View file

@ -189,7 +189,8 @@ type
nkProcTy, # proc type
nkEnumTy, # enum body
nkEnumFieldDef, # `ident = expr` in an enumeration
nkReturnToken # token used for interpretation
nkReturnToken, # token used for interpretation
nkClosure # (prc, env)-pair (internally used for code gen)
TNodeKinds* = set[TNodeKind]
type
@ -977,11 +978,12 @@ proc hasSonWith(n: PNode, kind: TNodeKind): bool =
result = false
proc containsNode*(n: PNode, kinds: TNodeKinds): bool =
if n == nil: return
case n.kind
of nkEmpty..nkNilLit: result = n.kind in kinds
else:
for i in countup(0, sonsLen(n) - 1):
if containsNode(n.sons[i], kinds): return true
if n.kind in kinds or containsNode(n.sons[i], kinds): return true
proc hasSubnodeWith(n: PNode, kind: TNodeKind): bool =
case n.kind
@ -1037,6 +1039,10 @@ proc isGenericRoutine*(s: PSym): bool =
result = s.ast != nil and s.ast[genericParamsPos].kind != nkEmpty
else: nil
proc isRoutine*(s: PSym): bool {.inline.} =
result = s.kind in {skProc, skTemplate, skMacro, skIterator, skMethod,
skConverter}
iterator items*(n: PNode): PNode =
for i in 0.. <n.len: yield n.sons[i]

View file

@ -48,7 +48,7 @@ proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc, pl: PRope) =
if d.k == locNone: getTemp(p, typ.sons[0], d)
assert(d.t != nil) # generate an assignment to d:
var list: TLoc
initLoc(list, locCall, nil, OnUnknown)
initLoc(list, locCall, d.t, OnUnknown)
list.r = pl
genAssignment(p, d, list, {}) # no need for deep copying
else:
@ -137,6 +137,67 @@ proc genPrefixCall(p: BProc, le, ri: PNode, d: var TLoc) =
if i < length - 1: app(pl, ", ")
fixupCall(p, le, ri, d, pl)
proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) =
proc getRawProcType(p: BProc, t: PType): PRope =
var d = copyType(t, t.owner, false)
d.callConv = ccDefault
result = getTypeDesc(p.module, d)
proc addComma(r: PRope): PRope =
result = if r == nil: r else: con(r, ", ")
const CallPattern = "$1.ClEnv? $1.ClPrc($3$1.ClEnv) : (($4)($1.ClPrc))($2)"
var op: TLoc
initLocExpr(p, ri.sons[0], op)
var pl: PRope
var typ = ri.sons[0].typ
assert(typ.kind == tyProc)
var length = sonsLen(ri)
for i in countup(1, length - 1):
assert(sonsLen(typ) == sonsLen(typ.n))
if i < sonsLen(typ):
assert(typ.n.sons[i].kind == nkSym)
app(pl, genArg(p, ri.sons[i], typ.n.sons[i].sym))
else:
app(pl, genArgNoParam(p, ri.sons[i]))
if i < length - 1: app(pl, ", ")
template genCallPattern =
appf(p.s[cpsStmts], CallPattern, op.r, pl, pl.addComma, rawProc)
let rawProc = getRawProcType(p, typ)
if typ.sons[0] != nil:
if isInvalidReturnType(typ.sons[0]):
if sonsLen(ri) > 1: app(pl, ", ")
# beware of 'result = p(result)'. We may need to allocate a temporary:
if d.k in {locTemp, locNone} or not leftAppearsOnRightSide(le, ri):
# Great, we can use 'd':
if d.k == locNone: getTemp(p, typ.sons[0], d)
elif d.k notin {locExpr, locTemp} and not hasNoInit(ri):
# reset before pass as 'result' var:
resetLoc(p, d)
app(pl, addrLoc(d))
genCallPattern()
appf(p.s[cpsStmts], ";$n")
else:
var tmp: TLoc
getTemp(p, typ.sons[0], tmp)
app(pl, addrLoc(tmp))
genCallPattern()
appf(p.s[cpsStmts], ";$n")
genAssignment(p, d, tmp, {}) # no need for deep copying
else:
if d.k == locNone: getTemp(p, typ.sons[0], d)
assert(d.t != nil) # generate an assignment to d:
var list: TLoc
initLoc(list, locCall, d.t, OnUnknown)
list.r = ropef(CallPattern, op.r, pl, pl.addComma, rawProc)
genAssignment(p, d, list, {}) # no need for deep copying
else:
genCallPattern()
appf(p.s[cpsStmts], ";$n")
proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) =
var op, a: TLoc
initLocExpr(p, ri.sons[0], op)
@ -224,7 +285,9 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
appf(p.s[cpsStmts], ";$n")
proc genCall(p: BProc, e: PNode, d: var TLoc) =
if e.sons[0].kind == nkSym and sfInfixCall in e.sons[0].sym.flags and
if e.sons[0].typ.callConv == ccClosure:
genClosureCall(p, nil, e, d)
elif e.sons[0].kind == nkSym and sfInfixCall in e.sons[0].sym.flags and
e.len >= 2:
genInfixCall(p, nil, e, d)
elif e.sons[0].kind == nkSym and sfNamedParamCall in e.sons[0].sym.flags:
@ -235,7 +298,9 @@ proc genCall(p: BProc, e: PNode, d: var TLoc) =
if d.s == onStack and containsGarbageCollectedRef(d.t): keepAlive(p, d)
proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
if ri.sons[0].kind == nkSym and sfInfixCall in ri.sons[0].sym.flags and
if ri.sons[0].typ.callConv == ccClosure:
genClosureCall(p, le, ri, d)
elif ri.sons[0].kind == nkSym and sfInfixCall in ri.sons[0].sym.flags and
ri.len >= 2:
genInfixCall(p, le, ri, d)
elif ri.sons[0].kind == nkSym and sfNamedParamCall in ri.sons[0].sym.flags:

View file

@ -244,7 +244,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
appcg(p, cpsStmts, "#unsureAsgnRef((void**) $1, #copyString($2));$n",
[addrLoc(dest), rdLoc(src)])
if needToKeepAlive in flags: keepAlive(p, dest)
of tyTuple, tyObject:
of tyTuple, tyObject, tyProc:
# XXX: check for subtyping?
if needsComplexAssignment(dest.t):
genGenericAsgn(p, dest, src, flags)
@ -274,7 +274,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
[rdLoc(dest), rdLoc(src), toRope(getSize(dest.t))])
else:
appcg(p, cpsStmts, "$1 = $2;$n", [rdLoc(dest), rdLoc(src)])
of tyPtr, tyPointer, tyChar, tyBool, tyProc, tyEnum, tyCString,
of tyPtr, tyPointer, tyChar, tyBool, tyEnum, tyCString,
tyInt..tyFloat128, tyRange:
appcg(p, cpsStmts, "$1 = $2;$n", [rdLoc(dest), rdLoc(src)])
else: InternalError("genAssignment(" & $ty.kind & ')')
@ -1308,7 +1308,6 @@ proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) =
[rdLoc(a)]))
proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) =
# XXX we don't generate keep alive info here
var a: TLoc
initLocExpr(p, n.sons[0], a)
putIntoDest(p, d, skipTypes(n.typ, abstractVar),
@ -1515,6 +1514,28 @@ proc genTupleConstr(p: BProc, n: PNode, d: var TLoc) =
[rdLoc(d), mangleRecFieldName(t.n.sons[i].sym, t)])
expr(p, it, rec)
proc IsConstClosure(n: PNode): bool {.inline.} =
result = n.sons[0].kind == nkSym and isRoutine(n.sons[0].sym) and
n.sons[1].kind == nkNilLit
proc genClosure(p: BProc, n: PNode, d: var TLoc) =
assert n.kind == nkClosure
if IsConstClosure(n):
inc(p.labels)
var tmp = con("LOC", toRope(p.labels))
appf(p.module.s[cfsData], "NIM_CONST $1 $2 = $3;$n",
[getTypeDesc(p.module, n.typ), tmp, genConstExpr(p, n)])
putIntoDest(p, d, n.typ, tmp)
else:
var tmp, a, b: TLoc
initLocExpr(p, n.sons[0], a)
initLocExpr(p, n.sons[1], b)
getTemp(p, n.typ, tmp)
appcg(p, cpsStmts, "$1.ClPrc = $2; $1.ClEnv = $3;$n",
tmp.rdLoc, a.rdLoc, b.rdLoc)
putLocIntoDest(p, d, tmp)
proc genArrayConstr(p: BProc, n: PNode, d: var TLoc) =
var arr: TLoc
if not handleConstExpr(p, n, d):
@ -1705,6 +1726,7 @@ proc expr(p: BProc, e: PNode, d: var TLoc) =
if sym.loc.r == nil or sym.loc.t == nil:
InternalError(e.info, "expr: proc not init " & sym.name.s)
putLocIntoDest(p, d, sym.loc)
of nkClosure: genClosure(p, e, d)
of nkMetaNode: expr(p, e.sons[0], d)
else: InternalError(e.info, "expr(" & $e.kind & "); unknown node kind")
@ -1751,7 +1773,7 @@ proc genConstExpr(p: BProc, n: PNode): PRope =
var cs: TBitSet
toBitSet(n, cs)
result = genRawSetData(cs, int(getSize(n.typ)))
of nkBracket, nkPar:
of nkBracket, nkPar, nkClosure:
var t = skipTypes(n.typ, abstractInst)
if t.kind == tySequence:
result = genConstSeq(p, n, t)

View file

@ -57,13 +57,24 @@ proc genSingleVar(p: BProc, a: PNode) =
genLineDir(p, a)
loadInto(p, a.sons[0], a.sons[2], v.loc)
proc genClosureVar(p: BProc, a: PNode) =
var immediateAsgn = a.sons[2].kind != nkEmpty
if immediateAsgn:
var v: TLoc
initLocExpr(p, a.sons[0], v)
genLineDir(p, a)
loadInto(p, a.sons[0], a.sons[2], v)
proc genVarStmt(p: BProc, n: PNode) =
for i in countup(0, sonsLen(n) - 1):
var a = n.sons[i]
if a.kind == nkCommentStmt: continue
if a.kind == nkIdentDefs:
assert(a.sons[0].kind == nkSym)
genSingleVar(p, a)
if a.kind == nkIdentDefs:
# can be a lifted var nowadays ...
if a.sons[0].kind == nkSym:
genSingleVar(p, a)
else:
genClosureVar(p, a)
else:
genVarTuple(p, a)
@ -704,7 +715,7 @@ proc genStmts(p: BProc, t: PNode) =
of nkReturnStmt: genReturnStmt(p, t)
of nkBreakStmt: genBreakStmt(p, t)
of nkCall, nkHiddenCallConv, nkInfix, nkPrefix, nkPostfix, nkCommand,
nkCallStrLit:
nkCallStrLit, nkClosure:
genLineDir(p, t)
initLocExpr(p, t, a)
of nkAsgn: genAsgn(p, t, fastAsgn=false)

View file

@ -94,7 +94,7 @@ proc mapType(typ: PType): TCTypeKind =
else: result = ctPtr
of tyPointer: result = ctPtr
of tySequence: result = ctNimSeq
of tyProc: result = ctProc
of tyProc: result = if typ.callConv != ccClosure: ctProc else: ctStruct
of tyString: result = ctNimStr
of tyCString: result = ctCString
of tyInt..tyFloat128:
@ -215,7 +215,7 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var PRope,
appff(params, " Result", " @Result", [])
if t.callConv == ccClosure:
if params != nil: app(params, ", ")
app(params, "void* ClPart")
app(params, "void* ClEnv")
if tfVarargs in t.flags:
if params != nil: app(params, ", ")
app(params, "...")
@ -331,7 +331,7 @@ proc genRecordFieldsAux(m: BModule, n: PNode,
appf(result, "} $1;$n", [uname])
of nkSym:
field = n.sym
assert(field.ast == nil)
#assert(field.ast == nil)
sname = mangleRecFieldName(field, rectype)
if accessExpr != nil: ae = ropef("$1.$2", [accessExpr, sname])
else: ae = sname
@ -436,9 +436,10 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var TIntSet): PRope =
if t.callConv != ccClosure: # procedure vars may need a closure!
appf(m.s[cfsTypes], "typedef $1_PTR($2, $3) $4;$n",
[toRope(CallingConvToStr[t.callConv]), rettype, result, desc])
else:
appf(m.s[cfsTypes], "typedef struct $1 {$n" &
"N_CDECL_PTR($2, PrcPart) $3;$n" & "void* ClPart;$n};$n",
else:
appf(m.s[cfsTypes], "typedef struct {$n" &
"N_CDECL_PTR($2, ClPrc) $3;$n" &
"void* ClEnv;$n} $1;$n",
[result, rettype, desc])
of tySequence:
# we cannot use getTypeForward here because then t would be associated
@ -673,7 +674,8 @@ proc genTupleInfo(m: BModule, typ: PType, name: PRope) =
var tmp2 = getNimNode(m)
appf(m.s[cfsTypeInit3], "$1[$2] = &$3;$n", [tmp, toRope(i), tmp2])
appf(m.s[cfsTypeInit3], "$1.kind = 1;$n" &
"$1.offset = offsetof($2, Field$3);$n" & "$1.typ = $4;$n" &
"$1.offset = offsetof($2, Field$3);$n" &
"$1.typ = $4;$n" &
"$1.name = \"Field$3\";$n",
[tmp2, getTypeDesc(m, typ), toRope(i), genTypeInfo(m, a)])
appf(m.s[cfsTypeInit3], "$1.len = $2; $1.kind = 2; $1.sons = &$3[0];$n",
@ -736,6 +738,14 @@ proc genSetInfo(m: BModule, typ: PType, name: PRope) =
proc genArrayInfo(m: BModule, typ: PType, name: PRope) =
genTypeInfoAuxBase(m, typ, name, genTypeInfo(m, typ.sons[1]))
proc fakeClosureType(owner: PSym): PType =
# we generate the same RTTI as for a tuple[pointer, ref tuple[]]
result = newType(tyTuple, owner)
result.addSon(newType(tyPointer, owner))
var r = newType(tyRef, owner)
r.addSon(newType(tyTuple, owner))
result.addSon(r)
proc genTypeInfo(m: BModule, typ: PType): PRope =
var t = getUniqueType(typ)
# gNimDat contains all the type information nowadays:
@ -750,9 +760,13 @@ proc genTypeInfo(m: BModule, typ: PType): PRope =
if dataGenerated: return
case t.kind
of tyEmpty: result = toRope"0"
of tyPointer, tyProc, tyBool, tyChar, tyCString, tyString, tyInt..tyFloat128,
tyVar:
of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyFloat128, tyVar:
genTypeInfoAuxBase(gNimDat, t, result, toRope"0")
of tyProc:
if t.callConv != ccClosure:
genTypeInfoAuxBase(gNimDat, t, result, toRope"0")
else:
genTupleInfo(gNimDat, fakeClosureType(t.owner), result)
of tyRef, tyPtr, tySequence, tyRange: genTypeInfoAux(gNimDat, t, result)
of tyArrayConstr, tyArray: genArrayInfo(gNimDat, t, result)
of tySet: genSetInfo(gNimDat, t, result)

View file

@ -75,8 +75,10 @@ proc fillLoc(a: var TLoc, k: TLocKind, typ: PType, r: PRope, s: TStorageLoc) =
if a.r == nil: a.r = r
proc isSimpleConst(typ: PType): bool =
result = skipTypes(typ, abstractVar).kind notin
{tyTuple, tyObject, tyArray, tyArrayConstr, tySet, tySequence}
let t = skipTypes(typ, abstractVar)
result = t.kind notin
{tyTuple, tyObject, tyArray, tyArrayConstr, tySet, tySequence} and not
(t.kind == tyProc and t.callConv == ccClosure)
proc useHeader(m: BModule, sym: PSym) =
if lfHeader in sym.loc.Flags:
@ -187,7 +189,7 @@ proc rdLoc(a: TLoc): PRope =
proc addrLoc(a: TLoc): PRope =
result = a.r
if lfIndirect notin a.flags and mapType(a.t) != ctArray:
if lfIndirect notin a.flags and mapType(a.t) != ctArray:
result = con("&", result)
proc rdCharLoc(a: TLoc): PRope =
@ -196,7 +198,7 @@ proc rdCharLoc(a: TLoc): PRope =
if skipTypes(a.t, abstractRange).kind == tyChar:
result = ropef("((NU8)($1))", [result])
proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: TLoc,
proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: TLoc,
takeAddr: bool) =
case analyseObjectWithTypeField(t)
of frNone:
@ -223,11 +225,12 @@ type
proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags)
const
complexValueType = {tyArray, tyArrayConstr, tySet, tyTuple, tyObject}
proc isComplexValueType(t: PType): bool {.inline.} =
result = t.kind in {tyArray, tyArrayConstr, tySet, tyTuple, tyObject} or
(t.kind == tyProc and t.callConv == ccClosure)
proc zeroVar(p: BProc, loc: TLoc, containsGCref: bool) =
if skipTypes(loc.t, abstractVarRange).Kind notin ComplexValueType:
if not isComplexValueType(skipTypes(loc.t, abstractVarRange)):
if containsGcref and p.WithInLoop > 0:
appf(p.s[cpsInit], "$1 = 0;$n", [rdLoc(loc)])
var nilLoc: TLoc
@ -249,8 +252,8 @@ proc zeroVar(p: BProc, loc: TLoc, containsGCref: bool) =
[addrLoc(loc), rdLoc(loc)])
genObjectInit(p, cpsStmts, loc.t, loc, true)
proc zeroTemp(p: BProc, loc: TLoc) =
if skipTypes(loc.t, abstractVarRange).Kind notin complexValueType:
proc zeroTemp(p: BProc, loc: TLoc) =
if not isComplexValueType(skipTypes(loc.t, abstractVarRange)):
appf(p.s[cpsStmts], "$1 = 0;$n", [rdLoc(loc)])
when false:
var nilLoc: TLoc
@ -313,7 +316,7 @@ proc keepAlive(p: BProc, toKeepAlive: TLoc) =
result.s = OnStack
result.flags = {}
if skipTypes(toKeepAlive.t, abstractVarRange).Kind notin complexValueType:
if not isComplexValueType(skipTypes(toKeepAlive.t, abstractVarRange)):
appf(p.s[cpsStmts], "$1 = $2;$n", [rdLoc(result), rdLoc(toKeepAlive)])
else:
appcg(p, cpsStmts,
@ -571,6 +574,15 @@ proc initFrame(p: BProc, procname, filename: PRope): PRope =
proc deinitFrame(p: BProc): PRope =
result = ropecg(p.module, "#popFrame();$n")
proc closureSetup(p: BProc, prc: PSym) =
if prc.typ.callConv != ccClosure: return
# prc.ast[paramsPos].last contains the type we're after:
var env = lastSon(prc.ast[paramsPos]).sym
assignLocalVar(p, env)
# generate cast assignment:
appcg(p, cpsStmts, "$1 = ($2) ClEnv;$n", rdLoc(env.loc),
getTypeDesc(p.module, env.typ))
proc genProcAux(m: BModule, prc: PSym) =
var p = newProc(prc, m)
var header = genProcHeader(m, prc)
@ -594,6 +606,7 @@ proc genProcAux(m: BModule, prc: PSym) =
for i in countup(1, sonsLen(prc.typ.n) - 1):
var param = prc.typ.n.sons[i].sym
assignParam(p, param)
closureSetup(p, prc)
genStmts(p, prc.getBody) # modifies p.locals, p.init, etc.
var generatedProc: PRope
if sfPure in prc.flags:

View file

@ -9,34 +9,46 @@
# This include file implements lambda lifting for the transformator.
# - Things to consider: Does capturing of 'result' work? (unknown)
# - Do generic inner procs work? (should)
# - Does nesting of closures work? (not yet)
# - Test that iterators within closures work etc.
const
procDefs = {nkLambda, nkProcDef, nkMethodDef, nkIteratorDef, nkMacroDef,
nkConverterDef}
proc indirectAccess(a, b: PSym): PNode =
proc indirectAccess(a, b: PSym, info: TLineInfo): PNode =
# returns a[].b as a node
var x = newSymNode(a)
var y = newSymNode(b)
var deref = newNodeI(nkHiddenDeref, x.info)
let x = newSymNode(a)
var deref = newNodeI(nkHiddenDeref, info)
deref.typ = x.typ.sons[0]
let field = getSymFromList(deref.typ.n, b.name)
addSon(deref, x)
result = newNodeI(nkDotExpr, x.info)
result = newNodeI(nkDotExpr, info)
addSon(result, deref)
addSon(result, y)
result.typ = y.typ
addSon(result, newSymNode(field))
result.typ = field.typ
proc Incl(container: PNode, s: PSym) =
for x in container:
if x.sym.id == s.id: return
container.add(newSymNode(s))
type
TCapture = seq[PSym]
proc gatherVars(c: PTransf, n: PNode, owner: PSym, container: PNode) =
# gather used vars for closure generation
proc Capture(cap: var TCapture, s: PSym) =
for x in cap:
if x.name.id == s.name.id: return
cap.add(s)
proc captureToTuple(cap: TCapture, owner: PSym): PType =
result = newType(tyTuple, owner)
result.n = newNodeI(nkRecList, owner.info)
for s in cap:
var field = newSym(skField, s.name, s.owner)
let typ = s.typ
field.typ = typ
field.position = sonsLen(result)
addSon(result.n, newSymNode(field))
addSon(result, typ)
proc gatherVars(c: PTransf, n: PNode, outerProc: PSym, cap: var TCapture) =
# gather used vars for closure generation into 'cap'
case n.kind
of nkSym:
var s = n.sym
@ -45,82 +57,88 @@ proc gatherVars(c: PTransf, n: PNode, owner: PSym, container: PNode) =
of skVar, skLet: found = sfGlobal notin s.flags
of skTemp, skForVar, skParam, skResult: found = true
else: nil
if found and owner.id != s.owner.id:
incl(container, s)
if found and outerProc.id == s.owner.id:
#echo "captured: ", s.name.s
Capture(cap, s)
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
else:
else:
for i in countup(0, sonsLen(n) - 1):
gatherVars(c, n.sons[i], owner, container)
gatherVars(c, n.sons[i], outerProc, cap)
proc replaceVars(c: PTransf, n: PNode, owner, env: PSym) =
proc replaceVars(c: PTransf, n: PNode, outerProc, env: PSym) =
for i in countup(0, safeLen(n) - 1):
if n.kind == nkSym:
let s = n.sym
let a = n.sons[i]
if a.kind == nkSym:
let s = a.sym
var found = false
case s.kind
of skVar, skLet: found = sfGlobal notin s.flags
of skTemp, skForVar, skParam, skResult: found = true
else: nil
if found and owner.id != s.owner.id:
if found and outerProc.id == s.owner.id:
# access through the closure param:
n.sons[i] = indirectAccess(env, s)
n.sons[i] = indirectAccess(env, s, n.info)
else:
replaceVars(c, n.sons[i], owner, env)
replaceVars(c, a, outerProc, env)
proc addFormalParam(routine: PType, param: PSym) =
addSon(routine, param.typ)
addSon(routine.n, newSymNode(param))
proc addFormalParam(routine: PSym, param: PSym) =
addSon(routine.typ, param.typ)
#addFormalParam(routine.typ, param)
addSon(routine.ast.sons[paramsPos], newSymNode(param))
proc isInnerProc(s, owner: PSym): bool {.inline.} =
proc isInnerProc(s, outerProc: PSym): bool {.inline.} =
result = s.kind in {skProc, skMacro, skIterator, skMethod, skConverter} and
s.owner.id == owner.id and not isGenericRoutine(s)
s.owner.id == outerProc.id and not isGenericRoutine(s) and
s.typ.callConv == ccClosure
proc searchForInnerProcs(c: PTransf, n: PNode, owner: PSym, container: PNode) =
proc searchForInnerProcs(c: PTransf, n: PNode, outerProc: PSym,
cap: var TCapture) =
case n.kind
of nkSym:
let s = n.sym
if isInnerProc(s, owner):
gatherVars(c, s.getBody, owner, container)
if isInnerProc(s, outerProc):
gatherVars(c, s.getBody, outerProc, cap)
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
else:
for i in 0.. <len(n):
searchForInnerProcs(c, n.sons[i], owner, container)
searchForInnerProcs(c, n.sons[i], outerProc, cap)
proc makeClosure(c: PTransf, prc, env: PSym): PNode =
var tup = newType(tyTuple, c.module)
tup.addson(prc.typ)
tup.addson(env.typ)
result = newNodeIT(nkPar, prc.info, tup)
proc makeClosure(c: PTransf, prc, env: PSym, info: TLineInfo): PNode =
result = newNodeIT(nkClosure, info, prc.typ)
result.add(newSymNode(prc))
result.add(newSymNode(env))
proc transformInnerProcs(c: PTransf, n: PNode, owner, env: PSym) =
proc transformInnerProcs(c: PTransf, n: PNode, outerProc, env: PSym) =
case n.kind
of nkSym:
let innerProc = n.sym
if isInnerProc(innerProc, owner):
if isInnerProc(innerProc, outerProc):
# inner proc could capture outer vars:
var param = newTemp(c, env.typ, n.info)
param.kind = skParam
addFormalParam(innerProc, param)
# 'anon' should be replaced by '(anon, env)':
IdNodeTablePut(c.transCon.mapping, innerProc,
makeClosure(c, innerProc, env))
makeClosure(c, innerProc, env, n.info))
# access all non-local vars through the 'env' param:
var body = innerProc.getBody
replaceVars(c, body, innerProc, param)
# XXX does not work with recursion!
replaceVars(c, body, outerProc, param)
innerProc.ast.sons[bodyPos] = body
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
else:
for i in 0.. <len(n):
transformInnerProcs(c, n.sons[i], owner, env)
transformInnerProcs(c, n.sons[i], outerProc, env)
proc newCall(a, b: PSym): PNode =
result = newNodeI(nkCall, a.info)
result.add newSymNode(a)
result.add newSymNode(b)
proc createEnvStmt(c: PTransf, varList: PNode, env: PSym): PTransNode =
proc createEnvStmt(c: PTransf, varList: TCapture, env: PSym): PTransNode =
# 'varlist' can contain parameters or variables. We don't eliminate yet
# local vars that end up in an environment. This could even be a for loop
# var!
@ -129,17 +147,33 @@ proc createEnvStmt(c: PTransf, varList: PNode, env: PSym): PTransNode =
addVar(v, newSymNode(env))
result.add(v.ptransNode)
# add 'new' statement:
result.add(newCall(getSysSym"new", env).ptransnode)
result.add(newCall(getSysSym"internalNew", env).ptransnode)
# add assignment statements:
for v in varList:
assert v.kind == nkSym
let fieldAccess = indirectAccess(env, v.sym)
if v.sym.kind == skParam:
let fieldAccess = indirectAccess(env, v, env.info)
if v.kind == skParam:
# add ``env.param = param``
result.add(newAsgnStmt(c, fieldAccess, v.ptransNode))
IdNodeTablePut(c.transCon.mapping, v.sym, fieldAccess)
result.add(newAsgnStmt(c, fieldAccess, newSymNode(v).ptransNode))
IdNodeTablePut(c.transCon.mapping, v, fieldAccess)
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.
s.ast.sons[bodyPos] = n.sons[bodyPos]
if n.kind == nkMethodDef: methodDef(s, false)
# should 's' be replaced by a tuple ('s', env)?
var tc = c.transCon
var repl: PNode = nil
while tc != nil:
repl = IdNodeTableGet(tc.mapping, s)
if repl != nil: break
tc = tc.next
if repl != nil:
result = PTransNode(repl)
else:
result = PTransNode(n)
proc transformProc(c: PTransf, n: PNode): PTransNode =
# don't process generics:
if n.sons[genericParamsPos].kind != nkEmpty:
@ -147,34 +181,32 @@ proc transformProc(c: PTransf, n: PNode): PTransNode =
var s = n.sons[namePos].sym
var body = s.getBody
if body.kind == nkEmpty:
return PTransNode(n)
if not containsNode(body, procDefs):
# fast path: no inner procs, so no closure needed:
n.sons[bodyPos] = PNode(transform(c, body))
if n.kind == nkMethodDef: methodDef(s, false)
return PTransNode(n)
return transformProcFin(c, n, s)
var closure = newNodeI(nkRecList, n.info)
searchForInnerProcs(c, body, s, closure)
# create environment:
var cap: TCapture = @[]
searchForInnerProcs(c, body, s, cap)
if closure.len == 0:
if cap.len == 0:
# fast path: no captured variables, so no closure needed:
n.sons[bodyPos] = PNode(transform(c, body))
if n.kind == nkMethodDef: methodDef(s, false)
return PTransNode(n)
return transformProcFin(c, n, s)
# create environment:
var envDesc = newType(tyObject, s)
envDesc.n = closure
addSon(envDesc, nil) # no super class
var envType = newType(tyRef, s)
addSon(envType, envDesc)
addSon(envType, captureToTuple(cap, s))
# XXX currently we always do a heap allocation. A simple escape analysis
# could turn the closure into a stack allocation. Later versions will
# implement that.
# Currently we always do a heap allocation. A simple escape analysis
# could turn the closure into a stack allocation. Later versions might
# implement that. This would require backend changes too though.
var envSym = newTemp(c, envType, s.info)
var newBody = createEnvStmt(c, closure, envSym)
var newBody = createEnvStmt(c, cap, envSym)
# modify any local proc to gain a new parameter; this also creates the
# mapping entries that turn (localProc) into (localProc, env):
transformInnerProcs(c, body, s, envSym)
@ -183,19 +215,18 @@ proc transformProc(c: PTransf, n: PNode): PTransNode =
# Careful this transforms the inner procs too!
newBody.add(transform(c, body))
n.sons[bodyPos] = newBody.pnode
if n.kind == nkMethodDef: methodDef(s, false)
result = newBody
result = transformProcFin(c, n, s)
proc generateThunk(c: PTransf, prc: PNode, closure: PType): PNode =
proc generateThunk(c: PTransf, prc: PNode, dest: PType): PNode =
## Converts 'prc' into '(thunk, nil)' so that it's compatible with
## a closure.
# XXX we hack around here by generating a 'cast' instead of a proper thunk.
result = newNodeIT(nkPar, prc.info, closure)
var conv = newNodeIT(nkHiddenStdConv, prc.info, closure.sons[0])
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, closure.sons[1]))
result.add(newNodeIT(nkNilLit, prc.info, getSysType(tyNil)))

View file

@ -93,7 +93,7 @@ type
errAssertionFailed, errCannotGenerateCodeForX, errXRequiresOneArgument,
errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate,
errXhasSideEffects, errIteratorExpected, errLetNeedsInit,
errThreadvarCannotInit, errWrongSymbolX,
errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX,
errUser,
warnCannotOpenFile,
warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit,
@ -325,6 +325,7 @@ const
errLetNeedsInit: "'let' symbol requires an initialization",
errThreadvarCannotInit: "a thread var cannot be initialized explicitly",
errWrongSymbolX: "usage of \'$1\' is a user-defined error",
errIllegalCaptureX: "illegal capture '$1'",
errUser: "$1",
warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]",
warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]",

View file

@ -339,7 +339,7 @@ proc lsub(n: PNode): int =
of nkHiddenAddr, nkHiddenDeref: result = lsub(n.sons[0])
of nkCommand: result = lsub(n.sons[0]) + lcomma(n, 1) + 1
of nkExprEqExpr, nkAsgn, nkFastAsgn: result = lsons(n) + 3
of nkPar, nkCurly, nkBracket: result = lcomma(n) + 2
of nkPar, nkCurly, nkBracket, nkClosure: result = lcomma(n) + 2
of nkTableConstr:
result = if n.len > 0: lcomma(n) + 2 else: len("{:}")
of nkSymChoice: result = lsons(n) + len("()") + sonsLen(n) - 1
@ -759,7 +759,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
if i > 0: put(g, tkOpr, "|")
gsub(g, n.sons[i], c)
put(g, tkParRi, ")")
of nkPar:
of nkPar, nkClosure:
put(g, tkParLe, "(")
gcomma(g, n, c)
put(g, tkParRi, ")")

View file

@ -50,6 +50,10 @@ proc inlineConst(n: PNode, s: PSym): PNode {.inline.} =
result.typ = s.typ
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 =
case s.kind
of skProc, skMethod, skIterator, skConverter:
@ -83,10 +87,16 @@ proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
result = newSymNode(s, n.info)
of skMacro: result = semMacroExpr(c, n, s)
of skTemplate: result = semTemplateExpr(c, n, s)
of skVar, skLet, skResult:
of skVar, skLet, skResult, skParam:
markUsed(n, s)
# if a proc accesses a global variable, it is not side effect free:
if sfGlobal in s.flags: incl(c.p.owner.flags, sfSideEffect)
if sfGlobal in s.flags:
incl(c.p.owner.flags, sfSideEffect)
elif s.owner != c.p.owner and s.owner.kind != skModule:
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:
GlobalError(n.info, errIllegalCaptureX, s.name.s)
result = newSymNode(s, n.info)
of skGenericParam:
if s.ast == nil: InternalError(n.info, "no default for")

View file

@ -644,8 +644,6 @@ proc semLambda(c: PContext, n: PNode): PNode =
else:
s.typ = newTypeS(tyProc, c)
addSon(s.typ, nil)
# no! do a proper analysis to determine calling convention
when false: s.typ.callConv = ccClosure
if n.sons[pragmasPos].kind != nkEmpty:
pragma(c, s, n.sons[pragmasPos], lambdaPragmas)
s.options = gOptions
@ -697,9 +695,6 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
# open for parameters
if proto == nil:
s.typ.callConv = lastOptionEntry(c).defaultCC
when false:
# do a proper analysis here:
if c.p.owner.kind != skModule: s.typ.callConv = ccClosure
# add it here, so that recursive procs are possible:
# -2 because we have a scope open for parameters
if kind in OverloadableSyms:

View file

@ -360,7 +360,7 @@ proc analyse(c: PProcCtx, n: PNode): TThreadOwner =
if n.sons[0].kind != nkEmpty: result = analyse(c, n.sons[0])
else: result = toVoid
of nkAsmStmt, nkPragma, nkIteratorDef, nkProcDef, nkMethodDef,
nkConverterDef, nkMacroDef, nkTemplateDef:
nkConverterDef, nkMacroDef, nkTemplateDef, nkLambda:
result = toVoid
of nkExprColonExpr:
result = analyse(c, n.sons[1])

View file

@ -15,6 +15,7 @@
# * performes contant folding
# * converts "continue" to "break"
# * introduces method dispatchers
# * performs lambda lifting for closure support
import
intsets, strutils, lists, options, ast, astalgo, trees, treetab, msgs, os,
@ -355,6 +356,8 @@ proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode =
if n.sons[0].kind == a or n.sons[0].kind == b:
# addr ( deref ( x )) --> x
result = PTransNode(n.sons[0].sons[0])
include lambdalifting
proc transformConv(c: PTransf, n: PNode): PTransNode =
# numeric types need range checks:
@ -428,6 +431,11 @@ proc transformConv(c: PTransf, n: PNode): PTransNode =
result[0] = transform(c, n.sons[1])
else:
result = transform(c, n.sons[1])
of tyProc:
if dest.callConv == ccClosure and source.callConv == ccDefault:
result = generateThunk(c, n.sons[1], dest).ptransnode
else:
result = transformSons(c, n)
of tyGenericParam, tyOrdinal:
result = transform(c, n.sons[1])
# happens sometimes for generated assignments, etc.
@ -510,8 +518,6 @@ proc getMagicOp(call: PNode): TMagic =
else:
result = mNone
include lambdalifting
proc transformCase(c: PTransf, n: PNode): PTransNode =
# removes `elif` branches of a case stmt
# adds ``else: nil`` if needed for the code generator

View file

@ -115,7 +115,7 @@ proc isDeepConstExpr*(n: PNode): bool =
result = true
of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv:
result = isDeepConstExpr(n.sons[1])
of nkCurly, nkBracket, nkPar:
of nkCurly, nkBracket, nkPar, nkClosure:
for i in 0 .. <n.len:
if not isDeepConstExpr(n.sons[i]): return false
result = true

View file

@ -276,16 +276,17 @@ proc analyseObjectWithTypeField(t: PType): TTypeFieldResult =
var marker = InitIntSet()
result = analyseObjectWithTypeFieldAux(t, marker)
proc isGBCRef(t: PType): bool =
result = t.kind in GcTypeKinds
proc isGCRef(t: PType): bool =
result = t.kind in GcTypeKinds or
(t.kind == tyProc and t.callConv == ccClosure)
proc containsGarbageCollectedRef(typ: PType): bool =
# returns true if typ contains a reference, sequence or string (all the
# things that are garbage-collected)
result = searchTypeFor(typ, isGBCRef)
result = searchTypeFor(typ, isGCRef)
proc isTyRef(t: PType): bool =
result = t.kind == tyRef
result = t.kind == tyRef or (t.kind == tyProc and t.callConv == ccClosure)
proc containsTyRef*(typ: PType): bool =
# returns true if typ contains a 'ref'
@ -332,6 +333,8 @@ proc canFormAcycleAux(marker: var TIntSet, typ: PType, startId: int): bool =
nil
proc canFormAcycle(typ: PType): bool =
# XXX as I expect cycles introduced by closures are very rare, we pretend
# they can't happen here.
var marker = InitIntSet()
result = canFormAcycleAux(marker, typ, typ.id)