mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-30 18:02:05 +00:00
further steps to closure support
This commit is contained in:
@@ -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]
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)))
|
||||
|
||||
|
||||
|
||||
@@ -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]",
|
||||
|
||||
@@ -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, ")")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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``!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
@@ -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)
|
||||
|
||||
14
todo.txt
14
todo.txt
@@ -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?
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user