diff --git a/liblangutil/CMakeLists.txt b/liblangutil/CMakeLists.txt index 4cac9d8bb..63c9de39d 100644 --- a/liblangutil/CMakeLists.txt +++ b/liblangutil/CMakeLists.txt @@ -6,6 +6,7 @@ set(sources ErrorReporter.cpp ErrorReporter.h EVMVersion.h + EVMVersion.cpp Exceptions.cpp Exceptions.h ParserBase.cpp diff --git a/liblangutil/EVMVersion.cpp b/liblangutil/EVMVersion.cpp new file mode 100644 index 000000000..eb87d4832 --- /dev/null +++ b/liblangutil/EVMVersion.cpp @@ -0,0 +1,47 @@ +/* + 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 . +*/ +/** + * EVM versioning. + */ + +#include + +using namespace langutil; +using namespace dev::eth; + +bool EVMVersion::hasOpcode(Instruction _opcode) const +{ + switch (_opcode) + { + case Instruction::RETURNDATACOPY: + case Instruction::RETURNDATASIZE: + return supportsReturndata(); + case Instruction::STATICCALL: + return hasStaticCall(); + case Instruction::SHL: + case Instruction::SHR: + case Instruction::SAR: + return hasBitwiseShifting(); + case Instruction::CREATE2: + return hasCreate2(); + case Instruction::EXTCODEHASH: + return hasExtCodeHash(); + default: + return true; + } +} + diff --git a/liblangutil/EVMVersion.h b/liblangutil/EVMVersion.h index 21a29cb82..f80a18708 100644 --- a/liblangutil/EVMVersion.h +++ b/liblangutil/EVMVersion.h @@ -20,11 +20,14 @@ #pragma once +#include + #include #include #include + namespace langutil { @@ -78,6 +81,8 @@ public: bool hasCreate2() const { return *this >= constantinople(); } bool hasExtCodeHash() const { return *this >= constantinople(); } + bool hasOpcode(dev::eth::Instruction _opcode) const; + /// Whether we have to retain the costs for the call opcode itself (false), /// or whether we can just forward easily all remaining gas (true). bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); } @@ -90,5 +95,4 @@ private: Version m_version = Version::Petersburg; }; - } diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index 71bc1313f..a81c3dbc6 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -169,6 +169,7 @@ Statement Parser::parseStatement() // literal, // identifier (might turn into label or functional assignment) ElementaryOperation elementary(parseElementaryOperation()); + switch (currentToken()) { case Token::LParen: @@ -243,10 +244,18 @@ Statement Parser::parseStatement() fatalParserError("Call or assignment expected."); break; } + if (elementary.type() == typeid(Identifier)) { - Expression expr = boost::get(elementary); - return ExpressionStatement{locationOf(expr), expr}; + Identifier& identifier = boost::get(elementary); + // Fallback from builtin function to Instruction for loose assembly. + if ( + m_dialect.flavour == AsmFlavour::Loose && + instructions().count(identifier.name.str()) + ) + return Instruction{identifier.location, instructions().at(identifier.name.str())}; + else + return ExpressionStatement{identifier.location, { move(identifier) }}; } else if (elementary.type() == typeid(Literal)) { @@ -307,12 +316,13 @@ ForLoop Parser::parseForLoop() Expression Parser::parseExpression() { RecursionGuard recursionGuard(*this); - // In strict mode, this might parse a plain Instruction, but - // it will be converted to a FunctionalInstruction inside - // parseCall below. + ElementaryOperation operation = parseElementaryOperation(); - if (operation.type() == typeid(Instruction)) + if (operation.type() == typeid(FunctionCall)) + return parseCall(std::move(operation)); + else if (operation.type() == typeid(Instruction)) { + solAssert(m_dialect.flavour == AsmFlavour::Loose, ""); Instruction const& instr = boost::get(operation); // Disallow instructions returning multiple values (and DUP/SWAP) as expression. if ( @@ -394,8 +404,14 @@ Parser::ElementaryOperation Parser::parseElementaryOperation() literal = YulString{currentLiteral()}; // first search the set of builtins, then the instructions. if (m_dialect.builtin(literal)) - ret = Identifier{location(), literal}; - else if (m_dialect.flavour != AsmFlavour::Yul && instructions().count(literal.str())) + { + // For builtins we already check here that they are followed by `(`. + ret = FunctionCall{location(), Identifier{location(), literal}, {}}; + advance(); + expectToken(Token::LParen, false); + return ret; + } + else if (m_dialect.flavour == AsmFlavour::Loose && instructions().count(literal.str())) { dev::eth::Instruction const& instr = instructions().at(literal.str()); ret = Instruction{location(), instr}; @@ -582,11 +598,16 @@ Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) expectToken(Token::RParen); return ret; } - else if (_initialOp.type() == typeid(Identifier)) + else if (_initialOp.type() == typeid(Identifier) || _initialOp.type() == typeid(FunctionCall)) { FunctionCall ret; - ret.functionName = std::move(boost::get(_initialOp)); - ret.location = ret.functionName.location; + if (_initialOp.type() == typeid(Identifier)) + { + ret.functionName = std::move(boost::get(_initialOp)); + ret.location = ret.functionName.location; + } + else + ret = std::move(boost::get(_initialOp)); expectToken(Token::LParen); while (currentToken() != Token::RParen) { diff --git a/libyul/AsmParser.h b/libyul/AsmParser.h index cc49681a5..6f88a211e 100644 --- a/libyul/AsmParser.h +++ b/libyul/AsmParser.h @@ -56,7 +56,7 @@ public: static std::map const& instructions(); protected: - using ElementaryOperation = boost::variant; + using ElementaryOperation = boost::variant; /// Creates an inline assembly node with the given source location. template T createWithLocation(langutil::SourceLocation const& _loc = {}) const @@ -81,9 +81,8 @@ protected: /// Parses a functional expression that has to push exactly one stack element Expression parseExpression(); static std::map const& instructionNames(); - /// Parses an elementary operation, i.e. a literal, identifier or instruction. - /// This will parse instructions even in strict mode as part of the full parser - /// for FunctionalInstruction. + /// Parses an elementary operation, i.e. a literal, identifier, instruction or + /// builtin functian call (only the name). ElementaryOperation parseElementaryOperation(); VariableDeclaration parseVariableDeclaration(); FunctionDefinition parseFunctionDefinition(); diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index f3ab44ce6..375d0c897 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -23,11 +23,14 @@ #include #include #include +#include +#include #include -#include +#include +#include -#include +#include #include @@ -37,6 +40,33 @@ using namespace yul; namespace { +pair createEVMFunction( + string const& _name, + dev::eth::Instruction _instruction +) +{ + eth::InstructionInfo info = dev::eth::instructionInfo(_instruction); + BuiltinFunctionForEVM f; + f.name = YulString{_name}; + f.parameters.resize(info.args); + f.returns.resize(info.ret); + f.movable = eth::SemanticInformation::movable(_instruction); + f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction); + f.literalArguments = false; + f.instruction = _instruction; + f.generateCode = [_instruction]( + FunctionCall const&, + AbstractAssembly& _assembly, + BuiltinContext&, + std::function _visitArguments + ) { + _visitArguments(); + _assembly.appendInstruction(_instruction); + }; + + return {f.name, move(f)}; +} + pair createFunction( string _name, size_t _params, @@ -55,13 +85,25 @@ pair createFunction( f.movable = _movable; f.literalArguments = _literalArguments; f.sideEffectFree = _sideEffectFree; + f.instruction = {}; f.generateCode = std::move(_generateCode); return {name, f}; } -map createBuiltins(langutil::EVMVersion, bool _objectAccess) +map createBuiltins(langutil::EVMVersion _evmVersion, bool _objectAccess, bool _evmOpcodes) { map builtins; + if (_evmOpcodes) + for (auto const& instr: Parser::instructions()) + if ( + !dev::eth::isDupInstruction(instr.second) && + !dev::eth::isSwapInstruction(instr.second) && + instr.second != eth::Instruction::JUMP && + instr.second != eth::Instruction::JUMPI && + _evmVersion.hasOpcode(instr.second) + ) + builtins.emplace(createEVMFunction(instr.first, instr.second)); + if (_objectAccess) { builtins.emplace(createFunction("datasize", 1, 1, true, true, true, []( @@ -125,7 +167,7 @@ EVMDialect::EVMDialect(AsmFlavour _flavour, bool _objectAccess, langutil::EVMVer Dialect{_flavour}, m_objectAccess(_objectAccess), m_evmVersion(_evmVersion), - m_functions(createBuiltins(_evmVersion, _objectAccess)) + m_functions(createBuiltins(_evmVersion, _objectAccess, _flavour != AsmFlavour::Loose)) { } diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index 3e14737dc..56dc99f87 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -47,6 +47,7 @@ struct BuiltinContext struct BuiltinFunctionForEVM: BuiltinFunction { + boost::optional instruction; /// Function to generate code for the given function call and append it to the abstract /// assembly. The fourth parameter is called to visit (and generate code for) the arguments /// from right to left. diff --git a/libyul/optimiser/ControlFlowSimplifier.cpp b/libyul/optimiser/ControlFlowSimplifier.cpp index 46d5b5bc4..ccd88051f 100644 --- a/libyul/optimiser/ControlFlowSimplifier.cpp +++ b/libyul/optimiser/ControlFlowSimplifier.cpp @@ -35,9 +35,9 @@ namespace ExpressionStatement makePopExpressionStatement(langutil::SourceLocation const& _location, Expression&& _expression) { - return {_location, FunctionalInstruction{ + return {_location, FunctionCall{ _location, - dev::eth::Instruction::POP, + Identifier{_location, "pop"_yulstring}, {std::move(_expression)} }}; } @@ -84,9 +84,9 @@ OptionalStatements reduceSingleCaseSwitch(Switch& _switchStmt) if (switchCase.value) return make_vector(If{ std::move(_switchStmt.location), - make_unique(FunctionalInstruction{ - std::move(loc), - dev::eth::Instruction::EQ, + make_unique(FunctionCall{ + loc, + Identifier{loc, "eq"_yulstring}, {std::move(*switchCase.value), std::move(*_switchStmt.expression)} }), std::move(switchCase.body) @@ -122,7 +122,7 @@ void ControlFlowSimplifier::visit(Statement& _st) if (!forLoop.body.statements.empty()) { bool isTerminating = false; - TerminationFinder::ControlFlow controlFlow = TerminationFinder::controlFlowKind(forLoop.body.statements.back()); + TerminationFinder::ControlFlow controlFlow = TerminationFinder{m_dialect}.controlFlowKind(forLoop.body.statements.back()); if (controlFlow == TerminationFinder::ControlFlow::Break) { isTerminating = true; diff --git a/libyul/optimiser/ControlFlowSimplifier.h b/libyul/optimiser/ControlFlowSimplifier.h index a2975e5a0..23dc3075b 100644 --- a/libyul/optimiser/ControlFlowSimplifier.h +++ b/libyul/optimiser/ControlFlowSimplifier.h @@ -20,6 +20,7 @@ namespace yul { +struct Dialect; /** * Simplifies several control-flow structures: @@ -45,6 +46,8 @@ namespace yul class ControlFlowSimplifier: public ASTModifier { public: + ControlFlowSimplifier(Dialect const& _dialect): m_dialect(_dialect) {} + using ASTModifier::operator(); void operator()(Break&) override { ++m_numBreakStatements; } void operator()(Continue&) override { ++m_numContinueStatements; } @@ -55,6 +58,7 @@ public: private: void simplify(std::vector& _statements); + Dialect const& m_dialect; size_t m_numBreakStatements = 0; size_t m_numContinueStatements = 0; }; diff --git a/libyul/optimiser/DeadCodeEliminator.cpp b/libyul/optimiser/DeadCodeEliminator.cpp index b39473758..5d123ca74 100644 --- a/libyul/optimiser/DeadCodeEliminator.cpp +++ b/libyul/optimiser/DeadCodeEliminator.cpp @@ -42,7 +42,7 @@ void DeadCodeEliminator::operator()(Block& _block) { TerminationFinder::ControlFlow controlFlowChange; size_t index; - tie(controlFlowChange, index) = TerminationFinder::firstUnconditionalControlFlowChange(_block.statements); + tie(controlFlowChange, index) = TerminationFinder{m_dialect}.firstUnconditionalControlFlowChange(_block.statements); // Erase everything after the terminating statement that is not a function definition. if (controlFlowChange != TerminationFinder::ControlFlow::FlowOut && index != size_t(-1)) diff --git a/libyul/optimiser/DeadCodeEliminator.h b/libyul/optimiser/DeadCodeEliminator.h index 16c8b6d99..50dbaaa75 100644 --- a/libyul/optimiser/DeadCodeEliminator.h +++ b/libyul/optimiser/DeadCodeEliminator.h @@ -28,6 +28,7 @@ namespace yul { +struct Dialect; /** * Optimisation stage that removes unreachable code @@ -46,9 +47,14 @@ namespace yul class DeadCodeEliminator: public ASTModifier { public: + DeadCodeEliminator(Dialect const& _dialect): m_dialect(_dialect) {} + using ASTModifier::operator(); void operator()(ForLoop& _for) override; void operator()(Block& _block) override; + +private: + Dialect const& m_dialect; }; } diff --git a/libyul/optimiser/Metrics.cpp b/libyul/optimiser/Metrics.cpp index 02b5a7179..11e0b31c3 100644 --- a/libyul/optimiser/Metrics.cpp +++ b/libyul/optimiser/Metrics.cpp @@ -22,6 +22,7 @@ #include #include +#include #include @@ -92,9 +93,9 @@ void CodeSize::visit(Expression const& _expression) } -size_t CodeCost::codeCost(Expression const& _expr) +size_t CodeCost::codeCost(Dialect const& _dialect, Expression const& _expr) { - CodeCost cc; + CodeCost cc(_dialect); cc.visit(_expr); return cc.m_cost; } @@ -102,23 +103,26 @@ size_t CodeCost::codeCost(Expression const& _expr) void CodeCost::operator()(FunctionCall const& _funCall) { - yulAssert(m_cost >= 1, "Should assign cost one in visit(Expression)."); - m_cost += 49; ASTWalker::operator()(_funCall); + + if (EVMDialect const* dialect = dynamic_cast(&m_dialect)) + if (BuiltinFunctionForEVM const* f = dialect->builtin(_funCall.functionName.name)) + if (f->instruction) + { + addInstructionCost(*f->instruction); + return; + } + + m_cost += 49; } void CodeCost::operator()(FunctionalInstruction const& _instr) { yulAssert(m_cost >= 1, "Should assign cost one in visit(Expression)."); - dev::eth::Tier gasPriceTier = dev::eth::instructionInfo(_instr.instruction).gasPriceTier; - if (gasPriceTier < dev::eth::Tier::VeryLow) - m_cost -= 1; - else if (gasPriceTier < dev::eth::Tier::High) - m_cost += 1; - else - m_cost += 49; + addInstructionCost(_instr.instruction); ASTWalker::operator()(_instr); } + void CodeCost::operator()(Literal const& _literal) { yulAssert(m_cost >= 1, "Should assign cost one in visit(Expression)."); @@ -151,6 +155,17 @@ void CodeCost::visit(Expression const& _expression) ASTWalker::visit(_expression); } +void CodeCost::addInstructionCost(eth::Instruction _instruction) +{ + dev::eth::Tier gasPriceTier = dev::eth::instructionInfo(_instruction).gasPriceTier; + if (gasPriceTier < dev::eth::Tier::VeryLow) + m_cost -= 1; + else if (gasPriceTier < dev::eth::Tier::High) + m_cost += 1; + else + m_cost += 49; +} + 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 1620d4d33..98041a606 100644 --- a/libyul/optimiser/Metrics.h +++ b/libyul/optimiser/Metrics.h @@ -22,9 +22,13 @@ #include +#include + namespace yul { +struct Dialect; + /** * Metric for the size of code. * More specifically, the number of AST nodes. @@ -71,9 +75,11 @@ private: class CodeCost: public ASTWalker { public: - static size_t codeCost(Expression const& _expression); + static size_t codeCost(Dialect const& _dialect, Expression const& _expression); private: + CodeCost(Dialect const& _dialect): m_dialect(_dialect) {} + void operator()(FunctionCall const& _funCall) override; void operator()(FunctionalInstruction const& _instr) override; void operator()(Literal const& _literal) override; @@ -81,6 +87,9 @@ private: void visit(Expression const& _expression) override; private: + void addInstructionCost(dev::eth::Instruction _instruction); + + Dialect const& m_dialect; size_t m_cost = 0; }; diff --git a/libyul/optimiser/Rematerialiser.cpp b/libyul/optimiser/Rematerialiser.cpp index de1b91439..c70b02423 100644 --- a/libyul/optimiser/Rematerialiser.cpp +++ b/libyul/optimiser/Rematerialiser.cpp @@ -77,7 +77,7 @@ void Rematerialiser::visit(Expression& _e) assertThrow(m_value.at(name), OptimizerException, ""); auto const& value = *m_value.at(name); size_t refs = m_referenceCounts[name]; - size_t cost = CodeCost::codeCost(value); + size_t cost = CodeCost::codeCost(m_dialect, value); if (refs <= 1 || cost == 0 || (refs <= 5 && cost <= 1) || m_varsToAlwaysRematerialize.count(name)) { assertThrow(m_referenceCounts[name] > 0, OptimizerException, ""); diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index df3d0cbb0..6291ce688 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -112,10 +113,14 @@ TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement cons bool TerminationFinder::isTerminatingBuiltin(ExpressionStatement const& _exprStmnt) { - if (_exprStmnt.expression.type() != typeid(FunctionalInstruction)) - return false; - - return eth::SemanticInformation::terminatesControlFlow( - boost::get(_exprStmnt.expression).instruction - ); + if (_exprStmnt.expression.type() == typeid(FunctionalInstruction)) + return eth::SemanticInformation::terminatesControlFlow( + boost::get(_exprStmnt.expression).instruction + ); + else if (_exprStmnt.expression.type() == typeid(FunctionCall)) + if (auto const* dialect = dynamic_cast(&m_dialect)) + if (auto const* builtin = dialect->builtin(boost::get(_exprStmnt.expression).functionName.name)) + if (builtin->instruction) + return eth::SemanticInformation::terminatesControlFlow(*builtin->instruction); + return false; } diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index 0899fc4c0..b50169f97 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -70,6 +70,8 @@ class TerminationFinder public: enum class ControlFlow { FlowOut, Break, Continue, Terminate }; + TerminationFinder(Dialect const& _dialect): m_dialect(_dialect) {} + /// @returns the index of the first statement in the provided sequence /// that is an unconditional ``break``, ``continue`` or a /// call to a terminating builtin function. @@ -77,18 +79,21 @@ public: /// returns `FlowOut` and ``size_t(-1)``. /// The function might return ``FlowOut`` even though control /// flow cannot actually continue. - static std::pair firstUnconditionalControlFlowChange( + std::pair firstUnconditionalControlFlowChange( std::vector const& _statements ); /// @returns the control flow type of the given statement. /// This function could return FlowOut even if control flow never continues. - static ControlFlow controlFlowKind(Statement const& _statement); + ControlFlow controlFlowKind(Statement const& _statement); /// @returns true if the expression statement is a direct /// call to a builtin terminating function like /// ``stop``, ``revert`` or ``return``. - static bool isTerminatingBuiltin(ExpressionStatement const& _exprStmnt); + bool isTerminatingBuiltin(ExpressionStatement const& _exprStmnt); + +private: + Dialect const& m_dialect; }; } diff --git a/libyul/optimiser/SimplificationRules.cpp b/libyul/optimiser/SimplificationRules.cpp index 205b42c07..861f8b25b 100644 --- a/libyul/optimiser/SimplificationRules.cpp +++ b/libyul/optimiser/SimplificationRules.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -41,14 +42,14 @@ SimplificationRule const* SimplificationRules::findFirstMatch( map const& _ssaValues ) { - if (_expr.type() != typeid(FunctionalInstruction)) + auto instruction = instructionAndArguments(_dialect, _expr); + if (!instruction) return nullptr; static SimplificationRules rules; assertThrow(rules.isInitialized(), OptimizerException, "Rule list not properly initialized."); - FunctionalInstruction const& instruction = boost::get(_expr); - for (auto const& rule: rules.m_rules[uint8_t(instruction.instruction)]) + for (auto const& rule: rules.m_rules[uint8_t(instruction->first)]) { rules.resetMatchGroups(); if (rule.pattern.matches(_expr, _dialect, _ssaValues)) @@ -63,6 +64,20 @@ bool SimplificationRules::isInitialized() const return !m_rules[uint8_t(dev::eth::Instruction::ADD)].empty(); } +boost::optional const*>> + SimplificationRules::instructionAndArguments(Dialect const& _dialect, Expression const& _expr) +{ + if (_expr.type() == typeid(FunctionalInstruction)) + return make_pair(boost::get(_expr).instruction, &boost::get(_expr).arguments); + else if (_expr.type() == typeid(FunctionCall)) + if (auto const* dialect = dynamic_cast(&_dialect)) + if (auto const* builtin = dialect->builtin(boost::get(_expr).functionName.name)) + if (builtin->instruction) + return make_pair(*builtin->instruction, &boost::get(_expr).arguments); + + return {}; +} + void SimplificationRules::addRules(vector> const& _rules) { for (auto const& r: _rules) @@ -139,14 +154,12 @@ bool Pattern::matches( } else if (m_kind == PatternKind::Operation) { - if (expr->type() != typeid(FunctionalInstruction)) + auto instrAndArgs = SimplificationRules::instructionAndArguments(_dialect, *expr); + if (!instrAndArgs || m_instruction != instrAndArgs->first) return false; - FunctionalInstruction const& instr = boost::get(*expr); - if (m_instruction != instr.instruction) - return false; - assertThrow(m_arguments.size() == instr.arguments.size(), OptimizerException, ""); + assertThrow(m_arguments.size() == instrAndArgs->second->size(), OptimizerException, ""); for (size_t i = 0; i < m_arguments.size(); ++i) - if (!m_arguments[i].matches(instr.arguments.at(i), _dialect, _ssaValues)) + if (!m_arguments[i].matches(instrAndArgs->second->at(i), _dialect, _ssaValues)) return false; } else @@ -208,6 +221,7 @@ Expression Pattern::toExpression(SourceLocation const& _location) const vector arguments; for (auto const& arg: m_arguments) arguments.emplace_back(arg.toExpression(_location)); + // TODO convert to FunctionCall return FunctionalInstruction{_location, m_instruction, std::move(arguments)}; } assertThrow(false, OptimizerException, "Pattern of kind 'any', but no match group."); diff --git a/libyul/optimiser/SimplificationRules.h b/libyul/optimiser/SimplificationRules.h index 40ae43260..a63071b9f 100644 --- a/libyul/optimiser/SimplificationRules.h +++ b/libyul/optimiser/SimplificationRules.h @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -55,6 +56,10 @@ public: /// Checks whether the rulelist is non-empty. This is usually enforced /// by the constructor, but we had some issues with static initialization. bool isInitialized() const; + + static boost::optional const*>> + instructionAndArguments(Dialect const& _dialect, Expression const& _expr); + private: void addRules(std::vector> const& _rules); void addRule(dev::eth::SimplificationRule const& _rule); diff --git a/libyul/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index 94b886074..f61e8925c 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -69,7 +69,7 @@ public: { YulString varName = _varDecl.variables.front().name; if (m_value.count(varName)) - m_expressionCodeCost[varName] = CodeCost::codeCost(*m_value[varName]); + m_expressionCodeCost[varName] = CodeCost::codeCost(m_dialect, *m_value[varName]); } } diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 3b497a117..a71b0ead8 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -73,14 +73,14 @@ void OptimiserSuite::run( FunctionHoister{}(ast); BlockFlattener{}(ast); ForLoopInitRewriter{}(ast); - DeadCodeEliminator{}(ast); + DeadCodeEliminator{_dialect}(ast); FunctionGrouper{}(ast); EquivalentFunctionCombiner::run(ast); UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); BlockFlattener{}(ast); - ControlFlowSimplifier{}(ast); + ControlFlowSimplifier{_dialect}(ast); StructuralSimplifier{_dialect}(ast); - ControlFlowSimplifier{}(ast); + ControlFlowSimplifier{_dialect}(ast); BlockFlattener{}(ast); // None of the above can make stack problems worse. @@ -110,11 +110,11 @@ void OptimiserSuite::run( { // still in SSA, perform structural simplification - ControlFlowSimplifier{}(ast); + ControlFlowSimplifier{_dialect}(ast); StructuralSimplifier{_dialect}(ast); - ControlFlowSimplifier{}(ast); + ControlFlowSimplifier{_dialect}(ast); BlockFlattener{}(ast); - DeadCodeEliminator{}(ast); + DeadCodeEliminator{_dialect}(ast); UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); } { @@ -166,8 +166,8 @@ void OptimiserSuite::run( ExpressionSimplifier::run(_dialect, ast); StructuralSimplifier{_dialect}(ast); BlockFlattener{}(ast); - DeadCodeEliminator{}(ast); - ControlFlowSimplifier{}(ast); + DeadCodeEliminator{_dialect}(ast); + ControlFlowSimplifier{_dialect}(ast); CommonSubexpressionEliminator{_dialect}(ast); SSATransform::run(ast, dispenser); RedundantAssignEliminator::run(_dialect, ast); @@ -202,8 +202,8 @@ void OptimiserSuite::run( // message once we perform code generation. StackCompressor::run(_dialect, ast, _optimizeStackAllocation, stackCompressorMaxIterations); BlockFlattener{}(ast); - DeadCodeEliminator{}(ast); - ControlFlowSimplifier{}(ast); + DeadCodeEliminator{_dialect}(ast); + ControlFlowSimplifier{_dialect}(ast); FunctionGrouper{}(ast); VarNameCleaner{ast, _dialect, reservedIdentifiers}(ast); diff --git a/test/cmdlineTests/strict_asm_jump/err b/test/cmdlineTests/strict_asm_jump/err index ba4623090..95dd70c75 100644 --- a/test/cmdlineTests/strict_asm_jump/err +++ b/test/cmdlineTests/strict_asm_jump/err @@ -1,4 +1,4 @@ Warning: Yul and its optimizer are still experimental. Please use the output with care. -strict_asm_jump/input.sol:1:3: Error: Jump instructions and labels are low-level EVM features that can lead to incorrect stack access. Because of that they are disallowed in strict assembly. Use functions, "switch", "if" or "for" statements instead. +strict_asm_jump/input.sol:1:3: Error: Function not found. { jump(1) } - ^-----^ + ^--^ diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 809a72636..364e46ee0 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -516,11 +516,11 @@ BOOST_AUTO_TEST_CASE(no_opcodes_in_strict) { BOOST_CHECK(successParse("{ pop(callvalue) }")); BOOST_CHECK(successParse("{ callvalue pop }")); - CHECK_STRICT_ERROR("{ pop(callvalue) }", ParserError, "Non-functional instructions are not allowed in this context."); - CHECK_STRICT_ERROR("{ callvalue pop }", ParserError, "Call or assignment expected"); + CHECK_STRICT_ERROR("{ pop(callvalue) }", ParserError, "Expected '(' but got ')'"); + CHECK_STRICT_ERROR("{ callvalue pop }", ParserError, "Expected '(' but got identifier"); SUCCESS_STRICT("{ pop(callvalue()) }"); BOOST_CHECK(successParse("{ switch callvalue case 0 {} }")); - CHECK_STRICT_ERROR("{ switch callvalue case 0 {} }", ParserError, "Non-functional instructions are not allowed in this context."); + CHECK_STRICT_ERROR("{ switch callvalue case 0 {} }", ParserError, "Expected '(' but got reserved keyword 'case'"); } BOOST_AUTO_TEST_CASE(no_labels_in_strict) @@ -546,7 +546,6 @@ BOOST_AUTO_TEST_CASE(no_dup_swap_in_strict) BOOST_CHECK(successParse("{ dup2 pop }")); CHECK_STRICT_ERROR("{ dup2 pop }", ParserError, "Call or assignment expected."); CHECK_PARSE_ERROR("{ switch dup1 case 0 {} }", ParserError, "Instruction \"dup1\" not allowed in this context"); - CHECK_STRICT_ERROR("{ switch dup1 case 0 {} }", ParserError, "Instruction \"dup1\" not allowed in this context"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libyul/Parser.cpp b/test/libyul/Parser.cpp index 37d6389dd..396ae5c30 100644 --- a/test/libyul/Parser.cpp +++ b/test/libyul/Parser.cpp @@ -549,8 +549,11 @@ BOOST_AUTO_TEST_CASE(builtins_parser) SimpleDialect dialect; CHECK_ERROR_DIALECT("{ let builtin := 6 }", ParserError, "Cannot use builtin function name \"builtin\" as identifier name.", dialect); CHECK_ERROR_DIALECT("{ function builtin() {} }", ParserError, "Cannot use builtin function name \"builtin\" as identifier name.", dialect); - CHECK_ERROR_DIALECT("{ builtin := 6 }", ParserError, "Cannot assign to builtin function \"builtin\".", dialect); - CHECK_ERROR_DIALECT("{ function g() -> a,b {} builtin, builtin2 := g() }", ParserError, "Cannot assign to builtin function \"builtin\".", dialect); + CHECK_ERROR_DIALECT("{ builtin := 6 }", ParserError, "Expected '(' but got ':='", dialect); + CHECK_ERROR_DIALECT("{ function f(x) { f(builtin) } }", ParserError, "Expected '(' but got ')'", dialect); + CHECK_ERROR_DIALECT("{ function f(builtin) {}", ParserError, "Cannot use builtin function name \"builtin\" as identifier name.", dialect); + CHECK_ERROR_DIALECT("{ function f() -> builtin {}", ParserError, "Cannot use builtin function name \"builtin\" as identifier name.", dialect); + CHECK_ERROR_DIALECT("{ function g() -> a,b {} builtin, builtin2 := g() }", ParserError, "Expected '(' but got ','", dialect); } BOOST_AUTO_TEST_CASE(builtins_analysis) diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 6f0f8df22..2964b212d 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -201,7 +201,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line CommonSubexpressionEliminator{*m_dialect}(*m_ast); ExpressionSimplifier::run(*m_dialect, *m_ast); UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); - DeadCodeEliminator{}(*m_ast); + DeadCodeEliminator{*m_dialect}(*m_ast); ExpressionJoiner::run(*m_ast); ExpressionJoiner::run(*m_ast); } @@ -214,7 +214,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line { disambiguate(); ForLoopInitRewriter{}(*m_ast); - DeadCodeEliminator{}(*m_ast); + DeadCodeEliminator{*m_dialect}(*m_ast); } else if (m_optimizerStep == "ssaTransform") { @@ -237,7 +237,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line else if (m_optimizerStep == "controlFlowSimplifier") { disambiguate(); - ControlFlowSimplifier{}(*m_ast); + ControlFlowSimplifier{*m_dialect}(*m_ast); } else if (m_optimizerStep == "structuralSimplifier") { diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 1a03704b4..13b8066fa 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -22,6 +22,7 @@ #include +#include #include #include @@ -444,13 +445,15 @@ u256 EVMInstructionInterpreter::eval( return 0; } -u256 EVMInstructionInterpreter::evalBuiltin(YulString _name, const std::vector& _arguments) +u256 EVMInstructionInterpreter::evalBuiltin(BuiltinFunctionForEVM const& _fun, const std::vector& _arguments) { - if (_name == "datasize"_yulstring) + if (_fun.instruction) + return eval(*_fun.instruction, _arguments); + else if (_fun.name == "datasize"_yulstring) return u256(keccak256(h256(_arguments.at(0)))) & 0xfff; - else if (_name == "dataoffset"_yulstring) + else if (_fun.name == "dataoffset"_yulstring) return u256(keccak256(h256(_arguments.at(0) + 2))) & 0xfff; - else if (_name == "datacopy"_yulstring) + else if (_fun.name == "datacopy"_yulstring) { // This is identical to codecopy. if (logMemoryWrite(_arguments.at(0), _arguments.at(2))) @@ -463,7 +466,7 @@ u256 EVMInstructionInterpreter::evalBuiltin(YulString _name, const std::vector const& _arguments); /// Evaluate builtin function - dev::u256 evalBuiltin(YulString _name, std::vector const& _arguments); + dev::u256 evalBuiltin(BuiltinFunctionForEVM const& _fun, std::vector const& _arguments); private: /// Record a memory read in the trace. Also updates m_state.msize diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index f63a73cda..ff6a0cc99 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -208,12 +208,13 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall) { evaluateArgs(_funCall.arguments); - if (dynamic_cast(&m_dialect) && m_dialect.builtin(_funCall.functionName.name)) - { - EVMInstructionInterpreter interpreter(m_state); - setValue(interpreter.evalBuiltin(_funCall.functionName.name, values())); - return; - } + if (EVMDialect const* dialect = dynamic_cast(&m_dialect)) + if (BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name)) + { + EVMInstructionInterpreter interpreter(m_state); + setValue(interpreter.evalBuiltin(*fun, values())); + return; + } solAssert(m_functions.count(_funCall.functionName.name), ""); FunctionDefinition const& fun = *m_functions.at(_funCall.functionName.name); diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 5ba1549ed..816361246 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -179,13 +179,13 @@ public: (StructuralSimplifier{m_dialect})(*m_ast); break; case 'n': - (ControlFlowSimplifier{})(*m_ast); + (ControlFlowSimplifier{m_dialect})(*m_ast); break; case 'u': UnusedPruner::runUntilStabilised(m_dialect, *m_ast); break; case 'D': - DeadCodeEliminator{}(*m_ast); + DeadCodeEliminator{m_dialect}(*m_ast); break; case 'a': SSATransform::run(*m_ast, *m_nameDispenser);