mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-10-20 18:56:17 +00:00
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:
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2867,6 +2867,52 @@ fn switch_callvalue_cse_dangling() {
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
/// A switch case whose region yields a bare `let cv = callvalue()` binding
|
||||
/// (the yield being its only use) must keep the binding at codegen. With three or more
|
||||
/// callvalue sites the outlined-callvalue path is enabled and its dead-callvalue analysis
|
||||
/// skips bindings whose every use is an If condition. Region yields were not counted as
|
||||
/// uses, so the binding was skipped and the yield referenced an undefined value, failing
|
||||
/// compilation. Compiling and running at all exercises the fix. See SwitchCvYieldOnly.yul
|
||||
/// (paritytech/revive#588).
|
||||
#[test]
|
||||
fn switch_callvalue_yield_only() {
|
||||
let mut actions = instantiate_yul("contracts/SwitchCvYieldOnly.yul", "SwitchCvYieldOnly");
|
||||
for (sel, value) in [(1u64, 0u128), (1, 5), (2, 3), (99, 7)] {
|
||||
actions.push(Call {
|
||||
origin: TestAddress::Alice,
|
||||
dest: TestAddress::Instantiated(0),
|
||||
value,
|
||||
gas_limit: None,
|
||||
storage_deposit_limit: None,
|
||||
data: U256::from(sel).to_be_bytes::<32>().to_vec(),
|
||||
});
|
||||
}
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
/// A function returning a bare `let cv = callvalue()` binding through its return variable
|
||||
/// must keep the binding at codegen. The dead-callvalue analysis counted uses over the
|
||||
/// statement tree only, but a return variable is recorded in `Function::return_values`,
|
||||
/// a field outside that tree, so a binding reaching the return slot on the fall-through
|
||||
/// path looked unused and was skipped. Codegen seeds return values with zero before the
|
||||
/// body runs, so the function silently returned zero instead of the transferred value.
|
||||
/// See FunctionCvReturnOnly.yul (paritytech/revive#589).
|
||||
#[test]
|
||||
fn function_callvalue_return_only() {
|
||||
let mut actions = instantiate_yul("contracts/FunctionCvReturnOnly.yul", "FunctionCvReturnOnly");
|
||||
for (sel, value) in [(1u64, 7u128), (2, 11), (3, 13), (99, 17)] {
|
||||
actions.push(Call {
|
||||
origin: TestAddress::Alice,
|
||||
dest: TestAddress::Instantiated(0),
|
||||
value,
|
||||
gas_limit: None,
|
||||
storage_deposit_limit: None,
|
||||
data: U256::from(sel).to_be_bytes::<32>().to_vec(),
|
||||
});
|
||||
}
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
/// The one-armed `if` lowering must materialize its fall-through `inputs`
|
||||
/// before the conditional branch terminates the entry block: a narrow (i1) input needs a `zext` to
|
||||
/// reach the join phi, and emitting it after the branch leaves the block without a trailing
|
||||
|
||||
Reference in new issue
Block a user