diff options
40 files changed, 706 insertions, 291 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index c975740d..2967e1fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -139,6 +139,29 @@ jobs: paths: - "*" + build_x86_clang7: + docker: + - image: buildpack-deps:cosmic + environment: + TERM: xterm + CC: /usr/bin/clang-7 + CXX: /usr/bin/clang++-7 + steps: + - checkout + - run: + name: Install build dependencies + command: | + apt-get -qq update + apt-get -qy install clang-7 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libz3-dev + ./scripts/install_obsolete_jsoncpp_1_7_4.sh + - run: *setup_prerelease_commit_hash + - run: *run_build + - store_artifacts: *solc_artifact + - persist_to_workspace: + root: build + paths: + - "*" + build_x86_mac: macos: xcode: "10.0.0" @@ -296,6 +319,7 @@ workflows: requires: - build_emscripten - build_x86_linux: *build_on_tags + - build_x86_clang7: *build_on_tags - build_x86_mac: *build_on_tags - test_x86_linux: <<: *build_on_tags diff --git a/Changelog.md b/Changelog.md index 01599026..5bf194e4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -132,6 +132,7 @@ Bugfixes: * Type Checker: Fix internal error when array index is not an unsigned. * Type System: Allow arbitrary exponents for literals with a mantissa of zero. * Parser: Fix incorrect source location for nameless parameters. + * Command Line Interface: Fix internal error when compiling stdin with no content and --ast option. ### 0.4.25 (2018-09-12) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 353bb61d..c2d60df1 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -416,7 +416,7 @@ that will be passed back to the caller. .. note:: There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which - whas deprecated in version 0.4.13 and removed in version 0.5.0. + was deprecated in version 0.4.13 and removed in version 0.5.0. When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are rethrown) automatically. Exceptions to this rule are ``send`` and the low-level functions ``call``, ``delegatecall`` and ``staticcall`` -- those return ``false`` as their first return value in case diff --git a/libyul/optimiser/ExpressionSimplifier.cpp b/libyul/optimiser/ExpressionSimplifier.cpp index c95fb3d5..64e9d7e7 100644 --- a/libyul/optimiser/ExpressionSimplifier.cpp +++ b/libyul/optimiser/ExpressionSimplifier.cpp @@ -22,6 +22,7 @@ #include <libyul/optimiser/SimplificationRules.h> #include <libyul/optimiser/Semantics.h> +#include <libyul/optimiser/SSAValueTracker.h> #include <libsolidity/inlineasm/AsmData.h> @@ -36,13 +37,24 @@ using namespace dev::solidity; void ExpressionSimplifier::visit(Expression& _expression) { ASTModifier::visit(_expression); - while (auto match = SimplificationRules::findFirstMatch(_expression)) + while (auto match = SimplificationRules::findFirstMatch(_expression, m_ssaValues)) { // 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". + // Note: References to variables that are only assigned once are always movable, + // so if the value of the variable is not movable, the expression that references + // the variable still is. + if (match->removesNonConstants && !MovableChecker(_expression).movable()) return; _expression = match->action().toExpression(locationOf(_expression)); } } + +void ExpressionSimplifier::run(Block& _ast) +{ + SSAValueTracker ssaValues; + ssaValues(_ast); + ExpressionSimplifier{ssaValues.values()}(_ast); +} diff --git a/libyul/optimiser/ExpressionSimplifier.h b/libyul/optimiser/ExpressionSimplifier.h index 1b9d6960..5419ff6a 100644 --- a/libyul/optimiser/ExpressionSimplifier.h +++ b/libyul/optimiser/ExpressionSimplifier.h @@ -31,6 +31,10 @@ namespace yul /** * Applies simplification rules to all expressions. + * The component will work best if the code is in SSA form, but + * this is not required for correctness. + * + * Prerequisite: Disambiguator. */ class ExpressionSimplifier: public ASTModifier { @@ -38,7 +42,13 @@ public: using ASTModifier::operator(); virtual void visit(Expression& _expression); + static void run(Block& _ast); private: + explicit ExpressionSimplifier(std::map<std::string, Expression const*> _ssaValues): + m_ssaValues(std::move(_ssaValues)) + {} + + std::map<std::string, Expression const*> m_ssaValues; }; } diff --git a/libyul/optimiser/FullInliner.cpp b/libyul/optimiser/FullInliner.cpp index 4e419987..cd0ed52a 100644 --- a/libyul/optimiser/FullInliner.cpp +++ b/libyul/optimiser/FullInliner.cpp @@ -23,12 +23,13 @@ #include <libyul/optimiser/ASTCopier.h> #include <libyul/optimiser/ASTWalker.h> #include <libyul/optimiser/NameCollector.h> -#include <libyul/optimiser/Semantics.h> +#include <libyul/optimiser/Utilities.h> #include <libyul/Exceptions.h> #include <libsolidity/inlineasm/AsmData.h> #include <libdevcore/CommonData.h> +#include <libdevcore/Visitor.h> #include <boost/range/adaptor/reversed.hpp> @@ -49,185 +50,112 @@ FullInliner::FullInliner(Block& _ast): assertThrow(m_ast.statements.at(i).type() == typeid(FunctionDefinition), OptimizerException, ""); FunctionDefinition& fun = boost::get<FunctionDefinition>(m_ast.statements.at(i)); m_functions[fun.name] = &fun; - m_functionsToVisit.insert(&fun); } } void FullInliner::run() { assertThrow(m_ast.statements[0].type() == typeid(Block), OptimizerException, ""); - InlineModifier(*this, m_nameDispenser, "").visit(m_ast.statements[0]); - while (!m_functionsToVisit.empty()) - handleFunction(**m_functionsToVisit.begin()); -} - -void FullInliner::handleFunction(FunctionDefinition& _fun) -{ - if (!m_functionsToVisit.count(&_fun)) - return; - m_functionsToVisit.erase(&_fun); - (InlineModifier(*this, m_nameDispenser, _fun.name))(_fun.body); -} -void InlineModifier::operator()(FunctionalInstruction& _instruction) -{ - visitArguments(_instruction.arguments); + handleBlock("", boost::get<Block>(m_ast.statements[0])); + for (auto const& fun: m_functions) + handleBlock(fun.second->name, fun.second->body); } -void InlineModifier::operator()(FunctionCall&) +void FullInliner::handleBlock(string const& _currentFunctionName, Block& _block) { - assertThrow(false, OptimizerException, "Should be handled in visit() instead."); -} - -void InlineModifier::operator()(ForLoop& _loop) -{ - (*this)(_loop.pre); - // Do not visit the condition because we cannot inline there. - (*this)(_loop.post); - (*this)(_loop.body); + InlineModifier{*this, m_nameDispenser, _currentFunctionName}(_block); } void InlineModifier::operator()(Block& _block) { - vector<Statement> saved; - saved.swap(m_statementsToPrefix); - - // This is only used if needed to minimize the number of move operations. - vector<Statement> modifiedStatements; - for (size_t i = 0; i < _block.statements.size(); ++i) - { - visit(_block.statements.at(i)); - if (!m_statementsToPrefix.empty()) - { - if (modifiedStatements.empty()) - std::move( - _block.statements.begin(), - _block.statements.begin() + i, - back_inserter(modifiedStatements) - ); - modifiedStatements += std::move(m_statementsToPrefix); - m_statementsToPrefix.clear(); - } - if (!modifiedStatements.empty()) - modifiedStatements.emplace_back(std::move(_block.statements[i])); - } - if (!modifiedStatements.empty()) - _block.statements = std::move(modifiedStatements); - - saved.swap(m_statementsToPrefix); + function<boost::optional<vector<Statement>>(Statement&)> f = [&](Statement& _statement) -> boost::optional<vector<Statement>> { + visit(_statement); + return tryInlineStatement(_statement); + }; + iterateReplacing(_block.statements, f); } -void InlineModifier::visit(Expression& _expression) +boost::optional<vector<Statement>> InlineModifier::tryInlineStatement(Statement& _statement) { - if (_expression.type() != typeid(FunctionCall)) - return ASTModifier::visit(_expression); - - FunctionCall& funCall = boost::get<FunctionCall>(_expression); - FunctionDefinition& fun = m_driver.function(funCall.functionName.name); - - m_driver.handleFunction(fun); - - // TODO: Insert good heuristic here. Perhaps implement that inside the driver. - bool doInline = funCall.functionName.name != m_currentFunction; - - if (fun.returnVariables.size() > 1) - doInline = false; - + // Only inline for expression statements, assignments and variable declarations. + Expression* e = boost::apply_visitor(GenericFallbackReturnsVisitor<Expression*, ExpressionStatement, Assignment, VariableDeclaration>( + [](ExpressionStatement& _s) { return &_s.expression; }, + [](Assignment& _s) { return _s.value.get(); }, + [](VariableDeclaration& _s) { return _s.value.get(); } + ), _statement); + if (e) { - vector<string> argNames; - vector<string> argTypes; - for (auto const& arg: fun.parameters) + // Only inline direct function calls. + FunctionCall* funCall = boost::apply_visitor(GenericFallbackReturnsVisitor<FunctionCall*, FunctionCall&>( + [](FunctionCall& _e) { return &_e; } + ), *e); + if (funCall) { - argNames.push_back(fun.name + "_" + arg.name); - argTypes.push_back(arg.type); - } - visitArguments(funCall.arguments, argNames, argTypes, doInline); - } - - if (!doInline) - return; + // TODO: Insert good heuristic here. Perhaps implement that inside the driver. + bool doInline = funCall->functionName.name != m_currentFunction; - map<string, string> variableReplacements; - for (size_t i = 0; i < funCall.arguments.size(); ++i) - variableReplacements[fun.parameters[i].name] = boost::get<Identifier>(funCall.arguments[i]).name; - if (fun.returnVariables.empty()) - _expression = noop(funCall.location); - else - { - string returnVariable = fun.returnVariables[0].name; - variableReplacements[returnVariable] = newName(fun.name + "_" + returnVariable); - - m_statementsToPrefix.emplace_back(VariableDeclaration{ - funCall.location, - {{funCall.location, variableReplacements[returnVariable], fun.returnVariables[0].type}}, - {} - }); - _expression = Identifier{funCall.location, variableReplacements[returnVariable]}; - } - m_statementsToPrefix.emplace_back(BodyCopier(m_nameDispenser, fun.name + "_", variableReplacements)(fun.body)); -} - -void InlineModifier::visit(Statement& _statement) -{ - ASTModifier::visit(_statement); - // Replace pop(0) expression statemets (and others) by empty blocks. - if (_statement.type() == typeid(ExpressionStatement)) - { - ExpressionStatement& expSt = boost::get<ExpressionStatement>(_statement); - if (expSt.expression.type() == typeid(FunctionalInstruction)) - { - FunctionalInstruction& funInstr = boost::get<FunctionalInstruction>(expSt.expression); - if (funInstr.instruction == solidity::Instruction::POP) - if (MovableChecker(funInstr.arguments.at(0)).movable()) - _statement = Block{expSt.location, {}}; + if (doInline) + return performInline(_statement, *funCall); } } + return {}; } -void InlineModifier::visitArguments( - vector<Expression>& _arguments, - vector<string> const& _nameHints, - vector<string> const& _types, - bool _moveToFront -) +vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionCall& _funCall) { - // If one of the elements moves parts to the front, all other elements right of it - // also have to be moved to the front to keep the order of evaluation. - vector<Statement> prefix; - for (size_t i = 0; i < _arguments.size(); ++i) - { - auto& arg = _arguments[i]; - // TODO optimize vector operations, check that it actually moves - auto internalPrefix = visitRecursively(arg); - if (!internalPrefix.empty()) + vector<Statement> newStatements; + map<string, string> variableReplacements; + + FunctionDefinition& function = m_driver.function(_funCall.functionName.name); + + // helper function to create a new variable that is supposed to model + // an existing variable. + auto newVariable = [&](TypedName const& _existingVariable, Expression* _value) { + string newName = m_nameDispenser.newName(function.name + "_" + _existingVariable.name); + variableReplacements[_existingVariable.name] = newName; + VariableDeclaration varDecl{_funCall.location, {{_funCall.location, newName, _existingVariable.type}}, {}}; + if (_value) + varDecl.value = make_shared<Expression>(std::move(*_value)); + newStatements.emplace_back(std::move(varDecl)); + }; + + for (size_t i = 0; i < _funCall.arguments.size(); ++i) + newVariable(function.parameters[i], &_funCall.arguments[i]); + for (auto const& var: function.returnVariables) + newVariable(var, nullptr); + + Statement newBody = BodyCopier(m_nameDispenser, function.name + "_", variableReplacements)(function.body); + newStatements += std::move(boost::get<Block>(newBody).statements); + + boost::apply_visitor(GenericFallbackVisitor<Assignment, VariableDeclaration>{ + [&](Assignment& _assignment) { - _moveToFront = true; - // We go through the arguments left to right, so we have to invert - // the prefixes. - prefix = std::move(internalPrefix) + std::move(prefix); - } - else if (_moveToFront) + for (size_t i = 0; i < _assignment.variableNames.size(); ++i) + newStatements.emplace_back(Assignment{ + _assignment.location, + {_assignment.variableNames[i]}, + make_shared<Expression>(Identifier{ + _assignment.location, + variableReplacements.at(function.returnVariables[i].name) + }) + }); + }, + [&](VariableDeclaration& _varDecl) { - auto location = locationOf(arg); - string var = newName(i < _nameHints.size() ? _nameHints[i] : ""); - prefix.emplace(prefix.begin(), VariableDeclaration{ - location, - {{TypedName{location, var, i < _types.size() ? _types[i] : ""}}}, - make_shared<Expression>(std::move(arg)) - }); - arg = Identifier{location, var}; + for (size_t i = 0; i < _varDecl.variables.size(); ++i) + newStatements.emplace_back(VariableDeclaration{ + _varDecl.location, + {std::move(_varDecl.variables[i])}, + make_shared<Expression>(Identifier{ + _varDecl.location, + variableReplacements.at(function.returnVariables[i].name) + }) + }); } - } - m_statementsToPrefix += std::move(prefix); -} - -vector<Statement> InlineModifier::visitRecursively(Expression& _expression) -{ - vector<Statement> saved; - saved.swap(m_statementsToPrefix); - visit(_expression); - saved.swap(m_statementsToPrefix); - return saved; + // nothing to be done for expression statement + }, _statement); + return newStatements; } string InlineModifier::newName(string const& _prefix) @@ -235,13 +163,6 @@ string InlineModifier::newName(string const& _prefix) return m_nameDispenser.newName(_prefix); } -Expression InlineModifier::noop(SourceLocation const& _location) -{ - return FunctionalInstruction{_location, solidity::Instruction::POP, { - Literal{_location, assembly::LiteralKind::Number, "0", ""} - }}; -} - Statement BodyCopier::operator()(VariableDeclaration const& _varDecl) { for (auto const& var: _varDecl.variables) diff --git a/libyul/optimiser/FullInliner.h b/libyul/optimiser/FullInliner.h index 8112fb4b..495286c0 100644 --- a/libyul/optimiser/FullInliner.h +++ b/libyul/optimiser/FullInliner.h @@ -42,29 +42,31 @@ class NameCollector; /** - * Optimiser component that modifies an AST in place, inlining arbitrary functions. + * Optimiser component that modifies an AST in place, inlining functions. + * Expressions are expected to be split, i.e. the component will only inline + * function calls that are at the root of the expression and that only contains + * variables as arguments. More specifically, it will inline + * - let x1, ..., xn := f(a1, ..., am) + * - x1, ..., xn := f(a1, ..., am) + * f(a1, ..., am) * - * Code of the form + * The transform changes code of the form * * function f(a, b) -> c { ... } - * h(g(x(...), f(arg1(...), arg2(...)), y(...)), z(...)) + * let z := f(x, y) * - * is transformed into + * into * * function f(a, b) -> c { ... } * - * let z1 := z(...) let y1 := y(...) let a2 := arg2(...) let a1 := arg1(...) - * let c1 := 0 - * { code of f, with replacements: a -> a1, b -> a2, c -> c1, d -> d1 } - * h(g(x(...), c1, y1), z1) + * let f_a := x + * let f_b := y + * let f_c + * code of f, with replacements: a -> f_a, b -> f_b, c -> f_c + * let z := f_c * - * No temporary variable is created for expressions that are "movable" - * (i.e. they are "pure", have no side-effects and also do not depend on other code - * that might have side-effects). - * - * This component can only be used on sources with unique names and with hoisted functions, - * i.e. the root node has to be a block that itself contains a single block followed by all - * function definitions. + * Prerequisites: Disambiguator, Function Hoister + * More efficient if run after: Expression Splitter */ class FullInliner: public ASTModifier { @@ -73,17 +75,15 @@ public: void run(); - /// Perform inlining operations inside the given function. - void handleFunction(FunctionDefinition& _function); - FunctionDefinition& function(std::string _name) { return *m_functions.at(_name); } private: + void handleBlock(std::string const& _currentFunctionName, Block& _block); + /// The AST to be modified. The root block itself will not be modified, because /// we store pointers to functions. Block& m_ast; std::map<std::string, FunctionDefinition*> m_functions; - std::set<FunctionDefinition*> m_functionsToVisit; NameDispenser m_nameDispenser; }; @@ -99,46 +99,15 @@ public: m_driver(_driver), m_nameDispenser(_nameDispenser) { } - ~InlineModifier() - { - assertThrow(m_statementsToPrefix.empty(), OptimizerException, ""); - } - - virtual void operator()(FunctionalInstruction&) override; - virtual void operator()(FunctionCall&) override; - virtual void operator()(ForLoop&) override; - virtual void operator()(Block& _block) override; - using ASTModifier::visit; - virtual void visit(Expression& _expression) override; - virtual void visit(Statement& _st) override; + virtual void operator()(Block& _block) override; private: - - /// Visits a list of expressions (usually an argument list to a function call) and tries - /// to inline them. If one of them is inlined, all right of it have to be moved to the front - /// (to keep the order of evaluation). If @a _moveToFront is true, all elements are moved - /// to the front. @a _nameHints and @_types are used for the newly created variables, but - /// both can be empty. - void visitArguments( - std::vector<Expression>& _arguments, - std::vector<std::string> const& _nameHints = {}, - std::vector<std::string> const& _types = {}, - bool _moveToFront = false - ); - - /// Visits an expression, but saves and restores the current statements to prefix and returns - /// the statements that should be prefixed for @a _expression. - std::vector<Statement> visitRecursively(Expression& _expression); + boost::optional<std::vector<Statement>> tryInlineStatement(Statement& _statement); + std::vector<Statement> performInline(Statement& _statement, FunctionCall& _funCall); std::string newName(std::string const& _prefix); - /// @returns an expression returning nothing. - Expression noop(SourceLocation const& _location); - - /// List of statements that should go in front of the currently visited AST element, - /// at the statement level. - std::vector<Statement> m_statementsToPrefix; std::string m_currentFunction; FullInliner& m_driver; NameDispenser& m_nameDispenser; diff --git a/libyul/optimiser/SSAValueTracker.cpp b/libyul/optimiser/SSAValueTracker.cpp new file mode 100644 index 00000000..a1291d67 --- /dev/null +++ b/libyul/optimiser/SSAValueTracker.cpp @@ -0,0 +1,53 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * Component that collects variables that are never assigned to and their + * initial values. + */ + +#include <libyul/optimiser/SSAValueTracker.h> + +#include <libsolidity/inlineasm/AsmData.h> + +using namespace std; +using namespace dev; +using namespace dev::yul; + +void SSAValueTracker::operator()(Assignment const& _assignment) +{ + for (auto const& var: _assignment.variableNames) + m_values.erase(var.name); +} + +void SSAValueTracker::operator()(VariableDeclaration const& _varDecl) +{ + if (_varDecl.variables.size() == 1) + setValue(_varDecl.variables.front().name, _varDecl.value.get()); + else + for (auto const& var: _varDecl.variables) + setValue(var.name, nullptr); +} + +void SSAValueTracker::setValue(string const& _name, Expression const* _value) +{ + assertThrow( + m_values.count(_name) == 0, + OptimizerException, + "Source needs to be disambiguated." + ); + m_values[_name] = _value; +} diff --git a/libyul/optimiser/SSAValueTracker.h b/libyul/optimiser/SSAValueTracker.h new file mode 100644 index 00000000..8c39a98e --- /dev/null +++ b/libyul/optimiser/SSAValueTracker.h @@ -0,0 +1,58 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * Component that collects variables that are never assigned to and their + * initial values. + */ + +#pragma once + +#include <libyul/optimiser/ASTWalker.h> + +#include <string> +#include <map> +#include <set> + +namespace dev +{ +namespace yul +{ + +/** + * Class that walks the AST and stores the initial value of each variable + * that is never assigned to. + * + * Prerequisite: Disambiguator + */ +class SSAValueTracker: public ASTWalker +{ +public: + using ASTWalker::operator(); + virtual void operator()(VariableDeclaration const& _varDecl) override; + virtual void operator()(Assignment const& _assignment) override; + + std::map<std::string, Expression const*> const& values() const { return m_values; } + Expression const* value(std::string const& _name) const { return m_values.at(_name); } + +private: + void setValue(std::string const& _name, Expression const* _value); + + std::map<std::string, Expression const*> m_values; +}; + +} +} diff --git a/libyul/optimiser/SimplificationRules.cpp b/libyul/optimiser/SimplificationRules.cpp index 762473e5..4d0468c7 100644 --- a/libyul/optimiser/SimplificationRules.cpp +++ b/libyul/optimiser/SimplificationRules.cpp @@ -34,7 +34,10 @@ using namespace dev; using namespace dev::yul; -SimplificationRule<Pattern> const* SimplificationRules::findFirstMatch(Expression const& _expr) +SimplificationRule<Pattern> const* SimplificationRules::findFirstMatch( + Expression const& _expr, + map<string, Expression const*> const& _ssaValues +) { if (_expr.type() != typeid(FunctionalInstruction)) return nullptr; @@ -46,7 +49,7 @@ SimplificationRule<Pattern> const* SimplificationRules::findFirstMatch(Expressio for (auto const& rule: rules.m_rules[byte(instruction.instruction)]) { rules.resetMatchGroups(); - if (rule.pattern.matches(_expr)) + if (rule.pattern.matches(_expr, _ssaValues)) return &rule; } return nullptr; @@ -101,13 +104,25 @@ void Pattern::setMatchGroup(unsigned _group, map<unsigned, Expression const*>& _ m_matchGroups = &_matchGroups; } -bool Pattern::matches(Expression const& _expr) const +bool Pattern::matches(Expression const& _expr, map<string, Expression const*> const& _ssaValues) const { + Expression const* expr = &_expr; + + // Resolve the variable if possible. + // Do not do it for "Any" because we can check identity better for variables. + if (m_kind != PatternKind::Any && _expr.type() == typeid(Identifier)) + { + string const& varName = boost::get<Identifier>(_expr).name; + if (_ssaValues.count(varName)) + expr = _ssaValues.at(varName); + } + assertThrow(expr, OptimizerException, ""); + if (m_kind == PatternKind::Constant) { - if (_expr.type() != typeid(Literal)) + if (expr->type() != typeid(Literal)) return false; - Literal const& literal = boost::get<Literal>(_expr); + Literal const& literal = boost::get<Literal>(*expr); if (literal.kind != assembly::LiteralKind::Number) return false; if (m_data && *m_data != u256(literal.value)) @@ -116,34 +131,51 @@ bool Pattern::matches(Expression const& _expr) const } else if (m_kind == PatternKind::Operation) { - if (_expr.type() != typeid(FunctionalInstruction)) + if (expr->type() != typeid(FunctionalInstruction)) return false; - FunctionalInstruction const& instr = boost::get<FunctionalInstruction>(_expr); + FunctionalInstruction const& instr = boost::get<FunctionalInstruction>(*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))) + if (!m_arguments[i].matches(instr.arguments.at(i), _ssaValues)) return false; } else { - assertThrow(m_arguments.empty(), OptimizerException, ""); + assertThrow(m_arguments.empty(), OptimizerException, "\"Any\" should not have arguments."); } - // We support matching multiple expressions that require the same value - // based on identical ASTs, which have to be movable. + if (m_matchGroup) { + // We support matching multiple expressions that require the same value + // based on identical ASTs, which have to be movable. + + // TODO: add tests: + // - { let x := mload(0) let y := and(x, x) } + // - { let x := 4 let y := and(x, y) } + + // This code uses `_expr` again for "Any", because we want the comparison to be done + // on the variables and not their values. + // The assumption is that CSE or local value numbering has been done prior to this step. + if (m_matchGroups->count(m_matchGroup)) { + assertThrow(m_kind == PatternKind::Any, OptimizerException, "Match group repetition for non-any."); Expression const* firstMatch = (*m_matchGroups)[m_matchGroup]; assertThrow(firstMatch, OptimizerException, "Match set but to null."); return SyntacticalEqualityChecker::equal(*firstMatch, _expr) && MovableChecker(_expr).movable(); } - else + else if (m_kind == PatternKind::Any) (*m_matchGroups)[m_matchGroup] = &_expr; + else + { + assertThrow(m_kind == PatternKind::Constant, OptimizerException, "Match group set for operation."); + // We do not use _expr here, because we want the actual number. + (*m_matchGroups)[m_matchGroup] = expr; + } } return true; } diff --git a/libyul/optimiser/SimplificationRules.h b/libyul/optimiser/SimplificationRules.h index 25d91573..82ae5d22 100644 --- a/libyul/optimiser/SimplificationRules.h +++ b/libyul/optimiser/SimplificationRules.h @@ -49,7 +49,11 @@ public: /// @returns a pointer to the first matching pattern and sets the match /// groups accordingly. - static SimplificationRule<Pattern> const* findFirstMatch(Expression const& _expr); + /// @param _ssaValues values of variables that are assigned exactly once. + static SimplificationRule<Pattern> const* findFirstMatch( + Expression const& _expr, + std::map<std::string, Expression const*> const& _ssaValues + ); /// Checks whether the rulelist is non-empty. This is usually enforced /// by the constructor, but we had some issues with static initialization. @@ -92,7 +96,7 @@ public: /// same expression equivalence class. void setMatchGroup(unsigned _group, std::map<unsigned, Expression const*>& _matchGroups); unsigned matchGroup() const { return m_matchGroup; } - bool matches(Expression const& _expr) const; + bool matches(Expression const& _expr, std::map<std::string, Expression const*> const& _ssaValues) const; std::vector<Pattern> arguments() const { return m_arguments; } diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 4052ed13..e0c6a2b6 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -994,16 +994,14 @@ void CommandLineInterface::handleAst(string const& _argStr) for (auto const& sourceCode: m_sourceCodes) asts.push_back(&m_compiler->ast(sourceCode.first)); map<ASTNode const*, eth::GasMeter::GasConsumption> gasCosts; - // FIXME: shouldn't this be done for every contract? - if (m_compiler->runtimeAssemblyItems(m_compiler->lastContractName())) + for (auto const& contract : m_compiler->contractNames()) { - //NOTE: keep the local variable `ret` to prevent a Heisenbug that could happen on certain mac os platform. - //See: https://github.com/ethereum/solidity/issues/3718 for details. auto ret = GasEstimator::breakToStatementLevel( - GasEstimator(m_evmVersion).structuralEstimation(*m_compiler->runtimeAssemblyItems(m_compiler->lastContractName()), asts), + GasEstimator(m_evmVersion).structuralEstimation(*m_compiler->runtimeAssemblyItems(contract), asts), asts ); - gasCosts = ret; + for (auto const& it: ret) + gasCosts[it.first] += it.second; } bool legacyFormat = !m_args.count(g_argAstCompactJson); diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 20254ef4..a8261693 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -292,6 +292,14 @@ SOLTMPDIR=$(mktemp -d) if [[ "$result" != 0 ]] ; then exit 1 fi + + # This should not fail + set +e + output=$(echo '' | "$SOLC" --ast - 2>/dev/null) + set -e + if [[ $? != 0 ]] ; then + exit 1 + fi ) printTask "Testing soljson via the fuzzer..." diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index ea8e4b5e..8e4771c8 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -130,7 +130,11 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con disambiguate(); (FunctionHoister{})(*m_ast); (FunctionGrouper{})(*m_ast); + NameDispenser nameDispenser; + nameDispenser.m_usedNames = NameCollector(*m_ast).names(); + ExpressionSplitter{nameDispenser}(*m_ast); FullInliner(*m_ast).run(); + ExpressionJoiner::run(*m_ast); } else if (m_optimizerStep == "mainFunction") { @@ -146,7 +150,19 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con else if (m_optimizerStep == "expressionSimplifier") { disambiguate(); - (ExpressionSimplifier{})(*m_ast); + ExpressionSimplifier::run(*m_ast); + } + else if (m_optimizerStep == "fullSimplify") + { + disambiguate(); + NameDispenser nameDispenser; + nameDispenser.m_usedNames = NameCollector(*m_ast).names(); + ExpressionSplitter{nameDispenser}(*m_ast); + CommonSubexpressionEliminator{}(*m_ast); + ExpressionSimplifier::run(*m_ast); + UnusedPruner::runUntilStabilised(*m_ast); + ExpressionJoiner::run(*m_ast); + ExpressionJoiner::run(*m_ast); } else if (m_optimizerStep == "unusedPruner") { diff --git a/test/libyul/yulOptimizerTests/fullInliner/double_inline.yul b/test/libyul/yulOptimizerTests/fullInliner/double_inline.yul new file mode 100644 index 00000000..dd1c1f8a --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullInliner/double_inline.yul @@ -0,0 +1,30 @@ +{ + function f(a) -> b, c { let x := mload(a) b := sload(x) c := 3 } + let a1 := calldataload(0) + let b3, c3 := f(a1) + let b4, c4 := f(c3) +} +// ---- +// fullInliner +// { +// { +// let f_a := calldataload(0) +// let f_b +// let f_c +// f_b := sload(mload(f_a)) +// f_c := 3 +// let b3 := f_b +// let f_a_1 := f_c +// let f_b_1 +// let f_c_1 +// f_b_1 := sload(mload(f_a_1)) +// f_c_1 := 3 +// let b4 := f_b_1 +// let c4 := f_c_1 +// } +// function f(a) -> b, c +// { +// b := sload(mload(a)) +// c := 3 +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullInliner/inside_condition.yul b/test/libyul/yulOptimizerTests/fullInliner/inside_condition.yul index 76b6054b..00bb6577 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/inside_condition.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/inside_condition.yul @@ -12,14 +12,12 @@ // fullInliner // { // { -// let _1 := mload(0) +// let _2 := mload(0) // let f_a := mload(1) // let f_r -// { -// f_a := mload(f_a) -// f_r := add(f_a, calldatasize()) -// } -// if gt(f_r, _1) +// f_a := mload(f_a) +// f_r := add(f_a, calldatasize()) +// if gt(f_r, _2) // { // sstore(0, 2) // } diff --git a/test/libyul/yulOptimizerTests/fullInliner/move_up_rightwards_argument.yul b/test/libyul/yulOptimizerTests/fullInliner/move_up_rightwards_argument.yul index e1def585..f3d0b286 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/move_up_rightwards_argument.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/move_up_rightwards_argument.yul @@ -9,16 +9,17 @@ // fullInliner // { // { -// let _1 := mload(5) -// let f_c := mload(4) -// let f_b := mload(3) +// let _2 := mload(5) +// let _4 := mload(4) +// let _6 := mload(3) // let f_a := mload(2) +// let f_b := _6 +// let f_c := _4 // let f_x -// { -// f_x := add(f_a, f_b) -// f_x := mul(f_x, f_c) -// } -// let y := add(mload(1), add(f_x, _1)) +// f_x := add(f_a, f_b) +// f_x := mul(f_x, f_c) +// let _10 := add(f_x, _2) +// let y := add(mload(1), _10) // } // function f(a, b, c) -> x // { diff --git a/test/libyul/yulOptimizerTests/fullInliner/multi_fun.yul b/test/libyul/yulOptimizerTests/fullInliner/multi_fun.yul index 94bbe5dc..c704944d 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/multi_fun.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/multi_fun.yul @@ -7,21 +7,14 @@ // fullInliner // { // { -// let g_c := 7 -// let f_a_1 := 3 -// let f_x_1 -// { -// f_x_1 := add(f_a_1, f_a_1) -// } +// let _1 := 7 +// let f_a := 3 +// let f_x +// f_x := add(f_a, f_a) +// let g_b := f_x +// let g_c := _1 // let g_y -// { -// let g_f_a := f_x_1 -// let g_f_x -// { -// g_f_x := add(g_f_a, g_f_a) -// } -// g_y := mul(mload(g_c), g_f_x) -// } +// g_y := mul(mload(g_c), f(g_b)) // let y_1 := g_y // } // function f(a) -> x @@ -30,11 +23,9 @@ // } // function g(b, c) -> y // { -// let f_a := b -// let f_x -// { -// f_x := add(f_a, f_a) -// } -// y := mul(mload(c), f_x) +// let f_a_1 := b +// let f_x_1 +// f_x_1 := add(f_a_1, f_a_1) +// y := mul(mload(c), f_x_1) // } // } diff --git a/test/libyul/yulOptimizerTests/fullInliner/multi_fun_callback.yul b/test/libyul/yulOptimizerTests/fullInliner/multi_fun_callback.yul new file mode 100644 index 00000000..bcdba8e0 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullInliner/multi_fun_callback.yul @@ -0,0 +1,62 @@ +{ + // This is a test for an older version where + // inlining was performed on a function + // just being called. This is a problem + // because the statemenst of the original + // function might be in an invalid state. + + function f(x) { + mstore(0, x) + mstore(7, h()) + g(10) + mstore(1, x) + } + function g(x) { + f(1) + } + function h() -> t { + t := 2 + + } + { + f(100) + } +} +// ---- +// fullInliner +// { +// { +// { +// let f_x := 100 +// mstore(0, f_x) +// mstore(7, h()) +// g(10) +// mstore(1, f_x) +// } +// } +// function f(x) +// { +// mstore(0, x) +// let h_t +// h_t := 2 +// mstore(7, h_t) +// let g_x_1 := 10 +// f(1) +// mstore(1, x) +// } +// function g(x_1) +// { +// let f_x_1 := 1 +// mstore(0, f_x_1) +// let f_h_t +// f_h_t := 2 +// mstore(7, f_h_t) +// let f_g_x_1 := 10 +// f(1) +// mstore(1, f_x_1) +// } +// function h() -> t +// { +// t := 2 +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullInliner/multi_return.yul b/test/libyul/yulOptimizerTests/fullInliner/multi_return.yul index f3c5b0ee..eebdec38 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/multi_return.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/multi_return.yul @@ -1,17 +1,22 @@ -// The full inliner currently does not work with -// functions returning multiple values. { function f(a) -> x, y { x := mul(a, a) y := add(a, x) } - let a, b := f(mload(0)) + let r, s := f(mload(0)) + mstore(r, s) } // ---- // fullInliner // { // { -// let a_1, b := f(mload(0)) +// let f_a := mload(0) +// let f_x +// let f_y +// f_x := mul(f_a, f_a) +// f_y := add(f_a, f_x) +// let r := f_x +// mstore(r, f_y) // } // function f(a) -> x, y // { diff --git a/test/libyul/yulOptimizerTests/fullInliner/no_return.yul b/test/libyul/yulOptimizerTests/fullInliner/no_return.yul index 53fe3527..3708c557 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/no_return.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/no_return.yul @@ -9,11 +9,7 @@ // { // { // let f_a := mload(0) -// { -// sstore(f_a, f_a) -// } -// { -// } +// sstore(f_a, f_a) // } // function f(a) // { diff --git a/test/libyul/yulOptimizerTests/fullInliner/not_inside_for.yul b/test/libyul/yulOptimizerTests/fullInliner/not_inside_for.yul new file mode 100644 index 00000000..44fc7b21 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullInliner/not_inside_for.yul @@ -0,0 +1,43 @@ +{ + for { let x := f(0) } f(x) { x := f(x) } + { + let t := f(x) + } + function f(a) -> r { + sstore(a, 0) + r := a + } +} +// ---- +// fullInliner +// { +// { +// for { +// let f_a := 0 +// let f_r +// sstore(f_a, 0) +// f_r := f_a +// let x := f_r +// } +// f(x) +// { +// let f_a_1 := x +// let f_r_1 +// sstore(f_a_1, 0) +// f_r_1 := f_a_1 +// x := f_r_1 +// } +// { +// let f_a_2 := x +// let f_r_2 +// sstore(f_a_2, 0) +// f_r_2 := f_a_2 +// let t := f_r_2 +// } +// } +// function f(a) -> r +// { +// sstore(a, 0) +// r := a +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullInliner/pop_result.yul b/test/libyul/yulOptimizerTests/fullInliner/pop_result.yul index 3883c67c..cd9e2746 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/pop_result.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/pop_result.yul @@ -1,4 +1,6 @@ -// This tests that `pop(r)` is removed. +// An earlier version of the inliner produced +// pop(...) statements and explicitly removed them. +// This used to test that they are removed. { function f(a) -> x { let r := mul(a, a) @@ -13,12 +15,9 @@ // let _1 := 2 // let f_a := 7 // let f_x -// { -// let f_r := mul(f_a, f_a) -// f_x := add(f_r, f_r) -// } -// { -// } +// let f_r := mul(f_a, f_a) +// f_x := add(f_r, f_r) +// pop(add(f_x, _1)) // } // function f(a) -> x // { diff --git a/test/libyul/yulOptimizerTests/fullInliner/simple.yul b/test/libyul/yulOptimizerTests/fullInliner/simple.yul index dd1a4e0a..fcdf453b 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/simple.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/simple.yul @@ -9,14 +9,12 @@ // fullInliner // { // { -// let _1 := mload(7) +// let _2 := mload(7) // let f_a := sload(mload(2)) // let f_x -// { -// let f_r := mul(f_a, f_a) -// f_x := add(f_r, f_r) -// } -// let y := add(f_x, _1) +// let f_r := mul(f_a, f_a) +// f_x := add(f_r, f_r) +// let y := add(f_x, _2) // } // function f(a) -> x // { diff --git a/test/libyul/yulOptimizerTests/fullSimplify/constant_propagation.yul b/test/libyul/yulOptimizerTests/fullSimplify/constant_propagation.yul new file mode 100644 index 00000000..90a3e16d --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/constant_propagation.yul @@ -0,0 +1,10 @@ +{ + let a := add(7, sub(mload(0), 7)) + mstore(a, 0) +} +// ---- +// fullSimplify +// { +// let _2 := 0 +// mstore(mload(_2), _2) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/constants.yul b/test/libyul/yulOptimizerTests/fullSimplify/constants.yul new file mode 100644 index 00000000..b9c7c1fc --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/constants.yul @@ -0,0 +1,9 @@ +{ + let a := add(1, mul(3, 4)) + mstore(0, a) +} +// ---- +// fullSimplify +// { +// mstore(0, 13) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_complex.yul b/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_complex.yul new file mode 100644 index 00000000..4b17d7ea --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_complex.yul @@ -0,0 +1,9 @@ +{ + let a := sub(calldataload(0), calldataload(0)) + mstore(a, 0) +} +// ---- +// fullSimplify +// { +// mstore(0, 0) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_negative.yul b/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_negative.yul new file mode 100644 index 00000000..a1737efa --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_negative.yul @@ -0,0 +1,10 @@ +{ + let a := sub(calldataload(1), calldataload(0)) + mstore(0, a) +} +// ---- +// fullSimplify +// { +// let _1 := 0 +// mstore(_1, sub(calldataload(1), calldataload(_1))) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_simple.yul b/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_simple.yul new file mode 100644 index 00000000..22a358fd --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/identity_rules_simple.yul @@ -0,0 +1,11 @@ +{ + let a := mload(0) + mstore(0, sub(a, a)) +} +// ---- +// fullSimplify +// { +// let _1 := 0 +// pop(mload(_1)) +// mstore(_1, 0) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/including_function_calls.yul b/test/libyul/yulOptimizerTests/fullSimplify/including_function_calls.yul new file mode 100644 index 00000000..fa3ff07c --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/including_function_calls.yul @@ -0,0 +1,13 @@ +{ + function f() -> a {} + let b := add(7, sub(f(), 7)) + mstore(b, 0) +} +// ---- +// fullSimplify +// { +// function f() -> a +// { +// } +// mstore(f(), 0) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/inside_for.yul b/test/libyul/yulOptimizerTests/fullSimplify/inside_for.yul new file mode 100644 index 00000000..f1b40301 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/inside_for.yul @@ -0,0 +1,17 @@ +{ + let x := calldataload(3) + for { let a := 10 } iszero(eq(a, sub(x, calldataload(3)))) { a := add(a, 1) } {} +} +// ---- +// fullSimplify +// { +// for { +// let a := 10 +// } +// iszero(iszero(a)) +// { +// a := add(a, 1) +// } +// { +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/invariant.yul b/test/libyul/yulOptimizerTests/fullSimplify/invariant.yul new file mode 100644 index 00000000..a8eedef1 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/invariant.yul @@ -0,0 +1,17 @@ +{ + let a := calldataload(sub(7, 7)) + let b := sub(a, 0) + // Below, `b` is not eliminated, because + // we run CSE and then Simplify. + // Elimination of `b` would require another + // run of CSE afterwards. + mstore(b, eq(calldataload(0), a)) +} +// ---- +// fullSimplify +// { +// let a := calldataload(0) +// let _4 := 0 +// let b := a +// mstore(b, eq(calldataload(_4), a)) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/mod_and_1.yul b/test/libyul/yulOptimizerTests/fullSimplify/mod_and_1.yul new file mode 100644 index 00000000..bba16a94 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/mod_and_1.yul @@ -0,0 +1,9 @@ +{ + mstore(0, mod(calldataload(0), exp(2, 8))) +} +// ---- +// fullSimplify +// { +// let _4 := 0 +// mstore(_4, and(calldataload(_4), 255)) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/mod_and_2.yul b/test/libyul/yulOptimizerTests/fullSimplify/mod_and_2.yul new file mode 100644 index 00000000..4a6eaa52 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/mod_and_2.yul @@ -0,0 +1,9 @@ +{ + mstore(0, mod(calldataload(0), exp(2, 255))) +} +// ---- +// fullSimplify +// { +// let _4 := 0 +// mstore(_4, and(calldataload(_4), 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_arguments.yul b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_arguments.yul new file mode 100644 index 00000000..0c5e3ed9 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_arguments.yul @@ -0,0 +1,14 @@ +{ + function f(a) -> b { } + mstore(0, sub(f(0), f(1))) +} +// ---- +// fullSimplify +// { +// function f(a) -> b +// { +// } +// let _2 := f(1) +// let _3 := 0 +// mstore(_3, sub(f(_3), _2)) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_names.yul b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_names.yul new file mode 100644 index 00000000..90e89fe1 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_different_names.yul @@ -0,0 +1,17 @@ +{ + function f1() -> a { } + function f2() -> b { } + let c := sub(f1(), f2()) + mstore(0, c) +} +// ---- +// fullSimplify +// { +// function f1() -> a +// { +// } +// function f2() -> b +// { +// } +// mstore(0, sub(f1(), f2())) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_equality_not_movable.yul b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_equality_not_movable.yul new file mode 100644 index 00000000..92e50ebe --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_equality_not_movable.yul @@ -0,0 +1,14 @@ +// Even if the functions pass the equality check, they are not movable. +{ + function f() -> a { } + let b := sub(f(), f()) + mstore(0, b) +} +// ---- +// fullSimplify +// { +// function f() -> a +// { +// } +// mstore(0, sub(f(), f())) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_removes_non_constant_and_not_movable.yul b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_removes_non_constant_and_not_movable.yul new file mode 100644 index 00000000..7dcdc280 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_removes_non_constant_and_not_movable.yul @@ -0,0 +1,12 @@ +// div is eliminated, but keccak256 has side-effects. +{ + let a := div(keccak256(0, 0), 0) + mstore(0, a) +} +// ---- +// fullSimplify +// { +// let _1 := 0 +// pop(keccak256(_1, _1)) +// mstore(_1, 0) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/reversed.yul b/test/libyul/yulOptimizerTests/fullSimplify/reversed.yul new file mode 100644 index 00000000..fb916e6a --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/reversed.yul @@ -0,0 +1,10 @@ +{ + let a := add(0, mload(0)) + mstore(0, a) +} +// ---- +// fullSimplify +// { +// let _1 := 0 +// mstore(_1, mload(_1)) +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/smoke.yul b/test/libyul/yulOptimizerTests/fullSimplify/smoke.yul new file mode 100644 index 00000000..a4fbb899 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSimplify/smoke.yul @@ -0,0 +1,5 @@ +{ } +// ---- +// fullSimplify +// { +// } |