NY: bugfix dead callvalue analysis (#589)

- Remove the buggy `fn find_value_uses()`
- The logic flips from default-skip to default-keep

Closes #588

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Co-authored-by: LJ <81748770+elle-j@users.noreply.github.com>
This commit is contained in:
xermicusandLJ authored and GitHub committed 2026-08-14 23:22:56 +02:00
1 parent 177c7dc58e
commit 68e8b167f5
5 files changed
+168 -263

No files matched your search

@@ -0,0 +1,31 @@
object "FunctionCvReturnOnly" {
code { let s := datasize("FunctionCvReturnOnly_deployed") codecopy(0, dataoffset("FunctionCvReturnOnly_deployed"), s) return(0, s) }
object "FunctionCvReturnOnly_deployed" {
code {
// `tally` hands callvalue() back through its return variable on the fall-through path,
// so the binding's only use is recorded in `Function::return_values` rather than in any
// statement. Three callvalue() sites put the module over the outlined-callvalue
// threshold, so codegen runs the dead-callvalue analysis; it must count return
// variables as uses, otherwise the binding is skipped and the function hands back the
// zero its return slot was seeded with. Two call sites and a body above the
// always-inline size keep `tally` a real function instead of being inlined away.
function tally(seed) -> r {
sstore(10, seed)
sstore(11, add(seed, 1))
sstore(12, add(seed, 2))
sstore(13, add(seed, 3))
sstore(14, add(seed, 4))
sstore(15, add(seed, 5))
sstore(16, add(seed, 6))
sstore(17, add(seed, 7))
r := callvalue()
}
let sel := calldataload(0)
switch sel
case 1 { sstore(0, tally(1)) }
case 2 { sstore(0, tally(2)) }
case 3 { sstore(0, add(callvalue(), calldatasize())) }
default { sstore(0, add(callvalue(), 0x20)) }
}
}
}
@@ -0,0 +1,20 @@
object "SwitchCvYieldOnly" {
code { let s := datasize("SwitchCvYieldOnly_deployed") codecopy(0, dataoffset("SwitchCvYieldOnly_deployed"), s) return(0, s) }
object "SwitchCvYieldOnly_deployed" {
code {
let sel := calldataload(0)
// Three callvalue() sites put the module over the outlined-callvalue threshold, so
// codegen runs the dead-callvalue analysis. Case 1 binds callvalue() and its only use
// is the region yield carrying the switch result into `r`. The other branches evaluate
// their second operand first, so no branch-leading callvalue hoist merges the bindings
// and the yield-only binding survives to codegen. The analysis must count region yields
// as uses, otherwise the binding is skipped and the yield references an undefined value.
let r := 0
switch sel
case 1 { r := callvalue() }
case 2 { r := add(callvalue(), calldatasize()) }
default { r := add(callvalue(), 0x20) }
sstore(0, r)
}
}
}