diff options
-rw-r--r-- | compiler/ast.nim | 6 | ||||
-rw-r--r-- | compiler/destroyer.nim | 247 | ||||
-rw-r--r-- | compiler/dfa.nim | 8 | ||||
-rw-r--r-- | compiler/lowerings.nim | 11 | ||||
-rw-r--r-- | compiler/options.nim | 1 | ||||
-rw-r--r-- | compiler/sempass2.nim | 7 | ||||
-rw-r--r-- | compiler/semstmts.nim | 13 | ||||
-rw-r--r-- | compiler/transf.nim | 6 |
8 files changed, 178 insertions, 121 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 97ff4b593..a6f774790 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -871,7 +871,8 @@ type # mean that there is no destructor. # see instantiateDestructor in semdestruct.nim deepCopy*: PSym # overriden 'deepCopy' operation - assignment*: PSym # overriden '=' operator + assignment*: PSym # overriden '=' operation + sink*: PSym # overriden '=sink' operation methods*: seq[(int,PSym)] # attached methods size*: BiggestInt # the size of the type in bytes # -1 means that the size is unkwown @@ -1047,6 +1048,8 @@ proc newNode*(kind: TNodeKind): PNode = proc newTree*(kind: TNodeKind; children: varargs[PNode]): PNode = result = newNode(kind) + if children.len > 0: + result.info = children[0].info result.sons = @children proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode = @@ -1290,6 +1293,7 @@ proc assignType*(dest, src: PType) = dest.align = src.align dest.destructor = src.destructor dest.deepCopy = src.deepCopy + dest.sink = src.sink dest.assignment = src.assignment dest.lockLevel = src.lockLevel # this fixes 'type TLock = TSysLock': diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim index e7ff00bb9..db26c5366 100644 --- a/compiler/destroyer.nim +++ b/compiler/destroyer.nim @@ -76,7 +76,10 @@ ## inefficiencies. A better strategy is to collect all the temporaries ## in a single object that we put into a single try-finally that ## surrounds the proc body. This means the code stays quite efficient -## when compiled to C. +## when compiled to C. In fact, we do the same for variables, so +## destructors are called when the proc returns, not at scope exit! +## This makes certains idioms easier to support. (Taking the slice +## of a temporary object.) ## ## foo(bar(X(), Y())) ## X and Y get destroyed after bar completes: @@ -94,103 +97,17 @@ import template hasDestructor(t: PType): bool = tfHasAsgn in t.flags -when false: - type - VarInfo = object - hasInitValue: bool - addrTaken: bool - assigned: int # we don't care about the 'var' vs 'let' - # distinction; it's an optimization pass - read: int - scope: int # the scope the variable is declared in - - Con = object - t: Table[int, VarInfo] - owner: PSym - scope: int - - const - InterestingSyms = {skVar, skResult} - - proc collectData(c: var Con; n: PNode) - - proc collectDef(c: var Con; n: PNode; hasInitValue: bool) = - if n.kind == nkSym: - c.t[n.sym.id] = VarInfo(hasInitValue: hasInitValue, - addrTaken: false, assigned: 0, read: 0, - scope: scope) - - proc collectVarSection(c: var Con; n: PNode) = - for a in n: - if a.kind == nkCommentStmt: continue - if a.kind == nkVarTuple: - collectData(c, a.lastSon) - for i in 0 .. a.len-3: collectDef(c, a[i], a.lastSon != nil) - else: - collectData(c, a.lastSon) - if a.lastSon.kind != nkEmpty: - collectDef(c, a.sons[0], a.lastSon != nil) - - proc collectData(c: var Con; n: PNode) = - case n.kind - of nkAsgn, nkFastAsgn: - if n[0].kind == nkSym and (let s = n[0].sym; s.owner == c.owner and - s.kind in InterestingSyms): - inc c.t[s.id].assigned - collectData(c, n[1]) - of nkSym: - if (let s = n[0].sym; s.owner == c.owner and - s.kind in InterestingSyms): - inc c.t[s.id].read - of nkAddr, nkHiddenAddr: - var n = n[0] - while n.kind == nkBracketExpr: n = n[0] - if (let s = n[0].sym; s.owner == c.owner and - s.kind in InterestingSyms): - c.t[s.id].addrTaken = true - - of nkCallKinds: - if n.sons[0].kind == nkSym: - let s = n.sons[0].sym - if s.magic != mNone: - genMagic(c, n, s.magic) - else: - genCall(c, n) - else: - genCall(c, n) - of nkCharLit..nkNilLit, nkIdent: discard - of nkDotExpr, nkCheckedFieldExpr, nkBracketExpr, - nkDerefExpr, nkHiddenDeref: - collectData(c, n[0]) - of nkIfStmt, nkIfExpr: genIf(c, n) - of nkWhenStmt: - # This is "when nimvm" node. Chose the first branch. - collectData(c, n.sons[0].sons[1]) - of nkCaseStmt: genCase(c, n) - of nkWhileStmt: genWhile(c, n) - of nkBlockExpr, nkBlockStmt: genBlock(c, n) - of nkReturnStmt: genReturn(c, n) - of nkRaiseStmt: genRaise(c, n) - of nkBreakStmt: genBreak(c, n) - of nkTryStmt: genTry(c, n) - of nkStmtList, nkStmtListExpr, nkChckRangeF, nkChckRange64, nkChckRange, - nkBracket, nkCurly, nkPar, nkClosure, nkObjConstr: - for x in n: collectData(c, x) - of nkPragmaBlock: collectData(c, n.lastSon) - of nkDiscardStmt: collectData(c, n.sons[0]) - of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkExprColonExpr, nkExprEqExpr, - nkCast: - collectData(c, n.sons[1]) - of nkObjDownConv, nkStringToCString, nkCStringToString: - collectData(c, n.sons[0]) - of nkVarSection, nkLetSection: collectVarSection(c, n) - else: discard +const + InterestingSyms = {skVar, skResult, skLet} type Con = object owner: PSym g: ControlFlowGraph - tmps: PType + jumpTargets: IntSet + tmpObj: PType + tmp: PSym + destroys, topLevelVars: PNode proc isHarmlessVar*(s: PSym; c: Con): bool = # 's' is harmless if it used only once and its @@ -224,29 +141,151 @@ proc isHarmlessVar*(s: PSym; c: Con): bool = # L3 # # So this analysis is for now overly conservative, but correct. - discard + var defsite = -1 + var usages = 0 + for i in 0..<c.g.len: + case c.g[i].kind + of def: + if c.g[i].sym == s: + if defsite < 0: defsite = i + else: return false + of use: + if c.g[i].sym == s: + if defsite < 0: return false + for j in defsite .. i: + # not within the same basic block? + if j in c.jumpTargets: return false + # if we want to die after the first 'use': + if usages > 1: return false + inc usages + of useWithinCall: + if c.g[i].sym == s: return false + of goto, fork: + discard "we do not perform an abstract interpretation yet" template interestingSym(s: PSym): bool = - s.owner == owner and s.kind in InterestingSyms and hasDestructor(s.typ) + s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ) + +proc genSink(t: PType; dest: PNode): PNode = + let op = if t.sink != nil: t.sink else: t.assignment + assert op != nil + result = newTree(nkCall, newSymNode(op), newTree(nkHiddenAddr, dest)) + +proc genCopy(t: PType; dest: PNode): PNode = + assert t.assignment != nil + result = newTree(nkCall, newSymNode(t.assignment), newTree(nkHiddenAddr, dest)) + +proc genDestroy(t: PType; dest: PNode): PNode = + assert t.destructor != nil + result = newTree(nkCall, newSymNode(t.destructor), newTree(nkHiddenAddr, dest)) + +proc moveOrCopy(dest, ri: PNode; c: var Con): PNode = + if ri.kind in nkCallKinds: + result = genSink(ri.typ, dest) + elif ri.kind == nkSym and isHarmlessVar(ri.sym, c): + result = genSink(ri.typ, dest) + else: + result = genCopy(ri.typ, dest) + +proc addTopVar(c: var Con; v: PNode) = + c.topLevelVars.add newTree(nkIdentDefs, v, emptyNode) proc p(n, parent: PNode; c: var Con) = + template recurse(n, dest) = + let x = dest + for i in 0..<n.safeLen: + p(n[i], x, c) + parent.add x + case n.kind of nkVarSection, nkLetSection: discard "transform; var x = y to var x; x op y where op is a move or copy" + var stmtList = newNodeI(nkStmtList, n.info) + + for i in 0..<n.len: + let it = n[i] + let L = it.len-1 + let ri = it[L] + if it.kind == nkVarTuple and hasDestructor(ri.typ): + let x = lowerTupleUnpacking(it, c.owner) + p(x, stmtList, c) + elif it.kind == nkIdentDefs and hasDestructor(it[0].typ): + it.sons[L] = emptyNode + for j in 0..L-1: + let v = it[j] + doAssert v.kind == nkSym + # move the variable declaration to the top of the frame: + c.addTopVar v + # make sure it's destroyed at the end of the proc: + c.destroys.add genDestroy(v.typ, v) + if ri.kind != nkEmpty: + let r = moveOrCopy(v, ri, c) + recurse(ri, r) + stmtList.add r + else: + # keep it, but transform 'ri': + var varSection = copyNode(n) + var itCopy = copyNode(it) + for j in 0..L-1: + itCopy.add it[j] + p(ri, itCopy, c) + varSection.add itCopy + stmtList.add varSection + parent.add stmtList of nkCallKinds: if n.typ != nil and hasDestructor(n.typ): discard "produce temp creation" + let stmtList = newNodeIT(nkStmtListExpr, n.info, n.typ) + let f = newSym(skField, getIdent(":d" & $c.tmpObj.n.len), c.owner, n.info) + rawAddField c.tmpObj, f + var m = genSink(n.typ, rawDirectAccess(c.tmp, f)) + recurse(n, m) + stmtList.add m + stmtList.add rawDirectAccess(c.tmp, f) + parent.add stmtList + c.destroys.add genDestroy(n.typ, rawDirectAccess(c.tmp, f)) + else: + recurse(n, copyNode(n)) of nkAsgn, nkFastAsgn: if n[0].kind == nkSym and interestingSym(n[0].sym): discard "use move or assignment" + let ri = n[1] + let r = moveOrCopy(n[0], ri, c) + # fortunately this skips the nkCall which we do not want to transform + # to a temp here! + recurse(ri, r) + parent.add r + else: + recurse(n, copyNode(n)) + of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, + nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef: + parent.add n else: - for i in 0..<n.len: - p(n[i], n, c) - -proc injectDestructorCalls*(owner: PSym; n: PNode; - disableExceptions = false): PNode = - when false: - var c = Con(t: initTable[int, VarInfo](), owner: owner) - collectData(c, n) - var allTemps = createObj(owner, n.info) + recurse(n, copyNode(n)) + +proc injectDestructorCalls*(owner: PSym; n: PNode): PNode = + var c: Con + c.owner = owner + c.tmp = newSym(skTemp, getIdent":d", owner, n.info) + c.tmpObj = createObj(owner, n.info) + c.tmp.typ = c.tmpObj + c.destroys = newNodeI(nkStmtList, n.info) + c.topLevelVars = newNodeI(nkVarSection, n.info) let cfg = constructCfg(owner, n) + shallowCopy(c.g, cfg) + c.jumpTargets = initIntSet() + for i in 0..<c.g.len: + if c.g[i].kind in {goto, fork}: + c.jumpTargets.incl(i+c.g[i].dest) + var stmtList = newNodeI(nkStmtList, n.info) + for i in 0..<n.len: + p(n[i], stmtList, c) + if c.tmp.typ.n.len > 0: + c.addTopVar(newSymNode c.tmp) + result = newNodeI(nkStmtList, n.info) + if c.topLevelVars.len > 0: + result.add c.topLevelVars + if c.destroys.len > 0: + result.add newTryFinally(stmtList, c.destroys) + else: + result.add stmtList diff --git a/compiler/dfa.nim b/compiler/dfa.nim index dd9dd4c79..ca1849a3c 100644 --- a/compiler/dfa.nim +++ b/compiler/dfa.nim @@ -235,14 +235,14 @@ proc genTry(c: var Con; n: PNode) = proc genRaise(c: var Con; n: PNode) = gen(c, n.sons[0]) - c.code.add Instr(n: n, kind: goto, dest: high(int)) + c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len) proc genReturn(c: var Con; n: PNode) = if n.sons[0].kind != nkEmpty: gen(c, n.sons[0]) - c.code.add Instr(n: n, kind: goto, dest: high(int)) + c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len) const - InterestingSyms = {skVar, skResult} + InterestingSyms = {skVar, skResult, skLet} proc genUse(c: var Con; n: PNode) = var n = n @@ -279,7 +279,7 @@ proc genMagic(c: var Con; n: PNode; m: TMagic) = for i in 2..<n.len: gen(c, n[i]) of mExit: genCall(c, n) - c.code.add Instr(n: n, kind: goto, dest: high(int)) + c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len) else: genCall(c, n) diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim index 033472c07..f895e887c 100644 --- a/compiler/lowerings.nim +++ b/compiler/lowerings.nim @@ -70,6 +70,9 @@ proc newTupleAccessRaw*(tup: PNode, i: int): PNode = lit.intVal = i addSon(result, lit) +proc newTryFinally*(body, final: PNode): PNode = + result = newTree(nkTryStmt, body, newTree(nkFinally, final)) + proc lowerTupleUnpackingForAsgn*(n: PNode; owner: PSym): PNode = let value = n.lastSon result = newNodeI(nkStmtList, n.info) @@ -139,6 +142,14 @@ proc rawIndirectAccess*(a: PNode; field: PSym; info: TLineInfo): PNode = addSon(result, newSymNode(field)) result.typ = field.typ +proc rawDirectAccess*(obj, field: PSym): PNode = + # returns a.field as a node + assert field.kind == skField + result = newNodeI(nkDotExpr, field.info) + addSon(result, newSymNode obj) + addSon(result, newSymNode field) + result.typ = field.typ + proc lookupInRecord(n: PNode, id: int): PSym = result = nil case n.kind diff --git a/compiler/options.nim b/compiler/options.nim index 4888cc6ba..984ef4859 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -14,6 +14,7 @@ const hasTinyCBackend* = defined(tinyc) useEffectSystem* = true useWriteTracking* = false + newDestructors* = true hasFFI* = defined(useFFI) newScopeForIf* = true useCaas* = not defined(noCaas) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 17bdf0902..9ce317f80 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -982,9 +982,10 @@ proc trackProc*(s: PSym, body: PNode) = message(s.info, warnLockLevel, "declared lock level is $1, but real lock level is $2" % [$s.typ.lockLevel, $t.maxLockLevel]) - if s.kind == skFunc: - when defined(dfa): dataflowAnalysis(s, body) - trackWrites(s, body) + when false: + if s.kind == skFunc: + when defined(dfa): dataflowAnalysis(s, body) + trackWrites(s, body) proc trackTopLevelStmt*(module: PSym; n: PNode) = if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef, diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index a83de6d27..70e258bac 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -399,7 +399,7 @@ proc addToVarSection(c: PContext; result: var PNode; orig, identDefs: PNode) = # in order for this transformation to be correct. let L = identDefs.len let value = identDefs[L-1] - if value.typ != nil and tfHasAsgn in value.typ.flags: + if value.typ != nil and tfHasAsgn in value.typ.flags and c.p.owner.kind != skFunc: # the spec says we need to rewrite 'var x = T()' to 'var x: T; x = T()': identDefs.sons[L-1] = emptyNode if result.kind != nkStmtList: @@ -1303,7 +1303,7 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = localError(n.info, errGenerated, "signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T") incl(s.flags, sfUsed) - of "=": + of "=", "=sink": if s.magic == mAsgn: return incl(s.flags, sfUsed) let t = s.typ @@ -1321,14 +1321,15 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = objB = objB.sons[0] else: break if obj.kind in {tyObject, tyDistinct} and sameType(obj, objB): - if obj.assignment.isNil: - obj.assignment = s + let opr = if s.name.s == "=": addr(obj.assignment) else: addr(obj.sink) + if opr[].isNil: + opr[] = s else: localError(n.info, errGenerated, - "cannot bind another '=' to: " & typeToString(obj)) + "cannot bind another '" & s.name.s & "' to: " & typeToString(obj)) return localError(n.info, errGenerated, - "signature for '=' must be proc[T: object](x: var T; y: T)") + "signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)") else: if sfOverriden in s.flags: localError(n.info, errGenerated, diff --git a/compiler/transf.nim b/compiler/transf.nim index f1ee49a54..f5ea8feb9 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -21,9 +21,7 @@ import intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os, idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread, - lambdalifting, sempass2, lowerings, lookups - -# implementation + lambdalifting, sempass2, lowerings, lookups, destroyer type PTransNode* = distinct PNode @@ -974,6 +972,8 @@ proc transformBody*(module: PSym, n: PNode, prc: PSym): PNode = #result = liftLambdas(prc, result) incl(result.flags, nfTransf) when useEffectSystem: trackProc(prc, result) + if prc.kind == skFunc: + result = injectDestructorCalls(prc, n) #if prc.name.s == "testbody": # echo renderTree(result) |