initial version of the new untested destructor pass

This commit is contained in:
Andreas Rumpf 2017-10-12 11:32:29 +02:00
commit ec253ada72
8 changed files with 177 additions and 120 deletions

View file

@ -871,7 +871,8 @@ type
# mean that there is no destructor. # mean that there is no destructor.
# see instantiateDestructor in semdestruct.nim # see instantiateDestructor in semdestruct.nim
deepCopy*: PSym # overriden 'deepCopy' operation deepCopy*: PSym # overriden 'deepCopy' operation
assignment*: PSym # overriden '=' operator assignment*: PSym # overriden '=' operation
sink*: PSym # overriden '=sink' operation
methods*: seq[(int,PSym)] # attached methods methods*: seq[(int,PSym)] # attached methods
size*: BiggestInt # the size of the type in bytes size*: BiggestInt # the size of the type in bytes
# -1 means that the size is unkwown # -1 means that the size is unkwown
@ -1047,6 +1048,8 @@ proc newNode*(kind: TNodeKind): PNode =
proc newTree*(kind: TNodeKind; children: varargs[PNode]): PNode = proc newTree*(kind: TNodeKind; children: varargs[PNode]): PNode =
result = newNode(kind) result = newNode(kind)
if children.len > 0:
result.info = children[0].info
result.sons = @children result.sons = @children
proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode = proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode =
@ -1290,6 +1293,7 @@ proc assignType*(dest, src: PType) =
dest.align = src.align dest.align = src.align
dest.destructor = src.destructor dest.destructor = src.destructor
dest.deepCopy = src.deepCopy dest.deepCopy = src.deepCopy
dest.sink = src.sink
dest.assignment = src.assignment dest.assignment = src.assignment
dest.lockLevel = src.lockLevel dest.lockLevel = src.lockLevel
# this fixes 'type TLock = TSysLock': # this fixes 'type TLock = TSysLock':

View file

@ -76,7 +76,10 @@
## inefficiencies. A better strategy is to collect all the temporaries ## inefficiencies. A better strategy is to collect all the temporaries
## in a single object that we put into a single try-finally that ## in a single object that we put into a single try-finally that
## surrounds the proc body. This means the code stays quite efficient ## 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())) ## foo(bar(X(), Y()))
## X and Y get destroyed after bar completes: ## X and Y get destroyed after bar completes:
@ -94,103 +97,17 @@ import
template hasDestructor(t: PType): bool = tfHasAsgn in t.flags template hasDestructor(t: PType): bool = tfHasAsgn in t.flags
when false: const
type InterestingSyms = {skVar, skResult, skLet}
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
type type
Con = object Con = object
owner: PSym owner: PSym
g: ControlFlowGraph g: ControlFlowGraph
tmps: PType jumpTargets: IntSet
tmpObj: PType
tmp: PSym
destroys, topLevelVars: PNode
proc isHarmlessVar*(s: PSym; c: Con): bool = proc isHarmlessVar*(s: PSym; c: Con): bool =
# 's' is harmless if it used only once and its # 's' is harmless if it used only once and its
@ -224,29 +141,151 @@ proc isHarmlessVar*(s: PSym; c: Con): bool =
# L3 # L3
# #
# So this analysis is for now overly conservative, but correct. # 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 = 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) = 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 case n.kind
of nkVarSection, nkLetSection: of nkVarSection, nkLetSection:
discard "transform; var x = y to var x; x op y where op is a move or copy" 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: of nkCallKinds:
if n.typ != nil and hasDestructor(n.typ): if n.typ != nil and hasDestructor(n.typ):
discard "produce temp creation" 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: of nkAsgn, nkFastAsgn:
if n[0].kind == nkSym and interestingSym(n[0].sym): if n[0].kind == nkSym and interestingSym(n[0].sym):
discard "use move or assignment" 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: else:
for i in 0..<n.len: recurse(n, copyNode(n))
p(n[i], n, c) of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef,
nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
parent.add n
else:
recurse(n, copyNode(n))
proc injectDestructorCalls*(owner: PSym; n: PNode; proc injectDestructorCalls*(owner: PSym; n: PNode): PNode =
disableExceptions = false): PNode = var c: Con
when false: c.owner = owner
var c = Con(t: initTable[int, VarInfo](), owner: owner) c.tmp = newSym(skTemp, getIdent":d", owner, n.info)
collectData(c, n) c.tmpObj = createObj(owner, n.info)
var allTemps = 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) 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

View file

@ -235,14 +235,14 @@ proc genTry(c: var Con; n: PNode) =
proc genRaise(c: var Con; n: PNode) = proc genRaise(c: var Con; n: PNode) =
gen(c, n.sons[0]) 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) = proc genReturn(c: var Con; n: PNode) =
if n.sons[0].kind != nkEmpty: gen(c, n.sons[0]) 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 const
InterestingSyms = {skVar, skResult} InterestingSyms = {skVar, skResult, skLet}
proc genUse(c: var Con; n: PNode) = proc genUse(c: var Con; n: PNode) =
var n = n 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]) for i in 2..<n.len: gen(c, n[i])
of mExit: of mExit:
genCall(c, n) 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: else:
genCall(c, n) genCall(c, n)

View file

@ -70,6 +70,9 @@ proc newTupleAccessRaw*(tup: PNode, i: int): PNode =
lit.intVal = i lit.intVal = i
addSon(result, lit) addSon(result, lit)
proc newTryFinally*(body, final: PNode): PNode =
result = newTree(nkTryStmt, body, newTree(nkFinally, final))
proc lowerTupleUnpackingForAsgn*(n: PNode; owner: PSym): PNode = proc lowerTupleUnpackingForAsgn*(n: PNode; owner: PSym): PNode =
let value = n.lastSon let value = n.lastSon
result = newNodeI(nkStmtList, n.info) result = newNodeI(nkStmtList, n.info)
@ -139,6 +142,14 @@ proc rawIndirectAccess*(a: PNode; field: PSym; info: TLineInfo): PNode =
addSon(result, newSymNode(field)) addSon(result, newSymNode(field))
result.typ = field.typ 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 = proc lookupInRecord(n: PNode, id: int): PSym =
result = nil result = nil
case n.kind case n.kind

View file

@ -14,6 +14,7 @@ const
hasTinyCBackend* = defined(tinyc) hasTinyCBackend* = defined(tinyc)
useEffectSystem* = true useEffectSystem* = true
useWriteTracking* = false useWriteTracking* = false
newDestructors* = true
hasFFI* = defined(useFFI) hasFFI* = defined(useFFI)
newScopeForIf* = true newScopeForIf* = true
useCaas* = not defined(noCaas) useCaas* = not defined(noCaas)

View file

@ -982,6 +982,7 @@ proc trackProc*(s: PSym, body: PNode) =
message(s.info, warnLockLevel, message(s.info, warnLockLevel,
"declared lock level is $1, but real lock level is $2" % "declared lock level is $1, but real lock level is $2" %
[$s.typ.lockLevel, $t.maxLockLevel]) [$s.typ.lockLevel, $t.maxLockLevel])
when false:
if s.kind == skFunc: if s.kind == skFunc:
when defined(dfa): dataflowAnalysis(s, body) when defined(dfa): dataflowAnalysis(s, body)
trackWrites(s, body) trackWrites(s, body)

View file

@ -399,7 +399,7 @@ proc addToVarSection(c: PContext; result: var PNode; orig, identDefs: PNode) =
# in order for this transformation to be correct. # in order for this transformation to be correct.
let L = identDefs.len let L = identDefs.len
let value = identDefs[L-1] 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()': # the spec says we need to rewrite 'var x = T()' to 'var x: T; x = T()':
identDefs.sons[L-1] = emptyNode identDefs.sons[L-1] = emptyNode
if result.kind != nkStmtList: if result.kind != nkStmtList:
@ -1303,7 +1303,7 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
localError(n.info, errGenerated, localError(n.info, errGenerated,
"signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T") "signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T")
incl(s.flags, sfUsed) incl(s.flags, sfUsed)
of "=": of "=", "=sink":
if s.magic == mAsgn: return if s.magic == mAsgn: return
incl(s.flags, sfUsed) incl(s.flags, sfUsed)
let t = s.typ let t = s.typ
@ -1321,14 +1321,15 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
objB = objB.sons[0] objB = objB.sons[0]
else: break else: break
if obj.kind in {tyObject, tyDistinct} and sameType(obj, objB): if obj.kind in {tyObject, tyDistinct} and sameType(obj, objB):
if obj.assignment.isNil: let opr = if s.name.s == "=": addr(obj.assignment) else: addr(obj.sink)
obj.assignment = s if opr[].isNil:
opr[] = s
else: else:
localError(n.info, errGenerated, localError(n.info, errGenerated,
"cannot bind another '=' to: " & typeToString(obj)) "cannot bind another '" & s.name.s & "' to: " & typeToString(obj))
return return
localError(n.info, errGenerated, 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: else:
if sfOverriden in s.flags: if sfOverriden in s.flags:
localError(n.info, errGenerated, localError(n.info, errGenerated,

View file

@ -21,9 +21,7 @@
import import
intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os, intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os,
idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread, idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread,
lambdalifting, sempass2, lowerings, lookups lambdalifting, sempass2, lowerings, lookups, destroyer
# implementation
type type
PTransNode* = distinct PNode PTransNode* = distinct PNode
@ -974,6 +972,8 @@ proc transformBody*(module: PSym, n: PNode, prc: PSym): PNode =
#result = liftLambdas(prc, result) #result = liftLambdas(prc, result)
incl(result.flags, nfTransf) incl(result.flags, nfTransf)
when useEffectSystem: trackProc(prc, result) when useEffectSystem: trackProc(prc, result)
if prc.kind == skFunc:
result = injectDestructorCalls(prc, n)
#if prc.name.s == "testbody": #if prc.name.s == "testbody":
# echo renderTree(result) # echo renderTree(result)