From 491d6d3e0c131bcafc10d4bc86df0d6833955cd4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 17 Jan 2018 17:56:33 +0100 Subject: [PATCH 01/11] Move out the rule list. --- libevmasm/RuleList.h | 214 ++++++++++++++++++++++++++++++ libevmasm/SimplificationRules.cpp | 166 +---------------------- 2 files changed, 217 insertions(+), 163 deletions(-) create mode 100644 libevmasm/RuleList.h diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h new file mode 100644 index 000000000..d95b014d9 --- /dev/null +++ b/libevmasm/RuleList.h @@ -0,0 +1,214 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @date 2018 + * Templatized list of simplification rules. + */ + +#pragma once + +#include +#include + +#include + +namespace dev +{ +namespace solidity +{ + +template S divWorkaround(S const& _a, S const& _b) +{ + return (S)(bigint(_a) / bigint(_b)); +} + +template S modWorkaround(S const& _a, S const& _b) +{ + return (S)(bigint(_a) % bigint(_b)); +} + +/// @returns a list of simplification rules given certain match placeholders. +/// A, B and C should represent constants, X and Y arbitrary expressions. +/// As the simplification can remove instructions, care has to be taken if multiple +/// non-constant expressions are used. The simplifications should not change the +/// order of operations, though. +template +std::vector>> simplificationRuleList( + Pattern A, + Pattern B, + Pattern C, + Pattern X, + Pattern Y +) +{ + std::vector>> rules; + rules += std::vector>>{ + // arithmetics on constants + {{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }}, + {{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }}, + {{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }}, + {{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }}, + {{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }}, + {{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }}, + {{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }}, + {{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }}, + {{Instruction::NOT, {A}}, [=]{ return ~A.d(); }}, + {{Instruction::LT, {A, B}}, [=]() -> u256 { return A.d() < B.d() ? 1 : 0; }}, + {{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }}, + {{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }}, + {{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }}, + {{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }}, + {{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }}, + {{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }}, + {{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }}, + {{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }}, + {{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }}, + {{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }}, + {{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }}, + {{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }}, + {{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 { + if (A.d() >= 31) + return B.d(); + unsigned testBit = unsigned(A.d()) * 8 + 7; + u256 mask = (u256(1) << testBit) - 1; + return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask); + }}, + + // invariants involving known constants (commutative instructions will be checked with swapped operants too) + {{Instruction::ADD, {X, 0}}, [=]{ return X; }}, + {{Instruction::SUB, {X, 0}}, [=]{ return X; }}, + {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::MUL, {X, 1}}, [=]{ return X; }}, + {{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::DIV, {0, X}}, [=]{ return u256(0); }}, + {{Instruction::DIV, {X, 1}}, [=]{ return X; }}, + {{Instruction::SDIV, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::SDIV, {0, X}}, [=]{ return u256(0); }}, + {{Instruction::SDIV, {X, 1}}, [=]{ return X; }}, + {{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }}, + {{Instruction::AND, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::OR, {X, 0}}, [=]{ return X; }}, + {{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }}, + {{Instruction::XOR, {X, 0}}, [=]{ return X; }}, + {{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::MOD, {0, X}}, [=]{ return u256(0); }}, + {{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } }, + + // operations involving an expression and itself + {{Instruction::AND, {X, X}}, [=]{ return X; }}, + {{Instruction::OR, {X, X}}, [=]{ return X; }}, + {{Instruction::XOR, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::SUB, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::EQ, {X, X}}, [=]{ return u256(1); }}, + {{Instruction::LT, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::SLT, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::GT, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::SGT, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }}, + + // logical instruction combinations + {{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }}, + {{Instruction::XOR, {{{X}, {Instruction::XOR, {X, Y}}}}}, [=]{ return Y; }}, + {{Instruction::OR, {{{X}, {Instruction::AND, {X, Y}}}}}, [=]{ return X; }}, + {{Instruction::AND, {{{X}, {Instruction::OR, {X, Y}}}}}, [=]{ return X; }}, + {{Instruction::AND, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return u256(0); }}, + {{Instruction::OR, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return ~u256(0); }}, + }; + + // Double negation of opcodes with binary result + for (auto const& op: std::vector{ + Instruction::EQ, + Instruction::LT, + Instruction::SLT, + Instruction::GT, + Instruction::SGT + }) + rules.push_back({ + {Instruction::ISZERO, {{Instruction::ISZERO, {{op, {X, Y}}}}}}, + [=]() -> Pattern { return {op, {X, Y}}; } + }); + + rules.push_back({ + {Instruction::ISZERO, {{Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}}}, + [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } + }); + + rules.push_back({ + {Instruction::ISZERO, {{Instruction::XOR, {X, Y}}}}, + [=]() -> Pattern { return { Instruction::EQ, {X, Y} }; } + }); + + // Associative operations + for (auto const& opFun: std::vector>>{ + {Instruction::ADD, std::plus()}, + {Instruction::MUL, std::multiplies()}, + {Instruction::AND, std::bit_and()}, + {Instruction::OR, std::bit_or()}, + {Instruction::XOR, std::bit_xor()} + }) + { + auto op = opFun.first; + auto fun = opFun.second; + // Moving constants to the outside, order matters here! + // we need actions that return expressions (or patterns?) here, and we need also reversed rules + // (X+A)+B -> X+(A+B) + rules += std::vector>>{{ + {op, {{op, {X, A}}, B}}, + [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } + }, { + // X+(Y+A) -> (X+Y)+A + {op, {{op, {X, A}}, Y}}, + [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } + }, { + // For now, we still need explicit commutativity for the inner pattern + {op, {{op, {A, X}}, B}}, + [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } + }, { + {op, {{op, {A, X}}, Y}}, + [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } + }}; + } + + // move constants across subtractions + rules += std::vector>>{ + { + // X - A -> X + (-A) + {Instruction::SUB, {X, A}}, + [=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; } + }, { + // (X + A) - Y -> (X - Y) + A + {Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } + }, { + // (A + X) - Y -> (X - Y) + A + {Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } + }, { + // X - (Y + A) -> (X - Y) + (-A) + {Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } + }, { + // X - (A + Y) -> (X - Y) + (-A) + {Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } + } + }; + return rules; +} + +} +} diff --git a/libevmasm/SimplificationRules.cpp b/libevmasm/SimplificationRules.cpp index e6c51f956..01cad9498 100644 --- a/libevmasm/SimplificationRules.cpp +++ b/libevmasm/SimplificationRules.cpp @@ -31,6 +31,8 @@ #include #include +#include + using namespace std; using namespace dev; using namespace dev::eth; @@ -64,16 +66,6 @@ void Rules::addRule(std::pair > const& _rule) m_rules[byte(_rule.first.instruction())].push_back(_rule); } -template S divWorkaround(S const& _a, S const& _b) -{ - return (S)(bigint(_a) / bigint(_b)); -} - -template S modWorkaround(S const& _a, S const& _b) -{ - return (S)(bigint(_a) % bigint(_b)); -} - Rules::Rules() { // Multiple occurences of one of these inside one rule must match the same equivalence class. @@ -84,165 +76,13 @@ Rules::Rules() // Anything. Pattern X; Pattern Y; - Pattern Z; A.setMatchGroup(1, m_matchGroups); B.setMatchGroup(2, m_matchGroups); C.setMatchGroup(3, m_matchGroups); X.setMatchGroup(4, m_matchGroups); Y.setMatchGroup(5, m_matchGroups); - Z.setMatchGroup(6, m_matchGroups); - addRules(vector>>{ - // arithmetics on constants - {{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }}, - {{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }}, - {{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }}, - {{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }}, - {{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }}, - {{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }}, - {{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }}, - {{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }}, - {{Instruction::NOT, {A}}, [=]{ return ~A.d(); }}, - {{Instruction::LT, {A, B}}, [=]() -> u256 { return A.d() < B.d() ? 1 : 0; }}, - {{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }}, - {{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }}, - {{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }}, - {{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }}, - {{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }}, - {{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }}, - {{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }}, - {{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }}, - {{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }}, - {{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }}, - {{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }}, - {{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }}, - {{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 { - if (A.d() >= 31) - return B.d(); - unsigned testBit = unsigned(A.d()) * 8 + 7; - u256 mask = (u256(1) << testBit) - 1; - return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask); - }}, - - // invariants involving known constants (commutative instructions will be checked with swapped operants too) - {{Instruction::ADD, {X, 0}}, [=]{ return X; }}, - {{Instruction::SUB, {X, 0}}, [=]{ return X; }}, - {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::MUL, {X, 1}}, [=]{ return X; }}, - {{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::DIV, {0, X}}, [=]{ return u256(0); }}, - {{Instruction::DIV, {X, 1}}, [=]{ return X; }}, - {{Instruction::SDIV, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::SDIV, {0, X}}, [=]{ return u256(0); }}, - {{Instruction::SDIV, {X, 1}}, [=]{ return X; }}, - {{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }}, - {{Instruction::AND, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::OR, {X, 0}}, [=]{ return X; }}, - {{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }}, - {{Instruction::XOR, {X, 0}}, [=]{ return X; }}, - {{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::MOD, {0, X}}, [=]{ return u256(0); }}, - {{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } }, - - // operations involving an expression and itself - {{Instruction::AND, {X, X}}, [=]{ return X; }}, - {{Instruction::OR, {X, X}}, [=]{ return X; }}, - {{Instruction::XOR, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::SUB, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::EQ, {X, X}}, [=]{ return u256(1); }}, - {{Instruction::LT, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::SLT, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::GT, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::SGT, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }}, - - // logical instruction combinations - {{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }}, - {{Instruction::XOR, {{{X}, {Instruction::XOR, {X, Y}}}}}, [=]{ return Y; }}, - {{Instruction::OR, {{{X}, {Instruction::AND, {X, Y}}}}}, [=]{ return X; }}, - {{Instruction::AND, {{{X}, {Instruction::OR, {X, Y}}}}}, [=]{ return X; }}, - {{Instruction::AND, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return u256(0); }}, - {{Instruction::OR, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return ~u256(0); }}, - }); - - // Double negation of opcodes with binary result - for (auto const& op: vector{ - Instruction::EQ, - Instruction::LT, - Instruction::SLT, - Instruction::GT, - Instruction::SGT - }) - addRule({ - {Instruction::ISZERO, {{Instruction::ISZERO, {{op, {X, Y}}}}}}, - [=]() -> Pattern { return {op, {X, Y}}; } - }); - - addRule({ - {Instruction::ISZERO, {{Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}}}, - [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } - }); - - addRule({ - {Instruction::ISZERO, {{Instruction::XOR, {X, Y}}}}, - [=]() -> Pattern { return { Instruction::EQ, {X, Y} }; } - }); - - // Associative operations - for (auto const& opFun: vector>>{ - {Instruction::ADD, plus()}, - {Instruction::MUL, multiplies()}, - {Instruction::AND, bit_and()}, - {Instruction::OR, bit_or()}, - {Instruction::XOR, bit_xor()} - }) - { - auto op = opFun.first; - auto fun = opFun.second; - // Moving constants to the outside, order matters here! - // we need actions that return expressions (or patterns?) here, and we need also reversed rules - // (X+A)+B -> X+(A+B) - addRules(vector>>{{ - {op, {{op, {X, A}}, B}}, - [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } - }, { - // X+(Y+A) -> (X+Y)+A - {op, {{op, {X, A}}, Y}}, - [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } - }, { - // For now, we still need explicit commutativity for the inner pattern - {op, {{op, {A, X}}, B}}, - [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } - }, { - {op, {{op, {A, X}}, Y}}, - [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } - }}); - } - - // move constants across subtractions - addRules(vector>>{ - { - // X - A -> X + (-A) - {Instruction::SUB, {X, A}}, - [=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; } - }, { - // (X + A) - Y -> (X - Y) + A - {Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}}, - [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } - }, { - // (A + X) - Y -> (X - Y) + A - {Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}}, - [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } - }, { - // X - (Y + A) -> (X - Y) + (-A) - {Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}}, - [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } - }, { - // X - (A + Y) -> (X - Y) + (-A) - {Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}}, - [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } - } - }); + addRules(simplificationRuleList(A, B, C, X, Y)); } Pattern::Pattern(Instruction _instruction, std::vector const& _arguments): From b8074cdf788ee1cae862929c0428a95cc5248269 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 17 Jan 2018 19:18:42 +0100 Subject: [PATCH 02/11] Add flag to indicate whether it can be applied to expressions with side-effects. --- libevmasm/ExpressionClasses.cpp | 2 +- libevmasm/RuleList.h | 169 ++++++++++++++++-------------- libevmasm/SimplificationRules.cpp | 10 +- libevmasm/SimplificationRules.h | 10 +- libjulia/optimiser/Utilities.h | 5 + 5 files changed, 109 insertions(+), 87 deletions(-) diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp index fc283b0bc..58c254b1d 100644 --- a/libevmasm/ExpressionClasses.cpp +++ b/libevmasm/ExpressionClasses.cpp @@ -202,7 +202,7 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, //cout << "with rule " << match->first.toString() << endl; //ExpressionTemplate t(match->second()); //cout << "to " << match->second().toString() << endl; - return rebuildExpression(ExpressionTemplate(match->second(), _expr.item->location())); + return rebuildExpression(ExpressionTemplate(std::get<1>(*match)(), _expr.item->location())); } if (!_secondRun && _expr.arguments.size() == 2 && SemanticInformation::isCommutativeOperation(*_expr.item)) diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index d95b014d9..70a3ef71b 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -26,6 +26,8 @@ #include +#include + namespace dev { namespace solidity @@ -43,11 +45,12 @@ template S modWorkaround(S const& _a, S const& _b) /// @returns a list of simplification rules given certain match placeholders. /// A, B and C should represent constants, X and Y arbitrary expressions. -/// As the simplification can remove instructions, care has to be taken if multiple -/// non-constant expressions are used. The simplifications should not change the -/// order of operations, though. +/// The third element in the tuple is a boolean flag that indicates whether +/// any non-constant elements in the pattern are removed by applying it. +/// The simplifications should neven change the order of evaluation of +/// arbitrary operations, though. template -std::vector>> simplificationRuleList( +std::vector, bool>> simplificationRuleList( Pattern A, Pattern B, Pattern C, @@ -55,78 +58,78 @@ std::vector>> simplificationRuleList Pattern Y ) { - std::vector>> rules; - rules += std::vector>>{ + std::vector, bool>> rules; + rules += std::vector, bool>>{ // arithmetics on constants - {{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }}, - {{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }}, - {{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }}, - {{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }}, - {{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }}, - {{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }}, - {{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }}, - {{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }}, - {{Instruction::NOT, {A}}, [=]{ return ~A.d(); }}, - {{Instruction::LT, {A, B}}, [=]() -> u256 { return A.d() < B.d() ? 1 : 0; }}, - {{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }}, - {{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }}, - {{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }}, - {{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }}, - {{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }}, - {{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }}, - {{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }}, - {{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }}, - {{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }}, - {{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }}, - {{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }}, - {{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }}, + {{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }, false}, + {{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }, false}, + {{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }, false}, + {{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }, false}, + {{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }, false}, + {{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }, false}, + {{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }, false}, + {{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }, false}, + {{Instruction::NOT, {A}}, [=]{ return ~A.d(); }, false}, + {{Instruction::LT, {A, B}}, [=]() -> u256 { return A.d() < B.d() ? 1 : 0; }, false}, + {{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }, false}, + {{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }, false}, + {{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }, false}, + {{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }, false}, + {{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }, false}, + {{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }, false}, + {{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }, false}, + {{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }, false}, + {{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }, false}, + {{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }, false}, + {{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }, false}, + {{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }, false}, {{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 { if (A.d() >= 31) return B.d(); unsigned testBit = unsigned(A.d()) * 8 + 7; u256 mask = (u256(1) << testBit) - 1; return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask); - }}, + }, false}, // invariants involving known constants (commutative instructions will be checked with swapped operants too) - {{Instruction::ADD, {X, 0}}, [=]{ return X; }}, - {{Instruction::SUB, {X, 0}}, [=]{ return X; }}, - {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::MUL, {X, 1}}, [=]{ return X; }}, - {{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::DIV, {0, X}}, [=]{ return u256(0); }}, - {{Instruction::DIV, {X, 1}}, [=]{ return X; }}, - {{Instruction::SDIV, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::SDIV, {0, X}}, [=]{ return u256(0); }}, - {{Instruction::SDIV, {X, 1}}, [=]{ return X; }}, - {{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }}, - {{Instruction::AND, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::OR, {X, 0}}, [=]{ return X; }}, - {{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }}, - {{Instruction::XOR, {X, 0}}, [=]{ return X; }}, - {{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }}, - {{Instruction::MOD, {0, X}}, [=]{ return u256(0); }}, - {{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } }, + {{Instruction::ADD, {X, 0}}, [=]{ return X; }, false}, + {{Instruction::SUB, {X, 0}}, [=]{ return X; }, false}, + {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }, true}, + {{Instruction::MUL, {X, 1}}, [=]{ return X; }, false}, + {{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }, true}, + {{Instruction::DIV, {0, X}}, [=]{ return u256(0); }, true}, + {{Instruction::DIV, {X, 1}}, [=]{ return X; }, false}, + {{Instruction::SDIV, {X, 0}}, [=]{ return u256(0); }, true}, + {{Instruction::SDIV, {0, X}}, [=]{ return u256(0); }, true}, + {{Instruction::SDIV, {X, 1}}, [=]{ return X; }, false}, + {{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }, false}, + {{Instruction::AND, {X, 0}}, [=]{ return u256(0); }, true}, + {{Instruction::OR, {X, 0}}, [=]{ return X; }, false}, + {{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }, true}, + {{Instruction::XOR, {X, 0}}, [=]{ return X; }, false}, + {{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }, true}, + {{Instruction::MOD, {0, X}}, [=]{ return u256(0); }, true}, + {{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; }, false }, // operations involving an expression and itself - {{Instruction::AND, {X, X}}, [=]{ return X; }}, - {{Instruction::OR, {X, X}}, [=]{ return X; }}, - {{Instruction::XOR, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::SUB, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::EQ, {X, X}}, [=]{ return u256(1); }}, - {{Instruction::LT, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::SLT, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::GT, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::SGT, {X, X}}, [=]{ return u256(0); }}, - {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::AND, {X, X}}, [=]{ return X; }, true}, + {{Instruction::OR, {X, X}}, [=]{ return X; }, true}, + {{Instruction::XOR, {X, X}}, [=]{ return u256(0); }, true}, + {{Instruction::SUB, {X, X}}, [=]{ return u256(0); }, true}, + {{Instruction::EQ, {X, X}}, [=]{ return u256(1); }, true}, + {{Instruction::LT, {X, X}}, [=]{ return u256(0); }, true}, + {{Instruction::SLT, {X, X}}, [=]{ return u256(0); }, true}, + {{Instruction::GT, {X, X}}, [=]{ return u256(0); }, true}, + {{Instruction::SGT, {X, X}}, [=]{ return u256(0); }, true}, + {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }, true}, // logical instruction combinations - {{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }}, - {{Instruction::XOR, {{{X}, {Instruction::XOR, {X, Y}}}}}, [=]{ return Y; }}, - {{Instruction::OR, {{{X}, {Instruction::AND, {X, Y}}}}}, [=]{ return X; }}, - {{Instruction::AND, {{{X}, {Instruction::OR, {X, Y}}}}}, [=]{ return X; }}, - {{Instruction::AND, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return u256(0); }}, - {{Instruction::OR, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return ~u256(0); }}, + {{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }, false}, + {{Instruction::XOR, {{{X}, {Instruction::XOR, {X, Y}}}}}, [=]{ return Y; }, true}, + {{Instruction::OR, {{{X}, {Instruction::AND, {X, Y}}}}}, [=]{ return X; }, true}, + {{Instruction::AND, {{{X}, {Instruction::OR, {X, Y}}}}}, [=]{ return X; }, true}, + {{Instruction::AND, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return u256(0); }, true}, + {{Instruction::OR, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return ~u256(0); }, true}, }; // Double negation of opcodes with binary result @@ -139,17 +142,20 @@ std::vector>> simplificationRuleList }) rules.push_back({ {Instruction::ISZERO, {{Instruction::ISZERO, {{op, {X, Y}}}}}}, - [=]() -> Pattern { return {op, {X, Y}}; } + [=]() -> Pattern { return {op, {X, Y}}; }, + false }); rules.push_back({ {Instruction::ISZERO, {{Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}}}, - [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } + [=]() -> Pattern { return {Instruction::ISZERO, {X}}; }, + false }); rules.push_back({ {Instruction::ISZERO, {{Instruction::XOR, {X, Y}}}}, - [=]() -> Pattern { return { Instruction::EQ, {X, Y} }; } + [=]() -> Pattern { return { Instruction::EQ, {X, Y} }; }, + false }); // Associative operations @@ -166,45 +172,54 @@ std::vector>> simplificationRuleList // Moving constants to the outside, order matters here! // we need actions that return expressions (or patterns?) here, and we need also reversed rules // (X+A)+B -> X+(A+B) - rules += std::vector>>{{ + rules += std::vector, bool>>{{ {op, {{op, {X, A}}, B}}, - [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } + [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }, + false }, { // X+(Y+A) -> (X+Y)+A {op, {{op, {X, A}}, Y}}, - [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } + [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }, + false }, { // For now, we still need explicit commutativity for the inner pattern {op, {{op, {A, X}}, B}}, - [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } + [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }, + false }, { {op, {{op, {A, X}}, Y}}, - [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } + [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }, + false }}; } // move constants across subtractions - rules += std::vector>>{ + rules += std::vector, bool>>{ { // X - A -> X + (-A) {Instruction::SUB, {X, A}}, - [=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; } + [=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; }, + false }, { // (X + A) - Y -> (X - Y) + A {Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}}, - [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; }, + false }, { // (A + X) - Y -> (X - Y) + A {Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}}, - [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; }, + false }, { // X - (Y + A) -> (X - Y) + (-A) {Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}}, - [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; }, + false }, { // X - (A + Y) -> (X - Y) + (-A) {Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}}, - [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; }, + false } }; return rules; diff --git a/libevmasm/SimplificationRules.cpp b/libevmasm/SimplificationRules.cpp index 01cad9498..d81a993f7 100644 --- a/libevmasm/SimplificationRules.cpp +++ b/libevmasm/SimplificationRules.cpp @@ -38,7 +38,7 @@ using namespace dev; using namespace dev::eth; -pair > const* Rules::findFirstMatch( +tuple, bool> const* Rules::findFirstMatch( Expression const& _expr, ExpressionClasses const& _classes ) @@ -48,22 +48,22 @@ pair > const* Rules::findFirstMatch( assertThrow(_expr.item, OptimizerException, ""); for (auto const& rule: m_rules[byte(_expr.item->instruction())]) { - if (rule.first.matches(_expr, _classes)) + if (std::get<0>(rule).matches(_expr, _classes)) return &rule; resetMatchGroups(); } return nullptr; } -void Rules::addRules(std::vector > > const& _rules) +void Rules::addRules(std::vector, bool>> const& _rules) { for (auto const& r: _rules) addRule(r); } -void Rules::addRule(std::pair > const& _rule) +void Rules::addRule(std::tuple, bool> const& _rule) { - m_rules[byte(_rule.first.instruction())].push_back(_rule); + m_rules[byte(std::get<0>(_rule).instruction())].push_back(_rule); } Rules::Rules() diff --git a/libevmasm/SimplificationRules.h b/libevmasm/SimplificationRules.h index a4da5849c..6040fd765 100644 --- a/libevmasm/SimplificationRules.h +++ b/libevmasm/SimplificationRules.h @@ -47,19 +47,21 @@ public: /// @returns a pointer to the first matching pattern and sets the match /// groups accordingly. - std::pair> const* findFirstMatch( + std::tuple, bool> const* findFirstMatch( Expression const& _expr, ExpressionClasses const& _classes ); private: - void addRules(std::vector>> const& _rules); - void addRule(std::pair> const& _rule); + void addRules(std::vector, bool>> const& _rules); + void addRule(std::tuple, bool> const& _rule); void resetMatchGroups() { m_matchGroups.clear(); } std::map m_matchGroups; - std::vector>> m_rules[256]; + /// Pattern to match, replacement to be applied and flag indicating whether + /// the replacement might remove some elements (except constants). + std::vector, bool>> m_rules[256]; }; /** diff --git a/libjulia/optimiser/Utilities.h b/libjulia/optimiser/Utilities.h index 88ba3f472..e3b4b087a 100644 --- a/libjulia/optimiser/Utilities.h +++ b/libjulia/optimiser/Utilities.h @@ -22,11 +22,16 @@ #include +#include + namespace dev { namespace julia { +struct IuliaException: virtual Exception {}; +struct OptimizerException: virtual IuliaException {}; + /// Removes statements that are just empty blocks (non-recursive). void removeEmptyBlocks(Block& _block); From 591813638e3074bf9f264b0ed29b581c74202ccf Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 18 Jan 2018 12:14:56 +0100 Subject: [PATCH 03/11] Explanation of expression simplifier. --- libjulia/optimiser/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libjulia/optimiser/README.md b/libjulia/optimiser/README.md index 24ee429c1..e71344404 100644 --- a/libjulia/optimiser/README.md +++ b/libjulia/optimiser/README.md @@ -78,3 +78,12 @@ a loop or conditional, the first one is not inside), the first assignment is rem ## Function Unifier + +## Expression Simplifier + +This step can only be applied for the EVM-flavoured dialect of iulia. It applies +simple rules like ``x + 0 == x`` to simplify expressions. + +## Ineffective Statement Remover + +This step removes statements that have no side-effects. From 9eea3f29ba7c0ce89b5b24c42a51cc8d9115dfa8 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 18 Jan 2018 12:15:22 +0100 Subject: [PATCH 04/11] Expression simplifier. --- libevmasm/RuleList.h | 5 + libjulia/optimiser/ExpressionSimplifier.cpp | 49 ++++++ libjulia/optimiser/ExpressionSimplifier.h | 45 +++++ libjulia/optimiser/SimplificationRules.cpp | 172 ++++++++++++++++++++ libjulia/optimiser/SimplificationRules.h | 116 +++++++++++++ test/libjulia/Simplifier.cpp | 66 ++++++++ 6 files changed, 453 insertions(+) create mode 100644 libjulia/optimiser/ExpressionSimplifier.cpp create mode 100644 libjulia/optimiser/ExpressionSimplifier.h create mode 100644 libjulia/optimiser/SimplificationRules.cpp create mode 100644 libjulia/optimiser/SimplificationRules.h create mode 100644 test/libjulia/Simplifier.cpp diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index 70a3ef71b..3e7be720a 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -43,6 +43,11 @@ template S modWorkaround(S const& _a, S const& _b) return (S)(bigint(_a) % bigint(_b)); } +// TODO: Add a parameter that will cause rules with swapped arguments +// to be added explicitly. This is needed by the new optimizer, but not +// by the old. The new optimizer requires that the order of arbitrary +// expressions is not altered. + /// @returns a list of simplification rules given certain match placeholders. /// A, B and C should represent constants, X and Y arbitrary expressions. /// The third element in the tuple is a boolean flag that indicates whether diff --git a/libjulia/optimiser/ExpressionSimplifier.cpp b/libjulia/optimiser/ExpressionSimplifier.cpp new file mode 100644 index 000000000..1d6d9f8bf --- /dev/null +++ b/libjulia/optimiser/ExpressionSimplifier.cpp @@ -0,0 +1,49 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Optimiser component that uses the simplification rules to simplify expressions. + */ + +#include + +#include +#include + +#include + +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::julia; +using namespace dev::solidity; + + +void ExpressionSimplifier::visit(Expression& _expression) +{ + ASTModifier::visit(_expression); + while (auto match = SimplificationRules::findFirstMatch(_expression)) + { + // TODO: The check could actually be less strict than "movable". + // We only require "Does not cause side-effects". + if (std::get<2>(*match) && !MovableChecker(_expression).movable()) + return; + _expression = std::get<1>(*match)().toExpression(locationOf(_expression)); + } +} diff --git a/libjulia/optimiser/ExpressionSimplifier.h b/libjulia/optimiser/ExpressionSimplifier.h new file mode 100644 index 000000000..8a9e0e200 --- /dev/null +++ b/libjulia/optimiser/ExpressionSimplifier.h @@ -0,0 +1,45 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Optimiser component that uses the simplification rules to simplify expressions. + */ + +#pragma once + +#include + +#include + +namespace dev +{ +namespace julia +{ + +/** + * Applies simplification rules to all expressions. + */ +class ExpressionSimplifier: public ASTModifier +{ +public: + using ASTModifier::operator(); + virtual void visit(Expression& _expression); + +private: +}; + +} +} diff --git a/libjulia/optimiser/SimplificationRules.cpp b/libjulia/optimiser/SimplificationRules.cpp new file mode 100644 index 000000000..5cd13a62a --- /dev/null +++ b/libjulia/optimiser/SimplificationRules.cpp @@ -0,0 +1,172 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Module for applying replacement rules against Expressions. + */ + +#include + +#include +#include + +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::julia; + + +tuple, bool> const* SimplificationRules::findFirstMatch(Expression const& _expr) +{ + if (_expr.type() != typeid(FunctionalInstruction)) + return nullptr; + + static SimplificationRules rules; + + FunctionalInstruction const& instruction = boost::get(_expr); + for (auto const& rule: rules.m_rules[byte(instruction.instruction)]) + { + rules.resetMatchGroups(); + if (std::get<0>(rule).matches(_expr)) + return &rule; + } + return nullptr; +} + +void SimplificationRules::addRules(vector, bool>> const& _rules) +{ + for (auto const& r: _rules) + addRule(r); +} + +void SimplificationRules::addRule(tuple, bool> const& _rule) +{ + m_rules[byte(std::get<0>(_rule).instruction())].push_back(_rule); +} + +SimplificationRules::SimplificationRules() +{ + // Multiple occurences of one of these inside one rule must match the same equivalence class. + // Constants. + Pattern A(PatternKind::Constant); + Pattern B(PatternKind::Constant); + Pattern C(PatternKind::Constant); + // Anything. + Pattern X; + Pattern Y; + A.setMatchGroup(1, m_matchGroups); + B.setMatchGroup(2, m_matchGroups); + C.setMatchGroup(3, m_matchGroups); + X.setMatchGroup(4, m_matchGroups); + Y.setMatchGroup(5, m_matchGroups); + + addRules(simplificationRuleList(A, B, C, X, Y)); +} + +Pattern::Pattern(solidity::Instruction _instruction, vector const& _arguments): + m_kind(PatternKind::Operation), + m_instruction(_instruction), + m_arguments(_arguments) +{ +} + +void Pattern::setMatchGroup(unsigned _group, map& _matchGroups) +{ + m_matchGroup = _group; + m_matchGroups = &_matchGroups; +} + +bool Pattern::matches(Expression const& _expr) const +{ + if (m_kind == PatternKind::Constant) + { + if (_expr.type() != typeid(Literal)) + return false; + Literal const& literal = boost::get(_expr); + if (literal.kind != assembly::LiteralKind::Number) + return false; + if (m_data && *m_data != u256(literal.value)) + return false; + assertThrow(m_arguments.empty(), OptimizerException, ""); + } + else if (m_kind == PatternKind::Operation) + { + if (_expr.type() != typeid(FunctionalInstruction)) + return false; + FunctionalInstruction const& instr = boost::get(_expr); + if (m_instruction != instr.instruction) + return false; + assertThrow(m_arguments.size() == instr.arguments.size(), OptimizerException, ""); + for (size_t i = 0; i < m_arguments.size(); ++i) + if (!m_arguments[i].matches(instr.arguments.at(i))) + return false; + } + else + { + assertThrow(m_arguments.empty(), OptimizerException, ""); + } + // We do not support matching multiple expressions that require the same value. + if (m_matchGroup) + { + if (m_matchGroups->count(m_matchGroup)) + return false; + (*m_matchGroups)[m_matchGroup] = &_expr; + } + return true; +} + +solidity::Instruction Pattern::instruction() const +{ + assertThrow(m_kind == PatternKind::Operation, OptimizerException, ""); + return m_instruction; +} + +Expression Pattern::toExpression(SourceLocation const& _location) const +{ + if (matchGroup()) + return ASTCopier().translate(matchGroupValue()); + if (m_kind == PatternKind::Constant) + { + assertThrow(m_data, OptimizerException, "No match group and no constant value given."); + return Literal{_location, assembly::LiteralKind::Number, m_data->str(), ""}; + } + else if (m_kind == PatternKind::Operation) + { + vector arguments; + for (auto const& arg: m_arguments) + arguments.emplace_back(arg.toExpression(_location)); + return FunctionalInstruction{_location, m_instruction, std::move(arguments)}; + } + assertThrow(false, OptimizerException, "Pattern of kind 'any', but no match group."); +} + +u256 Pattern::d() const +{ + Literal const& literal = boost::get(matchGroupValue()); + assertThrow(literal.kind == assembly::LiteralKind::Number, OptimizerException, ""); + return u256(literal.value); +} + +Expression const& Pattern::matchGroupValue() const +{ + assertThrow(m_matchGroup > 0, OptimizerException, ""); + assertThrow(!!m_matchGroups, OptimizerException, ""); + assertThrow((*m_matchGroups)[m_matchGroup], OptimizerException, ""); + return *(*m_matchGroups)[m_matchGroup]; +} diff --git a/libjulia/optimiser/SimplificationRules.h b/libjulia/optimiser/SimplificationRules.h new file mode 100644 index 000000000..96f3de218 --- /dev/null +++ b/libjulia/optimiser/SimplificationRules.h @@ -0,0 +1,116 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Module for applying replacement rules against Expressions. + */ + +#pragma once + +#include + +#include + +#include + +#include + +#include +#include + +namespace dev +{ +namespace julia +{ + +class Pattern; + +/** + * Container for all simplification rules. + */ +class SimplificationRules: public boost::noncopyable +{ +public: + SimplificationRules(); + + /// @returns a pointer to the first matching pattern and sets the match + /// groups accordingly. + static std::tuple, bool> const* findFirstMatch(Expression const& _expr); + +private: + void addRules(std::vector, bool>> const& _rules); + void addRule(std::tuple, bool> const& _rule); + + void resetMatchGroups() { m_matchGroups.clear(); } + + std::map m_matchGroups; + std::vector, bool>> m_rules[256]; +}; + +enum class PatternKind +{ + Operation, + Constant, + Any +}; + +/** + * Pattern to match against an expression. + * Also stores matched expressions to retrieve them later, for constructing new expressions using + * ExpressionTemplate. + */ +class Pattern +{ +public: + /// Matches any expression. + Pattern(PatternKind _kind = PatternKind::Any): m_kind(_kind) {} + // Matches a specific constant value. + Pattern(unsigned _value): Pattern(u256(_value)) {} + // Matches a specific constant value. + Pattern(u256 const& _value): m_kind(PatternKind::Constant), m_data(std::make_shared(_value)) {} + // Matches a given instruction with given arguments + Pattern(solidity::Instruction _instruction, std::vector const& _arguments = {}); + /// Sets this pattern to be part of the match group with the identifier @a _group. + /// Inside one rule, all patterns in the same match group have to match expressions from the + /// same expression equivalence class. + void setMatchGroup(unsigned _group, std::map& _matchGroups); + unsigned matchGroup() const { return m_matchGroup; } + bool matches(Expression const& _expr) const; + + std::vector arguments() const { return m_arguments; } + + /// @returns the data of the matched expression if this pattern is part of a match group. + u256 d() const; + + solidity::Instruction instruction() const; + + /// Turns this pattern into an actual expression. Should only be called + /// for patterns resulting from an action, i.e. with match groups assigned. + Expression toExpression(SourceLocation const& _location) const; + +private: + Expression const& matchGroupValue() const; + + PatternKind m_kind = PatternKind::Any; + solidity::Instruction m_instruction; ///< Only valid if m_kind is Operation + std::shared_ptr m_data; ///< Only valid if m_kind is Constant + std::vector m_arguments; + unsigned m_matchGroup = 0; + std::map* m_matchGroups = nullptr; +}; + +} +} diff --git a/test/libjulia/Simplifier.cpp b/test/libjulia/Simplifier.cpp new file mode 100644 index 000000000..83cc7687c --- /dev/null +++ b/test/libjulia/Simplifier.cpp @@ -0,0 +1,66 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @date 2017 + * Unit tests for the expression simplifier optimizer stage. + */ + +#include + +#include + +#include + +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::julia; +using namespace dev::julia::test; +using namespace dev::solidity; + + +#define CHECK(_original, _expectation)\ +do\ +{\ + assembly::AsmPrinter p;\ + Block b = *(parse(_original, false).first);\ + (ExpressionSimplifier{})(b);\ + string result = p(b);\ + BOOST_CHECK_EQUAL(result, format(_expectation, false));\ +}\ +while(false) + +BOOST_AUTO_TEST_SUITE(IuliaSimplifier) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + CHECK("{ }", "{ }"); +} + +BOOST_AUTO_TEST_CASE(constants) +{ + CHECK( + "{ let a := add(1, mul(3, 4)) }", + "{ let a := 13 }" + ); +} + +BOOST_AUTO_TEST_SUITE_END() From 295f8c07ad63cd1b0c5176bd39e9c13f64968c7e Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 18 Jan 2018 13:56:42 +0100 Subject: [PATCH 05/11] Explicitly add reversed operands for commutative operations. --- libevmasm/RuleList.h | 90 +++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index 3e7be720a..f8a6ce730 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -43,11 +43,6 @@ template S modWorkaround(S const& _a, S const& _b) return (S)(bigint(_a) % bigint(_b)); } -// TODO: Add a parameter that will cause rules with swapped arguments -// to be added explicitly. This is needed by the new optimizer, but not -// by the old. The new optimizer requires that the order of arbitrary -// expressions is not altered. - /// @returns a list of simplification rules given certain match placeholders. /// A, B and C should represent constants, X and Y arbitrary expressions. /// The third element in the tuple is a boolean flag that indicates whether @@ -96,11 +91,16 @@ std::vector, bool>> simplificationR return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask); }, false}, - // invariants involving known constants (commutative instructions will be checked with swapped operants too) + // invariants involving known constants {{Instruction::ADD, {X, 0}}, [=]{ return X; }, false}, + {{Instruction::ADD, {0, X}}, [=]{ return X; }, false}, {{Instruction::SUB, {X, 0}}, [=]{ return X; }, false}, {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }, true}, + {{Instruction::MUL, {0, X}}, [=]{ return u256(0); }, true}, {{Instruction::MUL, {X, 1}}, [=]{ return X; }, false}, + {{Instruction::MUL, {1, X}}, [=]{ return X; }, false}, + {{Instruction::MUL, {X, u256(-1)}}, [=]() -> Pattern { return {Instruction::SUB, {0, X}}; }, false}, + {{Instruction::MUL, {u256(-1), X}}, [=]() -> Pattern { return {Instruction::SUB, {0, X}}; }, false}, {{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }, true}, {{Instruction::DIV, {0, X}}, [=]{ return u256(0); }, true}, {{Instruction::DIV, {X, 1}}, [=]{ return X; }, false}, @@ -108,13 +108,19 @@ std::vector, bool>> simplificationR {{Instruction::SDIV, {0, X}}, [=]{ return u256(0); }, true}, {{Instruction::SDIV, {X, 1}}, [=]{ return X; }, false}, {{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }, false}, + {{Instruction::AND, {~u256(0), X}}, [=]{ return X; }, false}, {{Instruction::AND, {X, 0}}, [=]{ return u256(0); }, true}, + {{Instruction::AND, {0, X}}, [=]{ return u256(0); }, true}, {{Instruction::OR, {X, 0}}, [=]{ return X; }, false}, + {{Instruction::OR, {0, X}}, [=]{ return X; }, false}, {{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }, true}, + {{Instruction::OR, {~u256(0), X}}, [=]{ return ~u256(0); }, true}, {{Instruction::XOR, {X, 0}}, [=]{ return X; }, false}, + {{Instruction::XOR, {0, X}}, [=]{ return X; }, false}, {{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }, true}, {{Instruction::MOD, {0, X}}, [=]{ return u256(0); }, true}, {{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; }, false }, + {{Instruction::EQ, {0, X}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; }, false }, // operations involving an expression and itself {{Instruction::AND, {X, X}}, [=]{ return X; }, true}, @@ -130,14 +136,25 @@ std::vector, bool>> simplificationR // logical instruction combinations {{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }, false}, - {{Instruction::XOR, {{{X}, {Instruction::XOR, {X, Y}}}}}, [=]{ return Y; }, true}, - {{Instruction::OR, {{{X}, {Instruction::AND, {X, Y}}}}}, [=]{ return X; }, true}, - {{Instruction::AND, {{{X}, {Instruction::OR, {X, Y}}}}}, [=]{ return X; }, true}, - {{Instruction::AND, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return u256(0); }, true}, - {{Instruction::OR, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return ~u256(0); }, true}, + {{Instruction::XOR, {X, {Instruction::XOR, {X, Y}}}}, [=]{ return Y; }, true}, + {{Instruction::XOR, {X, {Instruction::XOR, {Y, X}}}}, [=]{ return Y; }, true}, + {{Instruction::XOR, {{Instruction::XOR, {X, Y}}, X}}, [=]{ return Y; }, true}, + {{Instruction::XOR, {{Instruction::XOR, {Y, X}}, X}}, [=]{ return Y; }, true}, + {{Instruction::OR, {X, {Instruction::AND, {X, Y}}}}, [=]{ return X; }, true}, + {{Instruction::OR, {X, {Instruction::AND, {Y, X}}}}, [=]{ return X; }, true}, + {{Instruction::OR, {{Instruction::AND, {X, Y}}, X}}, [=]{ return X; }, true}, + {{Instruction::OR, {{Instruction::AND, {Y, X}}, X}}, [=]{ return X; }, true}, + {{Instruction::AND, {X, {Instruction::OR, {X, Y}}}}, [=]{ return X; }, true}, + {{Instruction::AND, {X, {Instruction::OR, {Y, X}}}}, [=]{ return X; }, true}, + {{Instruction::AND, {{Instruction::OR, {X, Y}}, X}}, [=]{ return X; }, true}, + {{Instruction::AND, {{Instruction::OR, {Y, X}}, X}}, [=]{ return X; }, true}, + {{Instruction::AND, {X, {Instruction::NOT, {X}}}}, [=]{ return u256(0); }, true}, + {{Instruction::AND, {{Instruction::NOT, {X}}, X}}, [=]{ return u256(0); }, true}, + {{Instruction::OR, {X, {Instruction::NOT, {X}}}}, [=]{ return ~u256(0); }, true}, + {{Instruction::OR, {{Instruction::NOT, {X}}, X}}, [=]{ return ~u256(0); }, true}, }; - // Double negation of opcodes with binary result + // Double negation of opcodes with boolean result for (auto const& op: std::vector{ Instruction::EQ, Instruction::LT, @@ -174,28 +191,33 @@ std::vector, bool>> simplificationR { auto op = opFun.first; auto fun = opFun.second; - // Moving constants to the outside, order matters here! - // we need actions that return expressions (or patterns?) here, and we need also reversed rules - // (X+A)+B -> X+(A+B) - rules += std::vector, bool>>{{ - {op, {{op, {X, A}}, B}}, - [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }, - false - }, { - // X+(Y+A) -> (X+Y)+A - {op, {{op, {X, A}}, Y}}, - [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }, - false - }, { - // For now, we still need explicit commutativity for the inner pattern - {op, {{op, {A, X}}, B}}, - [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }, - false - }, { - {op, {{op, {A, X}}, Y}}, - [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }, - false - }}; + // Moving constants to the outside, order matters here - we first add rules + // for constants and then for non-constants. + // xa can be (X, A) or (A, X) + for (auto xa: {std::vector{X, A}, std::vector{A, X}}) + { + rules += std::vector, bool>>{{ + // (X+A)+B -> X+(A+B) + {op, {{op, xa}, B}}, + [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }, + false + }, { + // (X+A)+Y -> (X+Y)+A + {op, {{op, xa}, Y}}, + [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }, + false + }, { + // B+(X+A) -> X+(A+B) + {op, {B, {op, xa}}}, + [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }, + false + }, { + // Y+(X+A) -> (Y+X)+A + {op, {Y, {op, xa}}}, + [=]() -> Pattern { return {op, {{op, {Y, X}}, A}}; }, + false + }}; + } } // move constants across subtractions From 65c31ecaeb66e186a0ab6aa6cb2c44ec1a35a868 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 18 Jan 2018 13:58:51 +0100 Subject: [PATCH 06/11] Remove recursive call to simplify with reversed arguments. (We now have explicit rules for that). --- libevmasm/ExpressionClasses.cpp | 9 +-------- libevmasm/ExpressionClasses.h | 3 +-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp index 58c254b1d..3825f5ede 100644 --- a/libevmasm/ExpressionClasses.cpp +++ b/libevmasm/ExpressionClasses.cpp @@ -181,7 +181,7 @@ string ExpressionClasses::fullDAGToString(ExpressionClasses::Id _id) const return str.str(); } -ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, bool _secondRun) +ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr) { static Rules rules; @@ -205,13 +205,6 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, return rebuildExpression(ExpressionTemplate(std::get<1>(*match)(), _expr.item->location())); } - if (!_secondRun && _expr.arguments.size() == 2 && SemanticInformation::isCommutativeOperation(*_expr.item)) - { - Expression expr = _expr; - swap(expr.arguments[0], expr.arguments[1]); - return tryToSimplify(expr, true); - } - return -1; } diff --git a/libevmasm/ExpressionClasses.h b/libevmasm/ExpressionClasses.h index 6b426e971..df8082f93 100644 --- a/libevmasm/ExpressionClasses.h +++ b/libevmasm/ExpressionClasses.h @@ -108,8 +108,7 @@ public: 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); + Id tryToSimplify(Expression const& _expr); /// Rebuilds an expression from a (matched) pattern. Id rebuildExpression(ExpressionTemplate const& _template); From f7392cc6983a48ab0d482270912bd5de4042e577 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 18 Jan 2018 14:32:43 +0100 Subject: [PATCH 07/11] Tests. --- test/libjulia/Simplifier.cpp | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/libjulia/Simplifier.cpp b/test/libjulia/Simplifier.cpp index 83cc7687c..dc3a0485e 100644 --- a/test/libjulia/Simplifier.cpp +++ b/test/libjulia/Simplifier.cpp @@ -63,4 +63,44 @@ BOOST_AUTO_TEST_CASE(constants) ); } +BOOST_AUTO_TEST_CASE(invariant) +{ + CHECK( + "{ let a := mload(sub(7, 7)) let b := sub(a, 0) }", + "{ let a := mload(0) let b := a }" + ); +} + +BOOST_AUTO_TEST_CASE(reversed) +{ + CHECK( + "{ let a := add(0, mload(0)) }", + "{ let a := mload(0) }" + ); +} + +BOOST_AUTO_TEST_CASE(constant_propagation) +{ + CHECK( + "{ let a := add(7, sub(mload(0), 7)) }", + "{ let a := mload(0) }" + ); +} + +BOOST_AUTO_TEST_CASE(including_function_calls) +{ + CHECK( + "{ function f() -> a {} let b := add(7, sub(f(), 7)) }", + "{ function f() -> a {} let b := f() }" + ); +} + +BOOST_AUTO_TEST_CASE(inside_for) +{ + CHECK( + "{ for { let a := 10 } iszero(eq(a, 0)) { a := add(a, 1) } {} }", + "{ for { let a := 10 } iszero(iszero(a)) { a := add(a, 1) } {} }" + ); +} + BOOST_AUTO_TEST_SUITE_END() From 5523296eaa68a591a331d9b75dc19cf11d1c538e Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 1 Feb 2018 18:02:10 +0100 Subject: [PATCH 08/11] Also apply simplification rules that require multiple identical sub-expressions. --- libjulia/optimiser/ExpressionSimplifier.cpp | 1 + libjulia/optimiser/SimplificationRules.cpp | 16 ++++- libjulia/optimiser/SyntacticalEquality.cpp | 75 +++++++++++++++++++++ libjulia/optimiser/SyntacticalEquality.h | 50 ++++++++++++++ test/libjulia/Simplifier.cpp | 24 +++++++ 5 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 libjulia/optimiser/SyntacticalEquality.cpp create mode 100644 libjulia/optimiser/SyntacticalEquality.h diff --git a/libjulia/optimiser/ExpressionSimplifier.cpp b/libjulia/optimiser/ExpressionSimplifier.cpp index 1d6d9f8bf..bdcf31687 100644 --- a/libjulia/optimiser/ExpressionSimplifier.cpp +++ b/libjulia/optimiser/ExpressionSimplifier.cpp @@ -40,6 +40,7 @@ void ExpressionSimplifier::visit(Expression& _expression) ASTModifier::visit(_expression); while (auto match = SimplificationRules::findFirstMatch(_expression)) { + // Do not apply the rule if it removes non-constant parts of the expression. // TODO: The check could actually be less strict than "movable". // We only require "Does not cause side-effects". if (std::get<2>(*match) && !MovableChecker(_expression).movable()) diff --git a/libjulia/optimiser/SimplificationRules.cpp b/libjulia/optimiser/SimplificationRules.cpp index 5cd13a62a..234efea08 100644 --- a/libjulia/optimiser/SimplificationRules.cpp +++ b/libjulia/optimiser/SimplificationRules.cpp @@ -22,6 +22,8 @@ #include #include +#include +#include #include @@ -121,12 +123,20 @@ bool Pattern::matches(Expression const& _expr) const { assertThrow(m_arguments.empty(), OptimizerException, ""); } - // We do not support matching multiple expressions that require the same value. + // We support matching multiple expressions that require the same value + // based on identical ASTs, which have to be movable. if (m_matchGroup) { if (m_matchGroups->count(m_matchGroup)) - return false; - (*m_matchGroups)[m_matchGroup] = &_expr; + { + Expression const* firstMatch = (*m_matchGroups)[m_matchGroup]; + assertThrow(firstMatch, OptimizerException, "Match set but to null."); + return + SyntacticalEqualityChecker::equal(*firstMatch, _expr) && + MovableChecker(_expr).movable(); + } + else + (*m_matchGroups)[m_matchGroup] = &_expr; } return true; } diff --git a/libjulia/optimiser/SyntacticalEquality.cpp b/libjulia/optimiser/SyntacticalEquality.cpp new file mode 100644 index 000000000..2b90b0911 --- /dev/null +++ b/libjulia/optimiser/SyntacticalEquality.cpp @@ -0,0 +1,75 @@ +/*( + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Component that can compare ASTs for equality on a syntactic basis. + */ + +#include + +#include +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::julia; + +bool SyntacticalEqualityChecker::equal(Expression const& _e1, Expression const& _e2) +{ + if (_e1.type() != _e2.type()) + return false; + + // TODO This should be replaced by some kind of AST walker as soon as it gets + // more complex. + if (_e1.type() == typeid(FunctionalInstruction)) + { + auto const& e1 = boost::get(_e1); + auto const& e2 = boost::get(_e2); + return + e1.instruction == e2.instruction && + equalVector(e1.arguments, e2.arguments); + } + else if (_e1.type() == typeid(FunctionCall)) + { + auto const& e1 = boost::get(_e1); + auto const& e2 = boost::get(_e2); + return + equal(e1.functionName, e2.functionName) && + equalVector(e1.arguments, e2.arguments); + } + else if (_e1.type() == typeid(Identifier)) + return boost::get(_e1).name == boost::get(_e2).name; + else if (_e1.type() == typeid(Literal)) + { + auto const& e1 = boost::get(_e1); + auto const& e2 = boost::get(_e2); + return e1.kind == e2.kind && e1.value == e2.value && e1.type == e2.type; + } + else + { + solAssert(false, "Invlid expression"); + } + return false; +} + +bool SyntacticalEqualityChecker::equalVector(vector const& _e1, vector const& _e2) +{ + return _e1.size() == _e2.size() && + std::equal(begin(_e1), end(_e1), begin(_e2), SyntacticalEqualityChecker::equal); + +} diff --git a/libjulia/optimiser/SyntacticalEquality.h b/libjulia/optimiser/SyntacticalEquality.h new file mode 100644 index 000000000..b7c09330f --- /dev/null +++ b/libjulia/optimiser/SyntacticalEquality.h @@ -0,0 +1,50 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Component that can compare ASTs for equality on a syntactic basis. + */ + +#pragma once + +#include + +#include + +namespace dev +{ +namespace julia +{ + +/** + * Component that can compare ASTs for equality on a syntactic basis. + * Ignores source locations but requires exact matches otherwise. + * + * TODO: Only implemented for Expressions for now. + * A future version might also recognize renamed variables and thus could be used to + * remove duplicate functions. + */ +class SyntacticalEqualityChecker +{ +public: + static bool equal(Expression const& _e1, Expression const& _e2); + +protected: + static bool equalVector(std::vector const& _e1, std::vector const& _e2); +}; + +} +} diff --git a/test/libjulia/Simplifier.cpp b/test/libjulia/Simplifier.cpp index dc3a0485e..b48d45c84 100644 --- a/test/libjulia/Simplifier.cpp +++ b/test/libjulia/Simplifier.cpp @@ -87,6 +87,30 @@ BOOST_AUTO_TEST_CASE(constant_propagation) ); } +BOOST_AUTO_TEST_CASE(identity_rules_simple) +{ + CHECK( + "{ let a := mload(0) let b := sub(a, a) }", + "{ let a := mload(0) let b := 0 }" + ); +} + +BOOST_AUTO_TEST_CASE(identity_rules_complex) +{ + CHECK( + "{ let a := sub(calldataload(0), calldataload(0)) }", + "{ let a := 0 }" + ); +} + +BOOST_AUTO_TEST_CASE(identity_rules_negative) +{ + CHECK( + "{ let a := sub(calldataload(1), calldataload(0)) }", + "{ let a := sub(calldataload(1), calldataload(0)) }" + ); +} + BOOST_AUTO_TEST_CASE(including_function_calls) { CHECK( From c961a3079dda8735363872cdb84c489d61846003 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 6 Feb 2018 12:20:00 +0100 Subject: [PATCH 09/11] Turn simplification rule tuple into struct. --- libevmasm/ExpressionClasses.cpp | 2 +- libevmasm/RuleList.h | 15 ++++--- libevmasm/SimplificationRule.h | 45 +++++++++++++++++++++ libevmasm/SimplificationRules.cpp | 11 +++-- libevmasm/SimplificationRules.h | 9 +++-- libjulia/optimiser/ExpressionSimplifier.cpp | 4 +- libjulia/optimiser/SimplificationRules.cpp | 10 ++--- libjulia/optimiser/SimplificationRules.h | 9 +++-- 8 files changed, 75 insertions(+), 30 deletions(-) create mode 100644 libevmasm/SimplificationRule.h diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp index 3825f5ede..69b33ec5f 100644 --- a/libevmasm/ExpressionClasses.cpp +++ b/libevmasm/ExpressionClasses.cpp @@ -202,7 +202,7 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr) //cout << "with rule " << match->first.toString() << endl; //ExpressionTemplate t(match->second()); //cout << "to " << match->second().toString() << endl; - return rebuildExpression(ExpressionTemplate(std::get<1>(*match)(), _expr.item->location())); + return rebuildExpression(ExpressionTemplate(match->action(), _expr.item->location())); } return -1; diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index f8a6ce730..2312d673c 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -25,6 +25,7 @@ #include #include +#include #include @@ -45,12 +46,10 @@ template S modWorkaround(S const& _a, S const& _b) /// @returns a list of simplification rules given certain match placeholders. /// A, B and C should represent constants, X and Y arbitrary expressions. -/// The third element in the tuple is a boolean flag that indicates whether -/// any non-constant elements in the pattern are removed by applying it. /// The simplifications should neven change the order of evaluation of -/// arbitrary operations, though. +/// arbitrary operations. template -std::vector, bool>> simplificationRuleList( +std::vector> simplificationRuleList( Pattern A, Pattern B, Pattern C, @@ -58,8 +57,8 @@ std::vector, bool>> simplificationR Pattern Y ) { - std::vector, bool>> rules; - rules += std::vector, bool>>{ + std::vector> rules; + rules += std::vector>{ // arithmetics on constants {{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }, false}, {{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }, false}, @@ -196,7 +195,7 @@ std::vector, bool>> simplificationR // xa can be (X, A) or (A, X) for (auto xa: {std::vector{X, A}, std::vector{A, X}}) { - rules += std::vector, bool>>{{ + rules += std::vector>{{ // (X+A)+B -> X+(A+B) {op, {{op, xa}, B}}, [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }, @@ -221,7 +220,7 @@ std::vector, bool>> simplificationR } // move constants across subtractions - rules += std::vector, bool>>{ + rules += std::vector>{ { // X - A -> X + (-A) {Instruction::SUB, {X, A}}, diff --git a/libevmasm/SimplificationRule.h b/libevmasm/SimplificationRule.h new file mode 100644 index 000000000..7b4dea687 --- /dev/null +++ b/libevmasm/SimplificationRule.h @@ -0,0 +1,45 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Expression simplification pattern. + */ + +#pragma once + +#include + +namespace dev +{ +namespace solidity +{ + +/** + * Rule that contains a pattern, an action that can be applied + * after the pattern has matched and a bool that indicates + * whether the action would remove something from the expression + * than is not a constant literal. + */ +template +struct SimplificationRule +{ + Pattern pattern; + std::function action; + bool removesNonConstants; +}; + +} +} diff --git a/libevmasm/SimplificationRules.cpp b/libevmasm/SimplificationRules.cpp index d81a993f7..53a5f9fc5 100644 --- a/libevmasm/SimplificationRules.cpp +++ b/libevmasm/SimplificationRules.cpp @@ -23,7 +23,6 @@ #include #include -#include #include #include #include @@ -38,7 +37,7 @@ using namespace dev; using namespace dev::eth; -tuple, bool> const* Rules::findFirstMatch( +SimplificationRule const* Rules::findFirstMatch( Expression const& _expr, ExpressionClasses const& _classes ) @@ -48,22 +47,22 @@ tuple, bool> const* Rules::findFirstMatch( assertThrow(_expr.item, OptimizerException, ""); for (auto const& rule: m_rules[byte(_expr.item->instruction())]) { - if (std::get<0>(rule).matches(_expr, _classes)) + if (rule.pattern.matches(_expr, _classes)) return &rule; resetMatchGroups(); } return nullptr; } -void Rules::addRules(std::vector, bool>> const& _rules) +void Rules::addRules(std::vector> const& _rules) { for (auto const& r: _rules) addRule(r); } -void Rules::addRule(std::tuple, bool> const& _rule) +void Rules::addRule(SimplificationRule const& _rule) { - m_rules[byte(std::get<0>(_rule).instruction())].push_back(_rule); + m_rules[byte(_rule.pattern.instruction())].push_back(_rule); } Rules::Rules() diff --git a/libevmasm/SimplificationRules.h b/libevmasm/SimplificationRules.h index 6040fd765..53f7e5954 100644 --- a/libevmasm/SimplificationRules.h +++ b/libevmasm/SimplificationRules.h @@ -24,6 +24,7 @@ #pragma once #include +#include #include #include @@ -47,21 +48,21 @@ public: /// @returns a pointer to the first matching pattern and sets the match /// groups accordingly. - std::tuple, bool> const* findFirstMatch( + SimplificationRule const* findFirstMatch( Expression const& _expr, ExpressionClasses const& _classes ); private: - void addRules(std::vector, bool>> const& _rules); - void addRule(std::tuple, bool> const& _rule); + void addRules(std::vector> const& _rules); + void addRule(SimplificationRule const& _rule); void resetMatchGroups() { m_matchGroups.clear(); } std::map m_matchGroups; /// Pattern to match, replacement to be applied and flag indicating whether /// the replacement might remove some elements (except constants). - std::vector, bool>> m_rules[256]; + std::vector> m_rules[256]; }; /** diff --git a/libjulia/optimiser/ExpressionSimplifier.cpp b/libjulia/optimiser/ExpressionSimplifier.cpp index bdcf31687..3d471cb3f 100644 --- a/libjulia/optimiser/ExpressionSimplifier.cpp +++ b/libjulia/optimiser/ExpressionSimplifier.cpp @@ -43,8 +43,8 @@ void ExpressionSimplifier::visit(Expression& _expression) // Do not apply the rule if it removes non-constant parts of the expression. // TODO: The check could actually be less strict than "movable". // We only require "Does not cause side-effects". - if (std::get<2>(*match) && !MovableChecker(_expression).movable()) + if (match->removesNonConstants && !MovableChecker(_expression).movable()) return; - _expression = std::get<1>(*match)().toExpression(locationOf(_expression)); + _expression = match->action().toExpression(locationOf(_expression)); } } diff --git a/libjulia/optimiser/SimplificationRules.cpp b/libjulia/optimiser/SimplificationRules.cpp index 234efea08..a439caef2 100644 --- a/libjulia/optimiser/SimplificationRules.cpp +++ b/libjulia/optimiser/SimplificationRules.cpp @@ -34,7 +34,7 @@ using namespace dev; using namespace dev::julia; -tuple, bool> const* SimplificationRules::findFirstMatch(Expression const& _expr) +SimplificationRule const* SimplificationRules::findFirstMatch(Expression const& _expr) { if (_expr.type() != typeid(FunctionalInstruction)) return nullptr; @@ -45,21 +45,21 @@ tuple, bool> const* SimplificationRules::findFirstM for (auto const& rule: rules.m_rules[byte(instruction.instruction)]) { rules.resetMatchGroups(); - if (std::get<0>(rule).matches(_expr)) + if (rule.pattern.matches(_expr)) return &rule; } return nullptr; } -void SimplificationRules::addRules(vector, bool>> const& _rules) +void SimplificationRules::addRules(vector> const& _rules) { for (auto const& r: _rules) addRule(r); } -void SimplificationRules::addRule(tuple, bool> const& _rule) +void SimplificationRules::addRule(SimplificationRule const& _rule) { - m_rules[byte(std::get<0>(_rule).instruction())].push_back(_rule); + m_rules[byte(_rule.pattern.instruction())].push_back(_rule); } SimplificationRules::SimplificationRules() diff --git a/libjulia/optimiser/SimplificationRules.h b/libjulia/optimiser/SimplificationRules.h index 96f3de218..68b640b1d 100644 --- a/libjulia/optimiser/SimplificationRules.h +++ b/libjulia/optimiser/SimplificationRules.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include @@ -48,16 +49,16 @@ public: /// @returns a pointer to the first matching pattern and sets the match /// groups accordingly. - static std::tuple, bool> const* findFirstMatch(Expression const& _expr); + static SimplificationRule const* findFirstMatch(Expression const& _expr); private: - void addRules(std::vector, bool>> const& _rules); - void addRule(std::tuple, bool> const& _rule); + void addRules(std::vector> const& _rules); + void addRule(SimplificationRule const& _rule); void resetMatchGroups() { m_matchGroups.clear(); } std::map m_matchGroups; - std::vector, bool>> m_rules[256]; + std::vector> m_rules[256]; }; enum class PatternKind From c9a032a1e2600f732869a938d8cd349775f686e3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 7 Feb 2018 20:27:16 +0100 Subject: [PATCH 10/11] Larger stack for nodejs when building via emscripten. --- scripts/travis-emscripten/build_emscripten.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index 568269971..9520a7e8c 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -42,6 +42,10 @@ fi WORKSPACE=/root/project +# Increase nodejs stack size +sed -i -e 's/NODE_JS="nodejs"/NODE_JS=["nodejs", "--stack_size=8192"]/' /root/.emscripten + + # Boost echo -en 'travis_fold:start:compiling_boost\\r' cd "$WORKSPACE"/boost_1_57_0 From 7dd99a62c56e50b3deeccd77588c3a65fe6f7107 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 8 Feb 2018 14:59:17 +0100 Subject: [PATCH 11/11] Try something else. --- scripts/travis-emscripten/build_emscripten.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index 9520a7e8c..fd6434296 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -43,7 +43,12 @@ fi WORKSPACE=/root/project # Increase nodejs stack size -sed -i -e 's/NODE_JS="nodejs"/NODE_JS=["nodejs", "--stack_size=8192"]/' /root/.emscripten +if [ -e ~/.emscripten ] +then + sed -i -e 's/NODE_JS="nodejs"/NODE_JS=["nodejs", "--stack_size=8192"]/' ~/.emscripten +else + echo 'NODE_JS=["nodejs", "--stack_size=8192"]' > ~/.emscripten +fi # Boost