From e0b1d8b9bd149cd7c8634caacf4f896dae082411 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 29 Jun 2020 19:32:32 +0200 Subject: [PATCH] Make DataFlowAnalyzer aware of storage / memory slot after sload / mload. --- Changelog.md | 1 + libyul/optimiser/DataFlowAnalyzer.cpp | 37 +++++++++++++++++++ libyul/optimiser/DataFlowAnalyzer.h | 9 +++++ .../loadResolver/double_mload.yul | 13 +++++++ .../double_mload_with_other_reassignment.yul | 16 ++++++++ .../double_mload_with_reassignment.yul | 16 ++++++++ .../merge_mload_with_known_distance.yul | 19 ++++++++++ .../loadResolver/merge_mload_with_rewrite.yul | 22 +++++++++++ .../merge_mload_without_rewrite.yul | 19 ++++++++++ .../loadResolver/mload_self.yul | 15 ++++++++ .../loadResolver/multi_sload_loop.yul | 30 +++++++++++++++ 11 files changed, 197 insertions(+) create mode 100644 test/libyul/yulOptimizerTests/loadResolver/double_mload.yul create mode 100644 test/libyul/yulOptimizerTests/loadResolver/double_mload_with_other_reassignment.yul create mode 100644 test/libyul/yulOptimizerTests/loadResolver/double_mload_with_reassignment.yul create mode 100644 test/libyul/yulOptimizerTests/loadResolver/merge_mload_with_known_distance.yul create mode 100644 test/libyul/yulOptimizerTests/loadResolver/merge_mload_with_rewrite.yul create mode 100644 test/libyul/yulOptimizerTests/loadResolver/merge_mload_without_rewrite.yul create mode 100644 test/libyul/yulOptimizerTests/loadResolver/mload_self.yul create mode 100644 test/libyul/yulOptimizerTests/loadResolver/multi_sload_loop.yul diff --git a/Changelog.md b/Changelog.md index 9e9353c4e..4a590a5f3 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,6 +9,7 @@ Compiler Features: * NatSpec: Inherit tags from unique base if derived function does not provide any. * Commandline Interface: Prevent some incompatible commandline options from being used together. * NatSpec: Support NatSpec comments on events. + * Yul Optimizer: Store knowledge about storage / memory after ``a := sload(x)`` / ``a := mload(x)``. Bugfixes: * NatSpec: Do not consider ``////`` and ``/***`` as NatSpec comments. diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index d78d7870d..6755500b3 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -253,6 +253,21 @@ void DataFlowAnalyzer::handleAssignment(set const& _variables, Expres // assignment to slot contents denoted by "name" m_memory.eraseValue(name); } + + if (_value && _variables.size() == 1) + { + YulString variable = *_variables.begin(); + if (!movableChecker.referencedVariables().count(variable)) + { + // This might erase additional knowledge about the slot. + // On the other hand, if we knew the value in the slot + // already, then the sload() / mload() would have been replaced by a variable anyway. + if (auto key = isSimpleLoad(evmasm::Instruction::MLOAD, *_value)) + m_memory.set(*key, variable); + else if (auto key = isSimpleLoad(evmasm::Instruction::SLOAD, *_value)) + m_storage.set(*key, variable); + } + } } void DataFlowAnalyzer::pushScope(bool _functionScope) @@ -401,3 +416,25 @@ std::optional> DataFlowAnalyzer::isSimpleStore( return {}; } +std::optional DataFlowAnalyzer::isSimpleLoad( + evmasm::Instruction _load, + Expression const& _expression +) const +{ + yulAssert( + _load == evmasm::Instruction::MLOAD || + _load == evmasm::Instruction::SLOAD, + "" + ); + if (holds_alternative(_expression)) + { + FunctionCall const& funCall = std::get(_expression); + if (EVMDialect const* dialect = dynamic_cast(&m_dialect)) + if (auto const* builtin = dialect->builtin(funCall.functionName.name)) + if (builtin->instruction == _load) + if (holds_alternative(funCall.arguments.at(0))) + return std::get(funCall.arguments.at(0)).name; + } + return {}; +} + diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index d17ad4632..d3cb8d280 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -142,11 +142,20 @@ protected: /// Returns true iff the variable is in scope. bool inScope(YulString _variableName) const; + /// Checks if the statement is sstore(a, b) / mstore(a, b) + /// where a and b are variables and returns these variables in that case. std::optional> isSimpleStore( evmasm::Instruction _store, ExpressionStatement const& _statement ) const; + /// Checks if the expression is sload(a) / mload(a) + /// where a is a variable and returns the variable in that case. + std::optional isSimpleLoad( + evmasm::Instruction _load, + Expression const& _expression + ) const; + Dialect const& m_dialect; /// Side-effects of user-defined functions. Worst-case side-effects are assumed /// if this is not provided or the function is not found. diff --git a/test/libyul/yulOptimizerTests/loadResolver/double_mload.yul b/test/libyul/yulOptimizerTests/loadResolver/double_mload.yul new file mode 100644 index 000000000..b75f9c07b --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/double_mload.yul @@ -0,0 +1,13 @@ +{ + let x := calldataload(0) + let a := mload(x) + let b := mload(x) + sstore(a, b) +} +// ---- +// step: loadResolver +// +// { +// let a := mload(calldataload(0)) +// sstore(a, a) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/double_mload_with_other_reassignment.yul b/test/libyul/yulOptimizerTests/loadResolver/double_mload_with_other_reassignment.yul new file mode 100644 index 000000000..19d217423 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/double_mload_with_other_reassignment.yul @@ -0,0 +1,16 @@ +{ + let x := calldataload(0) + let a := mload(x) + x := 7 + let b := mload(x) + sstore(a, b) +} +// ---- +// step: loadResolver +// +// { +// let x := calldataload(0) +// let a := mload(x) +// x := 7 +// sstore(a, mload(x)) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/double_mload_with_reassignment.yul b/test/libyul/yulOptimizerTests/loadResolver/double_mload_with_reassignment.yul new file mode 100644 index 000000000..b5c2f1126 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/double_mload_with_reassignment.yul @@ -0,0 +1,16 @@ +{ + let x := calldataload(0) + let a := mload(x) + a := 7 + let b := mload(x) + sstore(a, b) +} +// ---- +// step: loadResolver +// +// { +// let x := calldataload(0) +// let a := mload(x) +// a := 7 +// sstore(a, mload(x)) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/merge_mload_with_known_distance.yul b/test/libyul/yulOptimizerTests/loadResolver/merge_mload_with_known_distance.yul new file mode 100644 index 000000000..cde3ec0e6 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/merge_mload_with_known_distance.yul @@ -0,0 +1,19 @@ +{ + let x := mload(calldataload(0)) + if calldataload(1) { + mstore(add(calldataload(0), 0x20), 1) + } + let t := mload(add(calldataload(0), 0x20)) + let q := mload(calldataload(0)) + sstore(t, q) +} +// ---- +// step: loadResolver +// +// { +// let _2 := calldataload(0) +// let x := mload(_2) +// let _3 := 1 +// if calldataload(_3) { mstore(add(_2, 0x20), _3) } +// sstore(mload(add(_2, 0x20)), x) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/merge_mload_with_rewrite.yul b/test/libyul/yulOptimizerTests/loadResolver/merge_mload_with_rewrite.yul new file mode 100644 index 000000000..5c09b9b43 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/merge_mload_with_rewrite.yul @@ -0,0 +1,22 @@ +{ + let b := mload(2) + if calldataload(1) { + mstore(2, 7) + // Re-writing the old value, should allow to eliminate the load below. + mstore(2, b) + } + sstore(0, mload(2)) +} +// ---- +// step: loadResolver +// +// { +// let _1 := 2 +// let b := mload(_1) +// if calldataload(1) +// { +// mstore(_1, 7) +// mstore(_1, b) +// } +// sstore(0, b) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/merge_mload_without_rewrite.yul b/test/libyul/yulOptimizerTests/loadResolver/merge_mload_without_rewrite.yul new file mode 100644 index 000000000..f6d0bdc7d --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/merge_mload_without_rewrite.yul @@ -0,0 +1,19 @@ +{ + let b := mload(2) + sstore(0, b) + if calldataload(1) { + mstore(2, 7) + } + sstore(0, mload(2)) +} +// ---- +// step: loadResolver +// +// { +// let _1 := 2 +// let b := mload(_1) +// let _2 := 0 +// sstore(_2, b) +// if calldataload(1) { mstore(_1, 7) } +// sstore(_2, mload(_1)) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/mload_self.yul b/test/libyul/yulOptimizerTests/loadResolver/mload_self.yul new file mode 100644 index 000000000..e299bcbb9 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/mload_self.yul @@ -0,0 +1,15 @@ +{ + let x := calldataload(0) + x := mload(x) + let y := mload(x) + sstore(0, y) +} +// ---- +// step: loadResolver +// +// { +// let _1 := 0 +// let x := calldataload(_1) +// x := mload(x) +// sstore(_1, mload(x)) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/multi_sload_loop.yul b/test/libyul/yulOptimizerTests/loadResolver/multi_sload_loop.yul new file mode 100644 index 000000000..9aecad529 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/multi_sload_loop.yul @@ -0,0 +1,30 @@ +{ + let x := calldataload(0) + let len := sload(x) + let sum + for { let i := 0} lt(i, sload(x)) { i := add(i, 1) } { + let p := add(x, add(i, 1)) + if gt(p, sload(x)) { revert(0, 0) } + sum := add(sum, sload(p)) + } + mstore(0, sum) + return(0, 0x20) +} +// ---- +// step: loadResolver +// +// { +// let _1 := 0 +// let x := calldataload(_1) +// let len := sload(x) +// let sum +// let i := _1 +// for { } lt(i, len) { i := add(i, 1) } +// { +// let p := add(add(x, i), 1) +// if gt(p, len) { revert(_1, _1) } +// sum := add(sum, sload(p)) +// } +// mstore(_1, sum) +// return(_1, 0x20) +// }