Rework DFA traversal (#18016)
* enable using dbg: without a context * Optimally joining first write/last read analysis * Add test for #18002 * potLastReads -> potentialLastReads
This commit is contained in:
parent
99788ee504
commit
3614523664
2 changed files with 133 additions and 109 deletions
|
|
@ -46,6 +46,13 @@ type
|
||||||
sinkArg
|
sinkArg
|
||||||
|
|
||||||
const toDebug {.strdefine.} = ""
|
const toDebug {.strdefine.} = ""
|
||||||
|
when toDebug.len > 0:
|
||||||
|
var shouldDebug = false
|
||||||
|
|
||||||
|
template dbg(body) =
|
||||||
|
when toDebug.len > 0:
|
||||||
|
if shouldDebug:
|
||||||
|
body
|
||||||
|
|
||||||
proc hasDestructor(c: Con; t: PType): bool {.inline.} =
|
proc hasDestructor(c: Con; t: PType): bool {.inline.} =
|
||||||
result = ast.hasDestructor(t)
|
result = ast.hasDestructor(t)
|
||||||
|
|
@ -54,11 +61,6 @@ proc hasDestructor(c: Con; t: PType): bool {.inline.} =
|
||||||
if not result and c.graph.config.selectedGC in {gcArc, gcOrc}:
|
if not result and c.graph.config.selectedGC in {gcArc, gcOrc}:
|
||||||
assert(not containsGarbageCollectedRef(t))
|
assert(not containsGarbageCollectedRef(t))
|
||||||
|
|
||||||
template dbg(body) =
|
|
||||||
when toDebug.len > 0:
|
|
||||||
if c.owner.name.s == toDebug or toDebug == "always":
|
|
||||||
body
|
|
||||||
|
|
||||||
proc getTemp(c: var Con; s: var Scope; typ: PType; info: TLineInfo): PNode =
|
proc getTemp(c: var Con; s: var Scope; typ: PType; info: TLineInfo): PNode =
|
||||||
let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), nextSymId c.idgen, c.owner, info)
|
let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), nextSymId c.idgen, c.owner, info)
|
||||||
sym.typ = typ
|
sym.typ = typ
|
||||||
|
|
@ -75,106 +77,111 @@ import sets, hashes, tables
|
||||||
|
|
||||||
proc hash(n: PNode): Hash = hash(cast[pointer](n))
|
proc hash(n: PNode): Hash = hash(cast[pointer](n))
|
||||||
|
|
||||||
type AliasCache = Table[(PNode, PNode), AliasKind]
|
proc aliasesCached(cache: var Table[(PNode, PNode), AliasKind], obj, field: PNode): AliasKind =
|
||||||
proc aliasesCached(cache: var AliasCache, obj, field: PNode): AliasKind =
|
|
||||||
let key = (obj, field)
|
let key = (obj, field)
|
||||||
if not cache.hasKey(key):
|
if not cache.hasKey(key):
|
||||||
cache[key] = aliases(obj, field)
|
cache[key] = aliases(obj, field)
|
||||||
cache[key]
|
cache[key]
|
||||||
|
|
||||||
proc collectLastReads(cfg: ControlFlowGraph; cache: var AliasCache, lastReads, potLastReads: var IntSet; pc: var int, until: int) =
|
type
|
||||||
template aliasesCached(obj, field: PNode): untyped =
|
State = ref object
|
||||||
|
lastReads: IntSet
|
||||||
|
potentialLastReads: IntSet
|
||||||
|
notLastReads: IntSet
|
||||||
|
alreadySeen: HashSet[PNode]
|
||||||
|
|
||||||
|
proc preprocessCfg(cfg: var ControlFlowGraph) =
|
||||||
|
for i in 0..<cfg.len:
|
||||||
|
if cfg[i].kind in {goto, fork} and i + cfg[i].dest > cfg.len:
|
||||||
|
cfg[i].dest = cfg.len - i
|
||||||
|
|
||||||
|
proc mergeStates(a: var State, b: sink State) =
|
||||||
|
# Inplace for performance:
|
||||||
|
# lastReads = a.lastReads + b.lastReads
|
||||||
|
# potentialLastReads = (a.potentialLastReads + b.potentialLastReads) - (a.notLastReads + b.notLastReads)
|
||||||
|
# notLastReads = a.notLastReads + b.notLastReads
|
||||||
|
# alreadySeen = a.alreadySeen + b.alreadySeen
|
||||||
|
# b is never nil
|
||||||
|
if a == nil:
|
||||||
|
a = b
|
||||||
|
else:
|
||||||
|
a.lastReads.incl b.lastReads
|
||||||
|
a.potentialLastReads.incl b.potentialLastReads
|
||||||
|
a.potentialLastReads.excl a.notLastReads
|
||||||
|
a.potentialLastReads.excl b.notLastReads
|
||||||
|
a.notLastReads.incl b.notLastReads
|
||||||
|
a.alreadySeen.incl b.alreadySeen
|
||||||
|
|
||||||
|
proc computeLastReadsAndFirstWrites(cfg: ControlFlowGraph) =
|
||||||
|
var cache = initTable[(PNode, PNode), AliasKind]()
|
||||||
|
template aliasesCached(obj, field: PNode): AliasKind =
|
||||||
aliasesCached(cache, obj, field)
|
aliasesCached(cache, obj, field)
|
||||||
while pc < until:
|
|
||||||
case cfg[pc].kind
|
|
||||||
of def:
|
|
||||||
let potLastReadsCopy = potLastReads
|
|
||||||
for r in potLastReadsCopy:
|
|
||||||
if cfg[pc].n.aliasesCached(cfg[r].n) == yes:
|
|
||||||
# the path leads to a redefinition of 's' --> sink 's'.
|
|
||||||
lastReads.incl r
|
|
||||||
potLastReads.excl r
|
|
||||||
elif cfg[r].n.aliasesCached(cfg[pc].n) != no:
|
|
||||||
# only partially writes to 's' --> can't sink 's', so this def reads 's'
|
|
||||||
# or maybe writes to 's' --> can't sink 's'
|
|
||||||
cfg[r].n.comment = '\n' & $pc
|
|
||||||
potLastReads.excl r
|
|
||||||
|
|
||||||
inc pc
|
var cfg = cfg
|
||||||
of use:
|
preprocessCfg(cfg)
|
||||||
let potLastReadsCopy = potLastReads
|
|
||||||
for r in potLastReadsCopy:
|
|
||||||
if cfg[pc].n.aliasesCached(cfg[r].n) != no or cfg[r].n.aliasesCached(cfg[pc].n) != no:
|
|
||||||
cfg[r].n.comment = '\n' & $pc
|
|
||||||
potLastReads.excl r
|
|
||||||
|
|
||||||
potLastReads.incl pc
|
var states = newSeq[State](cfg.len + 1)
|
||||||
|
states[0] = State()
|
||||||
|
|
||||||
inc pc
|
for pc in 0..<cfg.len:
|
||||||
of goto:
|
template state: State = states[pc]
|
||||||
pc += cfg[pc].dest
|
if state != nil:
|
||||||
of fork:
|
case cfg[pc].kind
|
||||||
var variantA = pc + 1
|
of def:
|
||||||
var variantB = pc + cfg[pc].dest
|
var potentialLastReadsCopy = state.potentialLastReads
|
||||||
var potLastReadsA, potLastReadsB = potLastReads
|
for r in potentialLastReadsCopy:
|
||||||
var lastReadsA, lastReadsB: IntSet
|
if cfg[pc].n.aliasesCached(cfg[r].n) == yes:
|
||||||
while variantA != variantB and max(variantA, variantB) < cfg.len and min(variantA, variantB) < until:
|
# the path leads to a redefinition of 's' --> sink 's'.
|
||||||
if variantA < variantB:
|
state.lastReads.incl r
|
||||||
collectLastReads(cfg, cache, lastReadsA, potLastReadsA, variantA, min(variantB, until))
|
state.potentialLastReads.excl r
|
||||||
else:
|
elif cfg[r].n.aliasesCached(cfg[pc].n) != no:
|
||||||
collectLastReads(cfg, cache, lastReadsB, potLastReadsB, variantB, min(variantA, until))
|
# only partially writes to 's' --> can't sink 's', so this def reads 's'
|
||||||
|
# or maybe writes to 's' --> can't sink 's'
|
||||||
|
cfg[r].n.comment = '\n' & $pc
|
||||||
|
state.potentialLastReads.excl r
|
||||||
|
state.notLastReads.incl r
|
||||||
|
|
||||||
# Add those last reads that were turned into last reads on both branches
|
var alreadySeenThisNode = false
|
||||||
lastReads.incl lastReadsA * lastReadsB
|
for s in state.alreadySeen:
|
||||||
# Add those last reads that were turned into last reads on only one branch,
|
if cfg[pc].n.aliasesCached(s) != no or s.aliasesCached(cfg[pc].n) != no:
|
||||||
# but where the read operation itself also belongs to only that branch
|
alreadySeenThisNode = true; break
|
||||||
lastReads.incl (lastReadsA + lastReadsB) - potLastReads
|
if alreadySeenThisNode: cfg[pc].n.flags.excl nfFirstWrite
|
||||||
|
else: cfg[pc].n.flags.incl nfFirstWrite
|
||||||
|
|
||||||
let oldPotLastReads = potLastReads
|
state.alreadySeen.incl cfg[pc].n
|
||||||
potLastReads = initIntSet()
|
|
||||||
|
|
||||||
potLastReads.incl potLastReadsA + potLastReadsB
|
mergeStates(states[pc + 1], move(states[pc]))
|
||||||
|
of use:
|
||||||
|
var potentialLastReadsCopy = state.potentialLastReads
|
||||||
|
for r in potentialLastReadsCopy:
|
||||||
|
if cfg[pc].n.aliasesCached(cfg[r].n) != no or cfg[r].n.aliasesCached(cfg[pc].n) != no:
|
||||||
|
cfg[r].n.comment = '\n' & $pc
|
||||||
|
state.potentialLastReads.excl r
|
||||||
|
state.notLastReads.incl r
|
||||||
|
|
||||||
# Remove potential last reads that were invalidated in a branch,
|
state.potentialLastReads.incl pc
|
||||||
# but don't remove those which were turned into last reads on that branch
|
|
||||||
potLastReads.excl ((oldPotLastReads - potLastReadsA) - lastReadsA)
|
|
||||||
potLastReads.excl ((oldPotLastReads - potLastReadsB) - lastReadsB)
|
|
||||||
|
|
||||||
pc = min(variantA, variantB)
|
state.alreadySeen.incl cfg[pc].n
|
||||||
|
|
||||||
proc collectFirstWrites(cfg: ControlFlowGraph; alreadySeen: var HashSet[PNode]; pc: var int, until: int) =
|
mergeStates(states[pc + 1], move(states[pc]))
|
||||||
while pc < until:
|
of goto:
|
||||||
case cfg[pc].kind
|
mergeStates(states[pc + cfg[pc].dest], move(states[pc]))
|
||||||
of def:
|
of fork:
|
||||||
var alreadySeenThisNode = false
|
var copy = State()
|
||||||
for s in alreadySeen:
|
copy[] = states[pc][]
|
||||||
if cfg[pc].n.aliases(s) != no or s.aliases(cfg[pc].n) != no:
|
mergeStates(states[pc + cfg[pc].dest], copy)
|
||||||
alreadySeenThisNode = true; break
|
mergeStates(states[pc + 1], move(states[pc]))
|
||||||
if alreadySeenThisNode: cfg[pc].n.flags.excl nfFirstWrite
|
|
||||||
else: cfg[pc].n.flags.incl nfFirstWrite
|
|
||||||
|
|
||||||
alreadySeen.incl cfg[pc].n
|
let lastReads = (states[^1].lastReads + states[^1].potentialLastReads) - states[^1].notLastReads
|
||||||
|
var lastReadTable: Table[PNode, seq[int]]
|
||||||
inc pc
|
for position, node in cfg:
|
||||||
of use:
|
if node.kind == use:
|
||||||
alreadySeen.incl cfg[pc].n
|
lastReadTable.mgetOrPut(node.n, @[]).add position
|
||||||
|
for node, positions in lastReadTable:
|
||||||
inc pc
|
block checkIfAllPosLastRead:
|
||||||
of goto:
|
for p in positions:
|
||||||
pc += cfg[pc].dest
|
if p notin lastReads: break checkIfAllPosLastRead
|
||||||
of fork:
|
node.flags.incl nfLastRead
|
||||||
var variantA = pc + 1
|
|
||||||
var variantB = pc + cfg[pc].dest
|
|
||||||
var alreadySeenA, alreadySeenB = alreadySeen
|
|
||||||
while variantA != variantB and max(variantA, variantB) < cfg.len and min(variantA, variantB) < until:
|
|
||||||
if variantA < variantB:
|
|
||||||
collectFirstWrites(cfg, alreadySeenA, variantA, min(variantB, until))
|
|
||||||
else:
|
|
||||||
collectFirstWrites(cfg, alreadySeenB, variantB, min(variantA, until))
|
|
||||||
|
|
||||||
alreadySeen.incl alreadySeenA + alreadySeenB
|
|
||||||
|
|
||||||
pc = min(variantA, variantB)
|
|
||||||
|
|
||||||
proc isLastRead(n: PNode; c: var Con): bool =
|
proc isLastRead(n: PNode; c: var Con): bool =
|
||||||
let m = dfa.skipConvDfa(n)
|
let m = dfa.skipConvDfa(n)
|
||||||
|
|
@ -1096,6 +1103,8 @@ proc injectDefaultCalls(n: PNode, c: var Con) =
|
||||||
injectDefaultCalls(n[i], c)
|
injectDefaultCalls(n[i], c)
|
||||||
|
|
||||||
proc injectDestructorCalls*(g: ModuleGraph; idgen: IdGenerator; owner: PSym; n: PNode): PNode =
|
proc injectDestructorCalls*(g: ModuleGraph; idgen: IdGenerator; owner: PSym; n: PNode): PNode =
|
||||||
|
when toDebug.len > 0:
|
||||||
|
shouldDebug = toDebug == owner.name.s or toDebug == "always"
|
||||||
if sfGeneratedOp in owner.flags or (owner.kind == skIterator and isInlineIterator(owner.typ)):
|
if sfGeneratedOp in owner.flags or (owner.kind == skIterator and isInlineIterator(owner.typ)):
|
||||||
return n
|
return n
|
||||||
var c = Con(owner: owner, graph: g, g: constructCfg(owner, n), idgen: idgen)
|
var c = Con(owner: owner, graph: g, g: constructCfg(owner, n), idgen: idgen)
|
||||||
|
|
@ -1107,26 +1116,7 @@ proc injectDestructorCalls*(g: ModuleGraph; idgen: IdGenerator; owner: PSym; n:
|
||||||
if optCursorInference in g.config.options:
|
if optCursorInference in g.config.options:
|
||||||
computeCursors(owner, n, g)
|
computeCursors(owner, n, g)
|
||||||
|
|
||||||
block:
|
computeLastReadsAndFirstWrites(c.g)
|
||||||
var cache = initTable[(PNode, PNode), AliasKind]()
|
|
||||||
var lastReads, potLastReads: IntSet
|
|
||||||
var pc = 0
|
|
||||||
collectLastReads(c.g, cache, lastReads, potLastReads, pc, c.g.len)
|
|
||||||
lastReads.incl potLastReads
|
|
||||||
var lastReadTable: Table[PNode, seq[int]]
|
|
||||||
for position, node in c.g:
|
|
||||||
if node.kind == use:
|
|
||||||
lastReadTable.mgetOrPut(node.n, @[]).add position
|
|
||||||
for node, positions in lastReadTable:
|
|
||||||
var allPositionsLastRead = true
|
|
||||||
for p in positions:
|
|
||||||
if p notin lastReads: allPositionsLastRead = false; break
|
|
||||||
if allPositionsLastRead:
|
|
||||||
node.flags.incl nfLastRead
|
|
||||||
|
|
||||||
var alreadySeen: HashSet[PNode]
|
|
||||||
pc = 0
|
|
||||||
collectFirstWrites(c.g, alreadySeen, pc, c.g.len)
|
|
||||||
|
|
||||||
var scope: Scope
|
var scope: Scope
|
||||||
let body = p(n, c, scope, normal)
|
let body = p(n, c, scope, normal)
|
||||||
|
|
|
||||||
|
|
@ -784,3 +784,37 @@ proc main3 =
|
||||||
|
|
||||||
main3()
|
main3()
|
||||||
|
|
||||||
|
# misc
|
||||||
|
proc smoltest(x: bool): bool =
|
||||||
|
while true:
|
||||||
|
if true: return x
|
||||||
|
|
||||||
|
discard smoltest(true)
|
||||||
|
|
||||||
|
# bug #18002
|
||||||
|
type
|
||||||
|
TTypeAttachedOp = enum
|
||||||
|
attachedAsgn
|
||||||
|
attachedSink
|
||||||
|
attachedTrace
|
||||||
|
|
||||||
|
PNode = ref object
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc genAddrOf(n: PNode) =
|
||||||
|
assert n != nil, "moved?!"
|
||||||
|
|
||||||
|
proc atomicClosureOp =
|
||||||
|
let x = PNode()
|
||||||
|
|
||||||
|
genAddrOf:
|
||||||
|
block:
|
||||||
|
x
|
||||||
|
|
||||||
|
case attachedTrace
|
||||||
|
of attachedSink: discard
|
||||||
|
of attachedAsgn: discard
|
||||||
|
of attachedTrace: genAddrOf(x)
|
||||||
|
|
||||||
|
atomicClosureOp()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue