diff --git a/compiler/aliasanalysis.nim b/compiler/aliasanalysis.nim index e24c6d8e260c0..26b300527f208 100644 --- a/compiler/aliasanalysis.nim +++ b/compiler/aliasanalysis.nim @@ -21,6 +21,24 @@ proc skipConvDfa*(n: PNode): PNode = else: break proc isAnalysableFieldAccess*(orig: PNode; owner: PSym): bool = + let stripped = skipConvDfa(orig) + if stripped.typ != nil and isSinkType(stripped.typ): + var base = stripped + while true: + case base.kind + of nkDotExpr, nkCheckedFieldExpr: + base = skipConvDfa(base[0]) + of nkHiddenDeref, nkDerefExpr: + base = skipConvDfa(base[0]) + of nkHiddenStdConv, nkHiddenSubConv, nkConv: + base = skipConvDfa(base[1]) + of nkObjUpConv, nkObjDownConv: + base = skipConvDfa(base[0]) + else: + break + if base.kind == nkSym and base.sym.owner == owner and + {sfGlobal, sfThread, sfCursor} * base.sym.flags == {}: + return true var n = orig while true: case n.kind diff --git a/compiler/dfa.nim b/compiler/dfa.nim index ef6a767f07d02..7f0474a330540 100644 --- a/compiler/dfa.nim +++ b/compiler/dfa.nim @@ -25,6 +25,8 @@ import ast, lineinfos, renderer, aliasanalysis import std/private/asciitables import std/intsets +import std/tables +import std/algorithm when defined(nimPreviewSlimSystem): import std/assertions @@ -57,6 +59,10 @@ type blocks: seq[TBlock] owner: PSym root: PSym + stateLabels: Table[int, TPosition] + stateFixups: Table[int, seq[TPosition]] + stateCaseDepth: int + stateCaseNextState: int proc codeListing(c: ControlFlowGraph, start = 0; last = -1): string = # for debugging purposes @@ -106,12 +112,25 @@ template checkedDistance(dist): int = proc jmpBack(c: var Con, p = TPosition(0)) = c.code.add Instr(kind: loop, dest: checkedDistance(p.int - c.code.len)) +proc genStateGoto(c: var Con; target: int) = + if target < 0: + c.code.add Instr(kind: goto, dest: high(int) - c.code.len) + else: + let lab = c.stateLabels.getOrDefault(target, TPosition(-1)) + if lab.int >= 0: + c.code.add Instr(kind: goto, dest: checkedDistance(lab.int - c.code.len)) + else: + let p = c.gotoI() + c.stateFixups.mgetOrPut(target, @[]).add p + proc patch(c: var Con, p: TPosition) = # patch with current index c.code[p.int].dest = checkedDistance(c.code.len - p.int) proc gen(c: var Con; n: PNode) +const stateUnset = high(int) + proc popBlock(c: var Con; oldLen: int) = var exits: seq[TPosition] = @[] exits.add c.gotoI() @@ -142,9 +161,34 @@ proc genWhile(c: var Con; n: PNode) = # body # jmp lab1 # lab2: + proc containsGotoState(n: PNode): bool = + if n.kind == nkGotoState: return true + for i in 0.. 0 and isStateAccess(n[0]): + return true + for i in 0..= 0: branchOrder.add elseIdx + else: + for i in 1..= 2: + for j in 0..it.len-2: + if it[j].kind == nkIntLit: + let stateId = it[j].intVal.int + let lab = c.genLabel + c.stateLabels[stateId] = lab + if c.stateFixups.hasKey(stateId): + for p in c.stateFixups[stateId]: + c.code[p.int].dest = checkedDistance(lab.int - p.int) + c.stateFixups.del(stateId) + break + let oldDepth = c.stateCaseDepth + let oldNext = c.stateCaseNextState + if isStateCase: + c.stateCaseDepth = oldDepth + 1 + c.stateCaseNextState = findStateAssignment(it.lastSon) if it.len == 1 or (i == n.len-1 and isExhaustive): # treat the last branch as 'else' if this is an exhaustive case statement. c.gen(it.lastSon) @@ -236,6 +347,10 @@ proc genCase(c: var Con; n: PNode) = forkT: c.gen(it.lastSon) endings.add c.gotoI() + if isStateCase: + c.stateCaseDepth = oldDepth + c.stateCaseNextState = oldNext + discard if oldInteresting == c.interestingInstructions: setLen c.code, oldLen @@ -247,6 +362,17 @@ proc genBlock(c: var Con; n: PNode) = withBlock(n[0].sym): c.gen(n[1]) +proc genGotoState(c: var Con; n: PNode) = + if n.len == 0: + c.code.add Instr(kind: goto, dest: high(int) - c.code.len) + return + if n[0].kind == nkIntLit: + let target = n[0].intVal.int + c.genStateGoto(target) + else: + # Dynamic target; be conservative and assume a loop. + c.jmpBack(TPosition(0)) + proc genBreakOrRaiseAux(c: var Con, i: int, n: PNode) = let lab1 = c.gotoI() if c.blocks[i].isTryBlock: @@ -263,6 +389,11 @@ proc genBreakOrRaiseAux(c: var Con, i: int, n: PNode) = proc genBreak(c: var Con; n: PNode) = inc c.interestingInstructions + if c.stateCaseDepth > 0 and n[0].kind == nkSym and + n[0].sym.name.s == ":stateLoop" and + c.stateCaseNextState != stateUnset: + c.genStateGoto(c.stateCaseNextState) + return if n[0].kind == nkSym: for i in countdown(c.blocks.high, 0): if not c.blocks[i].isTryBlock and c.blocks[i].label == n[0].sym: @@ -329,7 +460,13 @@ proc genReturn(c: var Con; n: PNode) = gen(c, n[0]) else: genImplicitReturn(c) - genBreakOrRaiseAux(c, 0, n) + if c.stateCaseDepth > 0 and c.stateCaseNextState != stateUnset and + c.stateCaseNextState >= 0: + forkT: + genBreakOrRaiseAux(c, 0, n) + c.genStateGoto(c.stateCaseNextState) + else: + genBreakOrRaiseAux(c, 0, n) const InterestingSyms = {skVar, skResult, skLet, skParam, skForVar, skTemp} @@ -347,23 +484,43 @@ proc skipTrivials(c: var Con, n: PNode): PNode = result = result[1] else: break +proc matchesFieldAccess(orig: PNode; field: PSym): bool = + result = false + var n = skipConvDfa(orig) + while true: + case n.kind + of nkDotExpr, nkCheckedFieldExpr: + if n[1].kind == nkSym and n[1].sym == field: + result = true + break + n = skipConvDfa(n[0]) + else: + break + proc genUse(c: var Con; orig: PNode) = let n = c.skipTrivials(orig) - if n.kind == nkSym: + if c.root.kind == skField and matchesFieldAccess(orig, c.root): + c.code.add Instr(kind: use, n: orig) + inc c.interestingInstructions + elif n.kind == nkSym: if n.sym.kind in InterestingSyms and n.sym == c.root: c.code.add Instr(kind: use, n: orig) inc c.interestingInstructions + else: + discard else: gen(c, n) proc genDef(c: var Con; orig: PNode) = let n = c.skipTrivials(orig) - if n.kind == nkSym and n.sym.kind in InterestingSyms: - if n.sym == c.root: - c.code.add Instr(kind: def, n: orig) - inc c.interestingInstructions + if c.root.kind == skField and matchesFieldAccess(orig, c.root): + c.code.add Instr(kind: def, n: orig) + inc c.interestingInstructions + elif n.kind == nkSym and n.sym.kind in InterestingSyms and n.sym == c.root: + c.code.add Instr(kind: def, n: orig) + inc c.interestingInstructions proc genCall(c: var Con; n: PNode) = gen(c, n[0]) @@ -448,9 +605,16 @@ proc gen(c: var Con; n: PNode) = of nkRaiseStmt: genRaise(c, n) of nkBreakStmt: genBreak(c, n) of nkTryStmt, nkHiddenTryStmt: genTry(c, n) - of nkStmtList, nkStmtListExpr, nkChckRangeF, nkChckRange64, nkChckRange, - nkBracket, nkCurly, nkPar, nkTupleConstr, nkClosure, nkObjConstr, nkYieldStmt: + of nkStmtList, nkStmtListExpr: + for x in n: gen(c, x) + of nkYieldStmt: + forkT: + c.code.add Instr(kind: goto, dest: high(int) - c.code.len) + of nkChckRangeF, nkChckRange64, nkChckRange, + nkBracket, nkCurly, nkPar, nkTupleConstr, nkClosure, nkObjConstr: for x in n: gen(c, x) + of nkGotoState: + genGotoState(c, n) of nkPragmaBlock: gen(c, n.lastSon) of nkDiscardStmt, nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString: gen(c, n[0]) @@ -478,7 +642,11 @@ when false: proc constructCfg*(s: PSym; body: PNode; root: PSym): ControlFlowGraph = ## constructs a control flow graph for ``body``. - var c = Con(code: @[], blocks: @[], owner: s, root: root) + var c = Con(code: @[], blocks: @[], owner: s, root: root, + stateLabels: initTable[int, TPosition](), + stateFixups: initTable[int, seq[TPosition]](), + stateCaseDepth: 0, + stateCaseNextState: stateUnset) withBlock(s): gen(c, body) if root.kind == skResult: diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index e6ddf79a8ad35..f5e3cfdaf1dc8 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -17,7 +17,7 @@ import ast, astalgo, msgs, renderer, magicsys, types, idents, options, lowerings, modulegraphs, lineinfos, parampatterns, sighashes, liftdestructors, optimizer, - varpartitions, aliasanalysis, dfa, wordrecg + varpartitions, aliasanalysis, dfa, wordrecg, lambdalifting import std/[strtabs, tables, strutils, intsets] @@ -97,8 +97,22 @@ when false: for i in low(InstrKind)..high(InstrKind): echo "INSTR ", i, " ", perfCounters[i] -proc isLastReadImpl(n: PNode; c: var Con; scope: var Scope): bool = - let root = parampatterns.exprRoot(n, allowCalls=false) +proc fieldRootFromAccess(n: PNode): PSym = + result = nil + var it = n + while true: + case it.kind + of nkHiddenStdConv, nkHiddenSubConv, nkConv: + it = it[1] + of nkObjUpConv, nkObjDownConv, nkHiddenDeref, nkDerefExpr, nkHiddenAddr, nkBracketExpr: + it = it[0] + of nkDotExpr, nkCheckedFieldExpr: + if it[1].kind == nkSym: return it[1].sym + return nil + else: + return nil + +proc isLastReadImpl(n: PNode; c: var Con; scope: var Scope; root: PSym): bool = if root == nil: return false elif sfSingleUsedTemp in root.flags: return true @@ -111,14 +125,13 @@ proc isLastReadImpl(n: PNode; c: var Con; scope: var Scope): bool = dbg: echo "\n### ", c.owner.name.s, ":\nCFG:" echoCfg(c.g) - #echo c.body - var j = 0 - while j < c.g.len: - if c.g[j].kind == use and c.g[j].n == n: break - inc j + var j = -1 + for i in 0..= 0: var pcs = @[j+1] var marked = initIntSet() result = true @@ -172,7 +185,12 @@ proc isLastRead(n: PNode; c: var Con; s: var Scope): bool = if not hasDestructorOrAsgn(c, n.typ): return true let m = skipConvDfa(n) - result = isLastReadImpl(n, c, s) + let root = parampatterns.exprRoot(n, allowCalls=false) + if n.typ != nil and isSinkType(n.typ) and isAnalysableFieldAccess(n, c.owner): + let fieldRoot = fieldRootFromAccess(n) + if fieldRoot != nil: + return isLastReadImpl(n, c, s, fieldRoot) + result = isLastReadImpl(n, c, s, root) proc isFirstWrite(n: PNode; c: var Con): bool = let m = skipConvDfa(n) @@ -814,34 +832,35 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing elif n.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkClosure, nkNilLit} + nkCallKinds + nkLiterals: result = p(n, c, s, consumed) - elif ((n.kind == nkSym and isSinkParam(n.sym)) or isAnalysableFieldAccess(n, c.owner)) and - isLastRead(n, c, s) and not (n.kind == nkSym and isCursor(n)): - # Sinked params can be consumed only once. We need to reset the memory - # to disable the destructor which we have not elided - result = destructiveMoveVar(n, c, s) - elif n.kind in {nkHiddenSubConv, nkHiddenStdConv, nkConv}: - result = copyTree(n) - if n.typ.skipTypes(abstractInst-{tyOwned}).kind != tyOwned and - n[1].typ.skipTypes(abstractInst-{tyOwned}).kind == tyOwned: - # allow conversions from owned to unowned via this little hack: - let nTyp = n[1].typ - n[1].typ = n.typ + else: + if ((n.kind == nkSym and isSinkParam(n.sym)) or isAnalysableFieldAccess(n, c.owner)) and + isLastRead(n, c, s) and not (n.kind == nkSym and isCursor(n)): + # Sinked params can be consumed only once. We need to reset the memory + # to disable the destructor which we have not elided + result = destructiveMoveVar(n, c, s) + elif n.kind in {nkHiddenSubConv, nkHiddenStdConv, nkConv}: + result = copyTree(n) + if n.typ.skipTypes(abstractInst-{tyOwned}).kind != tyOwned and + n[1].typ.skipTypes(abstractInst-{tyOwned}).kind == tyOwned: + # allow conversions from owned to unowned via this little hack: + let nTyp = n[1].typ + n[1].typ = n.typ + result[1] = p(n[1], c, s, sinkArg) + result[1].typ = nTyp + else: + result[1] = p(n[1], c, s, sinkArg) + elif n.kind in {nkObjDownConv, nkObjUpConv}: + result = copyTree(n) + result[0] = p(n[0], c, s, sinkArg) + elif n.kind == nkCast and n.typ.skipTypes(abstractInst).kind in {tyString, tySequence}: + result = copyTree(n) result[1] = p(n[1], c, s, sinkArg) - result[1].typ = nTyp + elif n.typ == nil: + # 'raise X' can be part of a 'case' expression. Deal with it here: + result = p(n, c, s, normal) else: - result[1] = p(n[1], c, s, sinkArg) - elif n.kind in {nkObjDownConv, nkObjUpConv}: - result = copyTree(n) - result[0] = p(n[0], c, s, sinkArg) - elif n.kind == nkCast and n.typ.skipTypes(abstractInst).kind in {tyString, tySequence}: - result = copyTree(n) - result[1] = p(n[1], c, s, sinkArg) - elif n.typ == nil: - # 'raise X' can be part of a 'case' expression. Deal with it here: - result = p(n, c, s, normal) - else: - # copy objects that are not temporary but passed to a 'sink' parameter - result = passCopyToSink(n, c, s) + # copy objects that are not temporary but passed to a 'sink' parameter + result = passCopyToSink(n, c, s) else: case n.kind of nkBracket, nkTupleConstr, nkClosure, nkCurly: @@ -860,13 +879,31 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing result = copyTree(n) for i in ord(n.kind == nkClosure)..