Tests for UnusedReturnParameterPruner

This commit is contained in:
hrkrshnn 2020-10-20 13:48:50 +02:00
parent 4553b8c0d5
commit 03cbc5f6e7
20 changed files with 396 additions and 10 deletions

View File

@ -2,6 +2,7 @@
Compiler Features:
* SMTChecker: Support named arguments in function calls.
* Yul Optimizer: Prune return parameters of functions that are unused at callsite.
### 0.7.5 (2020-11-18)

View File

@ -43,7 +43,7 @@ object "object" {
x_7 := x_11
}
{
let _5, _6, _7, _8 := iszero_169_788(_1, _1, _1, lt_171(x_4, x_5, x_6, x_7, _1, _1, _1, 10))
let _5, _6, _7, _8 := iszero_169_864(_1, _1, _1, lt_171(x_4, x_5, x_6, x_7, _1, _1, _1, 10))
if i32.eqz(i64.eqz(i64.or(i64.or(_5, _6), i64.or(_7, _8)))) { break }
if i32.eqz(i64.eqz(i64.or(_3, i64.or(_1, eq(x_4, x_5, x_6, x_7, _1, _1, _1, 2))))) { break }
if i32.eqz(i64.eqz(i64.or(_3, i64.or(_1, eq(x_4, x_5, x_6, x_7, _1, _1, _1, 4))))) { continue }
@ -67,7 +67,7 @@ object "object" {
let r1_1, carry_2 := add_carry(x1, y1, carry_1)
r1 := r1_1
}
function iszero_169_788(x1, x2, x3, x4) -> r1, r2, r3, r4
function iszero_169_864(x1, x2, x3, x4) -> r1, r2, r3, r4
{
r4 := i64.extend_i32_u(i64.eqz(i64.or(i64.or(x1, x2), i64.or(x3, x4))))
}
@ -206,7 +206,7 @@ Text representation:
(br_if $label__3 (i32.eqz (i32.eqz (local.get $_4))))
(block $label__4
(block
(local.set $_5 (call $iszero_169_788 (local.get $_1) (local.get $_1) (local.get $_1) (call $lt_171 (local.get $x_4) (local.get $x_5) (local.get $x_6) (local.get $x_7) (local.get $_1) (local.get $_1) (local.get $_1) (i64.const 10))))
(local.set $_5 (call $iszero_169_864 (local.get $_1) (local.get $_1) (local.get $_1) (call $lt_171 (local.get $x_4) (local.get $x_5) (local.get $x_6) (local.get $x_7) (local.get $_1) (local.get $_1) (local.get $_1) (i64.const 10))))
(local.set $_6 (global.get $global_))
(local.set $_7 (global.get $global__1))
(local.set $_8 (global.get $global__2))
@ -310,7 +310,7 @@ Text representation:
(local.get $r1)
)
(func $iszero_169_788
(func $iszero_169_864
(param $x1 i64)
(param $x2 i64)
(param $x3 i64)

View File

@ -1396,11 +1396,11 @@ BOOST_AUTO_TEST_CASE(use_stack_optimization)
BOOST_REQUIRE(contract["evm"]["bytecode"]["object"].isString());
BOOST_CHECK(contract["evm"]["bytecode"]["object"].asString().length() > 20);
// Now disable stack optimizations and UnusedFunctionParameterPruner (p)
// results in "stack too deep"
// Now disable stack optimizations, UnusedFunctionParameterPruner (p) and
// UnusedFunctionReturnParameterPruner (P). Results in "stack too deep"
string optimiserSteps = OptimiserSettings::DefaultYulOptimiserSteps;
optimiserSteps.erase(
remove_if(optimiserSteps.begin(), optimiserSteps.end(), [](char ch) { return ch == 'p'; }),
remove_if(optimiserSteps.begin(), optimiserSteps.end(), [](char ch) { return ch == 'p' || ch == 'P'; }),
optimiserSteps.end()
);
parsedInput["settings"]["optimizer"]["details"]["yulDetails"]["stackAllocation"] = false;

View File

@ -51,6 +51,7 @@
#include <libyul/optimiser/Rematerialiser.h>
#include <libyul/optimiser/ExpressionSimplifier.h>
#include <libyul/optimiser/UnusedFunctionParameterPruner.h>
#include <libyul/optimiser/UnusedFunctionReturnParameterPruner.h>
#include <libyul/optimiser/UnusedPruner.h>
#include <libyul/optimiser/ExpressionJoiner.h>
#include <libyul/optimiser/OptimiserStep.h>
@ -264,6 +265,12 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line
LiteralRematerialiser::run(*m_context, *m_object->code);
UnusedFunctionParameterPruner::run(*m_context, *m_object->code);
}
else if (m_optimizerStep == "unusedFunctionReturnParameterPruner")
{
disambiguate();
FunctionHoister::run(*m_context, *m_object->code);
UnusedFunctionReturnParameterPruner::run(*m_context, *m_object->code);
}
else if (m_optimizerStep == "unusedPruner")
{
disambiguate();

View File

@ -1085,7 +1085,7 @@
// sstore(x1, x0)
// sstore(x3, x2)
// sstore(1, x4)
// pop(abi_encode_bytes32_t_address_t_uint256_t_bytes32_t_enum$_Operation_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint(mload(30), mload(31), mload(32), mload(33), mload(34), mload(35), mload(36), mload(37), mload(38), mload(39), mload(40), mload(41)))
// abi_encode_bytes32_t_address_t_uint256_t_bytes32_t_enum$_Operation_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint_461(mload(30), mload(31), mload(32), mload(33), mload(34), mload(35), mload(36), mload(37), mload(38), mload(39), mload(40), mload(41))
// }
// function abi_decode_addresst_uint256t_bytes_calldatat_enum$_Operation(headStart, dataEnd) -> value0, value1, value2, value3, value4
// {
@ -1106,9 +1106,8 @@
// if iszero(lt(_3, 3)) { revert(value4, value4) }
// value4 := _3
// }
// function abi_encode_bytes32_t_address_t_uint256_t_bytes32_t_enum$_Operation_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint(headStart, value10, value9, value8, value7, value6, value5, value4, value3, value2, value1, value0) -> tail
// function abi_encode_bytes32_t_address_t_uint256_t_bytes32_t_enum$_Operation_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint_461(headStart, value10, value9, value8, value7, value6, value5, value4, value3, value2, value1, value0)
// {
// tail := add(headStart, 352)
// mstore(headStart, value0)
// let _1 := sub(shl(160, 1), 1)
// mstore(add(headStart, 32), and(value1, _1))

View File

@ -0,0 +1,34 @@
// A test to see if loop condition is properly split
{
let b := f()
// Return value of f is used in for loop condition. So f cannot be rewritten,
// unless LoopConditionIntoBody and ExpressionSplitter is run.
for {let a := 1} iszero(sub(f(), a)) {a := add(a, 1)}
{}
function f() -> x
{
x := sload(1)
sstore(x, x)
if calldataload(0) { leave }
}
}
// ----
// step: fullSuite
//
// {
// {
// pop(f())
// let a := 1
// let a_1 := a
// for { } true { a_1 := add(a_1, a) }
// {
// if iszero(iszero(sub(f(), a_1))) { break }
// }
// }
// function f() -> x
// {
// x := sload(1)
// sstore(x, x)
// if calldataload(0) { leave }
// }
// }

View File

@ -0,0 +1,30 @@
// A test to see if UnusedFunctionReturnParameterPruner can deal with pop
// Expression splitter converts `pop(f(a))` into
// {
// let z := f(a)
// pop(z)
// }
// Unless `pop(z)` is removed, `f` cannot be rewritten.
{
let a := sload(1)
pop(f(a))
function f(x) -> y
{
if iszero(calldataload(0)) { leave }
sstore(x, x)
y := sload(x)
}
}
// ----
// step: fullSuite
//
// {
// { pop(f_17(sload(1))) }
// function f(x)
// {
// if iszero(calldataload(0)) { leave }
// sstore(x, x)
// }
// function f_17(x) -> y
// { f(x) }
// }

View File

@ -0,0 +1,22 @@
{
let c, d := f(calldataload(0), calldataload(1))
sstore(c, 1)
function f(x, y) -> a, b
{
// to prevent the function getting inlined
if iszero(calldataload(0)) {leave}
a := sload(x)
b := sload(y)
}
}
// ----
// step: fullSuite
//
// {
// { sstore(f(calldataload(0)), 1) }
// function f(x) -> a
// {
// if iszero(calldataload(a)) { leave }
// a := sload(x)
// }
// }

View File

@ -0,0 +1,30 @@
{
// second return paramter is unused
let j, k, l := f(1, 2, 3)
sstore(0, j)
sstore(1, l)
function f(a, b, c) -> x, y, z
{
// second return parameter is unused
let x_1, y_1, z_1 := f(1, 2, 3)
x := x_1
z := z_1
x := add(x, 1)
}
}
// ----
// step: fullSuite
//
// {
// {
// let x, z := f()
// sstore(0, x)
// sstore(1, z)
// }
// function f() -> x, z
// {
// let x_1, z_1 := f()
// z := z_1
// x := add(x_1, 1)
// }
// }

View File

@ -0,0 +1,26 @@
{
let a, b, c := f(sload(0))
function f(d) -> x, y, z
{
y := mload(d)
z := mload(2)
sstore(y, z)
}
}
// ----
// step: unusedFunctionReturnParameterPruner
//
// {
// let a, b, c := f_1(sload(0))
// function f(d)
// {
// let x
// let y
// let z
// y := mload(d)
// z := mload(2)
// sstore(y, z)
// }
// function f_1(d_2) -> x_3, y_4, z_5
// { f(d_2) }
// }

View File

@ -0,0 +1,30 @@
// A test where the function should not be rewritten, since each parameter is used eventually.
{
let a, b, c := f(sload(0))
sstore(a, 0)
let a1, b1, c1 := f(sload(2))
sstore(b1, 0)
let a2, b2, c3 := f(sload(3))
sstore(c3, 0)
function f(d) -> x, y, z
{
y := mload(d)
z := mload(2)
}
}
// ----
// step: unusedFunctionReturnParameterPruner
//
// {
// let a, b, c := f(sload(0))
// sstore(a, 0)
// let a1, b1, c1 := f(sload(2))
// sstore(b1, 0)
// let a2, b2, c3 := f(sload(3))
// sstore(c3, 0)
// function f(d) -> x, y, z
// {
// y := mload(d)
// z := mload(2)
// }
// }

View File

@ -0,0 +1,26 @@
{
// b and c are unused
let a, b, c := f(sload(0))
sstore(a, 0)
function f(d) -> x, y, z
{
y := mload(d)
z := mload(2)
}
}
// ----
// step: unusedFunctionReturnParameterPruner
//
// {
// let a, b, c := f_1(sload(0))
// sstore(a, 0)
// function f(d) -> x
// {
// let y
// let z
// y := mload(d)
// z := mload(2)
// }
// function f_1(d_2) -> x_3, y_4, z_5
// { x_3 := f(d_2) }
// }

View File

@ -0,0 +1,32 @@
// A test to see if the optimization step can deal with pop
// Expression splitter converts `pop(f(a))` into
// {
// let z := f(a)
// pop(z)
// }
// Unless `pop(z)` is removed, `f` cannot be rewritten.
{
let a := sload(1)
let z := f(a)
pop(z)
function f(x) -> y
{
sstore(x, x)
y := sload(x)
}
}
// ----
// step: unusedFunctionReturnParameterPruner
//
// {
// let a := sload(1)
// let z := f_1(a)
// function f(x)
// {
// let y
// sstore(x, x)
// y := sload(x)
// }
// function f_1(x_2) -> y_3
// { f(x_2) }
// }

View File

@ -0,0 +1,20 @@
// An example where the return parameter of the function should not be pruned
{
function f() -> y {
y := sload(1)
}
// return value is unused here
let x := f()
// return value is used here, so f cannot be pruned.
sstore(1, f())
}
// ----
// step: unusedFunctionReturnParameterPruner
//
// {
// let x := f()
// sstore(1, f())
// function f() -> y
// { y := sload(1) }
// }

View File

@ -0,0 +1,24 @@
{
let c, d := f(1, 2)
sstore(c, 1)
function f(x, y) -> a, b
{
a := sload(x)
b := sload(y)
}
}
// ----
// step: unusedFunctionReturnParameterPruner
//
// {
// let c, d := f_1(1, 2)
// sstore(c, 1)
// function f(x, y) -> a
// {
// let b
// a := sload(x)
// b := sload(y)
// }
// function f_1(x_2, y_3) -> a_4, b_5
// { a_4 := f(x_2, y_3) }
// }

View File

@ -0,0 +1,5 @@
{ }
// ----
// step: unusedFunctionReturnParameterPruner
//
// { }

View File

@ -0,0 +1,38 @@
{
// The third and the last return value is unused
let a, b, c, d, e := f(sload(0))
sstore(a, b)
let a1, b1, c1, d1, e1 := f(sload(1))
sstore(a1, d1)
let a2, b2, c2, d2, e2 := f(sload(2))
sstore(d2, b2)
function f(a_1) -> v, w, x, y, z
{
w := mload(a_1)
y := mload(w)
z := mload(y)
sstore(y, z)
}
}
// ----
// step: unusedFunctionReturnParameterPruner
//
// {
// let a, b, c, d, e := f_1(sload(0))
// sstore(a, b)
// let a1, b1, c1, d1, e1 := f_1(sload(1))
// sstore(a1, d1)
// let a2, b2, c2, d2, e2 := f_1(sload(2))
// sstore(d2, b2)
// function f(a_1) -> v, w, y
// {
// let x
// let z
// w := mload(a_1)
// y := mload(w)
// z := mload(y)
// sstore(y, z)
// }
// function f_1(a_1_2) -> v_3, w_4, x_5, y_6, z_7
// { v_3, w_4, y_6 := f(a_1_2) }
// }

View File

@ -0,0 +1,22 @@
{
let a := f()
// Return value of f is used here. So f cannot be rewritten.
sstore(sload(f()), 2)
function f() -> x
{
x := sload(1)
sstore(x, x)
}
}
// ----
// step: unusedFunctionReturnParameterPruner
//
// {
// let a := f()
// sstore(sload(f()), 2)
// function f() -> x
// {
// x := sload(1)
// sstore(x, x)
// }
// }

View File

@ -0,0 +1,25 @@
// A test to see if loop condition is properly split
{
let b := f()
// Return value of f is used in for loop condition. So f cannot be rewritten.
for {let a := 1} iszero(sub(f(), a)) {a := add(a, 1)}
{}
function f() -> x
{
x := sload(1)
sstore(x, x)
}
}
// ----
// step: unusedFunctionReturnParameterPruner
//
// {
// let b := f()
// for { let a := 1 } iszero(sub(f(), a)) { a := add(a, 1) }
// { }
// function f() -> x
// {
// x := sload(1)
// sstore(x, x)
// }
// }

View File

@ -0,0 +1,15 @@
{
let a := f()
pop(a)
function f() -> y {
sstore(1, sload(1))
}
}
// ----
// step: unusedPruner
//
// {
// pop(f())
// function f() -> y
// { sstore(1, sload(1)) }
// }