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 nkProcTy, # proc type
nkEnumTy, # enum body nkEnumTy, # enum body
nkEnumFieldDef, # `ident = expr` in an enumeration 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] TNodeKinds* = set[TNodeKind]
type type
@ -977,11 +978,12 @@ proc hasSonWith(n: PNode, kind: TNodeKind): bool =
result = false result = false
proc containsNode*(n: PNode, kinds: TNodeKinds): bool = proc containsNode*(n: PNode, kinds: TNodeKinds): bool =
if n == nil: return
case n.kind case n.kind
of nkEmpty..nkNilLit: result = n.kind in kinds of nkEmpty..nkNilLit: result = n.kind in kinds
else: else:
for i in countup(0, sonsLen(n) - 1): 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 = proc hasSubnodeWith(n: PNode, kind: TNodeKind): bool =
case n.kind case n.kind
@ -1037,6 +1039,10 @@ proc isGenericRoutine*(s: PSym): bool =
result = s.ast != nil and s.ast[genericParamsPos].kind != nkEmpty result = s.ast != nil and s.ast[genericParamsPos].kind != nkEmpty
else: nil else: nil
proc isRoutine*(s: PSym): bool {.inline.} =
result = s.kind in {skProc, skTemplate, skMacro, skIterator, skMethod,
skConverter}
iterator items*(n: PNode): PNode = iterator items*(n: PNode): PNode =
for i in 0.. <n.len: yield n.sons[i] 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) if d.k == locNone: getTemp(p, typ.sons[0], d)
assert(d.t != nil) # generate an assignment to d: assert(d.t != nil) # generate an assignment to d:
var list: TLoc var list: TLoc
initLoc(list, locCall, nil, OnUnknown) initLoc(list, locCall, d.t, OnUnknown)
list.r = pl list.r = pl
genAssignment(p, d, list, {}) # no need for deep copying genAssignment(p, d, list, {}) # no need for deep copying
else: else:
@ -137,6 +137,67 @@ proc genPrefixCall(p: BProc, le, ri: PNode, d: var TLoc) =
if i < length - 1: app(pl, ", ") if i < length - 1: app(pl, ", ")
fixupCall(p, le, ri, d, 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) = proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) =
var op, a: TLoc var op, a: TLoc
initLocExpr(p, ri.sons[0], op) initLocExpr(p, ri.sons[0], op)
@ -224,7 +285,9 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
appf(p.s[cpsStmts], ";$n") appf(p.s[cpsStmts], ";$n")
proc genCall(p: BProc, e: PNode, d: var TLoc) = 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: e.len >= 2:
genInfixCall(p, nil, e, d) genInfixCall(p, nil, e, d)
elif e.sons[0].kind == nkSym and sfNamedParamCall in e.sons[0].sym.flags: 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) if d.s == onStack and containsGarbageCollectedRef(d.t): keepAlive(p, d)
proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) = 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: ri.len >= 2:
genInfixCall(p, le, ri, d) genInfixCall(p, le, ri, d)
elif ri.sons[0].kind == nkSym and sfNamedParamCall in ri.sons[0].sym.flags: 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", appcg(p, cpsStmts, "#unsureAsgnRef((void**) $1, #copyString($2));$n",
[addrLoc(dest), rdLoc(src)]) [addrLoc(dest), rdLoc(src)])
if needToKeepAlive in flags: keepAlive(p, dest) if needToKeepAlive in flags: keepAlive(p, dest)
of tyTuple, tyObject: of tyTuple, tyObject, tyProc:
# XXX: check for subtyping? # XXX: check for subtyping?
if needsComplexAssignment(dest.t): if needsComplexAssignment(dest.t):
genGenericAsgn(p, dest, src, flags) 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))]) [rdLoc(dest), rdLoc(src), toRope(getSize(dest.t))])
else: else:
appcg(p, cpsStmts, "$1 = $2;$n", [rdLoc(dest), rdLoc(src)]) 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: tyInt..tyFloat128, tyRange:
appcg(p, cpsStmts, "$1 = $2;$n", [rdLoc(dest), rdLoc(src)]) appcg(p, cpsStmts, "$1 = $2;$n", [rdLoc(dest), rdLoc(src)])
else: InternalError("genAssignment(" & $ty.kind & ')') else: InternalError("genAssignment(" & $ty.kind & ')')
@ -1308,7 +1308,6 @@ proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) =
[rdLoc(a)])) [rdLoc(a)]))
proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) = proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) =
# XXX we don't generate keep alive info here
var a: TLoc var a: TLoc
initLocExpr(p, n.sons[0], a) initLocExpr(p, n.sons[0], a)
putIntoDest(p, d, skipTypes(n.typ, abstractVar), 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)]) [rdLoc(d), mangleRecFieldName(t.n.sons[i].sym, t)])
expr(p, it, rec) 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) = proc genArrayConstr(p: BProc, n: PNode, d: var TLoc) =
var arr: TLoc var arr: TLoc
if not handleConstExpr(p, n, d): 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: if sym.loc.r == nil or sym.loc.t == nil:
InternalError(e.info, "expr: proc not init " & sym.name.s) InternalError(e.info, "expr: proc not init " & sym.name.s)
putLocIntoDest(p, d, sym.loc) putLocIntoDest(p, d, sym.loc)
of nkClosure: genClosure(p, e, d)
of nkMetaNode: expr(p, e.sons[0], d) of nkMetaNode: expr(p, e.sons[0], d)
else: InternalError(e.info, "expr(" & $e.kind & "); unknown node kind") else: InternalError(e.info, "expr(" & $e.kind & "); unknown node kind")
@ -1751,7 +1773,7 @@ proc genConstExpr(p: BProc, n: PNode): PRope =
var cs: TBitSet var cs: TBitSet
toBitSet(n, cs) toBitSet(n, cs)
result = genRawSetData(cs, int(getSize(n.typ))) result = genRawSetData(cs, int(getSize(n.typ)))
of nkBracket, nkPar: of nkBracket, nkPar, nkClosure:
var t = skipTypes(n.typ, abstractInst) var t = skipTypes(n.typ, abstractInst)
if t.kind == tySequence: if t.kind == tySequence:
result = genConstSeq(p, n, t) result = genConstSeq(p, n, t)

View file

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

View file

@ -94,7 +94,7 @@ proc mapType(typ: PType): TCTypeKind =
else: result = ctPtr else: result = ctPtr
of tyPointer: result = ctPtr of tyPointer: result = ctPtr
of tySequence: result = ctNimSeq of tySequence: result = ctNimSeq
of tyProc: result = ctProc of tyProc: result = if typ.callConv != ccClosure: ctProc else: ctStruct
of tyString: result = ctNimStr of tyString: result = ctNimStr
of tyCString: result = ctCString of tyCString: result = ctCString
of tyInt..tyFloat128: of tyInt..tyFloat128:
@ -215,7 +215,7 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var PRope,
appff(params, " Result", " @Result", []) appff(params, " Result", " @Result", [])
if t.callConv == ccClosure: if t.callConv == ccClosure:
if params != nil: app(params, ", ") if params != nil: app(params, ", ")
app(params, "void* ClPart") app(params, "void* ClEnv")
if tfVarargs in t.flags: if tfVarargs in t.flags:
if params != nil: app(params, ", ") if params != nil: app(params, ", ")
app(params, "...") app(params, "...")
@ -331,7 +331,7 @@ proc genRecordFieldsAux(m: BModule, n: PNode,
appf(result, "} $1;$n", [uname]) appf(result, "} $1;$n", [uname])
of nkSym: of nkSym:
field = n.sym field = n.sym
assert(field.ast == nil) #assert(field.ast == nil)
sname = mangleRecFieldName(field, rectype) sname = mangleRecFieldName(field, rectype)
if accessExpr != nil: ae = ropef("$1.$2", [accessExpr, sname]) if accessExpr != nil: ae = ropef("$1.$2", [accessExpr, sname])
else: ae = sname else: ae = sname
@ -437,8 +437,9 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var TIntSet): PRope =
appf(m.s[cfsTypes], "typedef $1_PTR($2, $3) $4;$n", appf(m.s[cfsTypes], "typedef $1_PTR($2, $3) $4;$n",
[toRope(CallingConvToStr[t.callConv]), rettype, result, desc]) [toRope(CallingConvToStr[t.callConv]), rettype, result, desc])
else: else:
appf(m.s[cfsTypes], "typedef struct $1 {$n" & appf(m.s[cfsTypes], "typedef struct {$n" &
"N_CDECL_PTR($2, PrcPart) $3;$n" & "void* ClPart;$n};$n", "N_CDECL_PTR($2, ClPrc) $3;$n" &
"void* ClEnv;$n} $1;$n",
[result, rettype, desc]) [result, rettype, desc])
of tySequence: of tySequence:
# we cannot use getTypeForward here because then t would be associated # 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) var tmp2 = getNimNode(m)
appf(m.s[cfsTypeInit3], "$1[$2] = &$3;$n", [tmp, toRope(i), tmp2]) appf(m.s[cfsTypeInit3], "$1[$2] = &$3;$n", [tmp, toRope(i), tmp2])
appf(m.s[cfsTypeInit3], "$1.kind = 1;$n" & 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", "$1.name = \"Field$3\";$n",
[tmp2, getTypeDesc(m, typ), toRope(i), genTypeInfo(m, a)]) [tmp2, getTypeDesc(m, typ), toRope(i), genTypeInfo(m, a)])
appf(m.s[cfsTypeInit3], "$1.len = $2; $1.kind = 2; $1.sons = &$3[0];$n", 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) = proc genArrayInfo(m: BModule, typ: PType, name: PRope) =
genTypeInfoAuxBase(m, typ, name, genTypeInfo(m, typ.sons[1])) 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 = proc genTypeInfo(m: BModule, typ: PType): PRope =
var t = getUniqueType(typ) var t = getUniqueType(typ)
# gNimDat contains all the type information nowadays: # gNimDat contains all the type information nowadays:
@ -750,9 +760,13 @@ proc genTypeInfo(m: BModule, typ: PType): PRope =
if dataGenerated: return if dataGenerated: return
case t.kind case t.kind
of tyEmpty: result = toRope"0" of tyEmpty: result = toRope"0"
of tyPointer, tyProc, tyBool, tyChar, tyCString, tyString, tyInt..tyFloat128, of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyFloat128, tyVar:
tyVar:
genTypeInfoAuxBase(gNimDat, t, result, toRope"0") 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 tyRef, tyPtr, tySequence, tyRange: genTypeInfoAux(gNimDat, t, result)
of tyArrayConstr, tyArray: genArrayInfo(gNimDat, t, result) of tyArrayConstr, tyArray: genArrayInfo(gNimDat, t, result)
of tySet: genSetInfo(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 if a.r == nil: a.r = r
proc isSimpleConst(typ: PType): bool = proc isSimpleConst(typ: PType): bool =
result = skipTypes(typ, abstractVar).kind notin let t = skipTypes(typ, abstractVar)
{tyTuple, tyObject, tyArray, tyArrayConstr, tySet, tySequence} 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) = proc useHeader(m: BModule, sym: PSym) =
if lfHeader in sym.loc.Flags: if lfHeader in sym.loc.Flags:
@ -223,11 +225,12 @@ type
proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags)
const proc isComplexValueType(t: PType): bool {.inline.} =
complexValueType = {tyArray, tyArrayConstr, tySet, tyTuple, tyObject} 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) = 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: if containsGcref and p.WithInLoop > 0:
appf(p.s[cpsInit], "$1 = 0;$n", [rdLoc(loc)]) appf(p.s[cpsInit], "$1 = 0;$n", [rdLoc(loc)])
var nilLoc: TLoc var nilLoc: TLoc
@ -250,7 +253,7 @@ proc zeroVar(p: BProc, loc: TLoc, containsGCref: bool) =
genObjectInit(p, cpsStmts, loc.t, loc, true) genObjectInit(p, cpsStmts, loc.t, loc, true)
proc zeroTemp(p: BProc, loc: TLoc) = proc zeroTemp(p: BProc, loc: TLoc) =
if skipTypes(loc.t, abstractVarRange).Kind notin complexValueType: if not isComplexValueType(skipTypes(loc.t, abstractVarRange)):
appf(p.s[cpsStmts], "$1 = 0;$n", [rdLoc(loc)]) appf(p.s[cpsStmts], "$1 = 0;$n", [rdLoc(loc)])
when false: when false:
var nilLoc: TLoc var nilLoc: TLoc
@ -313,7 +316,7 @@ proc keepAlive(p: BProc, toKeepAlive: TLoc) =
result.s = OnStack result.s = OnStack
result.flags = {} 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)]) appf(p.s[cpsStmts], "$1 = $2;$n", [rdLoc(result), rdLoc(toKeepAlive)])
else: else:
appcg(p, cpsStmts, appcg(p, cpsStmts,
@ -571,6 +574,15 @@ proc initFrame(p: BProc, procname, filename: PRope): PRope =
proc deinitFrame(p: BProc): PRope = proc deinitFrame(p: BProc): PRope =
result = ropecg(p.module, "#popFrame();$n") 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) = proc genProcAux(m: BModule, prc: PSym) =
var p = newProc(prc, m) var p = newProc(prc, m)
var header = genProcHeader(m, prc) 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): for i in countup(1, sonsLen(prc.typ.n) - 1):
var param = prc.typ.n.sons[i].sym var param = prc.typ.n.sons[i].sym
assignParam(p, param) assignParam(p, param)
closureSetup(p, prc)
genStmts(p, prc.getBody) # modifies p.locals, p.init, etc. genStmts(p, prc.getBody) # modifies p.locals, p.init, etc.
var generatedProc: PRope var generatedProc: PRope
if sfPure in prc.flags: if sfPure in prc.flags:

View file

@ -9,34 +9,46 @@
# This include file implements lambda lifting for the transformator. # 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 const
procDefs = {nkLambda, nkProcDef, nkMethodDef, nkIteratorDef, nkMacroDef, procDefs = {nkLambda, nkProcDef, nkMethodDef, nkIteratorDef, nkMacroDef,
nkConverterDef} nkConverterDef}
proc indirectAccess(a, b: PSym): PNode = proc indirectAccess(a, b: PSym, info: TLineInfo): PNode =
# returns a[].b as a node # returns a[].b as a node
var x = newSymNode(a) let x = newSymNode(a)
var y = newSymNode(b) var deref = newNodeI(nkHiddenDeref, info)
var deref = newNodeI(nkHiddenDeref, x.info)
deref.typ = x.typ.sons[0] deref.typ = x.typ.sons[0]
let field = getSymFromList(deref.typ.n, b.name)
addSon(deref, x) addSon(deref, x)
result = newNodeI(nkDotExpr, x.info) result = newNodeI(nkDotExpr, info)
addSon(result, deref) addSon(result, deref)
addSon(result, y) addSon(result, newSymNode(field))
result.typ = y.typ result.typ = field.typ
proc Incl(container: PNode, s: PSym) = type
for x in container: TCapture = seq[PSym]
if x.sym.id == s.id: return
container.add(newSymNode(s))
proc gatherVars(c: PTransf, n: PNode, owner: PSym, container: PNode) = proc Capture(cap: var TCapture, s: PSym) =
# gather used vars for closure generation 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 case n.kind
of nkSym: of nkSym:
var s = n.sym 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 skVar, skLet: found = sfGlobal notin s.flags
of skTemp, skForVar, skParam, skResult: found = true of skTemp, skForVar, skParam, skResult: found = true
else: nil else: nil
if found and owner.id != s.owner.id: if found and outerProc.id == s.owner.id:
incl(container, s) #echo "captured: ", s.name.s
Capture(cap, s)
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
else: else:
for i in countup(0, sonsLen(n) - 1): 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): for i in countup(0, safeLen(n) - 1):
if n.kind == nkSym: let a = n.sons[i]
let s = n.sym if a.kind == nkSym:
let s = a.sym
var found = false var found = false
case s.kind case s.kind
of skVar, skLet: found = sfGlobal notin s.flags of skVar, skLet: found = sfGlobal notin s.flags
of skTemp, skForVar, skParam, skResult: found = true of skTemp, skForVar, skParam, skResult: found = true
else: nil else: nil
if found and owner.id != s.owner.id: if found and outerProc.id == s.owner.id:
# access through the closure param: # access through the closure param:
n.sons[i] = indirectAccess(env, s) n.sons[i] = indirectAccess(env, s, n.info)
else: 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) = proc addFormalParam(routine: PSym, param: PSym) =
addSon(routine.typ, param.typ) #addFormalParam(routine.typ, param)
addSon(routine.ast.sons[paramsPos], newSymNode(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 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 case n.kind
of nkSym: of nkSym:
let s = n.sym let s = n.sym
if isInnerProc(s, owner): if isInnerProc(s, outerProc):
gatherVars(c, s.getBody, owner, container) gatherVars(c, s.getBody, outerProc, cap)
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
else: else:
for i in 0.. <len(n): 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 = proc makeClosure(c: PTransf, prc, env: PSym, info: TLineInfo): PNode =
var tup = newType(tyTuple, c.module) result = newNodeIT(nkClosure, info, prc.typ)
tup.addson(prc.typ)
tup.addson(env.typ)
result = newNodeIT(nkPar, prc.info, tup)
result.add(newSymNode(prc)) result.add(newSymNode(prc))
result.add(newSymNode(env)) 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 case n.kind
of nkSym: of nkSym:
let innerProc = n.sym let innerProc = n.sym
if isInnerProc(innerProc, owner): if isInnerProc(innerProc, outerProc):
# inner proc could capture outer vars: # inner proc could capture outer vars:
var param = newTemp(c, env.typ, n.info) var param = newTemp(c, env.typ, n.info)
param.kind = skParam param.kind = skParam
addFormalParam(innerProc, param) addFormalParam(innerProc, param)
# 'anon' should be replaced by '(anon, env)': # 'anon' should be replaced by '(anon, env)':
IdNodeTablePut(c.transCon.mapping, innerProc, IdNodeTablePut(c.transCon.mapping, innerProc,
makeClosure(c, innerProc, env)) makeClosure(c, innerProc, env, n.info))
# access all non-local vars through the 'env' param: # access all non-local vars through the 'env' param:
var body = innerProc.getBody 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 of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil
else: else:
for i in 0.. <len(n): 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 = proc newCall(a, b: PSym): PNode =
result = newNodeI(nkCall, a.info) result = newNodeI(nkCall, a.info)
result.add newSymNode(a) result.add newSymNode(a)
result.add newSymNode(b) 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 # '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 # local vars that end up in an environment. This could even be a for loop
# var! # var!
@ -129,16 +147,32 @@ proc createEnvStmt(c: PTransf, varList: PNode, env: PSym): PTransNode =
addVar(v, newSymNode(env)) addVar(v, newSymNode(env))
result.add(v.ptransNode) result.add(v.ptransNode)
# add 'new' statement: # add 'new' statement:
result.add(newCall(getSysSym"new", env).ptransnode) result.add(newCall(getSysSym"internalNew", env).ptransnode)
# add assignment statements: # add assignment statements:
for v in varList: for v in varList:
assert v.kind == nkSym let fieldAccess = indirectAccess(env, v, env.info)
let fieldAccess = indirectAccess(env, v.sym) if v.kind == skParam:
if v.sym.kind == skParam:
# add ``env.param = param`` # add ``env.param = param``
result.add(newAsgnStmt(c, fieldAccess, v.ptransNode)) result.add(newAsgnStmt(c, fieldAccess, newSymNode(v).ptransNode))
IdNodeTablePut(c.transCon.mapping, v.sym, fieldAccess) 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 = proc transformProc(c: PTransf, n: PNode): PTransNode =
# don't process generics: # don't process generics:
@ -147,34 +181,32 @@ proc transformProc(c: PTransf, n: PNode): PTransNode =
var s = n.sons[namePos].sym var s = n.sons[namePos].sym
var body = s.getBody var body = s.getBody
if body.kind == nkEmpty:
return PTransNode(n)
if not containsNode(body, procDefs): if not containsNode(body, procDefs):
# fast path: no inner procs, so no closure needed: # fast path: no inner procs, so no closure needed:
n.sons[bodyPos] = PNode(transform(c, body)) n.sons[bodyPos] = PNode(transform(c, body))
if n.kind == nkMethodDef: methodDef(s, false) return transformProcFin(c, n, s)
return PTransNode(n)
var closure = newNodeI(nkRecList, n.info)
searchForInnerProcs(c, body, s, closure)
if closure.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)
# create environment: # create environment:
var envDesc = newType(tyObject, s) var cap: TCapture = @[]
envDesc.n = closure searchForInnerProcs(c, body, s, cap)
addSon(envDesc, nil) # no super class
var envType = newType(tyRef, s)
addSon(envType, envDesc)
# XXX currently we always do a heap allocation. A simple escape analysis if cap.len == 0:
# could turn the closure into a stack allocation. Later versions will # fast path: no captured variables, so no closure needed:
# implement that. n.sons[bodyPos] = PNode(transform(c, body))
return transformProcFin(c, n, s)
var envType = newType(tyRef, s)
addSon(envType, captureToTuple(cap, s))
# 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 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 # modify any local proc to gain a new parameter; this also creates the
# mapping entries that turn (localProc) into (localProc, env): # mapping entries that turn (localProc) into (localProc, env):
transformInnerProcs(c, body, s, envSym) transformInnerProcs(c, body, s, envSym)
@ -183,19 +215,18 @@ proc transformProc(c: PTransf, n: PNode): PTransNode =
# Careful this transforms the inner procs too! # Careful this transforms the inner procs too!
newBody.add(transform(c, body)) newBody.add(transform(c, body))
n.sons[bodyPos] = newBody.pnode n.sons[bodyPos] = newBody.pnode
if n.kind == nkMethodDef: methodDef(s, false) result = transformProcFin(c, n, s)
result = newBody
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 ## Converts 'prc' into '(thunk, nil)' so that it's compatible with
## a closure. ## a closure.
# XXX we hack around here by generating a 'cast' instead of a proper thunk. # XXX we hack around here by generating a 'cast' instead of a proper thunk.
result = newNodeIT(nkPar, prc.info, closure) result = newNodeIT(nkClosure, prc.info, dest)
var conv = newNodeIT(nkHiddenStdConv, prc.info, closure.sons[0]) var conv = newNodeIT(nkHiddenStdConv, prc.info, dest)
conv.add(emptyNode) conv.add(emptyNode)
conv.add(prc) conv.add(prc)
result.add(conv) 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, errAssertionFailed, errCannotGenerateCodeForX, errXRequiresOneArgument,
errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate, errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate,
errXhasSideEffects, errIteratorExpected, errLetNeedsInit, errXhasSideEffects, errIteratorExpected, errLetNeedsInit,
errThreadvarCannotInit, errWrongSymbolX, errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX,
errUser, errUser,
warnCannotOpenFile, warnCannotOpenFile,
warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit,
@ -325,6 +325,7 @@ const
errLetNeedsInit: "'let' symbol requires an initialization", errLetNeedsInit: "'let' symbol requires an initialization",
errThreadvarCannotInit: "a thread var cannot be initialized explicitly", errThreadvarCannotInit: "a thread var cannot be initialized explicitly",
errWrongSymbolX: "usage of \'$1\' is a user-defined error", errWrongSymbolX: "usage of \'$1\' is a user-defined error",
errIllegalCaptureX: "illegal capture '$1'",
errUser: "$1", errUser: "$1",
warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]", warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]",
warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]", 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 nkHiddenAddr, nkHiddenDeref: result = lsub(n.sons[0])
of nkCommand: result = lsub(n.sons[0]) + lcomma(n, 1) + 1 of nkCommand: result = lsub(n.sons[0]) + lcomma(n, 1) + 1
of nkExprEqExpr, nkAsgn, nkFastAsgn: result = lsons(n) + 3 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: of nkTableConstr:
result = if n.len > 0: lcomma(n) + 2 else: len("{:}") result = if n.len > 0: lcomma(n) + 2 else: len("{:}")
of nkSymChoice: result = lsons(n) + len("()") + sonsLen(n) - 1 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, "|") if i > 0: put(g, tkOpr, "|")
gsub(g, n.sons[i], c) gsub(g, n.sons[i], c)
put(g, tkParRi, ")") put(g, tkParRi, ")")
of nkPar: of nkPar, nkClosure:
put(g, tkParLe, "(") put(g, tkParLe, "(")
gcomma(g, n, c) gcomma(g, n, c)
put(g, tkParRi, ")") put(g, tkParRi, ")")

View file

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

View file

@ -644,8 +644,6 @@ proc semLambda(c: PContext, n: PNode): PNode =
else: else:
s.typ = newTypeS(tyProc, c) s.typ = newTypeS(tyProc, c)
addSon(s.typ, nil) 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: if n.sons[pragmasPos].kind != nkEmpty:
pragma(c, s, n.sons[pragmasPos], lambdaPragmas) pragma(c, s, n.sons[pragmasPos], lambdaPragmas)
s.options = gOptions s.options = gOptions
@ -697,9 +695,6 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
# open for parameters # open for parameters
if proto == nil: if proto == nil:
s.typ.callConv = lastOptionEntry(c).defaultCC 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: # add it here, so that recursive procs are possible:
# -2 because we have a scope open for parameters # -2 because we have a scope open for parameters
if kind in OverloadableSyms: 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]) if n.sons[0].kind != nkEmpty: result = analyse(c, n.sons[0])
else: result = toVoid else: result = toVoid
of nkAsmStmt, nkPragma, nkIteratorDef, nkProcDef, nkMethodDef, of nkAsmStmt, nkPragma, nkIteratorDef, nkProcDef, nkMethodDef,
nkConverterDef, nkMacroDef, nkTemplateDef: nkConverterDef, nkMacroDef, nkTemplateDef, nkLambda:
result = toVoid result = toVoid
of nkExprColonExpr: of nkExprColonExpr:
result = analyse(c, n.sons[1]) result = analyse(c, n.sons[1])

View file

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

View file

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

View file

@ -276,16 +276,17 @@ proc analyseObjectWithTypeField(t: PType): TTypeFieldResult =
var marker = InitIntSet() var marker = InitIntSet()
result = analyseObjectWithTypeFieldAux(t, marker) result = analyseObjectWithTypeFieldAux(t, marker)
proc isGBCRef(t: PType): bool = proc isGCRef(t: PType): bool =
result = t.kind in GcTypeKinds result = t.kind in GcTypeKinds or
(t.kind == tyProc and t.callConv == ccClosure)
proc containsGarbageCollectedRef(typ: PType): bool = proc containsGarbageCollectedRef(typ: PType): bool =
# returns true if typ contains a reference, sequence or string (all the # returns true if typ contains a reference, sequence or string (all the
# things that are garbage-collected) # things that are garbage-collected)
result = searchTypeFor(typ, isGBCRef) result = searchTypeFor(typ, isGCRef)
proc isTyRef(t: PType): bool = 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 = proc containsTyRef*(typ: PType): bool =
# returns true if typ contains a 'ref' # returns true if typ contains a 'ref'
@ -332,6 +333,8 @@ proc canFormAcycleAux(marker: var TIntSet, typ: PType, startId: int): bool =
nil nil
proc canFormAcycle(typ: PType): bool = 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() var marker = InitIntSet()
result = canFormAcycleAux(marker, typ, typ.id) 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 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 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 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 interoperability is worth the cost of the ``if``. Thunk generation would be
possible too, but it's slightly more effort to implement. 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 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. '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: Example code:
@ -492,3 +504,19 @@ Accumulator
echo a() + b() 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 an allowed value for variables of a procedural type. Nimrod uses procedural
types to achieve `functional`:idx: programming techniques. types to achieve `functional`:idx: programming techniques.
Example: Examples:
.. code-block:: nimrod .. code-block:: nimrod
@ -1051,12 +1051,30 @@ Example:
forEach(printItem) # this will NOT work because calling conventions differ 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 A subtle issue with procedural types is that the calling convention of the
procedure influences the type compatibility: procedural types are only 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 Nimrod supports these `calling conventions`:idx:\:
each other:
`stdcall`:idx: `stdcall`:idx:
This the stdcall convention as specified by Microsoft. The generated C 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``. same as ``fastcall``, but only for C compilers that support ``fastcall``.
`closure`:idx: `closure`:idx:
indicates that the procedure expects a context, a closure that needs indicates that the procedure has a hidden implicit parameter
to be passed to the procedure. (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: `syscall`:idx:
The syscall convention is the same as ``__syscall`` in C. It is used for 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`` The rules' purpose is to prevent the case that extending a non-``procvar``
procedure with default parameters breaks client code. 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 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) ## creates a new object of type ``T`` and returns a safe (traced)
## reference to it in ``a``. ## 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)) {. proc new*[T](a: var ref T, finalizer: proc (x: ref T)) {.
magic: "NewFinalize", noSideEffect.} magic: "NewFinalize", noSideEffect.}
## creates a new object of type ``T`` and returns a safe (traced) ## creates a new object of type ``T`` and returns a safe (traced)

View file

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

View file

@ -2,7 +2,7 @@
News 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>`_. 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, 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 ``on`` and ``off`` are now aliases for ``true`` and ``false`` and declared
in the system module. 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 Library Additions