preparations for a new AST transformation pass with the aim of giving us reliable destructors
This commit is contained in:
parent
6e337fe58a
commit
cf01cb1935
2 changed files with 232 additions and 22 deletions
192
compiler/destroyer.nim
Normal file
192
compiler/destroyer.nim
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# The Nim Compiler
|
||||||
|
# (c) Copyright 2017 Andreas Rumpf
|
||||||
|
#
|
||||||
|
# See the file "copying.txt", included in this
|
||||||
|
# distribution, for details about the copyright.
|
||||||
|
#
|
||||||
|
|
||||||
|
## Injects destructor calls into Nim code as well as
|
||||||
|
## an optimizer that optimizes copies to moves. This is implemented as an
|
||||||
|
## AST to AST transformation so that every backend benefits from it.
|
||||||
|
|
||||||
|
## Rules for destructor injections:
|
||||||
|
##
|
||||||
|
## foo(bar(X(), Y()))
|
||||||
|
## X and Y get destroyed after bar completes:
|
||||||
|
##
|
||||||
|
## foo( (tmpX = X(); tmpY = Y(); tmpBar = bar(tmpX, tmpY);
|
||||||
|
## destroy(tmpX); destroy(tmpY);
|
||||||
|
## tmpBar))
|
||||||
|
## destroy(tmpBar)
|
||||||
|
##
|
||||||
|
## var x = f()
|
||||||
|
## body
|
||||||
|
##
|
||||||
|
## is the same as:
|
||||||
|
##
|
||||||
|
## var x;
|
||||||
|
## try:
|
||||||
|
## move(x, f())
|
||||||
|
## finally:
|
||||||
|
## destroy(x)
|
||||||
|
##
|
||||||
|
## But this really just an optimization that tries to avoid to
|
||||||
|
## introduce too many temporaries, the 'destroy' is caused by
|
||||||
|
## the 'f()' call. No! That is not true for 'result = f()'!
|
||||||
|
##
|
||||||
|
## x = y where y is read only once
|
||||||
|
## is the same as: move(x, y)
|
||||||
|
##
|
||||||
|
## We also need to keep in mind here that the number of reads is
|
||||||
|
## control flow dependent:
|
||||||
|
## let x = foo()
|
||||||
|
## while true:
|
||||||
|
## y = x # only one read, but the 2nd iteration will fail!
|
||||||
|
## This also affects recursions! Only usages that do not cross
|
||||||
|
## a loop boundary (scope) and are not used in function calls
|
||||||
|
## are safe.
|
||||||
|
##
|
||||||
|
##
|
||||||
|
## x = f() is the same as: move(x, f())
|
||||||
|
##
|
||||||
|
## x = y
|
||||||
|
## is the same as: copy(x, y)
|
||||||
|
##
|
||||||
|
## Reassignment works under this scheme:
|
||||||
|
## var x = f()
|
||||||
|
## x = y
|
||||||
|
##
|
||||||
|
## is the same as:
|
||||||
|
##
|
||||||
|
## var x;
|
||||||
|
## try:
|
||||||
|
## move(x, f())
|
||||||
|
## copy(x, y)
|
||||||
|
## finally:
|
||||||
|
## destroy(x)
|
||||||
|
##
|
||||||
|
## result = f() must not destroy 'result'!
|
||||||
|
##
|
||||||
|
## The produced temporaries clutter up the code and might lead to
|
||||||
|
## 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.
|
||||||
|
##
|
||||||
|
## foo(bar(X(), Y()))
|
||||||
|
## X and Y get destroyed after bar completes:
|
||||||
|
##
|
||||||
|
## var tmp: object
|
||||||
|
## foo( (move tmp.x, X(); move tmp.y, Y(); tmp.bar = bar(tmpX, tmpY);
|
||||||
|
## tmp.bar))
|
||||||
|
## destroy(tmp.bar)
|
||||||
|
## destroy(tmp.x); destroy(tmp.y)
|
||||||
|
|
||||||
|
|
||||||
|
import
|
||||||
|
intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
|
||||||
|
strutils, options, dfa, lowerings
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
@ -26,13 +26,19 @@
|
||||||
import ast, astalgo, types, intsets, tables, msgs
|
import ast, astalgo, types, intsets, tables, msgs
|
||||||
|
|
||||||
type
|
type
|
||||||
InstrKind = enum
|
InstrKind* = enum
|
||||||
goto, fork, def, use
|
goto, fork, def, use,
|
||||||
Instr = object
|
useWithinCall # this strange special case is used to get more
|
||||||
n: PNode
|
# move optimizations out of regular code
|
||||||
case kind: InstrKind
|
# XXX This is still overly pessimistic in
|
||||||
of def, use: sym: PSym
|
# call(let x = foo; bar(x))
|
||||||
of goto, fork: dest: int
|
Instr* = object
|
||||||
|
n*: PNode
|
||||||
|
case kind*: InstrKind
|
||||||
|
of def, use, useWithinCall: sym*: PSym
|
||||||
|
of goto, fork: dest*: int
|
||||||
|
|
||||||
|
ControlFlowGraph* = seq[Instr]
|
||||||
|
|
||||||
TPosition = distinct int
|
TPosition = distinct int
|
||||||
TBlock = object
|
TBlock = object
|
||||||
|
|
@ -43,40 +49,42 @@ type
|
||||||
undef, value, valueOrUndef
|
undef, value, valueOrUndef
|
||||||
|
|
||||||
Con = object
|
Con = object
|
||||||
code: seq[Instr]
|
code: ControlFlowGraph
|
||||||
|
inCall: int
|
||||||
blocks: seq[TBlock]
|
blocks: seq[TBlock]
|
||||||
|
|
||||||
proc debugInfo(info: TLineInfo): string =
|
proc debugInfo(info: TLineInfo): string =
|
||||||
result = info.toFilename & ":" & $info.line
|
result = info.toFilename & ":" & $info.line
|
||||||
|
|
||||||
proc codeListing(c: Con, result: var string, start=0; last = -1) =
|
proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) =
|
||||||
# for debugging purposes
|
# for debugging purposes
|
||||||
# first iteration: compute all necessary labels:
|
# first iteration: compute all necessary labels:
|
||||||
var jumpTargets = initIntSet()
|
var jumpTargets = initIntSet()
|
||||||
let last = if last < 0: c.code.len-1 else: min(last, c.code.len-1)
|
let last = if last < 0: c.len-1 else: min(last, c.len-1)
|
||||||
for i in start..last:
|
for i in start..last:
|
||||||
if c.code[i].kind in {goto, fork}:
|
if c[i].kind in {goto, fork}:
|
||||||
jumpTargets.incl(i+c.code[i].dest)
|
jumpTargets.incl(i+c[i].dest)
|
||||||
var i = start
|
var i = start
|
||||||
while i <= last:
|
while i <= last:
|
||||||
if i in jumpTargets: result.add("L" & $i & ":\n")
|
if i in jumpTargets: result.add("L" & $i & ":\n")
|
||||||
result.add "\t"
|
result.add "\t"
|
||||||
result.add $c.code[i].kind
|
result.add $c[i].kind
|
||||||
result.add "\t"
|
result.add "\t"
|
||||||
case c.code[i].kind
|
case c[i].kind
|
||||||
of def, use:
|
of def, use, useWithinCall:
|
||||||
result.add c.code[i].sym.name.s
|
result.add c[i].sym.name.s
|
||||||
of goto, fork:
|
of goto, fork:
|
||||||
result.add "L"
|
result.add "L"
|
||||||
result.add c.code[i].dest+i
|
result.add c[i].dest+i
|
||||||
result.add("\t#")
|
result.add("\t#")
|
||||||
result.add(debugInfo(c.code[i].n.info))
|
result.add(debugInfo(c[i].n.info))
|
||||||
result.add("\n")
|
result.add("\n")
|
||||||
inc i
|
inc i
|
||||||
if i in jumpTargets: result.add("L" & $i & ": End\n")
|
if i in jumpTargets: result.add("L" & $i & ": End\n")
|
||||||
|
|
||||||
|
|
||||||
proc echoCfg*(c: Con; start=0; last = -1) {.deprecated.} =
|
proc echoCfg*(c: ControlFlowGraph; start=0; last = -1) {.deprecated.} =
|
||||||
|
## echos the ControlFlowGraph for debugging purposes.
|
||||||
var buf = ""
|
var buf = ""
|
||||||
codeListing(c, buf, start, last)
|
codeListing(c, buf, start, last)
|
||||||
echo buf
|
echo buf
|
||||||
|
|
@ -243,7 +251,10 @@ proc genUse(c: var Con; n: PNode) =
|
||||||
nkAddr, nkHiddenAddr}:
|
nkAddr, nkHiddenAddr}:
|
||||||
n = n[0]
|
n = n[0]
|
||||||
if n.kind == nkSym and n.sym.kind in InterestingSyms:
|
if n.kind == nkSym and n.sym.kind in InterestingSyms:
|
||||||
c.code.add Instr(n: n, kind: use, sym: n.sym)
|
if c.inCall > 0:
|
||||||
|
c.code.add Instr(n: n, kind: useWithinCall, sym: n.sym)
|
||||||
|
else:
|
||||||
|
c.code.add Instr(n: n, kind: use, sym: n.sym)
|
||||||
|
|
||||||
proc genDef(c: var Con; n: PNode) =
|
proc genDef(c: var Con; n: PNode) =
|
||||||
if n.kind == nkSym and n.sym.kind in InterestingSyms:
|
if n.kind == nkSym and n.sym.kind in InterestingSyms:
|
||||||
|
|
@ -253,10 +264,12 @@ proc genCall(c: var Con; n: PNode) =
|
||||||
gen(c, n[0])
|
gen(c, n[0])
|
||||||
var t = n[0].typ
|
var t = n[0].typ
|
||||||
if t != nil: t = t.skipTypes(abstractInst)
|
if t != nil: t = t.skipTypes(abstractInst)
|
||||||
|
inc c.inCall
|
||||||
for i in 1..<n.len:
|
for i in 1..<n.len:
|
||||||
gen(c, n[i])
|
gen(c, n[i])
|
||||||
if t != nil and i < t.len and t.sons[i].kind == tyVar:
|
if t != nil and i < t.len and t.sons[i].kind == tyVar:
|
||||||
genDef(c, n[i])
|
genDef(c, n[i])
|
||||||
|
dec c.inCall
|
||||||
|
|
||||||
proc genMagic(c: var Con; n: PNode; m: TMagic) =
|
proc genMagic(c: var Con; n: PNode; m: TMagic) =
|
||||||
case m
|
case m
|
||||||
|
|
@ -356,7 +369,7 @@ proc dfa(code: seq[Instr]) =
|
||||||
var sid = -1
|
var sid = -1
|
||||||
case code[pc].kind
|
case code[pc].kind
|
||||||
of goto, fork: discard
|
of goto, fork: discard
|
||||||
of use:
|
of use, useWithinCall:
|
||||||
let sym = code[pc].sym
|
let sym = code[pc].sym
|
||||||
if s[pc].contains(sym.id):
|
if s[pc].contains(sym.id):
|
||||||
localError(code[pc].n.info, "variable read before initialized: " & sym.name.s)
|
localError(code[pc].n.info, "variable read before initialized: " & sym.name.s)
|
||||||
|
|
@ -417,5 +430,10 @@ proc dfa(code: seq[Instr]) =
|
||||||
proc dataflowAnalysis*(s: PSym; body: PNode) =
|
proc dataflowAnalysis*(s: PSym; body: PNode) =
|
||||||
var c = Con(code: @[], blocks: @[])
|
var c = Con(code: @[], blocks: @[])
|
||||||
gen(c, body)
|
gen(c, body)
|
||||||
echoCfg(c)
|
#echoCfg(c.code)
|
||||||
dfa(c.code)
|
dfa(c.code)
|
||||||
|
|
||||||
|
proc constructCfg*(s: PSym; body: PNode): ControlFlowGraph =
|
||||||
|
## constructs a control flow graph for ``body``.
|
||||||
|
var c = Con(code: @[], blocks: @[])
|
||||||
|
shallowCopy(result, c.code)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue