further steps to closure support

This commit is contained in:
Araq
2012-02-06 00:19:56 +01:00
parent 0d4c8ec70c
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)

View File

@@ -373,7 +373,7 @@ Design
A ``closure`` proc var can call ordinary procs of the default Nimrod calling
convention. But not the other way round! A closure is implemented as a
``tuple[prc, data]``. ``data`` can be nil implying a call without a closure.
``tuple[prc, env]``. ``env`` can be nil implying a call without a closure.
This means that a call through a closure generates an ``if`` but the
interoperability is worth the cost of the ``if``. Thunk generation would be
possible too, but it's slightly more effort to implement.
@@ -381,6 +381,18 @@ possible too, but it's slightly more effort to implement.
Tests with GCC on Amd64 showed that it's really beneficical if the
'environment' pointer is passed as the last argument, not as the first argument.
Proper thunk generation is harder because the proc that is to wrap
could stem from a complex expression:
.. code-block:: nimrod
receivesClosure(returnsDefaultCC[i])
A thunk would need to call 'returnsDefaultCC[i]' somehow and that would require
an *additional* closure generation... Ok, not really, but it requires to pass
the function to call. So we'd end up with 2 indirect calls instead of one.
Another much more severe problem which this solution is that it's not GC-safe
to pass a proc pointer around via a generic ``ref`` type.
Example code:
@@ -492,3 +504,19 @@ Accumulator
echo a() + b()
Internals
---------
Lambda lifting is implemented as part of the ``transf`` pass. The ``transf``
pass generates code to setup the environment and to pass it around. However,
this pass does not change the types! So we have some kind of mismatch here; on
the one hand the proc expression becomes an explicit tuple, on the other hand
the tyProc(ccClosure) type is not changed. For C code generation it's also
important the hidden formal param is ``void*`` and not something more
specialized. However the more specialized env type needs to passed to the
backend somehow. We deal with this by modifying ``s.ast[paramPos]`` to contain
the formal hidden parameter, but not ``s.typ``!

View File

@@ -1037,7 +1037,7 @@ A `procedural type`:idx: is internally a pointer to a procedure. ``nil`` is
an allowed value for variables of a procedural type. Nimrod uses procedural
types to achieve `functional`:idx: programming techniques.
Example:
Examples:
.. code-block:: nimrod
@@ -1051,12 +1051,30 @@ Example:
forEach(printItem) # this will NOT work because calling conventions differ
.. code-block:: nimrod
type
TOnMouseMove = proc (x, y: int) {.closure.}
proc onMouseMove(mouseX, mouseY: int) =
# has default calling convention
echo "x: ", mouseX, " y: ", mouseY
proc setOnMouseMove(mouseMoveEvent: TOnMouseMove) = nil
# ok, 'onMouseMove' has the default calling convention, which is compatible
# to 'closure':
setOnMouseMove(onMouseMove)
A subtle issue with procedural types is that the calling convention of the
procedure influences the type compatibility: procedural types are only
compatible if they have the same calling convention.
compatible if they have the same calling convention. As a special extension,
a procedure of the calling convention ``nimcall`` can be passed to a parameter
that expects a proc of the calling convention ``closure``.
Nimrod supports these `calling conventions`:idx:, which are all incompatible to
each other:
Nimrod supports these `calling conventions`:idx:\:
`stdcall`:idx:
This the stdcall convention as specified by Microsoft. The generated C
@@ -1089,8 +1107,10 @@ each other:
same as ``fastcall``, but only for C compilers that support ``fastcall``.
`closure`:idx:
indicates that the procedure expects a context, a closure that needs
to be passed to the procedure.
indicates that the procedure has a hidden implicit parameter
(an *environment*). Proc vars that have the calling convention ``closure``
take up two machine words: One for the proc pointer and another one for
the pointer to implicitely passed environment.
`syscall`:idx:
The syscall convention is the same as ``__syscall`` in C. It is used for
@@ -1114,6 +1134,11 @@ of the following conditions hold:
The rules' purpose is to prevent the case that extending a non-``procvar``
procedure with default parameters breaks client code.
The default calling convention is ``nimcall``, unless it is an inner proc (
a proc inside of a proc). For an inner proc an analysis is performed wether it
accesses its environment. If it does so, it has the calling convention
``closure``, otherwise it has the calling convention ``nimcall``.
Distinct type
~~~~~~~~~~~~~

View File

@@ -83,6 +83,9 @@ proc new*[T](a: var ref T) {.magic: "New", noSideEffect.}
## creates a new object of type ``T`` and returns a safe (traced)
## reference to it in ``a``.
proc internalNew*[T](a: var ref T) {.magic: "New", noSideEffect.}
## leaked implementation detail. Do not use.
proc new*[T](a: var ref T, finalizer: proc (x: ref T)) {.
magic: "NewFinalize", noSideEffect.}
## creates a new object of type ``T`` and returns a safe (traced)

View File

@@ -2,6 +2,13 @@ version 0.8.14
==============
- implement closures
- test evals.nim with closures
- deactivate lambda lifting for JS backend
- Test that iterators within closures work etc; test generics;
test recursion
- test constant closures
- 'closureEnv' magic for easy interfacing with C
- object {.pure, final.} does not work again!
- bug: tsortdev does not run with native GC?
- ``=`` should be overloadable; requires specialization for ``=``?
@@ -45,8 +52,7 @@ Bugs
result = forward(x)
- bug: stress testing basic method example (eval example)
without ``-d:release`` leaks memory; good way to figure out how a
fixed amount of stack can hold an arbitrary number of GC roots!
without ``-d:release`` leaks memory?
- bug: temp2.nim triggers weird compiler and except.nim bug
- bug: negative array indexes fail to index check
@@ -131,6 +137,8 @@ Low priority
- warning for implicit openArray -> varargs conversion
- implement explicit varargs; **but** ``len(varargs)`` problem remains!
--> solve by implicit conversion from varargs to openarray
- implement closures that support nesting > 1
Version 2
=========
@@ -166,6 +174,4 @@ Version 2
a full blown statement; a ``try`` expression might be a good idea to make
error handling more light-weight
people also want ``inc a; inc b``
--> solved by providing an expr version of most control structures?

View File

@@ -2,7 +2,7 @@
News
====
2012-XX-XX Version 0.8.14 released
2012-02-XX Version 0.8.14 released
==================================
Version 0.8.14 has been released! Get it `here <download.html>`_.
@@ -125,6 +125,8 @@ Compiler Additions
for ``on|off`` switches in pragmas. In order to not break existing code,
``on`` and ``off`` are now aliases for ``true`` and ``false`` and declared
in the system module.
- The compiler finally supports **closures**. This is a preliminary
implementation, which does not yet support nestings deeper than 1 level.
Library Additions