diff --git a/libevmcore/Assembly.cpp b/libevmcore/Assembly.cpp index ab9d76911..bd504dc22 100644 --- a/libevmcore/Assembly.cpp +++ b/libevmcore/Assembly.cpp @@ -288,18 +288,6 @@ inline bool matches(AssemblyItemsConstRef _a, AssemblyItemsConstRef _b) return true; } -//@todo this has to move to a special optimizer class soon -template -unsigned bytesRequiredBySlice(Iterator _begin, Iterator _end) -{ - // this is only used in the optimizer, so we can provide a guess for the address length - unsigned addressLength = 4; - unsigned size = 0; - for (; _begin != _end; ++_begin) - size += _begin->bytesRequired(addressLength); - return size; -} - struct OptimiserChannel: public LogChannel { static const char* name() { return "OPT"; } static const int verbosity = 12; }; #define copt dev::LogOutputStream() @@ -315,8 +303,6 @@ Assembly& Assembly::optimise(bool _enable) { Instruction::OR, [](u256 a, u256 b)->u256{return a | b;} }, { Instruction::XOR, [](u256 a, u256 b)->u256{return a ^ b;} }, }; - std::vector> const c_identities = - { { Instruction::ADD, 0}, { Instruction::MUL, 1}, { Instruction::MOD, 0}, { Instruction::OR, 0}, { Instruction::XOR, 0} }; std::vector>> rules = { { { Push, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, @@ -334,17 +320,13 @@ Assembly& Assembly::optimise(bool _enable) rules.push_back({ { Push, Push, i.first }, [&](AssemblyItemsConstRef m) -> AssemblyItems { return { i.second(m[1].data(), m[0].data()) }; } }); rules.push_back({ { Push, i.first, Push, i.first }, [&](AssemblyItemsConstRef m) -> AssemblyItems { return { i.second(m[2].data(), m[0].data()), i.first }; } }); } - for (auto const& i: c_identities) - rules.push_back({{Push, i.first}, [&](AssemblyItemsConstRef m) -> AssemblyItems - { return m[0].data() == i.second ? AssemblyItems() : m.toVector(); }}); // jump to next instruction rules.push_back({ { PushTag, Instruction::JUMP, Tag }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].m_data == m[2].m_data) return {m[2]}; else return m.toVector(); }}); - copt << *this; - unsigned total = 0; for (unsigned count = 1; count > 0; total += count) { + copt << *this; count = 0; copt << "Performing common subexpression elimination..."; @@ -353,11 +335,22 @@ Assembly& Assembly::optimise(bool _enable) CommonSubexpressionEliminator eliminator; auto orig = iter; iter = eliminator.feedItems(iter, m_items.end()); - AssemblyItems optItems = eliminator.getOptimizedItems(); - copt << "Old size: " << (iter - orig) << ", new size: " << optItems.size(); - if (optItems.size() < size_t(iter - orig)) + AssemblyItems optItems; + bool shouldReplace = false; + try + { + optItems = eliminator.getOptimizedItems(); + shouldReplace = (optItems.size() < size_t(iter - orig)); + } + catch (StackTooDeepException const&) + { + // This might happen if the opcode reconstruction is not as efficient + // as the hand-crafted code. + } + + if (shouldReplace) { - // replace items + copt << "Old size: " << (iter - orig) << ", new size: " << optItems.size(); count++; for (auto moveIter = optItems.begin(); moveIter != optItems.end(); ++orig, ++moveIter) *orig = move(*moveIter); diff --git a/libevmcore/CommonSubexpressionEliminator.cpp b/libevmcore/CommonSubexpressionEliminator.cpp index 8102721cc..65aac74f1 100644 --- a/libevmcore/CommonSubexpressionEliminator.cpp +++ b/libevmcore/CommonSubexpressionEliminator.cpp @@ -157,10 +157,8 @@ ExpressionClasses::Id CommonSubexpressionEliminator::getStackElement(int _stackH return m_stackElements[make_pair(_stackHeight, nextSequence - 1)]; // Stack element not found (not assigned yet), create new equivalence class. - if (_stackHeight > 0) - BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Stack element accessed before assignment.")); - if (_stackHeight <= -16) - BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Stack too deep.")); + assertThrow(_stackHeight <= 0, OptimizerException, "Stack element accessed before assignment."); + assertThrow(_stackHeight > -16, StackTooDeepException, ""); // This is a special assembly item that refers to elements pre-existing on the initial stack. return m_stackElements[make_pair(_stackHeight, nextSequence)] = m_expressionClasses.find(AssemblyItem(dupInstruction(1 - _stackHeight))); @@ -423,7 +421,8 @@ bool CSECodeGenerator::removeStackTopIfPossible() void CSECodeGenerator::appendDup(int _fromPosition) { int nr = 1 + m_stackHeight - _fromPosition; - assertThrow(1 <= nr && nr <= 16, OptimizerException, "Stack too deep."); + assertThrow(nr <= 16, StackTooDeepException, "Stack too deep."); + assertThrow(1 <= nr, OptimizerException, "Invalid stack access."); m_generatedItems.push_back(AssemblyItem(dupInstruction(nr))); m_stackHeight++; m_stack[m_stackHeight] = m_stack[_fromPosition]; @@ -434,7 +433,8 @@ void CSECodeGenerator::appendSwapOrRemove(int _fromPosition) if (_fromPosition == m_stackHeight) return; int nr = m_stackHeight - _fromPosition; - assertThrow(1 <= nr && nr <= 16, OptimizerException, "Stack too deep."); + assertThrow(nr <= 16, StackTooDeepException, "Stack too deep."); + assertThrow(1 <= nr, OptimizerException, "Invalid stack access."); m_generatedItems.push_back(AssemblyItem(swapInstruction(nr))); // The value of a class can be present in multiple locations on the stack. We only update the // "canonical" one that is tracked by m_classPositions diff --git a/libevmcore/Exceptions.h b/libevmcore/Exceptions.h index fad972179..fa3c19f13 100644 --- a/libevmcore/Exceptions.h +++ b/libevmcore/Exceptions.h @@ -32,6 +32,7 @@ struct AssemblyException: virtual Exception {}; struct InvalidDeposit: virtual AssemblyException {}; struct InvalidOpcode: virtual AssemblyException {}; struct OptimizerException: virtual AssemblyException {}; +struct StackTooDeepException: virtual OptimizerException {}; } } diff --git a/libevmcore/ExpressionClasses.cpp b/libevmcore/ExpressionClasses.cpp index 5d7b3e11b..eebc98f66 100644 --- a/libevmcore/ExpressionClasses.cpp +++ b/libevmcore/ExpressionClasses.cpp @@ -22,6 +22,9 @@ */ #include +#include +#include +#include #include #include #include @@ -31,22 +34,26 @@ using namespace dev; using namespace dev::eth; -ExpressionClasses::Id ExpressionClasses::find(AssemblyItem const& _item, Ids const& _arguments) +bool ExpressionClasses::Expression::operator<(const ExpressionClasses::Expression& _other) const { - // TODO: do a clever search, i.e. - // - check for the presence of constants in the argument classes and do arithmetic - // - check whether the two items are equal for a SUB instruction - // - check whether 0 or 1 is in one of the classes for a MUL + auto type = item->type(); + auto otherType = _other.item->type(); + return std::tie(type, item->data(), arguments) < + std::tie(otherType, _other.item->data(), _other.arguments); +} +ExpressionClasses::Id ExpressionClasses::find(AssemblyItem const& _item, Ids const& _arguments) +{ Expression exp; exp.item = &_item; exp.arguments = _arguments; + if (SemanticInformation::isCommutativeOperation(_item)) sort(exp.arguments.begin(), exp.arguments.end()); - //@todo use a data structure that allows better searches + //@todo store all class members (not only the representatives) in an efficient data structure to search here for (Expression const& e: m_representatives) - if (std::tie(*e.item, e.arguments) == std::tie(*exp.item, exp.arguments)) + if (!(e < exp || exp < e)) return e.id; if (SemanticInformation::isDupInstruction(_item)) @@ -55,56 +62,175 @@ ExpressionClasses::Id ExpressionClasses::find(AssemblyItem const& _item, Ids con m_spareAssemblyItem.push_back(make_shared(_item)); exp.item = m_spareAssemblyItem.back().get(); } - else if (_item.type() == Operation) + + ExpressionClasses::Id id = tryToSimplify(exp); + if (id < m_representatives.size()) + return id; + + exp.id = m_representatives.size(); + m_representatives.push_back(exp); + return exp.id; +} + +ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, bool _secondRun) +{ + if (_expr.item->type() != Operation) + return -1; + + // @todo: + // ISZERO ISZERO + // associative operations (as done in Assembly.cpp) + // 2 * x == x + x + + Id arg1; + Id arg2; + Id arg3; + u256 data1; + u256 data2; + u256 data3; + switch (_expr.arguments.size()) { - //@todo try to avoid having to do this multiple times by storing not only one representative of - // an equivalence class + default: + arg3 = _expr.arguments.at(2); + data3 = representative(arg3).item->data(); + case 2: + arg2 = _expr.arguments.at(1); + data2 = representative(arg2).item->data(); + case 1: + arg1 = _expr.arguments.at(0); + data1 = representative(arg1).item->data(); + case 0: + break; + } - // constant folding - auto isConstant = [this](Id eqc) { return representative(eqc).item->match(Push); }; - if (exp.arguments.size() == 2 && all_of(exp.arguments.begin(), exp.arguments.end(), isConstant)) + /** + * Simplification rule. If _strict is false, Push or a constant matches any constant, + * otherwise Push matches "0" and a constant matches itself. + * "UndefinedItem" matches any expression, but all of them must be equal inside one rule. + */ + struct Rule + { + Rule(AssemblyItems const& _pattern, bool _strict, function const& _action): + pattern(_pattern), + assemblyItemAction(_action), + strict(_strict) + {} + Rule(AssemblyItems const& _pattern, function _action): + Rule(_pattern, false, _action) + {} + Rule(AssemblyItems const& _pattern, bool _strict, function const& _action): + pattern(_pattern), + idAction(_action), + strict(_strict) + {} + Rule(AssemblyItems const& _pattern, function _action): + Rule(_pattern, false, _action) + {} + bool matches(ExpressionClasses const& _classes, Expression const& _expr) const { - auto signextend = [](u256 a, u256 b) -> u256 - { - if (a >= 31) - return b; - unsigned testBit = unsigned(a) * 8 + 7; - u256 mask = (u256(1) << testBit) - 1; - return boost::multiprecision::bit_test(b, testBit) ? b | ~mask : b & mask; - }; - map> const arithmetics = + if (!_expr.item->match(pattern.front())) + return false; + assertThrow(_expr.arguments.size() == pattern.size() - 1, OptimizerException, ""); + Id argRequiredToBeEqual(-1); + for (size_t i = 1; i < pattern.size(); ++i) { - { Instruction::SUB, [](u256 a, u256 b) -> u256 {return a - b; } }, - { Instruction::DIV, [](u256 a, u256 b) -> u256 {return b == 0 ? 0 : a / b; } }, - { Instruction::SDIV, [](u256 a, u256 b) -> u256 { return b == 0 ? 0 : s2u(u2s(a) / u2s(b)); } }, - { Instruction::MOD, [](u256 a, u256 b) -> u256 { return b == 0 ? 0 : a % b; } }, - { Instruction::SMOD, [](u256 a, u256 b) -> u256 { return b == 0 ? 0 : s2u(u2s(a) % u2s(b)); } }, - { Instruction::EXP, [](u256 a, u256 b) -> u256 { return (u256)boost::multiprecision::powm(bigint(a), bigint(b), bigint(1) << 256); } }, - { Instruction::SIGNEXTEND, signextend }, - { Instruction::LT, [](u256 a, u256 b) -> u256 { return a < b ? 1 : 0; } }, - { Instruction::GT, [](u256 a, u256 b) -> u256 { return a > b ? 1 : 0; } }, - { Instruction::SLT, [](u256 a, u256 b) -> u256 { return u2s(a) < u2s(b) ? 1 : 0; } }, - { Instruction::SGT, [](u256 a, u256 b) -> u256 { return u2s(a) > u2s(b) ? 1 : 0; } }, - { Instruction::EQ, [](u256 a, u256 b) -> u256 { return a == b ? 1 : 0; } }, - { Instruction::ADD, [](u256 a, u256 b) -> u256 { return a + b; } }, - { Instruction::MUL, [](u256 a, u256 b) -> u256 { return a * b; } }, - { Instruction::AND, [](u256 a, u256 b) -> u256 { return a & b; } }, - { Instruction::OR, [](u256 a, u256 b) -> u256 { return a | b; } }, - { Instruction::XOR, [](u256 a, u256 b) -> u256 { return a ^ b; } }, - }; - if (arithmetics.count(_item.instruction())) + Id arg = _expr.arguments[i - 1]; + if (pattern[i].type() == UndefinedItem) + { + if (argRequiredToBeEqual == Id(-1)) + argRequiredToBeEqual = arg; + else if (argRequiredToBeEqual != arg) + return false; + } + else + { + AssemblyItem const& argItem = *_classes.representative(arg).item; + if (strict && argItem != pattern[i]) + return false; + else if (!strict && !argItem.match(pattern[i])) + return false; + } + } + return true; + } + + AssemblyItems pattern; + function assemblyItemAction; + function idAction; + bool strict; + }; + + vector c_singleLevel{ + // arithmetics on constants involving only stack variables + {{Instruction::ADD, Push, Push}, [&]{ return data1 + data2; }}, + {{Instruction::MUL, Push, Push}, [&]{ return data1 * data2; }}, + {{Instruction::SUB, Push, Push}, [&]{ return data1 - data2; }}, + {{Instruction::DIV, Push, Push}, [&]{ return data2 == 0 ? 0 : data1 / data2; }}, + {{Instruction::SDIV, Push, Push}, [&]{ return data2 == 0 ? 0 : s2u(u2s(data1) / u2s(data2)); }}, + {{Instruction::MOD, Push, Push}, [&]{ return data2 == 0 ? 0 : data1 % data2; }}, + {{Instruction::SMOD, Push, Push}, [&]{ return data2 == 0 ? 0 : s2u(u2s(data1) % u2s(data2)); }}, + {{Instruction::EXP, Push, Push}, [&]{ return u256(boost::multiprecision::powm(bigint(data1), bigint(data2), bigint(1) << 256)); }}, + {{Instruction::NOT, Push}, [&]{ return ~data1; }}, + {{Instruction::LT, Push, Push}, [&]() -> u256 { return data1 < data2 ? 1 : 0; }}, + {{Instruction::GT, Push, Push}, [&]() -> u256 { return data1 > data2 ? 1 : 0; }}, + {{Instruction::SLT, Push, Push}, [&]() -> u256 { return u2s(data1) < u2s( data2) ? 1 : 0; }}, + {{Instruction::SGT, Push, Push}, [&]() -> u256 { return u2s(data1) > u2s( data2) ? 1 : 0; }}, + {{Instruction::EQ, Push, Push}, [&]() -> u256 { return data1 == data2 ? 1 : 0; }}, + {{Instruction::ISZERO, Push}, [&]() -> u256 { return data1 == 0 ? 1 : 0; }}, + {{Instruction::AND, Push, Push}, [&]{ return data1 & data2; }}, + {{Instruction::OR, Push, Push}, [&]{ return data1 | data2; }}, + {{Instruction::XOR, Push, Push}, [&]{ return data1 ^ data2; }}, + {{Instruction::BYTE, Push, Push}, [&]{ return data1 >= 32 ? 0 : (data2 >> unsigned(8 * (31 - data1))) & 0xff; }}, + {{Instruction::ADDMOD, Push, Push, Push}, [&]{ return data3 == 0 ? 0 : u256((bigint(data1) + bigint(data2)) % data3); }}, + {{Instruction::MULMOD, Push, Push, Push}, [&]{ return data3 == 0 ? 0 : u256((bigint(data1) * bigint(data2)) % data3); }}, + {{Instruction::MULMOD, Push, Push, Push}, [&]{ return data1 * data2; }}, + {{Instruction::SIGNEXTEND, Push, Push}, [&]{ + if (data1 >= 31) + return data2; + unsigned testBit = unsigned(data1) * 8 + 7; + u256 mask = (u256(1) << testBit) - 1; + return u256(boost::multiprecision::bit_test(data2, testBit) ? data2 | ~mask : data2 & mask); + }}, + {{Instruction::ADD, UndefinedItem, u256(0)}, true, [&]{ return arg1; }}, + {{Instruction::MUL, UndefinedItem, u256(1)}, true, [&]{ return arg1; }}, + {{Instruction::OR, UndefinedItem, u256(0)}, true, [&]{ return arg1; }}, + {{Instruction::XOR, UndefinedItem, u256(0)}, true, [&]{ return arg1; }}, + {{Instruction::AND, UndefinedItem, ~u256(0)}, true, [&]{ return arg1; }}, + {{Instruction::MUL, UndefinedItem, u256(0)}, true, [&]{ return u256(0); }}, + {{Instruction::DIV, UndefinedItem, u256(0)}, true, [&]{ return u256(0); }}, + {{Instruction::MOD, UndefinedItem, u256(0)}, true, [&]{ return u256(0); }}, + {{Instruction::MOD, u256(0), UndefinedItem}, true, [&]{ return u256(0); }}, + {{Instruction::AND, UndefinedItem, u256(0)}, true, [&]{ return u256(0); }}, + {{Instruction::OR, UndefinedItem, ~u256(0)}, true, [&]{ return ~u256(0); }}, + {{Instruction::AND, UndefinedItem, UndefinedItem}, true, [&]{ return arg1; }}, + {{Instruction::OR, UndefinedItem, UndefinedItem}, true, [&]{ return arg1; }}, + {{Instruction::SUB, UndefinedItem, UndefinedItem}, true, [&]{ return u256(0); }}, + {{Instruction::EQ, UndefinedItem, UndefinedItem}, true, [&]{ return u256(1); }}, + {{Instruction::LT, UndefinedItem, UndefinedItem}, true, [&]{ return u256(0); }}, + {{Instruction::SLT, UndefinedItem, UndefinedItem}, true, [&]{ return u256(0); }}, + {{Instruction::GT, UndefinedItem, UndefinedItem}, true, [&]{ return u256(0); }}, + {{Instruction::SGT, UndefinedItem, UndefinedItem}, true, [&]{ return u256(0); }}, + {{Instruction::MOD, UndefinedItem, UndefinedItem}, true, [&]{ return u256(0); }}, + }; + + for (auto const& rule: c_singleLevel) + if (rule.matches(*this, _expr)) + { + if (rule.idAction) + return rule.idAction(); + else { - u256 result = arithmetics.at(_item.instruction())( - representative(exp.arguments[0]).item->data(), - representative(exp.arguments[1]).item->data() - ); - m_spareAssemblyItem.push_back(make_shared(result)); + m_spareAssemblyItem.push_back(make_shared(rule.assemblyItemAction())); return find(*m_spareAssemblyItem.back()); } } + + if (!_secondRun && _expr.arguments.size() == 2 && SemanticInformation::isCommutativeOperation(*_expr.item)) + { + Expression expr = _expr; + swap(expr.arguments[0], expr.arguments[1]); + return tryToSimplify(expr, true); } - exp.id = m_representatives.size(); - m_representatives.push_back(exp); - return exp.id; -} + return -1; +} diff --git a/libevmcore/ExpressionClasses.h b/libevmcore/ExpressionClasses.h index 89485214c..71fc96109 100644 --- a/libevmcore/ExpressionClasses.h +++ b/libevmcore/ExpressionClasses.h @@ -24,7 +24,7 @@ #pragma once #include -#include +#include #include namespace dev @@ -49,6 +49,7 @@ public: Id id; AssemblyItem const* item; Ids arguments; + bool operator<(Expression const& _other) const; }; /// Retrieves the id of the expression equivalence class resulting from the given item applied to the @@ -60,6 +61,10 @@ public: Id size() const { return m_representatives.size(); } private: + /// Tries to simplify the given expression. + /// @returns its class if it possible or Id(-1) otherwise. + /// @param _secondRun is set to true for the second run where arguments of commutative expressions are reversed + Id tryToSimplify(Expression const& _expr, bool _secondRun = false); /// Expression equivalence class representatives - we only store one item of an equivalence. std::vector m_representatives; diff --git a/test/SolidityOptimizer.cpp b/test/SolidityOptimizer.cpp index 9c6a4e361..2ced97430 100644 --- a/test/SolidityOptimizer.cpp +++ b/test/SolidityOptimizer.cpp @@ -74,6 +74,14 @@ public: "\nOptimized: " + toHex(optimizedOutput)); } + void checkCSE(AssemblyItems const& _input, AssemblyItems const& _expectation) + { + eth::CommonSubexpressionEliminator cse; + BOOST_REQUIRE(cse.feedItems(_input.begin(), _input.end()) == _input.end()); + AssemblyItems output = cse.getOptimizedItems(); + BOOST_CHECK_EQUAL_COLLECTIONS(_expectation.begin(), _expectation.end(), output.begin(), output.end()); + } + protected: Address m_optimizedContract; Address m_nonOptimizedContract; @@ -199,61 +207,59 @@ BOOST_AUTO_TEST_CASE(cse_intermediate_swap) BOOST_AUTO_TEST_CASE(cse_negative_stack_access) { - eth::CommonSubexpressionEliminator cse; - AssemblyItems input{AssemblyItem(Instruction::DUP2), AssemblyItem(u256(0))}; - BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end()); - AssemblyItems output = cse.getOptimizedItems(); - BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); + AssemblyItems input{Instruction::DUP2, u256(0)}; + checkCSE(input, input); } BOOST_AUTO_TEST_CASE(cse_negative_stack_end) { - eth::CommonSubexpressionEliminator cse; - AssemblyItems input{ - AssemblyItem(Instruction::ADD) - }; - BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end()); - AssemblyItems output = cse.getOptimizedItems(); - BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); + AssemblyItems input{Instruction::ADD}; + checkCSE(input, input); } BOOST_AUTO_TEST_CASE(cse_intermediate_negative_stack) { - eth::CommonSubexpressionEliminator cse; - AssemblyItems input{ - AssemblyItem(Instruction::ADD), - AssemblyItem(u256(1)), - AssemblyItem(Instruction::DUP2) - }; - BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end()); - AssemblyItems output = cse.getOptimizedItems(); - BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); + AssemblyItems input{Instruction::ADD, u256(1), Instruction::DUP1}; + checkCSE(input, input); } BOOST_AUTO_TEST_CASE(cse_pop) { - eth::CommonSubexpressionEliminator cse; + checkCSE({Instruction::POP}, {Instruction::POP}); +} + +BOOST_AUTO_TEST_CASE(cse_unneeded_items) +{ AssemblyItems input{ - AssemblyItem(Instruction::POP) + Instruction::ADD, + Instruction::SWAP1, + Instruction::POP, + u256(7), + u256(8), }; - BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end()); - AssemblyItems output = cse.getOptimizedItems(); - BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); + checkCSE(input, input); } -BOOST_AUTO_TEST_CASE(cse_unneeded_items) +BOOST_AUTO_TEST_CASE(cse_invariants) { - eth::CommonSubexpressionEliminator cse; AssemblyItems input{ - AssemblyItem(Instruction::ADD), - AssemblyItem(Instruction::SWAP1), - AssemblyItem(Instruction::POP), - AssemblyItem(u256(7)), - AssemblyItem(u256(8)), + Instruction::DUP1, + Instruction::DUP1, + u256(0), + Instruction::OR, + Instruction::OR }; - BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end()); - AssemblyItems output = cse.getOptimizedItems(); - BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); + checkCSE(input, {Instruction::DUP1}); +} + +BOOST_AUTO_TEST_CASE(cse_subself) +{ + checkCSE({Instruction::DUP1, Instruction::SUB}, {Instruction::POP, u256(0)}); +} + +BOOST_AUTO_TEST_CASE(cse_subother) +{ + checkCSE({Instruction::SUB}, {Instruction::SUB}); } BOOST_AUTO_TEST_SUITE_END()