diff options
-rw-r--r-- | compiler/injectdestructors.nim | 226 | ||||
-rw-r--r-- | tests/arc/tmovebug.nim | 34 |
2 files changed, 142 insertions, 118 deletions
diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index 152efd8a1..48a852d26 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -46,6 +46,13 @@ type sinkArg 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.} = 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}: 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 = let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), nextSymId c.idgen, c.owner, info) sym.typ = typ @@ -75,106 +77,111 @@ import sets, hashes, tables proc hash(n: PNode): Hash = hash(cast[pointer](n)) -type AliasCache = Table[(PNode, PNode), AliasKind] -proc aliasesCached(cache: var AliasCache, obj, field: PNode): AliasKind = +proc aliasesCached(cache: var Table[(PNode, PNode), AliasKind], obj, field: PNode): AliasKind = let key = (obj, field) if not cache.hasKey(key): cache[key] = aliases(obj, field) cache[key] -proc collectLastReads(cfg: ControlFlowGraph; cache: var AliasCache, lastReads, potLastReads: var IntSet; pc: var int, until: int) = - template aliasesCached(obj, field: PNode): untyped = +type + 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) - 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 - of use: - 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 - - inc pc - of goto: - pc += cfg[pc].dest - of fork: - var variantA = pc + 1 - var variantB = pc + cfg[pc].dest - var potLastReadsA, potLastReadsB = potLastReads - var lastReadsA, lastReadsB: IntSet - while variantA != variantB and max(variantA, variantB) < cfg.len and min(variantA, variantB) < until: - if variantA < variantB: - collectLastReads(cfg, cache, lastReadsA, potLastReadsA, variantA, min(variantB, until)) - else: - collectLastReads(cfg, cache, lastReadsB, potLastReadsB, variantB, min(variantA, until)) - - # Add those last reads that were turned into last reads on both branches - lastReads.incl lastReadsA * lastReadsB - # Add those last reads that were turned into last reads on only one branch, - # but where the read operation itself also belongs to only that branch - lastReads.incl (lastReadsA + lastReadsB) - potLastReads - - let oldPotLastReads = potLastReads - potLastReads = initIntSet() - potLastReads.incl potLastReadsA + potLastReadsB - - # Remove potential last reads that were invalidated in a branch, - # 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) - -proc collectFirstWrites(cfg: ControlFlowGraph; alreadySeen: var HashSet[PNode]; pc: var int, until: int) = - while pc < until: - case cfg[pc].kind - of def: - var alreadySeenThisNode = false - for s in alreadySeen: - if cfg[pc].n.aliases(s) != no or s.aliases(cfg[pc].n) != no: - alreadySeenThisNode = true; break - if alreadySeenThisNode: cfg[pc].n.flags.excl nfFirstWrite - else: cfg[pc].n.flags.incl nfFirstWrite - - alreadySeen.incl cfg[pc].n - - inc pc - of use: - alreadySeen.incl cfg[pc].n - - inc pc - of goto: - pc += cfg[pc].dest - of fork: - 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) + var cfg = cfg + preprocessCfg(cfg) + + var states = newSeq[State](cfg.len + 1) + states[0] = State() + + for pc in 0..<cfg.len: + template state: State = states[pc] + if state != nil: + case cfg[pc].kind + of def: + var potentialLastReadsCopy = state.potentialLastReads + for r in potentialLastReadsCopy: + if cfg[pc].n.aliasesCached(cfg[r].n) == yes: + # the path leads to a redefinition of 's' --> sink 's'. + state.lastReads.incl r + state.potentialLastReads.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 + state.potentialLastReads.excl r + state.notLastReads.incl r + + var alreadySeenThisNode = false + for s in state.alreadySeen: + if cfg[pc].n.aliasesCached(s) != no or s.aliasesCached(cfg[pc].n) != no: + alreadySeenThisNode = true; break + if alreadySeenThisNode: cfg[pc].n.flags.excl nfFirstWrite + else: cfg[pc].n.flags.incl nfFirstWrite + + state.alreadySeen.incl cfg[pc].n + + 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 + + state.potentialLastReads.incl pc + + state.alreadySeen.incl cfg[pc].n + + mergeStates(states[pc + 1], move(states[pc])) + of goto: + mergeStates(states[pc + cfg[pc].dest], move(states[pc])) + of fork: + var copy = State() + copy[] = states[pc][] + mergeStates(states[pc + cfg[pc].dest], copy) + mergeStates(states[pc + 1], move(states[pc])) + + let lastReads = (states[^1].lastReads + states[^1].potentialLastReads) - states[^1].notLastReads + var lastReadTable: Table[PNode, seq[int]] + for position, node in cfg: + if node.kind == use: + lastReadTable.mgetOrPut(node.n, @[]).add position + for node, positions in lastReadTable: + block checkIfAllPosLastRead: + for p in positions: + if p notin lastReads: break checkIfAllPosLastRead + node.flags.incl nfLastRead proc isLastRead(n: PNode; c: var Con): bool = let m = dfa.skipConvDfa(n) @@ -1096,6 +1103,8 @@ proc injectDefaultCalls(n: PNode, c: var Con) = injectDefaultCalls(n[i], c) 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)): return n 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: computeCursors(owner, n, g) - block: - 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) + computeLastReadsAndFirstWrites(c.g) var scope: Scope let body = p(n, c, scope, normal) diff --git a/tests/arc/tmovebug.nim b/tests/arc/tmovebug.nim index 3ff1c4a0c..7977d330a 100644 --- a/tests/arc/tmovebug.nim +++ b/tests/arc/tmovebug.nim @@ -784,3 +784,37 @@ proc 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() + |