diff options
Diffstat (limited to 'compiler')
-rwxr-xr-x | compiler/ast.nim | 10 | ||||
-rw-r--r-- | compiler/ccgcalls.nim | 71 | ||||
-rwxr-xr-x | compiler/ccgexprs.nim | 30 | ||||
-rwxr-xr-x | compiler/ccgstmts.nim | 19 | ||||
-rwxr-xr-x | compiler/ccgtypes.nim | 32 | ||||
-rwxr-xr-x | compiler/cgen.nim | 33 | ||||
-rw-r--r-- | compiler/lambdalifting.nim | 183 | ||||
-rwxr-xr-x | compiler/msgs.nim | 3 | ||||
-rwxr-xr-x | compiler/renderer.nim | 4 | ||||
-rwxr-xr-x | compiler/semexprs.nim | 14 | ||||
-rwxr-xr-x | compiler/semstmts.nim | 5 | ||||
-rwxr-xr-x | compiler/semthreads.nim | 2 | ||||
-rwxr-xr-x | compiler/transf.nim | 10 | ||||
-rwxr-xr-x | compiler/trees.nim | 2 | ||||
-rwxr-xr-x | compiler/types.nim | 11 |
15 files changed, 303 insertions, 126 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 45b1ebfd0..65d7bccf1 100755 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -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] diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index b468c4dee..d6e2d67b7 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -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: diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index ef412d753..6a8156220 100755 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -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) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index ad1b5646f..8e7b05c0f 100755 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -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) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index a2faf3cbf..c7002954a 100755 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -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) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index d60f11639..9784b21bb 100755 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -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: diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index 8aa490bbb..4a2a8997c 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -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 + +type + TCapture = seq[PSym] -proc Incl(container: PNode, s: PSym) = - for x in container: - if x.sym.id == s.id: return - container.add(newSymNode(s)) +proc Capture(cap: var TCapture, s: PSym) = + for x in cap: + if x.name.id == s.name.id: return + cap.add(s) -proc gatherVars(c: PTransf, n: PNode, owner: PSym, container: PNode) = - # gather used vars for closure generation +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))) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index e1deb6e35..43ab7a192 100755 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -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]", diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 6fb5da2a9..dd62363e1 100755 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -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, ")") diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 32244af85..24161e85e 100755 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -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") diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index ab8081031..424950056 100755 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -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: diff --git a/compiler/semthreads.nim b/compiler/semthreads.nim index 59ff4e652..f542e04d6 100755 --- a/compiler/semthreads.nim +++ b/compiler/semthreads.nim @@ -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]) diff --git a/compiler/transf.nim b/compiler/transf.nim index 51188928c..be8f7aeb5 100755 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -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 diff --git a/compiler/trees.nim b/compiler/trees.nim index f393bfc66..57acfba8a 100755 --- a/compiler/trees.nim +++ b/compiler/trees.nim @@ -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 diff --git a/compiler/types.nim b/compiler/types.nim index 42acae831..d2359faae 100755 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -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) |