diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index 0b2d085a3f..2b5ae6421e 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -803,6 +803,23 @@ proc hasCustomDestructor(c: Con, t: PType): bool = obj = skipTypes(obj.baseClass, abstractPtrs) result = result or isCustomDestructor(c, obj) +const + exprBranchKinds = {nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt, + nkTryStmt, nkPragmaBlock} + +proc distributeAsgn(asgnKind: TNodeKind; dest, ri: PNode; c: var Con; s: var Scope): PNode = + ## Distributes an assignment ``dest = ri`` into the leaf expressions of + ## ``ri`` when ``ri`` is an expression-based control flow construct. This + ## avoids creating pointless intermediate temporaries (bug #25850). The + ## descent is recursive so that nestings like ``block: ...; if c: a else: b`` + ## assign directly to ``dest`` instead of going through a temp per branch. + if ri.kind in exprBranchKinds: + template process(child, s): untyped = + distributeAsgn(asgnKind, dest, child, c, s) + handleNestedTempl(ri, process, willProduceStmt = true) + else: + result = newTree(asgnKind, dest, p(ri, c, s, consumed)) + proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSingleUsedTemp}; inReturn = false): PNode = if n.kind in {nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkIfStmt, nkIfExpr, nkCaseStmt, nkWhen, nkWhileStmt, nkParForStmt, nkTryStmt, nkPragmaBlock}: @@ -1004,13 +1021,11 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing result = moveOrCopy(p(n[0], c, s, mode), n[1], c, s, flags) elif isDiscriminantField(n[0]): result = c.genDiscriminantAsgn(s, n) - elif n[1].kind in {nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt, nkTryStmt, nkPragmaBlock}: + elif n[1].kind in exprBranchKinds: # Distribute the assignment into each branch to avoid # creating pointless temporaries for expression-based control flow. let dest = p(n[0], c, s, mode) - template process(child, s): untyped = - newTree(n.kind, dest, p(child, c, s, consumed)) - handleNestedTempl(n[1], process, willProduceStmt = true) + result = distributeAsgn(n.kind, dest, n[1], c, s) else: result = copyNode(n) result.add p(n[0], c, s, mode) diff --git a/tests/arc/t25850.nim b/tests/arc/t25850.nim new file mode 100644 index 0000000000..abdb772d37 --- /dev/null +++ b/tests/arc/t25850.nim @@ -0,0 +1,47 @@ +discard """ + cmd: '''nim c --mm:orc --expandArc:uIf --expandArc:uCase $file''' + nimout: ''' +--expandArc: uIf + +block :tmp: + let s = w() + if true: + r[] = s + else: + r[] = s +-- end of expandArc ------------------------ +--expandArc: uCase + +block :tmp: + let s = w() + case n + of 0: + r[] = s + else: + r[] = w() +-- end of expandArc ------------------------ +''' +""" + +# bug #25850 +# Assigning an expression-based control flow construct (an `if`/`case` nested in +# a `block`) must distribute the assignment directly into the leaf branches +# instead of creating redundant intermediate temporaries per branch. + +proc w(): array[1000, byte] {.noinline.} = discard + +proc uIf(r: ptr array[1000, byte]) = + r[] = (block: + let s = w() + if true: s else: s) + +proc uCase(r: ptr array[1000, byte], n: int) = + r[] = (block: + let s = w() + case n + of 0: s + else: w()) + +var d: array[1000, byte] +uIf(addr d) +uCase(addr d, 0)