diff --git a/Changelog.md b/Changelog.md index 12b2e848d..3e7a19393 100644 --- a/Changelog.md +++ b/Changelog.md @@ -15,7 +15,9 @@ Compiler Features: * SMTChecker: Support tuples and function calls with multiple return values. * SMTChecker: Support ``delete``. * SMTChecker: Inline external function calls to ``this``. + * Optimizer: Add rules for multiplication and division by left-shifted one. * Yul Optimizer: Simplify single-run ``for`` loops to ``if`` statements. + * Yul Optimizer: Optimize representation of numbers. * Yul Optimizer: Do not inline recursive functions. diff --git a/libdevcore/Common.h b/libdevcore/Common.h index 35574909c..3ce116da9 100644 --- a/libdevcore/Common.h +++ b/libdevcore/Common.h @@ -91,6 +91,20 @@ inline u256 s2u(s256 _u) return u256(c_end + _u); } +inline u256 exp256(u256 _base, u256 _exponent) +{ + using boost::multiprecision::limb_type; + u256 result = 1; + while (_exponent) + { + if (boost::multiprecision::bit_test(_exponent, 0)) + result *= _base; + _base *= _base; + _exponent >>= 1; + } + return result; +} + inline std::ostream& operator<<(std::ostream& os, bytes const& _bytes) { std::ostringstream ss; diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 153ee5b8a..e208a8c3c 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -382,7 +383,8 @@ void CompilerContext::appendInlineAssembly( ErrorList errors; ErrorReporter errorReporter(errors); auto scanner = make_shared(langutil::CharStream(_assembly, "--CODEGEN--")); - auto parserResult = yul::Parser(errorReporter, yul::EVMDialect::strictAssemblyForEVM(m_evmVersion)).parse(scanner, false); + yul::EVMDialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion); + auto parserResult = yul::Parser(errorReporter, dialect).parse(scanner, false); #ifdef SOL_OUTPUT_ASM cout << yul::AsmPrinter()(*parserResult) << endl; #endif @@ -409,7 +411,7 @@ void CompilerContext::appendInlineAssembly( analysisInfo, errorReporter, boost::none, - yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), + dialect, identifierAccess.resolve ).analyze(*parserResult); if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) @@ -419,8 +421,10 @@ void CompilerContext::appendInlineAssembly( // so we essentially only optimize the ABI functions. if (_optimiserSettings.runYulOptimiser && _localVariables.empty()) { + bool const isCreation = m_runtimeContext != nullptr; yul::OptimiserSuite::run( - yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), + dialect, + yul::GasMeter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment), *parserResult, analysisInfo, _optimiserSettings.optimizeStackAllocation, @@ -431,7 +435,7 @@ void CompilerContext::appendInlineAssembly( analysisInfo, errorReporter, boost::none, - yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), + dialect, identifierAccess.resolve ).analyze(*parserResult)) reportError("Optimizer introduced error into inline assembly."); diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index 98c9288eb..dec7d21e8 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -98,7 +99,7 @@ void AssemblyStack::optimize() m_analysisSuccessful = false; solAssert(m_parserResult, ""); - optimize(*m_parserResult); + optimize(*m_parserResult, true); solAssert(analyzeParsed(), "Invalid source code after optimization."); } @@ -138,15 +139,18 @@ void AssemblyStack::compileEVM(AbstractAssembly& _assembly, bool _evm15, bool _o EVMObjectCompiler::compile(*m_parserResult, _assembly, *dialect, _evm15, _optimize); } -void AssemblyStack::optimize(Object& _object) +void AssemblyStack::optimize(Object& _object, bool _isCreation) { solAssert(_object.code, ""); solAssert(_object.analysisInfo, ""); for (auto& subNode: _object.subObjects) if (auto subObject = dynamic_cast(subNode.get())) - optimize(*subObject); + optimize(*subObject, false); + EVMDialect const& dialect = dynamic_cast(languageToDialect(m_language, m_evmVersion)); + GasMeter meter(dialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment); OptimiserSuite::run( - languageToDialect(m_language, m_evmVersion), + dialect, + meter, *_object.code, *_object.analysisInfo, m_optimiserSettings.optimizeStackAllocation diff --git a/libyul/AssemblyStack.h b/libyul/AssemblyStack.h index 40649fc56..8367ce786 100644 --- a/libyul/AssemblyStack.h +++ b/libyul/AssemblyStack.h @@ -99,7 +99,7 @@ private: void compileEVM(yul::AbstractAssembly& _assembly, bool _evm15, bool _optimize) const; - void optimize(yul::Object& _object); + void optimize(yul::Object& _object, bool _isCreation); Language m_language = Language::Assembly; langutil::EVMVersion m_evmVersion; diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 433099eb4..12a31c2bb 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -58,6 +58,8 @@ add_library(yul optimiser/BlockHasher.h optimiser/CommonSubexpressionEliminator.cpp optimiser/CommonSubexpressionEliminator.h + optimiser/ConstantOptimiser.cpp + optimiser/ConstantOptimiser.h optimiser/ControlFlowSimplifier.cpp optimiser/ControlFlowSimplifier.h optimiser/DataFlowAnalyzer.cpp diff --git a/libyul/optimiser/ConstantOptimiser.cpp b/libyul/optimiser/ConstantOptimiser.cpp new file mode 100644 index 000000000..2fa200e73 --- /dev/null +++ b/libyul/optimiser/ConstantOptimiser.cpp @@ -0,0 +1,227 @@ +/* + 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 . +*/ +/** + * Optimisation stage that replaces constants by expressions that compute them. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace dev; +using namespace yul; + +using Representation = ConstantOptimiser::Representation; + +namespace +{ +struct MiniEVMInterpreter: boost::static_visitor +{ + explicit MiniEVMInterpreter(EVMDialect const& _dialect): m_dialect(_dialect) {} + + u256 eval(Expression const& _expr) + { + return boost::apply_visitor(*this, _expr); + } + + u256 eval(dev::eth::Instruction _instr, vector const& _arguments) + { + vector args; + for (auto const& arg: _arguments) + args.emplace_back(eval(arg)); + switch (_instr) + { + case eth::Instruction::ADD: + return args.at(0) + args.at(1); + case eth::Instruction::SUB: + return args.at(0) - args.at(1); + case eth::Instruction::MUL: + return args.at(0) * args.at(1); + case eth::Instruction::EXP: + return exp256(args.at(0), args.at(1)); + case eth::Instruction::SHL: + return args.at(0) > 255 ? 0 : (args.at(1) << unsigned(args.at(0))); + case eth::Instruction::NOT: + return ~args.at(0); + default: + yulAssert(false, "Invalid operation generated in constant optimizer."); + } + return 0; + } + + u256 operator()(FunctionalInstruction const& _instr) + { + return eval(_instr.instruction, _instr.arguments); + } + u256 operator()(FunctionCall const& _funCall) + { + BuiltinFunctionForEVM const* fun = m_dialect.builtin(_funCall.functionName.name); + yulAssert(fun, "Expected builtin function."); + yulAssert(fun->instruction, "Expected EVM instruction."); + return eval(*fun->instruction, _funCall.arguments); + } + u256 operator()(Literal const& _literal) + { + return valueOfLiteral(_literal); + } + u256 operator()(Identifier const&) { yulAssert(false, ""); } + + EVMDialect const& m_dialect; +}; +} + +void ConstantOptimiser::visit(Expression& _e) +{ + if (_e.type() == typeid(Literal)) + { + Literal const& literal = boost::get(_e); + if (literal.kind != LiteralKind::Number) + return; + + if ( + Expression const* repr = + RepresentationFinder(m_dialect, m_meter, locationOf(_e), m_cache) + .tryFindRepresentation(valueOfLiteral(literal)) + ) + _e = ASTCopier{}.translate(*repr); + } + else + ASTModifier::visit(_e); +} + +Expression const* RepresentationFinder::tryFindRepresentation(dev::u256 const& _value) +{ + if (_value < 0x10000) + return nullptr; + + Representation const& repr = findRepresentation(_value); + if (repr.expression->type() == typeid(Literal)) + return nullptr; + else + return repr.expression.get(); +} + +Representation const& RepresentationFinder::findRepresentation(dev::u256 const& _value) +{ + if (m_cache.count(_value)) + return m_cache.at(_value); + + Representation routine = represent(_value); + + if (dev::bytesRequired(~_value) < dev::bytesRequired(_value)) + // Negated is shorter to represent + routine = min(move(routine), represent("not"_yulstring, findRepresentation(~_value))); + + // Decompose value into a * 2**k + b where abs(b) << 2**k + for (unsigned bits = 255; bits > 8 && m_maxSteps > 0; --bits) + { + unsigned gapDetector = unsigned((_value >> (bits - 8)) & 0x1ff); + if (gapDetector != 0xff && gapDetector != 0x100) + continue; + + u256 powerOfTwo = u256(1) << bits; + u256 upperPart = _value >> bits; + bigint lowerPart = _value & (powerOfTwo - 1); + if ((powerOfTwo - lowerPart) < lowerPart) + { + lowerPart = lowerPart - powerOfTwo; // make it negative + upperPart++; + } + if (upperPart == 0) + continue; + if (abs(lowerPart) >= (powerOfTwo >> 8)) + continue; + Representation newRoutine; + if (m_dialect.evmVersion().hasBitwiseShifting()) + newRoutine = represent("shl"_yulstring, represent(bits), findRepresentation(upperPart)); + else + { + newRoutine = represent("exp"_yulstring, represent(2), represent(bits)); + if (upperPart != 1) + newRoutine = represent("mul"_yulstring, findRepresentation(upperPart), newRoutine); + } + + if (newRoutine.cost >= routine.cost) + continue; + + if (lowerPart > 0) + newRoutine = represent("add"_yulstring, newRoutine, findRepresentation(u256(abs(lowerPart)))); + else if (lowerPart < 0) + newRoutine = represent("sub"_yulstring, newRoutine, findRepresentation(u256(abs(lowerPart)))); + + if (m_maxSteps > 0) + m_maxSteps--; + routine = min(move(routine), move(newRoutine)); + } + yulAssert(MiniEVMInterpreter{m_dialect}.eval(*routine.expression) == _value, "Invalid expression generated."); + return m_cache[_value] = move(routine); +} + +Representation RepresentationFinder::represent(dev::u256 const& _value) const +{ + Representation repr; + repr.expression = make_unique(Literal{m_location, LiteralKind::Number, YulString{formatNumber(_value)}, {}}); + repr.cost = m_meter.costs(*repr.expression); + return repr; +} + +Representation RepresentationFinder::represent( + YulString _instruction, + Representation const& _argument +) const +{ + Representation repr; + repr.expression = make_unique(FunctionCall{ + m_location, + Identifier{m_location, _instruction}, + {ASTCopier{}.translate(*_argument.expression)} + }); + repr.cost = _argument.cost + m_meter.instructionCosts(*m_dialect.builtin(_instruction)->instruction); + return repr; +} + +Representation RepresentationFinder::represent( + YulString _instruction, + Representation const& _arg1, + Representation const& _arg2 +) const +{ + Representation repr; + repr.expression = make_unique(FunctionCall{ + m_location, + Identifier{m_location, _instruction}, + {ASTCopier{}.translate(*_arg1.expression), ASTCopier{}.translate(*_arg2.expression)} + }); + repr.cost = m_meter.instructionCosts(*m_dialect.builtin(_instruction)->instruction) + _arg1.cost + _arg2.cost; + return repr; +} + +Representation RepresentationFinder::min(Representation _a, Representation _b) +{ + if (_a.cost <= _b.cost) + return _a; + else + return _b; +} diff --git a/libyul/optimiser/ConstantOptimiser.h b/libyul/optimiser/ConstantOptimiser.h new file mode 100644 index 000000000..0383943e9 --- /dev/null +++ b/libyul/optimiser/ConstantOptimiser.h @@ -0,0 +1,108 @@ +/* + 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 . +*/ +/** + * Optimisation stage that replaces constants by expressions that compute them. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +namespace yul +{ +struct Dialect; +class GasMeter; + +/** + * Optimisation stage that replaces constants by expressions that compute them. + * + * Prerequisite: None + */ +class ConstantOptimiser: public ASTModifier +{ +public: + ConstantOptimiser(EVMDialect const& _dialect, GasMeter const& _meter): + m_dialect(_dialect), + m_meter(_meter) + {} + + void visit(Expression& _e) override; + + struct Representation + { + std::unique_ptr expression; + size_t cost = size_t(-1); + }; + +private: + EVMDialect const& m_dialect; + GasMeter const& m_meter; + std::map m_cache; +}; + +class RepresentationFinder +{ +public: + using Representation = ConstantOptimiser::Representation; + RepresentationFinder( + EVMDialect const& _dialect, + GasMeter const& _meter, + langutil::SourceLocation _location, + std::map& _cache + ): + m_dialect(_dialect), + m_meter(_meter), + m_location(std::move(_location)), + m_cache(_cache) + {} + + /// @returns a cheaper representation for the number than its representation + /// as a literal or nullptr otherwise. + Expression const* tryFindRepresentation(dev::u256 const& _value); + +private: + /// Recursively try to find the cheapest representation of the given number, + /// literal if necessary. + Representation const& findRepresentation(dev::u256 const& _value); + + Representation represent(dev::u256 const& _value) const; + Representation represent(YulString _instruction, Representation const& _arg) const; + Representation represent(YulString _instruction, Representation const& _arg1, Representation const& _arg2) const; + + Representation min(Representation _a, Representation _b); + + EVMDialect const& m_dialect; + GasMeter const& m_meter; + langutil::SourceLocation m_location; + /// Counter for the complexity of optimization, will stop when it reaches zero. + size_t m_maxSteps = 10000; + std::map& m_cache; +}; + +} diff --git a/libyul/optimiser/Metrics.cpp b/libyul/optimiser/Metrics.cpp index 11e0b31c3..a8f9807df 100644 --- a/libyul/optimiser/Metrics.cpp +++ b/libyul/optimiser/Metrics.cpp @@ -22,11 +22,14 @@ #include #include +#include #include #include +#include #include +#include using namespace std; using namespace dev; @@ -166,6 +169,93 @@ void CodeCost::addInstructionCost(eth::Instruction _instruction) m_cost += 49; } +size_t GasMeter::costs(Expression const& _expression) const +{ + return combineCosts(GasMeterVisitor::costs(_expression, m_dialect, m_isCreation)); +} + +size_t GasMeter::instructionCosts(eth::Instruction _instruction) const +{ + return combineCosts(GasMeterVisitor::instructionCosts(_instruction, m_dialect, m_isCreation)); +} + +size_t GasMeter::combineCosts(std::pair _costs) const +{ + return _costs.first * m_runs + _costs.second; +} + + +pair GasMeterVisitor::costs( + Expression const& _expression, + EVMDialect const& _dialect, + bool _isCreation +) +{ + GasMeterVisitor gmv(_dialect, _isCreation); + gmv.visit(_expression); + return {gmv.m_runGas, gmv.m_dataGas}; +} + +pair GasMeterVisitor::instructionCosts( + dev::eth::Instruction _instruction, + EVMDialect const& _dialect, + bool _isCreation +) +{ + GasMeterVisitor gmv(_dialect, _isCreation); + gmv.instructionCostsInternal(_instruction); + return {gmv.m_runGas, gmv.m_dataGas}; +} + +void GasMeterVisitor::operator()(FunctionCall const& _funCall) +{ + ASTWalker::operator()(_funCall); + if (BuiltinFunctionForEVM const* f = m_dialect.builtin(_funCall.functionName.name)) + if (f->instruction) + { + instructionCostsInternal(*f->instruction); + return; + } + yulAssert(false, "Functions not implemented."); +} + +void GasMeterVisitor::operator()(FunctionalInstruction const& _fun) +{ + ASTWalker::operator()(_fun); + instructionCostsInternal(_fun.instruction); +} + +void GasMeterVisitor::operator()(Literal const& _lit) +{ + m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::PUSH1); + m_dataGas += + singleByteDataGas() + + size_t(dev::eth::GasMeter::dataGas(dev::toCompactBigEndian(valueOfLiteral(_lit), 1), m_isCreation)); +} + +void GasMeterVisitor::operator()(Identifier const&) +{ + m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::DUP1); + m_dataGas += singleByteDataGas(); +} + +size_t GasMeterVisitor::singleByteDataGas() const +{ + if (m_isCreation) + return dev::eth::GasCosts::txDataNonZeroGas; + else + return dev::eth::GasCosts::createDataGas; +} + +void GasMeterVisitor::instructionCostsInternal(dev::eth::Instruction _instruction) +{ + if (_instruction == eth::Instruction::EXP) + m_runGas += dev::eth::GasCosts::expGas + dev::eth::GasCosts::expByteGas(m_dialect.evmVersion()); + else + m_runGas += dev::eth::GasMeter::runGas(_instruction); + m_dataGas += singleByteDataGas(); +} + void AssignmentCounter::operator()(Assignment const& _assignment) { for (auto const& variable: _assignment.variableNames) diff --git a/libyul/optimiser/Metrics.h b/libyul/optimiser/Metrics.h index 98041a606..51c6df9a7 100644 --- a/libyul/optimiser/Metrics.h +++ b/libyul/optimiser/Metrics.h @@ -21,6 +21,8 @@ #pragma once #include +#include +#include #include @@ -28,6 +30,7 @@ namespace yul { struct Dialect; +struct EVMDialect; /** * Metric for the size of code. @@ -93,6 +96,75 @@ private: size_t m_cost = 0; }; +/** + * Gas meter for expressions only involving literals, identifiers and + * EVM instructions. + * + * Assumes that EXP is not used with exponents larger than a single byte. + * Is not particularly exact for anything apart from arithmetic. + */ +class GasMeter +{ +public: + GasMeter(EVMDialect const& _dialect, bool _isCreation, size_t _runs): + m_dialect(_dialect), + m_isCreation{_isCreation}, + m_runs(_runs) + {} + + /// @returns the full combined costs of deploying and evaluating the expression. + size_t costs(Expression const& _expression) const; + /// @returns the combined costs of deploying and running the instruction, not including + /// the costs for its arguments. + size_t instructionCosts(dev::eth::Instruction _instruction) const; + +private: + size_t combineCosts(std::pair _costs) const; + + EVMDialect const& m_dialect; + bool m_isCreation = false; + size_t m_runs; +}; + +class GasMeterVisitor: public ASTWalker +{ +public: + static std::pair costs( + Expression const& _expression, + EVMDialect const& _dialect, + bool _isCreation + ); + + static std::pair instructionCosts( + dev::eth::Instruction _instruction, + EVMDialect const& _dialect, + bool _isCreation = false + ); + +public: + GasMeterVisitor(EVMDialect const& _dialect, bool _isCreation): + m_dialect(_dialect), + m_isCreation{_isCreation} + {} + + void operator()(FunctionCall const& _funCall) override; + void operator()(FunctionalInstruction const& _instr) override; + void operator()(Literal const& _literal) override; + void operator()(Identifier const& _identifier) override; + +private: + size_t singleByteDataGas() const; + /// Computes the cost of storing and executing the single instruction (excluding its arguments). + /// For EXP, it assumes that the exponent is at most 255. + /// Does not work particularly exact for anything apart from arithmetic. + void instructionCostsInternal(dev::eth::Instruction _instruction); + + EVMDialect const& m_dialect; + bool m_isCreation = false; + size_t m_runGas = 0; + size_t m_dataGas = 0; +}; + /** * Counts the number of assignments to every variable. * Only works after running the Disambiguator. diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index a71b0ead8..4772bbb3a 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,7 @@ using namespace yul; void OptimiserSuite::run( Dialect const& _dialect, + GasMeter const& _meter, Block& _ast, AsmAnalysisInfo const& _analysisInfo, bool _optimizeStackAllocation, @@ -206,6 +208,8 @@ void OptimiserSuite::run( ControlFlowSimplifier{_dialect}(ast); FunctionGrouper{}(ast); + + ConstantOptimiser{dynamic_cast(_dialect), _meter}(ast); VarNameCleaner{ast, _dialect, reservedIdentifiers}(ast); yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, ast); diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index 70a3057fd..03806eee6 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -31,6 +31,7 @@ namespace yul struct AsmAnalysisInfo; struct Dialect; +class GasMeter; /** * Optimiser suite that combines all steps and also provides the settings for the heuristics @@ -40,6 +41,7 @@ class OptimiserSuite public: static void run( Dialect const& _dialect, + GasMeter const& _meter, Block& _ast, AsmAnalysisInfo const& _analysisInfo, bool _optimizeStackAllocation, diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index f397ef4f1..e5552d675 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -118,6 +120,11 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line disambiguate(); BlockFlattener{}(*m_ast); } + else if (m_optimizerStep == "constantOptimiser") + { + GasMeter meter(dynamic_cast(*m_dialect), false, 200); + ConstantOptimiser{dynamic_cast(*m_dialect), meter}(*m_ast); + } else if (m_optimizerStep == "varDeclInitializer") VarDeclInitializer{}(*m_ast); else if (m_optimizerStep == "varNameCleaner") @@ -288,7 +295,10 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line WordSizeTransform::run(*m_ast, nameDispenser); } else if (m_optimizerStep == "fullSuite") - OptimiserSuite::run(*m_dialect, *m_ast, *m_analysisInfo, true); + { + GasMeter meter(dynamic_cast(*m_dialect), false, 200); + OptimiserSuite::run(*m_dialect, meter, *m_ast, *m_analysisInfo, true); + } else { AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Invalid optimizer step: " << m_optimizerStep << endl; diff --git a/test/libyul/yulOptimizerTests/constantOptimiser/difficult.yul b/test/libyul/yulOptimizerTests/constantOptimiser/difficult.yul new file mode 100644 index 000000000..47b76fa7c --- /dev/null +++ b/test/libyul/yulOptimizerTests/constantOptimiser/difficult.yul @@ -0,0 +1,15 @@ +{ + let x := 0xff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00 + let y := 0x1100ff00ff00ff001100ff00ff001100ff00ff001100ff00ff001100ff001100 + let z := 0xffff0000ffff0000ffff0000ffff0000ff00ff00ffff0000ffff0000ffff0000 + let w := 0xffffffff000000ffffef000001feff000067ffefff0000ff230002ffee00fff7 +} +// ==== +// step: constantOptimiser +// ---- +// { +// let x := 0xff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00 +// let y := 0x1100ff00ff00ff001100ff00ff001100ff00ff001100ff00ff001100ff001100 +// let z := 0xffff0000ffff0000ffff0000ffff0000ff00ff00ffff0000ffff0000ffff0000 +// let w := 0xffffffff000000ffffef000001feff000067ffefff0000ff230002ffee00fff7 +// } diff --git a/test/libyul/yulOptimizerTests/constantOptimiser/gaps.yul b/test/libyul/yulOptimizerTests/constantOptimiser/gaps.yul new file mode 100644 index 000000000..6371638eb --- /dev/null +++ b/test/libyul/yulOptimizerTests/constantOptimiser/gaps.yul @@ -0,0 +1,20 @@ +{ + let a := 0x10000000000000000000000000000000000000000000 + let x := 0x11000000000000000000000000000000000000ffffffffffffffffffffffff23 + let y := 0xfffffffff00000000000000000000000000000000000000000000000000000ff + for { let i := 0xff00000000000 } lt(i, 873687623427364) { i := add(i, 3234234234234) } { + } +} +// ==== +// EVMVersion: >=constantinople +// step: constantOptimiser +// ---- +// { +// let a := shl(172, 1) +// let x := add(shl(248, 17), 0xffffffffffffffffffffffff23) +// let y := add(shl(220, 0x0fffffffff), 255) +// for { let i := 0xff00000000000 } +// lt(i, 873687623427364) +// { i := add(i, 3234234234234) } +// { } +// } diff --git a/test/libyul/yulOptimizerTests/constantOptimiser/smallNumbers.yul b/test/libyul/yulOptimizerTests/constantOptimiser/smallNumbers.yul new file mode 100644 index 000000000..0315fdab8 --- /dev/null +++ b/test/libyul/yulOptimizerTests/constantOptimiser/smallNumbers.yul @@ -0,0 +1,15 @@ +{ + let x := 8 + let y := 0xffff + for { let i := 0xff00 } lt(i, 2) { i := add(i, 3) } { + } +} +// ==== +// step: constantOptimiser +// ---- +// { +// let x := 8 +// let y := 0xffff +// for { let i := 0xff00 } lt(i, 2) { i := add(i, 3) } +// { } +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/abi2.yul b/test/libyul/yulOptimizerTests/fullSuite/abi2.yul index 587d5c8c2..384a47d89 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/abi2.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/abi2.yul @@ -1,3 +1,4 @@ +// We require constantinople because numbers are represented using shifts. { // This ignores many of the encoding / decoding functions. Over time, // we should add them all here. @@ -1071,13 +1072,14 @@ } // ==== // step: fullSuite +// EVMVersion: >=constantinople // ---- // { // { // let _1 := mload(1) // let _2 := mload(0) // if slt(sub(_1, _2), 64) { revert(0, 0) } -// sstore(0, and(calldataload(_2), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) +// sstore(0, and(calldataload(_2), sub(shl(160, 1), 1))) // let x0, x1, x2, x3, x4 := abi_decode_tuple_t_addresst_uint256t_bytes_calldata_ptrt_enum$_Operation_$1949(mload(7), mload(8)) // sstore(x1, x0) // sstore(x3, x2) @@ -1087,7 +1089,7 @@ // function abi_decode_tuple_t_addresst_uint256t_bytes_calldata_ptrt_enum$_Operation_$1949(headStart, dataEnd) -> value0, value1, value2, value3, value4 // { // if slt(sub(dataEnd, headStart), 128) { revert(value4, value4) } -// value0 := and(calldataload(headStart), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) +// value0 := and(calldataload(headStart), sub(shl(160, 1), 1)) // value1 := calldataload(add(headStart, 32)) // let offset := calldataload(add(headStart, 64)) // let _1 := 0xffffffffffffffff @@ -1107,7 +1109,7 @@ // { // tail := add(headStart, 352) // mstore(headStart, value0) -// let _1 := 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +// let _1 := sub(shl(160, 1), 1) // mstore(add(headStart, 32), and(value1, _1)) // mstore(add(headStart, 64), value2) // mstore(add(headStart, 96), value3) diff --git a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul index 945b90521..fdb6e724e 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul @@ -1,3 +1,4 @@ +// We require constantinople because numbers are represented using shifts. { let x := abi_encode_t_array$_t_array$_t_contract$_C_$55_$3_memory_$dyn_memory_ptr_to_t_array$_t_array$_t_address_$3_memory_$dyn_memory_ptr(mload(0), 0x20) let a, b, c, d := abi_decode_tuple_t_uint256t_uint256t_array$_t_uint256_$dyn_memory_ptrt_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(mload(0x20), mload(0x40)) @@ -456,6 +457,7 @@ } } // ==== +// EVMVersion: >=constantinople // step: fullSuite // ---- // { @@ -546,7 +548,7 @@ // let i := 0 // for { } lt(i, 0x3) { i := add(i, 1) } // { -// mstore(pos, and(mload(srcPtr), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) +// mstore(pos, and(mload(srcPtr), sub(shl(160, 1), 1))) // srcPtr := add(srcPtr, 0x20) // pos := add(pos, 0x20) // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul index 5ad04f4c8..82a6dcb6a 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul @@ -245,7 +245,7 @@ // mstore(0x00, 404) // revert(0x00, 0x20) // } -// let kn := calldataload(add(calldatasize(), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40)) +// let kn := calldataload(add(calldatasize(), not(191))) // mstore(0x2a0, caller()) // mstore(0x2c0, kn) // mstore(0x2e0, m) @@ -314,7 +314,7 @@ // b := add(b, _3) // } // if lt(m, n) { validatePairing(0x64) } -// if iszero(eq(mod(keccak256(0x2a0, add(b, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd60)), gen_order), challenge)) +// if iszero(eq(mod(keccak256(0x2a0, add(b, not(671))), gen_order), challenge)) // { // mstore(i_1, 404) // revert(i_1, 0x20) diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index a86614b0c..d060f45b6 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -37,20 +37,6 @@ using namespace yul::test; namespace { -u256 exp256(u256 _base, u256 _exponent) -{ - using boost::multiprecision::limb_type; - u256 result = 1; - while (_exponent) - { - if (boost::multiprecision::bit_test(_exponent, 0)) - result *= _base; - _base *= _base; - _exponent >>= 1; - } - return result; -} - /// Reads 32 bytes from @a _data at position @a _offset bytes while /// interpreting @a _data to be padded with an infinite number of zero /// bytes beyond its end.