diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp index a42cec172d49..11d8e84c6601 100644 --- a/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/libsolidity/analysis/ControlFlowBuilder.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include using namespace solidity::langutil; @@ -582,14 +583,13 @@ void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall) solAssert(m_currentNode && m_inlineAssembly, ""); yul::ASTWalker::operator()(_functionCall); - if (auto const& builtinHandle = m_inlineAssembly->dialect().findBuiltin(_functionCall.functionName.name.str())) + if (auto const* builtinFunction = resolveBuiltinFunction(_functionCall.functionName, m_inlineAssembly->dialect())) { - auto const& builtinFunction = m_inlineAssembly->dialect().builtin(*builtinHandle); - if (builtinFunction.controlFlowSideEffects.canTerminate) + if (builtinFunction->controlFlowSideEffects.canTerminate) connect(m_currentNode, m_transactionReturnNode); - if (builtinFunction.controlFlowSideEffects.canRevert) + if (builtinFunction->controlFlowSideEffects.canRevert) connect(m_currentNode, m_revertNode); - if (!builtinFunction.controlFlowSideEffects.canContinue) + if (!builtinFunction->controlFlowSideEffects.canContinue) m_currentNode = newLabel(); } } diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 6a3a902edffe..cbd538551106 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -18,8 +18,9 @@ #include #include -#include #include +#include +#include #include #include @@ -66,9 +67,9 @@ class AssemblyViewPureChecker void operator()(yul::FunctionCall const& _funCall) { if (yul::EVMDialect const* dialect = dynamic_cast(&m_dialect)) - if (std::optional builtinHandle = dialect->findBuiltin(_funCall.functionName.name.str())) - if (auto const& instruction = dialect->builtin(*builtinHandle).instruction) - checkInstruction(nativeLocationOf(_funCall), *instruction); + if (yul::BuiltinFunctionForEVM const* builtin = resolveBuiltinFunctionForEVM(_funCall.functionName, *dialect)) + if (builtin->instruction) + checkInstruction(nativeLocationOf(_funCall), *builtin->instruction); for (auto const& arg: _funCall.arguments) std::visit(*this, arg); diff --git a/libsolidity/ast/ASTJsonImporter.cpp b/libsolidity/ast/ASTJsonImporter.cpp index 5d59308ae3d9..344bfb39d2a6 100644 --- a/libsolidity/ast/ASTJsonImporter.cpp +++ b/libsolidity/ast/ASTJsonImporter.cpp @@ -742,7 +742,9 @@ ASTPointer ASTJsonImporter::createInlineAssembly(Json const& _no flags->emplace_back(std::make_shared(flag.get())); } } - std::shared_ptr operations = std::make_shared(yul::AsmJsonImporter(dialect, m_sourceNames).createAST(member(_node, "AST"))); + std::shared_ptr operations = std::make_shared( + yul::AsmJsonImporter(dialect, m_sourceNames).createAST(member(_node, "AST")) + ); return createASTNode( _node, nullOrASTString(_node, "documentation"), diff --git a/libyul/AST.h b/libyul/AST.h index be734fa6b906..b19499b9a7d1 100644 --- a/libyul/AST.h +++ b/libyul/AST.h @@ -24,6 +24,7 @@ #pragma once #include +#include #include #include @@ -71,6 +72,9 @@ class LiteralValue { struct Literal { langutil::DebugData::ConstPtr debugData; LiteralKind kind; LiteralValue value; }; /// External / internal identifier or label reference struct Identifier { langutil::DebugData::ConstPtr debugData; YulName name; }; +/// AST Node representing a reference to one of the built-in functions (as defined by the dialect). +/// In the source it's an actual name, while in the AST we only store a handle that can be used to find the the function in the Dialect +struct BuiltinName { langutil::DebugData::ConstPtr debugData; BuiltinHandle handle; }; /// Assignment ("x := mload(20:u256)", expects push-1-expression on the right hand /// side and requires x to occupy exactly one stack slot. /// @@ -78,7 +82,7 @@ struct Identifier { langutil::DebugData::ConstPtr debugData; YulName name; }; /// a single stack slot and expects a single expression on the right hand returning /// the same amount of items as the number of variables. struct Assignment { langutil::DebugData::ConstPtr debugData; std::vector variableNames; std::unique_ptr value; }; -struct FunctionCall { langutil::DebugData::ConstPtr debugData; Identifier functionName; std::vector arguments; }; +struct FunctionCall { langutil::DebugData::ConstPtr debugData; FunctionName functionName; std::vector arguments; }; /// Statement that contains only a single expression struct ExpressionStatement { langutil::DebugData::ConstPtr debugData; Expression expression; }; /// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted @@ -114,6 +118,11 @@ class AST Block m_root; }; +bool constexpr isBuiltinFunctionCall(FunctionCall const& _functionCall) noexcept +{ + return std::holds_alternative(_functionCall.functionName); +} + /// Extracts the IR source location from a Yul node. template inline langutil::SourceLocation nativeLocationOf(T const& _node) diff --git a/libyul/ASTForward.h b/libyul/ASTForward.h index 8aba4f248c07..868fc84b2827 100644 --- a/libyul/ASTForward.h +++ b/libyul/ASTForward.h @@ -28,6 +28,9 @@ namespace solidity::yul { +class YulString; +using YulName = YulString; + enum class LiteralKind; class LiteralValue; struct Literal; @@ -46,11 +49,17 @@ struct Continue; struct Leave; struct ExpressionStatement; struct Block; +struct BuiltinName; +struct BuiltinHandle; class AST; struct NameWithDebugData; using Expression = std::variant; +using FunctionName = std::variant; +/// Type that can refer to both user-defined functions and built-ins. +/// Technically the AST allows these names to overlap, but this is not possible to represent in the source. +using FunctionHandle = std::variant; using Statement = std::variant; } diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index b00982faf882..ddd7ad1539aa 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -313,15 +313,14 @@ void AsmAnalyzer::operator()(FunctionDefinition const& _funDef) size_t AsmAnalyzer::operator()(FunctionCall const& _funCall) { - yulAssert(!_funCall.functionName.name.empty(), ""); auto watcher = m_errorReporter.errorWatcher(); std::optional numParameters; std::optional numReturns; std::vector> const* literalArguments = nullptr; - if (std::optional handle = m_dialect.findBuiltin(_funCall.functionName.name.str())) + if (BuiltinFunction const* builtin = resolveBuiltinFunction(_funCall.functionName, m_dialect)) { - if (_funCall.functionName.name == "selfdestruct"_yulname) + if (builtin->name == "selfdestruct") m_errorReporter.warning( 1699_error, nativeLocationOf(_funCall.functionName), @@ -334,7 +333,7 @@ size_t AsmAnalyzer::operator()(FunctionCall const& _funCall) ); else if ( m_evmVersion.supportsTransientStorage() && - _funCall.functionName.name == "tstore"_yulname && + builtin->name == "tstore" && !m_errorReporter.hasError({2394}) ) m_errorReporter.warning( @@ -347,16 +346,15 @@ size_t AsmAnalyzer::operator()(FunctionCall const& _funCall) "The use of transient storage for reentrancy guards that are cleared at the end of the call is safe." ); - BuiltinFunction const& f = m_dialect.builtin(*handle); - numParameters = f.numParameters; - numReturns = f.numReturns; - if (!f.literalArguments.empty()) - literalArguments = &f.literalArguments; + numParameters = builtin->numParameters; + numReturns = builtin->numReturns; + if (!builtin->literalArguments.empty()) + literalArguments = &builtin->literalArguments; validateInstructions(_funCall); - m_sideEffects += f.sideEffects; + m_sideEffects += builtin->sideEffects; } - else if (m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{ + else if (m_currentScope->lookup(YulName{resolveFunctionName(_funCall.functionName, m_dialect)}, GenericVisitor{ [&](Scope::Variable const&) { m_errorReporter.typeError( @@ -372,10 +370,11 @@ size_t AsmAnalyzer::operator()(FunctionCall const& _funCall) } })) { + yulAssert(std::holds_alternative(_funCall.functionName)); if (m_resolver) // We found a local reference, make sure there is no external reference. m_resolver( - _funCall.functionName, + std::get(_funCall.functionName), yul::IdentifierContext::NonExternal, m_currentScope->insideFunction() ); @@ -386,7 +385,7 @@ size_t AsmAnalyzer::operator()(FunctionCall const& _funCall) m_errorReporter.declarationError( 4619_error, nativeLocationOf(_funCall.functionName), - "Function \"" + _funCall.functionName.name.str() + "\" not found." + fmt::format("Function \"{}\" not found.", resolveFunctionName(_funCall.functionName, m_dialect)) ); yulAssert(!watcher.ok(), "Expected a reported error."); } @@ -395,10 +394,12 @@ size_t AsmAnalyzer::operator()(FunctionCall const& _funCall) m_errorReporter.typeError( 7000_error, nativeLocationOf(_funCall.functionName), - "Function \"" + _funCall.functionName.name.str() + "\" expects " + - std::to_string(*numParameters) + - " arguments but got " + - std::to_string(_funCall.arguments.size()) + "." + fmt::format( + "Function \"{}\" expects {} arguments but got {}.", + resolveFunctionName(_funCall.functionName, m_dialect), + *numParameters, + _funCall.arguments.size() + ) ); for (size_t i = _funCall.arguments.size(); i > 0; i--) @@ -424,7 +425,7 @@ size_t AsmAnalyzer::operator()(FunctionCall const& _funCall) ); else if (*literalArgumentKind == LiteralKind::String) { - std::string functionName = _funCall.functionName.name.str(); + std::string_view functionName = resolveFunctionName(_funCall.functionName, m_dialect); if (functionName == "datasize" || functionName == "dataoffset") { auto const& argumentAsLiteral = std::get(arg); @@ -455,7 +456,7 @@ size_t AsmAnalyzer::operator()(FunctionCall const& _funCall) m_errorReporter.typeError( 2186_error, nativeLocationOf(arg), - "Name required but path given as \"" + functionName + "\" argument." + fmt::format("Name required but path given as \"{}\" argument.", functionName) ); if (!m_objectStructure.topLevelSubObjectNames().count(formattedLiteral)) @@ -480,7 +481,7 @@ size_t AsmAnalyzer::operator()(FunctionCall const& _funCall) } else if (*literalArgumentKind == LiteralKind::Number) { - std::string functionName = _funCall.functionName.name.str(); + std::string_view functionName = resolveFunctionName(_funCall.functionName, m_dialect); if (functionName == "auxdataloadn") { auto const& argumentAsLiteral = std::get(arg); @@ -686,7 +687,7 @@ void AsmAnalyzer::expectValidIdentifier(YulName _identifier, SourceLocation cons ); } -bool AsmAnalyzer::validateInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location) +bool AsmAnalyzer::validateInstructions(std::string_view _instructionIdentifier, langutil::SourceLocation const& _location) { // NOTE: This function uses the default EVM version instead of the currently selected one. auto const& defaultEVMDialect = EVMDialect::strictAssemblyForEVM(EVMVersion{}, std::nullopt); @@ -813,7 +814,10 @@ bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocatio bool AsmAnalyzer::validateInstructions(FunctionCall const& _functionCall) { - return validateInstructions(_functionCall.functionName.name.str(), nativeLocationOf(_functionCall.functionName)); + return validateInstructions( + resolveFunctionName(_functionCall.functionName, m_dialect), + nativeLocationOf(_functionCall.functionName) + ); } void AsmAnalyzer::validateObjectStructure(langutil::SourceLocation _astRootLocation) diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index 6235b530d16d..e8c02fbec132 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -118,7 +118,7 @@ class AsmAnalyzer void expectValidIdentifier(YulName _identifier, langutil::SourceLocation const& _location); bool validateInstructions(evmasm::Instruction _instr, langutil::SourceLocation const& _location); - bool validateInstructions(std::string const& _instrIdentifier, langutil::SourceLocation const& _location); + bool validateInstructions(std::string_view _instrIdentifier, langutil::SourceLocation const& _location); bool validateInstructions(FunctionCall const& _functionCall); void validateObjectStructure(langutil::SourceLocation _astRootLocation); diff --git a/libyul/AsmJsonConverter.cpp b/libyul/AsmJsonConverter.cpp index 226ac52a5ac8..fd36dd26022c 100644 --- a/libyul/AsmJsonConverter.cpp +++ b/libyul/AsmJsonConverter.cpp @@ -20,8 +20,10 @@ * Converts inline assembly AST to JSON format */ -#include #include + +#include +#include #include #include #include @@ -83,6 +85,14 @@ Json AsmJsonConverter::operator()(Identifier const& _node) const return ret; } +Json AsmJsonConverter::operator()(BuiltinName const& _node) const +{ + // represents BuiltinName also with YulIdentifier node type to avoid a breaking change in the JSON interface + Json ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulIdentifier"); + ret["name"] = m_dialect.builtin(_node.handle).name; + return ret; +} + Json AsmJsonConverter::operator()(Assignment const& _node) const { yulAssert(_node.variableNames.size() >= 1, "Invalid assignment syntax"); @@ -96,7 +106,7 @@ Json AsmJsonConverter::operator()(Assignment const& _node) const Json AsmJsonConverter::operator()(FunctionCall const& _node) const { Json ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulFunctionCall"); - ret["functionName"] = (*this)(_node.functionName); + ret["functionName"] = std::visit(*this, _node.functionName); ret["arguments"] = vectorOfVariantsToJson(_node.arguments); return ret; } diff --git a/libyul/AsmJsonConverter.h b/libyul/AsmJsonConverter.h index 75e3400b7d3e..458142e29346 100644 --- a/libyul/AsmJsonConverter.h +++ b/libyul/AsmJsonConverter.h @@ -43,12 +43,14 @@ class AsmJsonConverter: public boost::static_visitor public: /// Create a converter to JSON for any block of inline assembly /// @a _sourceIndex to be used to abbreviate source name in the source locations - explicit AsmJsonConverter(Dialect const&, std::optional _sourceIndex): m_sourceIndex(_sourceIndex) {} + AsmJsonConverter(Dialect const& _dialect, std::optional _sourceIndex): + m_dialect(_dialect), m_sourceIndex(_sourceIndex) {} Json operator()(Block const& _node) const; Json operator()(NameWithDebugData const& _node) const; Json operator()(Literal const& _node) const; Json operator()(Identifier const& _node) const; + Json operator()(BuiltinName const& _node) const; Json operator()(Assignment const& _node) const; Json operator()(VariableDeclaration const& _node) const; Json operator()(FunctionDefinition const& _node) const; @@ -68,6 +70,7 @@ class AsmJsonConverter: public boost::static_visitor template Json vectorOfVariantsToJson(std::vector const& vec) const; + Dialect const& m_dialect; std::optional const m_sourceIndex; }; diff --git a/libyul/AsmJsonImporter.cpp b/libyul/AsmJsonImporter.cpp index 692169fd29c6..a937be62fb7a 100644 --- a/libyul/AsmJsonImporter.cpp +++ b/libyul/AsmJsonImporter.cpp @@ -23,7 +23,9 @@ */ #include + #include +#include #include #include @@ -255,7 +257,16 @@ FunctionCall AsmJsonImporter::createFunctionCall(Json const& _node) for (auto const& var: member(_node, "arguments")) functionCall.arguments.emplace_back(createExpression(var)); - functionCall.functionName = createIdentifier(member(_node, "functionName")); + auto const functionNameNode = member(_node, "functionName"); + auto const name = member(functionNameNode, "name").get(); + if (std::optional builtinHandle = m_dialect.findBuiltin(name)) + { + auto builtin = createAsmNode(functionNameNode); + builtin.handle = *builtinHandle; + functionCall.functionName = builtin; + } + else + functionCall.functionName = createIdentifier(functionNameNode); return functionCall; } diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index baef6a3c52ad..66ac22a08e72 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -405,7 +405,7 @@ Statement Parser::parseStatement() // Options left: // Expression/FunctionCall // Assignment - std::variant elementary(parseLiteralOrIdentifier()); + std::variant elementary(parseLiteralOrIdentifier()); switch (currentToken()) { @@ -420,35 +420,47 @@ Statement Parser::parseStatement() Assignment assignment; assignment.debugData = debugDataOf(elementary); - while (true) + bool foundListEnd = false; + do { - if (!std::holds_alternative(elementary)) - { - auto const token = currentToken() == Token::Comma ? "," : ":="; - - fatalParserError( - 2856_error, - std::string("Variable name must precede \"") + - token + - "\"" + - (currentToken() == Token::Comma ? " in multiple assignment." : " in assignment.") - ); - } - - auto const& identifier = std::get(elementary); - - if (m_dialect.findBuiltin(identifier.name.str())) - fatalParserError(6272_error, "Cannot assign to builtin function \"" + identifier.name.str() + "\"."); - - assignment.variableNames.emplace_back(identifier); - - if (currentToken() != Token::Comma) - break; - - expectToken(Token::Comma); - - elementary = parseLiteralOrIdentifier(); - } + std::visit(GenericVisitor{ + [&](Literal const&) + { + auto const token = currentToken() == Token::Comma ? "," : ":="; + + fatalParserError( + 2856_error, + std::string("Variable name must precede \"") + + token + + "\"" + + (currentToken() == Token::Comma ? " in multiple assignment." : " in assignment.") + ); + }, + [&](BuiltinName const& _builtin) + { + fatalParserError( + 6272_error, + fmt::format( + "Cannot assign to builtin function \"{}\".", + m_dialect.builtin(_builtin.handle).name + ) + ); + }, + [&](Identifier const& _identifier) + { + assignment.variableNames.emplace_back(_identifier); + + if (currentToken() != Token::Comma) + foundListEnd = true; + else + { + expectToken(Token::Comma); + elementary = parseLiteralOrIdentifier(); + } + } + }, elementary); + + } while (!foundListEnd); expectToken(Token::AssemblyAssign); @@ -475,7 +487,7 @@ Case Parser::parseCase() else if (currentToken() == Token::Case) { advance(); - std::variant literal = parseLiteralOrIdentifier(); + std::variant literal = parseLiteralOrIdentifier(); if (!std::holds_alternative(literal)) fatalParserError(4805_error, "Literal expected."); _case.value = std::make_unique(std::get(std::move(literal))); @@ -514,20 +526,25 @@ Expression Parser::parseExpression(bool _unlimitedLiteralArgument) { RecursionGuard recursionGuard(*this); - std::variant operation = parseLiteralOrIdentifier(_unlimitedLiteralArgument); + std::variant operation = parseLiteralOrIdentifier(_unlimitedLiteralArgument); return visit(GenericVisitor{ [&](Identifier& _identifier) -> Expression { if (currentToken() == Token::LParen) return parseCall(std::move(operation)); - if (m_dialect.findBuiltin(_identifier.name.str())) - fatalParserError( - 7104_error, - nativeLocationOf(_identifier), - "Builtin function \"" + _identifier.name.str() + "\" must be called." - ); return std::move(_identifier); }, + [&](BuiltinName& _builtin) -> Expression + { + if (currentToken() == Token::LParen) + return parseCall(std::move(operation)); + fatalParserError( + 7104_error, + nativeLocationOf(_builtin), + "Builtin function \"" + m_dialect.builtin(_builtin.handle).name + "\" must be called." + ); + unreachable(); + }, [&](Literal& _literal) -> Expression { return std::move(_literal); @@ -535,16 +552,20 @@ Expression Parser::parseExpression(bool _unlimitedLiteralArgument) }, operation); } -std::variant Parser::parseLiteralOrIdentifier(bool _unlimitedLiteralArgument) +std::variant Parser::parseLiteralOrIdentifier(bool _unlimitedLiteralArgument) { RecursionGuard recursionGuard(*this); switch (currentToken()) { case Token::Identifier: { - Identifier identifier{createDebugData(), YulName{currentLiteral()}}; + std::variant literalOrIdentifier; + if (std::optional const builtinHandle = m_dialect.findBuiltin(currentLiteral())) + literalOrIdentifier = BuiltinName{createDebugData(), *builtinHandle}; + else + literalOrIdentifier = Identifier{createDebugData(), YulName{currentLiteral()}}; advance(); - return identifier; + return literalOrIdentifier; } case Token::StringLiteral: case Token::HexStringLiteral: @@ -671,36 +692,45 @@ FunctionDefinition Parser::parseFunctionDefinition() return funDef; } -FunctionCall Parser::parseCall(std::variant&& _initialOp) +FunctionCall Parser::parseCall(std::variant&& _initialOp) { RecursionGuard recursionGuard(*this); - if (!std::holds_alternative(_initialOp)) - fatalParserError(9980_error, "Function name expected."); - - FunctionCall ret; - ret.functionName = std::move(std::get(_initialOp)); - ret.debugData = ret.functionName.debugData; - auto const isUnlimitedLiteralArgument = [handle=m_dialect.findBuiltin(ret.functionName.name.str()), this](size_t const index) { - if (!handle) - return false; - auto const& function = m_dialect.builtin(*handle); - return index < function.literalArguments.size() && function.literalArgument(index).has_value(); - }; + std::function isUnlimitedLiteralArgument = [](size_t) -> bool { return false; }; + FunctionCall functionCall; + std::visit(GenericVisitor{ + [&](Literal const&) { fatalParserError(9980_error, "Function name expected."); }, + [&](Identifier const& _identifier) + { + functionCall.debugData = _identifier.debugData; + functionCall.functionName = _identifier; + }, + [&](BuiltinName const& _builtin) + { + isUnlimitedLiteralArgument = [builtinFunction=m_dialect.builtin(_builtin.handle)](size_t _index) { + if (_index < builtinFunction.literalArguments.size()) + return builtinFunction.literalArgument(_index).has_value(); + return false; + }; + functionCall.debugData = _builtin.debugData; + functionCall.functionName = _builtin; + } + }, _initialOp); + size_t argumentIndex {0}; expectToken(Token::LParen); if (currentToken() != Token::RParen) { - ret.arguments.emplace_back(parseExpression(isUnlimitedLiteralArgument(argumentIndex++))); + functionCall.arguments.emplace_back(parseExpression(isUnlimitedLiteralArgument(argumentIndex++))); while (currentToken() != Token::RParen) { expectToken(Token::Comma); - ret.arguments.emplace_back(parseExpression(isUnlimitedLiteralArgument(argumentIndex++))); + functionCall.arguments.emplace_back(parseExpression(isUnlimitedLiteralArgument(argumentIndex++))); } } - updateLocationEndFrom(ret.debugData, currentLocation()); + updateLocationEndFrom(functionCall.debugData, currentLocation()); expectToken(Token::RParen); - return ret; + return functionCall; } NameWithDebugData Parser::parseNameWithDebugData() diff --git a/libyul/AsmParser.h b/libyul/AsmParser.h index d4616a2a9508..b546e179fd96 100644 --- a/libyul/AsmParser.h +++ b/libyul/AsmParser.h @@ -141,10 +141,10 @@ class Parser: public langutil::ParserBase Expression parseExpression(bool _unlimitedLiteralArgument = false); /// Parses an elementary operation, i.e. a literal, identifier, instruction or /// builtin function call (only the name). - std::variant parseLiteralOrIdentifier(bool _unlimitedLiteralArgument = false); + std::variant parseLiteralOrIdentifier(bool _unlimitedLiteralArgument = false); VariableDeclaration parseVariableDeclaration(); FunctionDefinition parseFunctionDefinition(); - FunctionCall parseCall(std::variant&& _initialOp); + FunctionCall parseCall(std::variant&& _index); NameWithDebugData parseNameWithDebugData(); YulName expectAsmIdentifier(); void raiseUnsupportedTypesError(langutil::SourceLocation const& _location) const; diff --git a/libyul/AsmPrinter.cpp b/libyul/AsmPrinter.cpp index 5ba19014995b..a3f7b4ca2b23 100644 --- a/libyul/AsmPrinter.cpp +++ b/libyul/AsmPrinter.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -77,6 +78,11 @@ std::string AsmPrinter::operator()(Identifier const& _identifier) return formatDebugData(_identifier) + _identifier.name.str(); } +std::string AsmPrinter::operator()(BuiltinName const& _builtin) +{ + return formatDebugData(_builtin) + m_dialect.builtin(_builtin.handle).name; +} + std::string AsmPrinter::operator()(ExpressionStatement const& _statement) { std::string const locationComment = formatDebugData(_statement); @@ -145,7 +151,7 @@ std::string AsmPrinter::operator()(FunctionDefinition const& _functionDefinition std::string AsmPrinter::operator()(FunctionCall const& _functionCall) { std::string const locationComment = formatDebugData(_functionCall); - std::string const functionName = (*this)(_functionCall.functionName); + std::string const functionName = std::visit(*this, _functionCall.functionName); return locationComment + functionName + "(" + diff --git a/libyul/AsmPrinter.h b/libyul/AsmPrinter.h index 6b63c863883e..d889d83f80ec 100644 --- a/libyul/AsmPrinter.h +++ b/libyul/AsmPrinter.h @@ -54,11 +54,12 @@ class AsmPrinter ); explicit AsmPrinter( - Dialect const&, + Dialect const& _dialect, std::optional>> const& _sourceIndexToName = {}, langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(), langutil::CharStreamProvider const* _soliditySourceProvider = nullptr ): + m_dialect(_dialect), m_debugInfoSelection(_debugInfoSelection), m_soliditySourceProvider(_soliditySourceProvider) { @@ -69,6 +70,7 @@ class AsmPrinter std::string operator()(Literal const& _literal); std::string operator()(Identifier const& _identifier); + std::string operator()(BuiltinName const& _builtin); std::string operator()(ExpressionStatement const& _expr); std::string operator()(Assignment const& _assignment); std::string operator()(VariableDeclaration const& _variableDeclaration); @@ -99,6 +101,7 @@ class AsmPrinter return formatDebugData(_node.debugData, !isExpression); } + Dialect const& m_dialect; std::map m_nameToSourceIndex; langutil::SourceLocation m_lastLocation = {}; langutil::DebugInfoSelection m_debugInfoSelection = {}; diff --git a/libyul/ControlFlowSideEffectsCollector.cpp b/libyul/ControlFlowSideEffectsCollector.cpp index 2694eef1c385..28089b029fca 100644 --- a/libyul/ControlFlowSideEffectsCollector.cpp +++ b/libyul/ControlFlowSideEffectsCollector.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -271,8 +272,8 @@ ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(Func ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(FunctionCall const& _call) const { - if (std::optional builtinHandle = m_dialect.findBuiltin(_call.functionName.name.str())) - return m_dialect.builtin(*builtinHandle).controlFlowSideEffects; + if (BuiltinFunction const* builtin = resolveBuiltinFunction(_call.functionName, m_dialect)) + return builtin->controlFlowSideEffects; else return m_functionSideEffects.at(m_functionReferences.at(&_call)); } diff --git a/libyul/FunctionReferenceResolver.cpp b/libyul/FunctionReferenceResolver.cpp index 99e83c2130e7..bf3f536a49a8 100644 --- a/libyul/FunctionReferenceResolver.cpp +++ b/libyul/FunctionReferenceResolver.cpp @@ -34,15 +34,17 @@ FunctionReferenceResolver::FunctionReferenceResolver(Block const& _ast) void FunctionReferenceResolver::operator()(FunctionCall const& _functionCall) { - for (auto&& scope: m_scopes | ranges::views::reverse) - if (FunctionDefinition const** function = util::valueOrNullptr(scope, _functionCall.functionName.name)) + if (!isBuiltinFunctionCall(_functionCall)) + for (auto&& scope: m_scopes | ranges::views::reverse) { - m_functionReferences[&_functionCall] = *function; - break; + yulAssert(std::holds_alternative(_functionCall.functionName)); + if (FunctionDefinition const** function = util::valueOrNullptr(scope, std::get(_functionCall.functionName).name)) + { + m_functionReferences[&_functionCall] = *function; + break; + } } - // If we did not find anything, it was a builtin call. - ASTWalker::operator()(_functionCall); } diff --git a/libyul/Utilities.cpp b/libyul/Utilities.cpp index 4bbfb42437b0..e7caf1b2dff4 100644 --- a/libyul/Utilities.cpp +++ b/libyul/Utilities.cpp @@ -21,11 +21,15 @@ #include +#include + #include +#include #include #include #include +#include #include @@ -237,3 +241,40 @@ bool SwitchCaseCompareByLiteralValue::operator()(Case const* _lhs, Case const* _ yulAssert(_lhs && _rhs, ""); return Less{}(_lhs->value.get(), _rhs->value.get()); } + +std::string_view yul::resolveFunctionName(FunctionName const& _functionName, Dialect const& _dialect) +{ + GenericVisitor visitor{ + [&](Identifier const& _identifier) -> std::string const& { return _identifier.name.str(); }, + [&](BuiltinName const& _builtin) -> std::string const& { return _dialect.builtin(_builtin.handle).name; } + }; + return std::visit(visitor, _functionName); +} + +BuiltinFunction const* yul::resolveBuiltinFunction(FunctionName const& _functionName, Dialect const& _dialect) +{ + GenericVisitor visitor{ + [&](Identifier const&) -> BuiltinFunction const* { return nullptr; }, + [&](BuiltinName const& _builtin) -> BuiltinFunction const* { return &_dialect.builtin(_builtin.handle); } + }; + return std::visit(visitor, _functionName); +} + +BuiltinFunctionForEVM const* yul::resolveBuiltinFunctionForEVM(FunctionName const& _functionName, EVMDialect const& _dialect) +{ + GenericVisitor visitor{ + [&](Identifier const&) -> BuiltinFunctionForEVM const* { return nullptr; }, + [&](BuiltinName const& _builtin) -> BuiltinFunctionForEVM const* { return &_dialect.builtin(_builtin.handle); } + }; + return std::visit(visitor, _functionName); +} + +FunctionHandle yul::functionNameToHandle(FunctionName const& _functionName) +{ + GenericVisitor visitor{ + [&](Identifier const& _identifier) -> FunctionHandle { return _identifier.name; }, + [&](BuiltinName const& _builtin) -> FunctionHandle { return _builtin.handle; } + }; + return std::visit(visitor, _functionName); +} + diff --git a/libyul/Utilities.h b/libyul/Utilities.h index 434c5e9c5a3a..a4a558a855b9 100644 --- a/libyul/Utilities.h +++ b/libyul/Utilities.h @@ -30,6 +30,11 @@ namespace solidity::yul { +struct Dialect; +class EVMDialect; +struct BuiltinFunction; +struct BuiltinFunctionForEVM; + std::string reindent(std::string const& _code); LiteralValue valueOfNumberLiteral(std::string_view _literal); @@ -83,4 +88,10 @@ struct SwitchCaseCompareByLiteralValue bool operator()(Case const* _lhsCase, Case const* _rhsCase) const; }; +std::string_view resolveFunctionName(FunctionName const& _functionName, Dialect const& _dialect); + +BuiltinFunction const* resolveBuiltinFunction(FunctionName const& _functionName, Dialect const& _dialect); +BuiltinFunctionForEVM const* resolveBuiltinFunctionForEVM(FunctionName const& _functionName, EVMDialect const& _dialect); +FunctionHandle functionNameToHandle(FunctionName const& _functionName); + } diff --git a/libyul/YulString.h b/libyul/YulString.h index bd9438e5abc9..d6c13f0d3751 100644 --- a/libyul/YulString.h +++ b/libyul/YulString.h @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace solidity::yul @@ -51,7 +52,7 @@ class YulStringRepository return inst; } - Handle stringToHandle(std::string const& _string) + Handle stringToHandle(std::string_view const _string) { if (_string.empty()) return { 0, emptyHash() }; @@ -68,7 +69,7 @@ class YulStringRepository } std::string const& idToString(size_t _id) const { return *m_strings.at(_id); } - static std::uint64_t hash(std::string const& v) + static std::uint64_t hash(std::string_view const v) { // FNV hash - can be replaced by a better one, e.g. xxhash64 std::uint64_t hash = emptyHash(); @@ -126,7 +127,7 @@ class YulString { public: YulString() = default; - explicit YulString(std::string const& _s): m_handle(YulStringRepository::instance().stringToHandle(_s)) {} + explicit YulString(std::string_view const _s): m_handle(YulStringRepository::instance().stringToHandle(_s)) {} YulString(YulString const&) = default; YulString(YulString&&) = default; YulString& operator=(YulString const&) = default; diff --git a/libyul/backends/evm/ConstantOptimiser.cpp b/libyul/backends/evm/ConstantOptimiser.cpp index 0ea8f4e5a9fc..d7aed27c339f 100644 --- a/libyul/backends/evm/ConstantOptimiser.cpp +++ b/libyul/backends/evm/ConstantOptimiser.cpp @@ -74,11 +74,10 @@ struct MiniEVMInterpreter u256 operator()(FunctionCall const& _funCall) { - std::optional funHandle = m_dialect.findBuiltin(_funCall.functionName.name.str()); - yulAssert(funHandle, "Expected builtin function."); - BuiltinFunctionForEVM const& fun = m_dialect.builtin(*funHandle); - yulAssert(fun.instruction, "Expected EVM instruction."); - return eval(*fun.instruction, _funCall.arguments); + BuiltinFunctionForEVM const* builtin = resolveBuiltinFunctionForEVM(_funCall.functionName, m_dialect); + yulAssert(builtin, "Expected builtin function."); + yulAssert(builtin->instruction, "Expected EVM instruction."); + return eval(*builtin->instruction, _funCall.arguments); } u256 operator()(Literal const& _literal) { @@ -126,11 +125,20 @@ Representation const& RepresentationFinder::findRepresentation(u256 const& _valu if (m_cache.count(_value)) return m_cache.at(_value); + yulAssert(m_dialect.auxiliaryBuiltinHandles().not_); + yulAssert(m_dialect.auxiliaryBuiltinHandles().shl); + yulAssert(m_dialect.auxiliaryBuiltinHandles().exp); + yulAssert(m_dialect.auxiliaryBuiltinHandles().mul); + yulAssert(m_dialect.auxiliaryBuiltinHandles().add); + yulAssert(m_dialect.auxiliaryBuiltinHandles().sub); + + auto const& auxHandles = m_dialect.auxiliaryBuiltinHandles(); + Representation routine = represent(_value); if (numberEncodingSize(~_value) < numberEncodingSize(_value)) // Negated is shorter to represent - routine = min(std::move(routine), represent("not"_yulname, findRepresentation(~_value))); + routine = min(std::move(routine), represent(*auxHandles.not_, findRepresentation(~_value))); // Decompose value into a * 2**k + b where abs(b) << 2**k for (unsigned bits = 255; bits > 8 && m_maxSteps > 0; --bits) @@ -153,21 +161,21 @@ Representation const& RepresentationFinder::findRepresentation(u256 const& _valu continue; Representation newRoutine; if (m_dialect.evmVersion().hasBitwiseShifting()) - newRoutine = represent("shl"_yulname, represent(bits), findRepresentation(upperPart)); + newRoutine = represent(*auxHandles.shl, represent(bits), findRepresentation(upperPart)); else { - newRoutine = represent("exp"_yulname, represent(2), represent(bits)); + newRoutine = represent(*auxHandles.exp, represent(2), represent(bits)); if (upperPart != 1) - newRoutine = represent("mul"_yulname, findRepresentation(upperPart), newRoutine); + newRoutine = represent(*auxHandles.mul, findRepresentation(upperPart), newRoutine); } if (newRoutine.cost >= routine.cost) continue; if (lowerPart > 0) - newRoutine = represent("add"_yulname, newRoutine, findRepresentation(u256(abs(lowerPart)))); + newRoutine = represent(*auxHandles.add, newRoutine, findRepresentation(u256(abs(lowerPart)))); else if (lowerPart < 0) - newRoutine = represent("sub"_yulname, newRoutine, findRepresentation(u256(abs(lowerPart)))); + newRoutine = represent(*auxHandles.sub, newRoutine, findRepresentation(u256(abs(lowerPart)))); if (m_maxSteps > 0) m_maxSteps--; @@ -186,24 +194,22 @@ Representation RepresentationFinder::represent(u256 const& _value) const } Representation RepresentationFinder::represent( - YulName _instruction, + BuiltinHandle const& _instruction, Representation const& _argument ) const { Representation repr; repr.expression = std::make_unique(FunctionCall{ m_debugData, - Identifier{m_debugData, _instruction}, + BuiltinName{m_debugData, _instruction}, {ASTCopier{}.translate(*_argument.expression)} }); - repr.cost = _argument.cost + m_meter.instructionCosts( - *m_dialect.builtin(*m_dialect.findBuiltin(_instruction.str())).instruction - ); + repr.cost = _argument.cost + m_meter.instructionCosts(*m_dialect.builtin(_instruction).instruction); return repr; } Representation RepresentationFinder::represent( - YulName _instruction, + BuiltinHandle const& _instruction, Representation const& _arg1, Representation const& _arg2 ) const @@ -211,11 +217,10 @@ Representation RepresentationFinder::represent( Representation repr; repr.expression = std::make_unique(FunctionCall{ m_debugData, - Identifier{m_debugData, _instruction}, + BuiltinName{m_debugData, _instruction}, {ASTCopier{}.translate(*_arg1.expression), ASTCopier{}.translate(*_arg2.expression)} }); - repr.cost = m_meter.instructionCosts( - *m_dialect.builtin(*m_dialect.findBuiltin(_instruction.str())).instruction) + _arg1.cost + _arg2.cost; + repr.cost = m_meter.instructionCosts(*m_dialect.builtin(_instruction).instruction) + _arg1.cost + _arg2.cost; return repr; } diff --git a/libyul/backends/evm/ConstantOptimiser.h b/libyul/backends/evm/ConstantOptimiser.h index 35d7dfa43254..5e4b2002b0d0 100644 --- a/libyul/backends/evm/ConstantOptimiser.h +++ b/libyul/backends/evm/ConstantOptimiser.h @@ -93,8 +93,8 @@ class RepresentationFinder Representation const& findRepresentation(u256 const& _value); Representation represent(u256 const& _value) const; - Representation represent(YulName _instruction, Representation const& _arg) const; - Representation represent(YulName _instruction, Representation const& _arg1, Representation const& _arg2) const; + Representation represent(BuiltinHandle const& _instruction, Representation const& _arg) const; + Representation represent(BuiltinHandle const& _instruction, Representation const& _arg1, Representation const& _arg2) const; Representation min(Representation _a, Representation _b); diff --git a/libyul/backends/evm/ControlFlowGraphBuilder.cpp b/libyul/backends/evm/ControlFlowGraphBuilder.cpp index e92515e988ad..990456732bb9 100644 --- a/libyul/backends/evm/ControlFlowGraphBuilder.cpp +++ b/libyul/backends/evm/ControlFlowGraphBuilder.cpp @@ -524,29 +524,29 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal Stack const* output = nullptr; bool canContinue = true; - if (std::optional const& builtinHandle = m_dialect.findBuiltin(_call.functionName.name.str())) + if (BuiltinFunction const* builtin = resolveBuiltinFunction(_call.functionName, m_dialect)) { - auto const& builtin = m_dialect.builtin(*builtinHandle); Stack inputs; for (auto&& [idx, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse) - if (!builtin.literalArgument(idx).has_value()) + if (!builtin->literalArgument(idx).has_value()) inputs.emplace_back(std::visit(*this, arg)); - CFG::BuiltinCall builtinCall{_call.debugData, builtin, _call, inputs.size()}; + CFG::BuiltinCall builtinCall{_call.debugData, *builtin, _call, inputs.size()}; output = &m_currentBlock->operations.emplace_back(CFG::Operation{ // input std::move(inputs), // output - ranges::views::iota(0u, builtin.numReturns) | ranges::views::transform([&](size_t _i) { + ranges::views::iota(0u, builtin->numReturns) | ranges::views::transform([&](size_t _i) { return TemporarySlot{_call, _i}; }) | ranges::to, // operation std::move(builtinCall) }).output; - canContinue = builtin.controlFlowSideEffects.canContinue; + canContinue = builtin->controlFlowSideEffects.canContinue; } else { - Scope::Function const& function = lookupFunction(_call.functionName.name); + yulAssert(std::holds_alternative(_call.functionName)); + Scope::Function const& function = lookupFunction(std::get(_call.functionName).name); canContinue = m_graph.functionInfo.at(&function).canContinue; Stack inputs; // For EOF (m_simulateFunctionsWithJumps == false) we do not have to put return label on stack. diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index e354649e75d6..f77e4e047d7a 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -230,22 +230,22 @@ void CodeTransform::operator()(FunctionCall const& _call) yulAssert(m_scope, ""); m_assembly.setSourceLocation(originLocationOf(_call)); - if (std::optional builtinHandle = m_dialect.findBuiltin(_call.functionName.name.str())) + if (BuiltinFunctionForEVM const* builtin = resolveBuiltinFunctionForEVM(_call.functionName, m_dialect)) { - BuiltinFunctionForEVM const& builtin = m_dialect.builtin(*builtinHandle); for (auto&& [i, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse) - if (!builtin.literalArgument(i)) + if (!builtin->literalArgument(i)) visitExpression(arg); m_assembly.setSourceLocation(originLocationOf(_call)); - builtin.generateCode(_call, m_assembly, m_builtinContext); + builtin->generateCode(_call, m_assembly, m_builtinContext); } else { + yulAssert(std::holds_alternative(_call.functionName)); AbstractAssembly::LabelID returnLabel = m_assembly.newLabelId(); m_assembly.appendLabelReference(returnLabel); Scope::Function* function = nullptr; - yulAssert(m_scope->lookup(_call.functionName.name, GenericVisitor{ + yulAssert(m_scope->lookup(std::get(_call.functionName).name, GenericVisitor{ [](Scope::Variable&) { yulAssert(false, "Expected function name."); }, [&](Scope::Function& _function) { function = &_function; } }), "Function name not found."); diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 4d160b03049e..c73a9a79483f 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -475,6 +475,13 @@ EVMDialect::EVMDialect(langutil::EVMVersion _evmVersion, std::optional m_storageStoreFunction = findBuiltin("sstore"); m_storageLoadFunction = findBuiltin("sload"); m_hashFunction = findBuiltin("keccak256"); + + m_auxiliaryBuiltinHandles.add = EVMDialect::findBuiltin("add"); + m_auxiliaryBuiltinHandles.exp = EVMDialect::findBuiltin("exp"); + m_auxiliaryBuiltinHandles.mul = EVMDialect::findBuiltin("mul"); + m_auxiliaryBuiltinHandles.not_ = EVMDialect::findBuiltin("not"); + m_auxiliaryBuiltinHandles.shl = EVMDialect::findBuiltin("shl"); + m_auxiliaryBuiltinHandles.sub = EVMDialect::findBuiltin("sub"); } std::optional EVMDialect::findBuiltin(std::string_view _name) const diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index 58944bdc701f..5d5ea90a99be 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -68,6 +68,18 @@ struct BuiltinFunctionForEVM: public BuiltinFunction class EVMDialect: public Dialect { public: + /// Handles to (depending on dialect, potentially existing) builtins, which are not accessible via the + /// `...FunctionHandle` functions of `Dialect` and of which it is statically known, that they are needed in, + /// e.g., certain optimization steps. + struct AuxiliaryBuiltinHandles + { + std::optional add; + std::optional exp; + std::optional mul; + std::optional not_; + std::optional shl; + std::optional sub; + }; /// Constructor, should only be used internally. Use the factory functions below. EVMDialect(langutil::EVMVersion _evmVersion, std::optional _eofVersion, bool _objectAccess); @@ -85,6 +97,7 @@ class EVMDialect: public Dialect std::optional storageStoreFunctionHandle() const override { return m_storageStoreFunction; } std::optional storageLoadFunctionHandle() const override { return m_storageLoadFunction; } std::optional hashFunctionHandle() const override { return m_hashFunction; } + AuxiliaryBuiltinHandles const& auxiliaryBuiltinHandles() const { return m_auxiliaryBuiltinHandles; } static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _evmVersion, std::optional _eofVersion); static EVMDialect const& strictAssemblyForEVMObjects(langutil::EVMVersion _evmVersion, std::optional _eofVersion); @@ -123,6 +136,7 @@ class EVMDialect: public Dialect std::optional m_storageStoreFunction; std::optional m_storageLoadFunction; std::optional m_hashFunction; + AuxiliaryBuiltinHandles m_auxiliaryBuiltinHandles; }; } diff --git a/libyul/backends/evm/EVMMetrics.cpp b/libyul/backends/evm/EVMMetrics.cpp index 64ff94cf6102..57593fdc26da 100644 --- a/libyul/backends/evm/EVMMetrics.cpp +++ b/libyul/backends/evm/EVMMetrics.cpp @@ -76,10 +76,10 @@ std::pair GasMeterVisitor::instructionCosts( void GasMeterVisitor::operator()(FunctionCall const& _funCall) { ASTWalker::operator()(_funCall); - if (std::optional handle = m_dialect.findBuiltin(_funCall.functionName.name.str())) - if (std::optional const& instruction = m_dialect.builtin(*handle).instruction) + if (BuiltinFunctionForEVM const* builtin = resolveBuiltinFunctionForEVM(_funCall.functionName, m_dialect)) + if (builtin->instruction) { - instructionCostsInternal(*instruction); + instructionCostsInternal(*builtin->instruction); return; } yulAssert(false, "Functions not implemented."); diff --git a/libyul/backends/evm/EVMObjectCompiler.cpp b/libyul/backends/evm/EVMObjectCompiler.cpp index a7ac67c3a6c3..de1174acee00 100644 --- a/libyul/backends/evm/EVMObjectCompiler.cpp +++ b/libyul/backends/evm/EVMObjectCompiler.cpp @@ -90,9 +90,11 @@ void EVMObjectCompiler::run(Object const& _object, bool _optimize) ); if (!stackErrors.empty()) { + yulAssert(_object.dialect()); std::vector memoryGuardCalls = findFunctionCalls( _object.code()->root(), - "memoryguard"_yulname + "memoryguard", + *_object.dialect() ); auto stackError = stackErrors.front(); std::string msg = stackError.comment() ? *stackError.comment() : ""; diff --git a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp index 89a75019ec31..3a1b78e1eb31 100644 --- a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp +++ b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp @@ -73,7 +73,8 @@ std::vector OptimizedEVMCodeTransform::run( _useNamedLabelsForFunctions, *dfg, stackLayout, - !_dialect.eofVersion().has_value() + !_dialect.eofVersion().has_value(), + _dialect ); // Create initial entry layout. optimizedCodeTransform.createStackLayout(debugDataOf(*dfg->entry), stackLayout.blockInfos.at(dfg->entry).entryLayout); @@ -202,12 +203,14 @@ OptimizedEVMCodeTransform::OptimizedEVMCodeTransform( UseNamedLabels _useNamedLabelsForFunctions, CFG const& _dfg, StackLayout const& _stackLayout, - bool _simulateFunctionsWithJumps + bool _simulateFunctionsWithJumps, + EVMDialect const& _dialect ): m_assembly(_assembly), m_builtinContext(_builtinContext), m_dfg(_dfg), m_stackLayout(_stackLayout), + m_dialect(_dialect), m_functionLabels(!_simulateFunctionsWithJumps ? decltype(m_functionLabels)() : [&](){ std::map functionLabels; std::set assignedFunctionNames; @@ -294,9 +297,9 @@ void OptimizedEVMCodeTransform::createStackLayout(langutil::DebugData::ConstPtr YulName varNameDeep = slotVariableName(deepSlot); YulName varNameTop = slotVariableName(m_stack.back()); std::string msg = - "Cannot swap " + (varNameDeep.empty() ? "Slot " + stackSlotToString(deepSlot) : "Variable " + varNameDeep.str()) + - " with " + (varNameTop.empty() ? "Slot " + stackSlotToString(m_stack.back()) : "Variable " + varNameTop.str()) + - ": too deep in the stack by " + std::to_string(deficit) + " slots in " + stackToString(m_stack); + "Cannot swap " + (varNameDeep.empty() ? "Slot " + stackSlotToString(deepSlot, m_dialect) : "Variable " + varNameDeep.str()) + + " with " + (varNameTop.empty() ? "Slot " + stackSlotToString(m_stack.back(), m_dialect) : "Variable " + varNameTop.str()) + + ": too deep in the stack by " + std::to_string(deficit) + " slots in " + stackToString(m_stack, m_dialect); m_stackErrors.emplace_back(StackTooDeepError( m_currentFunctionInfo ? m_currentFunctionInfo->function.name : YulName{}, varNameDeep.empty() ? varNameTop : varNameDeep, @@ -324,8 +327,8 @@ void OptimizedEVMCodeTransform::createStackLayout(langutil::DebugData::ConstPtr int deficit = static_cast(*depth - 15); YulName varName = slotVariableName(_slot); std::string msg = - (varName.empty() ? "Slot " + stackSlotToString(_slot) : "Variable " + varName.str()) - + " is " + std::to_string(*depth - 15) + " too deep in the stack " + stackToString(m_stack); + (varName.empty() ? "Slot " + stackSlotToString(_slot, m_dialect) : "Variable " + varName.str()) + + " is " + std::to_string(*depth - 15) + " too deep in the stack " + stackToString(m_stack, m_dialect); m_stackErrors.emplace_back(StackTooDeepError( m_currentFunctionInfo ? m_currentFunctionInfo->function.name : YulName{}, varName, diff --git a/libyul/backends/evm/OptimizedEVMCodeTransform.h b/libyul/backends/evm/OptimizedEVMCodeTransform.h index 2c072bcca2f6..88ecc2967763 100644 --- a/libyul/backends/evm/OptimizedEVMCodeTransform.h +++ b/libyul/backends/evm/OptimizedEVMCodeTransform.h @@ -69,7 +69,8 @@ class OptimizedEVMCodeTransform UseNamedLabels _useNamedLabelsForFunctions, CFG const& _dfg, StackLayout const& _stackLayout, - bool _simulateFunctionsWithJumps + bool _simulateFunctionsWithJumps, + EVMDialect const& _dialect ); /// Assert that it is valid to transition from @a _currentStack to @a _desiredStack. @@ -102,6 +103,7 @@ class OptimizedEVMCodeTransform BuiltinContext& m_builtinContext; CFG const& m_dfg; StackLayout const& m_stackLayout; + EVMDialect const& m_dialect; Stack m_stack; std::map m_returnLabels; std::map m_blockLabels; diff --git a/libyul/backends/evm/SSAControlFlowGraphBuilder.cpp b/libyul/backends/evm/SSAControlFlowGraphBuilder.cpp index e7b3c9e6d73e..4597c56a5917 100644 --- a/libyul/backends/evm/SSAControlFlowGraphBuilder.cpp +++ b/libyul/backends/evm/SSAControlFlowGraphBuilder.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -342,19 +343,21 @@ void SSAControlFlowGraphBuilder::operator()(Switch const& _switch) } else { + std::optional equalityBuiltinHandle = m_dialect.equalityFunctionHandle(); + yulAssert(equalityBuiltinHandle); + auto makeValueCompare = [&](Case const& _case) { FunctionCall const& ghostCall = m_graph.ghostCalls.emplace_back(FunctionCall{ debugDataOf(_case), - Identifier{{}, "eq"_yulname}, + BuiltinName{{}, *equalityBuiltinHandle}, {*_case.value /* skip second argument */ } }); auto outputValue = m_graph.newVariable(m_currentBlock); - std::optional builtinHandle = m_dialect.findBuiltin(ghostCall.functionName.name.str()); currentBlock().operations.emplace_back(SSACFG::Operation{ {outputValue}, SSACFG::BuiltinCall{ debugDataOf(_case), - m_dialect.builtin(*builtinHandle), + m_dialect.builtin(*equalityBuiltinHandle), ghostCall }, {m_graph.newLiteral(debugDataOf(_case), _case.value->value.value()), expression} @@ -547,21 +550,21 @@ std::vector SSAControlFlowGraphBuilder::visitFunctionCall(Funct { bool canContinue = true; SSACFG::Operation operation = [&](){ - if (std::optional const& builtinHandle = m_dialect.findBuiltin(_call.functionName.name.str())) + if (BuiltinFunction const* builtin = resolveBuiltinFunction(_call.functionName, m_dialect)) { - auto const& builtinFunction = m_dialect.builtin(*builtinHandle); - SSACFG::Operation result{{}, SSACFG::BuiltinCall{_call.debugData, builtinFunction, _call}, {}}; + SSACFG::Operation result{{}, SSACFG::BuiltinCall{_call.debugData, *builtin, _call}, {}}; for (auto&& [idx, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse) - if (!builtinFunction.literalArgument(idx).has_value()) + if (!builtin->literalArgument(idx).has_value()) result.inputs.emplace_back(std::visit(*this, arg)); - for (size_t i = 0; i < builtinFunction.numReturns; ++i) + for (size_t i = 0; i < builtin->numReturns; ++i) result.outputs.emplace_back(m_graph.newVariable(m_currentBlock)); - canContinue = builtinFunction.controlFlowSideEffects.canContinue; + canContinue = builtin->controlFlowSideEffects.canContinue; return result; } else { - Scope::Function const& function = lookupFunction(_call.functionName.name); + YulName const functionName{resolveFunctionName(_call.functionName, m_dialect)}; + Scope::Function const& function = lookupFunction(functionName); auto const* definition = findFunctionDefinition(&function); yulAssert(definition); canContinue = m_sideEffects.functionSideEffects().at(definition).canContinue; diff --git a/libyul/backends/evm/StackHelpers.h b/libyul/backends/evm/StackHelpers.h index dae4352a02c4..0fd238c4052d 100644 --- a/libyul/backends/evm/StackHelpers.h +++ b/libyul/backends/evm/StackHelpers.h @@ -20,6 +20,7 @@ #include #include +#include #include @@ -33,23 +34,23 @@ namespace solidity::yul { -inline std::string stackSlotToString(StackSlot const& _slot) +inline std::string stackSlotToString(StackSlot const& _slot, Dialect const& _dialect) { return std::visit(util::GenericVisitor{ - [](FunctionCallReturnLabelSlot const& _ret) -> std::string { return "RET[" + _ret.call.get().functionName.name.str() + "]"; }, + [&](FunctionCallReturnLabelSlot const& _ret) -> std::string { return "RET[" + std::string(resolveFunctionName(_ret.call.get().functionName, _dialect)) + "]"; }, [](FunctionReturnLabelSlot const&) -> std::string { return "RET"; }, [](VariableSlot const& _var) { return _var.variable.get().name.str(); }, [](LiteralSlot const& _lit) { return toCompactHexWithPrefix(_lit.value); }, - [](TemporarySlot const& _tmp) -> std::string { return "TMP[" + _tmp.call.get().functionName.name.str() + ", " + std::to_string(_tmp.index) + "]"; }, + [&](TemporarySlot const& _tmp) -> std::string { return "TMP[" + std::string(resolveFunctionName(_tmp.call.get().functionName, _dialect)) + ", " + std::to_string(_tmp.index) + "]"; }, [](JunkSlot const&) -> std::string { return "JUNK"; } }, _slot); } -inline std::string stackToString(Stack const& _stack) +inline std::string stackToString(Stack const& _stack, Dialect const& _dialect) { std::string result("[ "); for (auto const& slot: _stack) - result += stackSlotToString(slot) + ' '; + result += stackSlotToString(slot, _dialect) + ' '; result += ']'; return result; } diff --git a/libyul/optimiser/ASTCopier.cpp b/libyul/optimiser/ASTCopier.cpp index 269c7adeedff..9e85651ad346 100644 --- a/libyul/optimiser/ASTCopier.cpp +++ b/libyul/optimiser/ASTCopier.cpp @@ -24,6 +24,7 @@ #include #include +#include using namespace solidity; using namespace solidity::yul; @@ -153,6 +154,15 @@ Case ASTCopier::translate(Case const& _case) return Case{_case.debugData, translate(_case.value), translate(_case.body)}; } +FunctionName ASTCopier::translate(FunctionName const& _functionName) +{ + GenericVisitor visitor{ + [&](Identifier const& _identifier) -> FunctionName { return translate(_identifier); }, + [](BuiltinName const& _builtin) -> FunctionName { return _builtin; } + }; + return std::visit(visitor, _functionName); +} + Identifier ASTCopier::translate(Identifier const& _identifier) { return Identifier{_identifier.debugData, translateIdentifier(_identifier.name)}; diff --git a/libyul/optimiser/ASTCopier.h b/libyul/optimiser/ASTCopier.h index 04b1c96cd2e1..e1c3a16aa3ea 100644 --- a/libyul/optimiser/ASTCopier.h +++ b/libyul/optimiser/ASTCopier.h @@ -100,6 +100,7 @@ class ASTCopier: public ExpressionCopier, public StatementCopier Case translate(Case const& _case); virtual Identifier translate(Identifier const& _identifier); Literal translate(Literal const& _literal); + FunctionName translate(FunctionName const& _functionName); NameWithDebugData translate(NameWithDebugData const& _typedName); virtual void enterScope(Block const&) { } diff --git a/libyul/optimiser/BlockHasher.cpp b/libyul/optimiser/BlockHasher.cpp index 5470558ce93a..6e8e8ed62f57 100644 --- a/libyul/optimiser/BlockHasher.cpp +++ b/libyul/optimiser/BlockHasher.cpp @@ -23,6 +23,8 @@ #include #include +#include + using namespace solidity; using namespace solidity::yul; using namespace solidity::util; @@ -51,6 +53,24 @@ void ASTHasherBase::hashLiteral(solidity::yul::Literal const& _literal) hash8(_literal.value.unlimited()); } +void ASTHasherBase::hashFunctionCall(FunctionCall const& _funCall) +{ + hash64(compileTimeLiteralHash("FunctionCall")); + GenericVisitor visitor{ + [&](BuiltinName const& _builtin) + { + hash64(compileTimeLiteralHash("Builtin")); + hash64(_builtin.handle.id); + }, + [&](Identifier const& _identifier) + { + hash64(compileTimeLiteralHash("UserDefined")); + hash64(_identifier.name.hash()); + } + }; + std::visit(visitor, _funCall.functionName); +} + std::map BlockHasher::run(Block const& _block) { std::map result; @@ -85,9 +105,7 @@ void BlockHasher::operator()(Identifier const& _identifier) void BlockHasher::operator()(FunctionCall const& _funCall) { - hash64(compileTimeLiteralHash("FunctionCall")); - hash64(_funCall.functionName.name.hash()); - hash64(_funCall.arguments.size()); + hashFunctionCall(_funCall); ASTWalker::operator()(_funCall); } @@ -218,8 +236,6 @@ void ExpressionHasher::operator()(Identifier const& _identifier) void ExpressionHasher::operator()(FunctionCall const& _funCall) { - hash64(compileTimeLiteralHash("FunctionCall")); - hash64(_funCall.functionName.name.hash()); - hash64(_funCall.arguments.size()); + hashFunctionCall(_funCall); ASTWalker::operator()(_funCall); } diff --git a/libyul/optimiser/BlockHasher.h b/libyul/optimiser/BlockHasher.h index 427161b3c784..12c1c5e72b19 100644 --- a/libyul/optimiser/BlockHasher.h +++ b/libyul/optimiser/BlockHasher.h @@ -62,6 +62,7 @@ class ASTHasherBase: public HasherBase { protected: void hashLiteral(solidity::yul::Literal const& _literal); + void hashFunctionCall(FunctionCall const& _functionCall); }; /** diff --git a/libyul/optimiser/CallGraphGenerator.cpp b/libyul/optimiser/CallGraphGenerator.cpp index f457f55838d3..9b69835dc4f1 100644 --- a/libyul/optimiser/CallGraphGenerator.cpp +++ b/libyul/optimiser/CallGraphGenerator.cpp @@ -23,6 +23,8 @@ #include #include +#include + #include using namespace solidity; @@ -35,11 +37,11 @@ namespace struct CallGraphCycleFinder { CallGraph const& callGraph; - std::set containedInCycle{}; - std::set visited{}; - std::vector currentPath{}; + std::set containedInCycle{}; + std::set visited{}; + std::vector currentPath{}; - void visit(YulName _function) + void visit(FunctionHandle const& _function) { if (visited.count(_function)) return; @@ -61,7 +63,7 @@ struct CallGraphCycleFinder }; } -std::set CallGraph::recursiveFunctions() const +std::set CallGraph::recursiveFunctions() const { CallGraphCycleFinder cycleFinder{*this}; // Visiting the root only is not enough, since there may be disconnected recursive functions. @@ -80,8 +82,12 @@ CallGraph CallGraphGenerator::callGraph(Block const& _ast) void CallGraphGenerator::operator()(FunctionCall const& _functionCall) { auto& functionCalls = m_callGraph.functionCalls[m_currentFunction]; - if (!util::contains(functionCalls, _functionCall.functionName.name)) - functionCalls.emplace_back(_functionCall.functionName.name); + FunctionHandle identifier = std::visit(GenericVisitor{ + [](BuiltinName const& _builtin) -> FunctionHandle { return _builtin.handle; }, + [](Identifier const& _identifier) -> FunctionHandle { return _identifier.name; }, + }, _functionCall.functionName); + if (!util::contains(functionCalls, identifier)) + functionCalls.emplace_back(identifier); ASTWalker::operator()(_functionCall); } diff --git a/libyul/optimiser/CallGraphGenerator.h b/libyul/optimiser/CallGraphGenerator.h index 23e9a2511588..722cbba4a796 100644 --- a/libyul/optimiser/CallGraphGenerator.h +++ b/libyul/optimiser/CallGraphGenerator.h @@ -22,9 +22,9 @@ #pragma once #include +#include #include -#include #include namespace solidity::yul @@ -32,12 +32,13 @@ namespace solidity::yul struct CallGraph { - std::map> functionCalls; + /// Map function definition name -> function name + std::map> functionCalls; std::set functionsWithLoops; /// @returns the set of functions contained in cycles in the call graph, i.e. /// functions that are part of a (mutual) recursion. /// Note that this does not include functions that merely call recursive functions. - std::set recursiveFunctions() const; + std::set recursiveFunctions() const; }; /** diff --git a/libyul/optimiser/CircularReferencesPruner.cpp b/libyul/optimiser/CircularReferencesPruner.cpp index a34f3f4e2aa3..d238f8da2148 100644 --- a/libyul/optimiser/CircularReferencesPruner.cpp +++ b/libyul/optimiser/CircularReferencesPruner.cpp @@ -57,7 +57,7 @@ std::set CircularReferencesPruner::functionsCalledFromOutermostContext( [&_callGraph](YulName _function, auto&& _addChild) { if (_callGraph.functionCalls.count(_function)) for (auto const& callee: _callGraph.functionCalls.at(_function)) - if (_callGraph.functionCalls.count(callee)) - _addChild(callee); + if (std::holds_alternative(callee) && _callGraph.functionCalls.count(std::get(callee))) + _addChild(std::get(callee)); }).visited; } diff --git a/libyul/optimiser/CommonSubexpressionEliminator.cpp b/libyul/optimiser/CommonSubexpressionEliminator.cpp index f74568421a4f..87a14d259070 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.cpp +++ b/libyul/optimiser/CommonSubexpressionEliminator.cpp @@ -46,7 +46,7 @@ void CommonSubexpressionEliminator::run(OptimiserStepContext& _context, Block& _ CommonSubexpressionEliminator::CommonSubexpressionEliminator( Dialect const& _dialect, - std::map _functionSideEffects + std::map _functionSideEffects ): DataFlowAnalyzer(_dialect, MemoryAndStorage::Ignore, std::move(_functionSideEffects)) { @@ -72,14 +72,13 @@ void CommonSubexpressionEliminator::visit(Expression& _e) { FunctionCall& funCall = std::get(_e); - if (std::optional builtinHandle = m_dialect.findBuiltin(funCall.functionName.name.str())) + if (BuiltinFunction const* builtin = resolveBuiltinFunction(funCall.functionName, m_dialect)) { - BuiltinFunction const& builtin = m_dialect.builtin(*builtinHandle); for (size_t i = funCall.arguments.size(); i > 0; i--) // We should not modify function arguments that have to be literals // Note that replacing the function call entirely is fine, // if the function call is movable. - if (!builtin.literalArgument(i - 1)) + if (!builtin->literalArgument(i - 1)) visit(funCall.arguments[i - 1]); descend = false; diff --git a/libyul/optimiser/CommonSubexpressionEliminator.h b/libyul/optimiser/CommonSubexpressionEliminator.h index d7dc42596ca4..35f8168eba73 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.h +++ b/libyul/optimiser/CommonSubexpressionEliminator.h @@ -53,7 +53,7 @@ class CommonSubexpressionEliminator: public DataFlowAnalyzer private: CommonSubexpressionEliminator( Dialect const& _dialect, - std::map _functionSideEffects + std::map _functionSideEffects ); protected: diff --git a/libyul/optimiser/ControlFlowSimplifier.cpp b/libyul/optimiser/ControlFlowSimplifier.cpp index a9a8282a680e..53afb5e77785 100644 --- a/libyul/optimiser/ControlFlowSimplifier.cpp +++ b/libyul/optimiser/ControlFlowSimplifier.cpp @@ -37,13 +37,13 @@ namespace ExpressionStatement makeDiscardCall( langutil::DebugData::ConstPtr const& _debugData, - BuiltinFunction const& _discardFunction, + BuiltinHandle const& _discardFunction, Expression&& _expression ) { return {_debugData, FunctionCall{ _debugData, - Identifier{_debugData, YulName{_discardFunction.name}}, + BuiltinName{_debugData, _discardFunction}, {std::move(_expression)} }}; } @@ -141,7 +141,7 @@ void ControlFlowSimplifier::simplify(std::vector& _statements) OptionalStatements s = std::vector{}; s->emplace_back(makeDiscardCall( _ifStmt.debugData, - m_dialect.builtin(*m_dialect.discardFunctionHandle()), + *m_dialect.discardFunctionHandle(), std::move(*_ifStmt.condition) )); return s; @@ -184,7 +184,7 @@ OptionalStatements ControlFlowSimplifier::reduceNoCaseSwitch(Switch& _switchStmt return make_vector(makeDiscardCall( debugDataOf(*_switchStmt.expression), - m_dialect.builtin(*discardFunctionHandle), + *discardFunctionHandle, std::move(*_switchStmt.expression) )); } @@ -199,11 +199,12 @@ OptionalStatements ControlFlowSimplifier::reduceSingleCaseSwitch(Switch& _switch { if (!m_dialect.equalityFunctionHandle()) return {}; + BuiltinName const builtinName{debugData, *m_dialect.equalityFunctionHandle()}; return make_vector(If{ std::move(_switchStmt.debugData), std::make_unique(FunctionCall{ debugData, - Identifier{debugData, YulName{m_dialect.builtin(*m_dialect.equalityFunctionHandle()).name}}, + builtinName, {std::move(*switchCase.value), std::move(*_switchStmt.expression)} }), std::move(switchCase.body) @@ -217,7 +218,7 @@ OptionalStatements ControlFlowSimplifier::reduceSingleCaseSwitch(Switch& _switch return make_vector( makeDiscardCall( debugData, - m_dialect.builtin(*m_dialect.discardFunctionHandle()), + *m_dialect.discardFunctionHandle(), std::move(*_switchStmt.expression) ), std::move(switchCase.body) diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index 8b3282888865..ac08fb0be196 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -45,23 +45,19 @@ using namespace solidity::yul; DataFlowAnalyzer::DataFlowAnalyzer( Dialect const& _dialect, MemoryAndStorage _analyzeStores, - std::map _functionSideEffects + std::map _functionSideEffects ): m_dialect(_dialect), m_functionSideEffects(std::move(_functionSideEffects)), - m_knowledgeBase([this](YulName _var) { return variableValue(_var); }), + m_knowledgeBase([this](YulName _var) { return variableValue(_var); }, _dialect), m_analyzeStores(_analyzeStores == MemoryAndStorage::Analyze) { if (m_analyzeStores) { - if (auto const& builtinHandle = _dialect.memoryStoreFunctionHandle()) - m_storeFunctionName[static_cast(StoreLoadLocation::Memory)] = YulName{_dialect.builtin(*builtinHandle).name}; - if (auto const& builtinHandle = _dialect.memoryLoadFunctionHandle()) - m_loadFunctionName[static_cast(StoreLoadLocation::Memory)] = YulName{_dialect.builtin(*builtinHandle).name}; - if (auto const& builtinHandle = _dialect.storageStoreFunctionHandle()) - m_storeFunctionName[static_cast(StoreLoadLocation::Storage)] = YulName{_dialect.builtin(*builtinHandle).name}; - if (auto const& builtinHandle = _dialect.storageLoadFunctionHandle()) - m_loadFunctionName[static_cast(StoreLoadLocation::Storage)] = YulName{_dialect.builtin(*builtinHandle).name}; + m_storeFunctionName[static_cast(StoreLoadLocation::Memory)] = _dialect.memoryStoreFunctionHandle(); + m_loadFunctionName[static_cast(StoreLoadLocation::Memory)] = _dialect.memoryLoadFunctionHandle(); + m_storeFunctionName[static_cast(StoreLoadLocation::Storage)] = _dialect.storageStoreFunctionHandle(); + m_loadFunctionName[static_cast(StoreLoadLocation::Storage)] = _dialect.storageLoadFunctionHandle(); } } @@ -424,7 +420,10 @@ std::optional> DataFlowAnalyzer::isSimpleStore( ) const { if (FunctionCall const* funCall = std::get_if(&_statement.expression)) - if (funCall->functionName.name == m_storeFunctionName[static_cast(_location)]) + if ( + std::holds_alternative(funCall->functionName) && + std::get(funCall->functionName).handle == m_storeFunctionName[static_cast(_location)] + ) if (Identifier const* key = std::get_if(&funCall->arguments.front())) if (Identifier const* value = std::get_if(&funCall->arguments.back())) return std::make_pair(key->name, value->name); @@ -437,7 +436,10 @@ std::optional DataFlowAnalyzer::isSimpleLoad( ) const { if (FunctionCall const* funCall = std::get_if(&_expression)) - if (funCall->functionName.name == m_loadFunctionName[static_cast(_location)]) + if ( + std::holds_alternative(funCall->functionName) && + std::get(funCall->functionName).handle == m_loadFunctionName[static_cast(_location)] + ) if (Identifier const* key = std::get_if(&funCall->arguments.front())) return key->name; return {}; @@ -446,7 +448,10 @@ std::optional DataFlowAnalyzer::isSimpleLoad( std::optional> DataFlowAnalyzer::isKeccak(Expression const& _expression) const { if (FunctionCall const* funCall = std::get_if(&_expression)) - if (funCall->functionName.name.str() == m_dialect.builtin(*m_dialect.hashFunctionHandle()).name) + if ( + std::holds_alternative(funCall->functionName) && + std::get(funCall->functionName).handle == m_dialect.hashFunctionHandle() + ) if (Identifier const* start = std::get_if(&funCall->arguments.at(0))) if (Identifier const* length = std::get_if(&funCall->arguments.at(1))) return std::make_pair(start->name, length->name); diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index 5a4b199caefd..7d0f78b534c2 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -89,7 +89,7 @@ class DataFlowAnalyzer: public ASTModifier explicit DataFlowAnalyzer( Dialect const& _dialect, MemoryAndStorage _analyzeStores, - std::map _functionSideEffects = {} + std::map _functionSideEffects = {} ); using ASTModifier::operator(); @@ -165,7 +165,7 @@ class DataFlowAnalyzer: public ASTModifier Dialect const& m_dialect; /// Side-effects of user-defined functions. Worst-case side-effects are assumed /// if this is not provided or the function is not found. - std::map m_functionSideEffects; + std::map m_functionSideEffects; private: struct Environment @@ -203,8 +203,8 @@ class DataFlowAnalyzer: public ASTModifier /// If true, analyzes memory and storage content via mload/mstore and sload/sstore. bool m_analyzeStores = true; - YulName m_storeFunctionName[static_cast(StoreLoadLocation::Last) + 1]; - YulName m_loadFunctionName[static_cast(StoreLoadLocation::Last) + 1]; + std::optional m_storeFunctionName[static_cast(StoreLoadLocation::Last) + 1]; + std::optional m_loadFunctionName[static_cast(StoreLoadLocation::Last) + 1]; /// Current nesting depth of loops. size_t m_loopDepth{0}; diff --git a/libyul/optimiser/EqualStoreEliminator.h b/libyul/optimiser/EqualStoreEliminator.h index a0b54c590ff5..87f8cae161a6 100644 --- a/libyul/optimiser/EqualStoreEliminator.h +++ b/libyul/optimiser/EqualStoreEliminator.h @@ -45,7 +45,7 @@ class EqualStoreEliminator: public DataFlowAnalyzer private: EqualStoreEliminator( Dialect const& _dialect, - std::map _functionSideEffects + std::map _functionSideEffects ): DataFlowAnalyzer(_dialect, MemoryAndStorage::Analyze, std::move(_functionSideEffects)) {} diff --git a/libyul/optimiser/EquivalentFunctionCombiner.cpp b/libyul/optimiser/EquivalentFunctionCombiner.cpp index 10d15126e645..bbbf039d56ed 100644 --- a/libyul/optimiser/EquivalentFunctionCombiner.cpp +++ b/libyul/optimiser/EquivalentFunctionCombiner.cpp @@ -33,8 +33,13 @@ void EquivalentFunctionCombiner::run(OptimiserStepContext&, Block& _ast) void EquivalentFunctionCombiner::operator()(FunctionCall& _funCall) { - auto it = m_duplicates.find(_funCall.functionName.name); - if (it != m_duplicates.end()) - _funCall.functionName.name = it->second->name; + if (!isBuiltinFunctionCall(_funCall)) + { + auto* identifier = std::get_if(&_funCall.functionName); + yulAssert(identifier); + auto it = m_duplicates.find(identifier->name); + if (it != m_duplicates.end()) + identifier->name = it->second->name; + } ASTModifier::operator()(_funCall); } diff --git a/libyul/optimiser/ExpressionInliner.cpp b/libyul/optimiser/ExpressionInliner.cpp index ab4bbcfa43a8..0d59dab2d2e7 100644 --- a/libyul/optimiser/ExpressionInliner.cpp +++ b/libyul/optimiser/ExpressionInliner.cpp @@ -29,6 +29,7 @@ #include #include +#include using namespace solidity; using namespace solidity::yul; @@ -52,9 +53,10 @@ void ExpressionInliner::visit(Expression& _expression) if (std::holds_alternative(_expression)) { FunctionCall& funCall = std::get(_expression); - if (!m_inlinableFunctions.count(funCall.functionName.name)) + YulString const functionName{resolveFunctionName(funCall.functionName, m_dialect)}; + if (!m_inlinableFunctions.count(functionName)) return; - FunctionDefinition const& fun = *m_inlinableFunctions.at(funCall.functionName.name); + FunctionDefinition const& fun = *m_inlinableFunctions.at(functionName); std::map substitutions; for (size_t i = 0; i < funCall.arguments.size(); i++) diff --git a/libyul/optimiser/ExpressionSimplifier.cpp b/libyul/optimiser/ExpressionSimplifier.cpp index 598d2e90037d..4aa7be1bb556 100644 --- a/libyul/optimiser/ExpressionSimplifier.cpp +++ b/libyul/optimiser/ExpressionSimplifier.cpp @@ -21,6 +21,7 @@ #include +#include #include #include #include @@ -46,10 +47,14 @@ void ExpressionSimplifier::visit(Expression& _expression) m_dialect, [this](YulName _var) { return variableValue(_var); } )) - _expression = match->action().toExpression(debugDataOf(_expression), evmVersionFromDialect(m_dialect)); + { + auto const* evmDialect = dynamic_cast(&m_dialect); + yulAssert(evmDialect); + _expression = match->action().toExpression(debugDataOf(_expression), *evmDialect); + } if (auto* functionCall = std::get_if(&_expression)) - if (std::optional instruction = toEVMInstruction(m_dialect, functionCall->functionName.name)) + if (std::optional instruction = toEVMInstruction(m_dialect, functionCall->functionName)) for (auto op: evmasm::SemanticInformation::readWriteOperations(*instruction)) if (op.startParameter && op.lengthParameter) { diff --git a/libyul/optimiser/ExpressionSplitter.cpp b/libyul/optimiser/ExpressionSplitter.cpp index 71a0963d5542..1bdaa552ba16 100644 --- a/libyul/optimiser/ExpressionSplitter.cpp +++ b/libyul/optimiser/ExpressionSplitter.cpp @@ -26,6 +26,7 @@ #include #include +#include #include @@ -41,10 +42,10 @@ void ExpressionSplitter::run(OptimiserStepContext& _context, Block& _ast) void ExpressionSplitter::operator()(FunctionCall& _funCall) { - std::optional builtinHandle = m_dialect.findBuiltin(_funCall.functionName.name.str()); + BuiltinFunction const* builtin = resolveBuiltinFunction(_funCall.functionName, m_dialect); for (size_t i = _funCall.arguments.size(); i > 0; i--) - if (!builtinHandle || !m_dialect.builtin(*builtinHandle).literalArgument(i - 1)) + if (!builtin || !builtin->literalArgument(i - 1)) outlineExpression(_funCall.arguments[i - 1]); } diff --git a/libyul/optimiser/ForLoopConditionIntoBody.cpp b/libyul/optimiser/ForLoopConditionIntoBody.cpp index 7ee9a9b8c5d5..6324085e65c2 100644 --- a/libyul/optimiser/ForLoopConditionIntoBody.cpp +++ b/libyul/optimiser/ForLoopConditionIntoBody.cpp @@ -47,7 +47,7 @@ void ForLoopConditionIntoBody::operator()(ForLoop& _forLoop) std::make_unique( FunctionCall { debugData, - {debugData, YulName{m_dialect.builtin(*m_dialect.booleanNegationFunctionHandle()).name}}, + BuiltinName{debugData, *m_dialect.booleanNegationFunctionHandle()}, util::make_vector(std::move(*_forLoop.condition)) } ), diff --git a/libyul/optimiser/ForLoopConditionOutOfBody.cpp b/libyul/optimiser/ForLoopConditionOutOfBody.cpp index d082bb78d676..111f26fec5f3 100644 --- a/libyul/optimiser/ForLoopConditionOutOfBody.cpp +++ b/libyul/optimiser/ForLoopConditionOutOfBody.cpp @@ -53,18 +53,21 @@ void ForLoopConditionOutOfBody::operator()(ForLoop& _forLoop) if (!SideEffectsCollector(m_dialect, *firstStatement.condition).movable()) return; - YulName const iszero{m_dialect.builtin(*m_dialect.booleanNegationFunctionHandle()).name}; + std::optional iszero = m_dialect.booleanNegationFunctionHandle(); + yulAssert(iszero.has_value()); + auto const& isZeroHandle = *iszero; langutil::DebugData::ConstPtr debugData = debugDataOf(*firstStatement.condition); if ( std::holds_alternative(*firstStatement.condition) && - std::get(*firstStatement.condition).functionName.name == iszero + std::holds_alternative(std::get(*firstStatement.condition).functionName) && + std::get(std::get(*firstStatement.condition).functionName).handle == isZeroHandle ) _forLoop.condition = std::make_unique(std::move(std::get(*firstStatement.condition).arguments.front())); else _forLoop.condition = std::make_unique(FunctionCall{ debugData, - Identifier{debugData, iszero}, + BuiltinName{debugData, isZeroHandle}, util::make_vector( std::move(*firstStatement.condition) ) diff --git a/libyul/optimiser/FullInliner.cpp b/libyul/optimiser/FullInliner.cpp index 328ef83b8e7b..4b390f4f271a 100644 --- a/libyul/optimiser/FullInliner.cpp +++ b/libyul/optimiser/FullInliner.cpp @@ -67,7 +67,7 @@ FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser, Dialect const& // Store size of global statements. m_functionSizes[YulName{}] = CodeSize::codeSize(_ast); - std::map references = ReferencesCounter::countReferences(m_ast); + std::map references = ReferencesCounter::countReferences(m_ast); for (auto& statement: m_ast.statements) { if (!std::holds_alternative(statement)) @@ -83,7 +83,7 @@ FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser, Dialect const& } // Check for memory guard. - std::vector memoryGuardCalls = findFunctionCalls(_ast, "memoryguard"_yulname); + std::vector memoryGuardCalls = findFunctionCalls(_ast, "memoryguard", m_dialect); // We will perform less aggressive inlining, if no ``memoryguard`` call is found. if (!memoryGuardCalls.empty()) m_hasMemoryGuard = true; @@ -101,7 +101,7 @@ void FullInliner::run(Pass _pass) // function name) order. // We use stable_sort below to keep the inlining order of two functions // with the same depth. - std::map depths = callDepths(); + std::map depths = callDepths(); std::vector functions; for (auto& statement: m_ast.statements) if (std::holds_alternative(statement)) @@ -123,7 +123,7 @@ void FullInliner::run(Pass _pass) handleBlock({}, std::get(statement)); } -std::map FullInliner::callDepths() const +std::map FullInliner::callDepths() const { CallGraph cg = CallGraphGenerator::callGraph(m_ast); cg.functionCalls.erase(""_yulname); @@ -131,17 +131,17 @@ std::map FullInliner::callDepths() const // Remove calls to builtin functions. for (auto& call: cg.functionCalls) for (auto it = call.second.begin(); it != call.second.end();) - if (m_dialect.findBuiltin(it->str())) + if (std::holds_alternative(*it)) it = call.second.erase(it); else ++it; - std::map depths; + std::map depths; size_t currentDepth = 0; while (true) { - std::vector removed; + std::vector removed; for (auto it = cg.functionCalls.begin(); it != cg.functionCalls.end();) { auto const& [fun, callees] = *it; @@ -156,7 +156,7 @@ std::map FullInliner::callDepths() const } for (auto& call: cg.functionCalls) - for (YulName toBeRemoved: removed) + for (FunctionHandle toBeRemoved: removed) ranges::actions::remove(call.second, toBeRemoved); currentDepth++; @@ -174,15 +174,19 @@ std::map FullInliner::callDepths() const bool FullInliner::shallInline(FunctionCall const& _funCall, YulName _callSite) { + if (isBuiltinFunctionCall(_funCall)) + return false; + yulAssert(std::holds_alternative(_funCall.functionName)); + auto const& functionName = std::get(_funCall.functionName).name; // No recursive inlining - if (_funCall.functionName.name == _callSite) + if (functionName == _callSite) return false; - FunctionDefinition* calledFunction = function(_funCall.functionName.name); + FunctionDefinition* calledFunction = function(functionName); if (!calledFunction) return false; - if (m_noInlineFunctions.count(_funCall.functionName.name) || recursive(*calledFunction)) + if (m_noInlineFunctions.count(functionName) || recursive(*calledFunction)) return false; // No inlining of calls where argument expressions may have side-effects. @@ -251,7 +255,7 @@ void FullInliner::handleBlock(YulName _currentFunctionName, Block& _block) bool FullInliner::recursive(FunctionDefinition const& _fun) const { - std::map references = ReferencesCounter::countReferences(_fun); + std::map references = ReferencesCounter::countReferences(_fun); return references[_fun.name] > 0; } @@ -291,7 +295,8 @@ std::vector InlineModifier::performInline(Statement& _statement, Func std::vector newStatements; std::map variableReplacements; - FunctionDefinition* function = m_driver.function(_funCall.functionName.name); + yulAssert(std::holds_alternative(_funCall.functionName)); + FunctionDefinition* function = m_driver.function(std::get(_funCall.functionName).name); assertThrow(!!function, OptimizerException, "Attempt to inline invalid function."); m_driver.tentativelyUpdateCodeSize(function->name, m_currentFunction); diff --git a/libyul/optimiser/FullInliner.h b/libyul/optimiser/FullInliner.h index e939070238e5..4620194a4e51 100644 --- a/libyul/optimiser/FullInliner.h +++ b/libyul/optimiser/FullInliner.h @@ -98,7 +98,7 @@ class FullInliner: public ASTModifier /// @returns a map containing the maximum depths of a call chain starting at each /// function. For recursive functions, the value is one larger than for all others. - std::map callDepths() const; + std::map callDepths() const; void updateCodeSize(FunctionDefinition const& _fun); void handleBlock(YulName _currentFunctionName, Block& _block); @@ -114,7 +114,7 @@ class FullInliner: public ASTModifier /// True, if the code contains a ``memoryguard`` and we can expect to be able to move variables to memory later. bool m_hasMemoryGuard = false; /// Set of recursive functions. - std::set m_recursiveFunctions; + std::set m_recursiveFunctions; /// Names of functions to always inline. std::set m_singleUse; /// Variables that are constants (used for inlining heuristic) diff --git a/libyul/optimiser/FunctionCallFinder.cpp b/libyul/optimiser/FunctionCallFinder.cpp index aaf8b9f68232..4c85d77f5246 100644 --- a/libyul/optimiser/FunctionCallFinder.cpp +++ b/libyul/optimiser/FunctionCallFinder.cpp @@ -19,6 +19,8 @@ #include #include +#include +#include using namespace solidity; using namespace solidity::yul; @@ -30,32 +32,35 @@ class MaybeConstFunctionCallFinder: Base { public: using MaybeConstBlock = std::conditional_t, Block const, Block>; - static std::vector run(MaybeConstBlock& _block, YulName const _functionName) + static std::vector run(MaybeConstBlock& _block, std::string_view const _functionName, Dialect const& _dialect) { - MaybeConstFunctionCallFinder functionCallFinder(_functionName); + MaybeConstFunctionCallFinder functionCallFinder(_functionName, _dialect); functionCallFinder(_block); return functionCallFinder.m_calls; } private: - explicit MaybeConstFunctionCallFinder(YulName const _functionName): m_functionName(_functionName), m_calls() {} + explicit MaybeConstFunctionCallFinder(std::string_view const _functionName, Dialect const& _dialect): + m_dialect(_dialect), m_functionName(_functionName), m_calls() {} + using Base::operator(); void operator()(ResultType& _functionCall) override { Base::operator()(_functionCall); - if (_functionCall.functionName.name == m_functionName) + if (resolveFunctionName(_functionCall.functionName, m_dialect) == m_functionName) m_calls.emplace_back(&_functionCall); } - YulName m_functionName; + Dialect const& m_dialect; + std::string_view m_functionName; std::vector m_calls; }; } -std::vector solidity::yul::findFunctionCalls(Block& _block, YulName _functionName) +std::vector solidity::yul::findFunctionCalls(Block& _block, std::string_view const _functionName, Dialect const& _dialect) { - return MaybeConstFunctionCallFinder::run(_block, _functionName); + return MaybeConstFunctionCallFinder::run(_block, _functionName, _dialect); } -std::vector solidity::yul::findFunctionCalls(Block const& _block, YulName _functionName) +std::vector solidity::yul::findFunctionCalls(Block const& _block, std::string_view const _functionName, Dialect const& _dialect) { - return MaybeConstFunctionCallFinder::run(_block, _functionName); + return MaybeConstFunctionCallFinder::run(_block, _functionName, _dialect); } diff --git a/libyul/optimiser/FunctionCallFinder.h b/libyul/optimiser/FunctionCallFinder.h index 41a19fe99fc7..067aee2a399c 100644 --- a/libyul/optimiser/FunctionCallFinder.h +++ b/libyul/optimiser/FunctionCallFinder.h @@ -21,25 +21,27 @@ #pragma once #include -#include +#include #include namespace solidity::yul { +struct Dialect; + /** * Finds all calls to a function of a given name using an ASTModifier. * * Prerequisite: Disambiguator */ -std::vector findFunctionCalls(Block& _block, YulName _functionName); +std::vector findFunctionCalls(Block& _block, std::string_view _functionName, Dialect const& _dialect); /** * Finds all calls to a function of a given name using an ASTWalker. * * Prerequisite: Disambiguator */ -std::vector findFunctionCalls(Block const& _block, YulName _functionName); +std::vector findFunctionCalls(Block const& _block, std::string_view _functionName, Dialect const& _dialect); } diff --git a/libyul/optimiser/FunctionSpecializer.cpp b/libyul/optimiser/FunctionSpecializer.cpp index bab02a2e1813..cbbf14e8f84a 100644 --- a/libyul/optimiser/FunctionSpecializer.cpp +++ b/libyul/optimiser/FunctionSpecializer.cpp @@ -55,21 +55,23 @@ void FunctionSpecializer::operator()(FunctionCall& _f) // TODO When backtracking is implemented, the restriction of recursive functions can be lifted. if ( - m_dialect.findBuiltin(_f.functionName.name.str()) || - m_recursiveFunctions.count(_f.functionName.name) + isBuiltinFunctionCall(_f) || + (std::holds_alternative(_f.functionName) && m_recursiveFunctions.count(std::get(_f.functionName).name)) ) return; + yulAssert(std::holds_alternative(_f.functionName)); + auto& identifier = std::get(_f.functionName); LiteralArguments arguments = specializableArguments(_f); if (ranges::any_of(arguments, [](auto& _a) { return _a.has_value(); })) { - YulName oldName = std::move(_f.functionName.name); + YulName oldName = std::move(identifier.name); auto newName = m_nameDispenser.newName(oldName); m_oldToNewMap[oldName].emplace_back(std::make_pair(newName, arguments)); - _f.functionName.name = newName; + identifier.name = newName; _f.arguments = util::filter( _f.arguments, applyMap(arguments, [](auto& _a) { return !_a; }) @@ -128,8 +130,7 @@ void FunctionSpecializer::run(OptimiserStepContext& _context, Block& _ast) { FunctionSpecializer f{ CallGraphGenerator::callGraph(_ast).recursiveFunctions(), - _context.dispenser, - _context.dialect + _context.dispenser }; f(_ast); diff --git a/libyul/optimiser/FunctionSpecializer.h b/libyul/optimiser/FunctionSpecializer.h index 784ec4311e0f..4df59b354c45 100644 --- a/libyul/optimiser/FunctionSpecializer.h +++ b/libyul/optimiser/FunctionSpecializer.h @@ -22,8 +22,7 @@ #include #include -#include -#include +#include #include #include @@ -67,13 +66,11 @@ class FunctionSpecializer: public ASTModifier private: explicit FunctionSpecializer( - std::set _recursiveFunctions, - NameDispenser& _nameDispenser, - Dialect const& _dialect + std::set _recursiveFunctions, + NameDispenser& _nameDispenser ): m_recursiveFunctions(std::move(_recursiveFunctions)), - m_nameDispenser(_nameDispenser), - m_dialect(_dialect) + m_nameDispenser(_nameDispenser) {} /// Returns a vector of Expressions, where the index `i` is an expression if the function's /// `i`-th argument can be specialized, nullopt otherwise. @@ -104,10 +101,9 @@ class FunctionSpecializer: public ASTModifier /// Note that at least one of the argument will have a literal value. std::map>> m_oldToNewMap; /// We skip specializing recursive functions. Need backtracking to properly deal with them. - std::set const m_recursiveFunctions; + std::set const m_recursiveFunctions; NameDispenser& m_nameDispenser; - Dialect const& m_dialect; }; } diff --git a/libyul/optimiser/InlinableExpressionFunctionFinder.cpp b/libyul/optimiser/InlinableExpressionFunctionFinder.cpp index d719302dcbc2..6061fa3e9725 100644 --- a/libyul/optimiser/InlinableExpressionFunctionFinder.cpp +++ b/libyul/optimiser/InlinableExpressionFunctionFinder.cpp @@ -29,13 +29,13 @@ using namespace solidity::yul; void InlinableExpressionFunctionFinder::operator()(Identifier const& _identifier) { - checkAllowed(_identifier.name); + checkAllowed(_identifier); ASTWalker::operator()(_identifier); } void InlinableExpressionFunctionFinder::operator()(FunctionCall const& _funCall) { - checkAllowed(_funCall.functionName.name); + checkAllowed(_funCall.functionName); ASTWalker::operator()(_funCall); } @@ -67,3 +67,9 @@ void InlinableExpressionFunctionFinder::operator()(FunctionDefinition const& _fu } ASTWalker::operator()(_function.body); } +void InlinableExpressionFunctionFinder::checkAllowed(FunctionName const& _name) +{ + // disallowed function names can only ever be user-defined `yul::Identifier`s, not builtins + if (std::holds_alternative(_name) && m_disallowedIdentifiers.count(std::get(_name).name) != 0) + m_foundDisallowedIdentifier = true; +} diff --git a/libyul/optimiser/InlinableExpressionFunctionFinder.h b/libyul/optimiser/InlinableExpressionFunctionFinder.h index e7210da69a0a..c6897c173529 100644 --- a/libyul/optimiser/InlinableExpressionFunctionFinder.h +++ b/libyul/optimiser/InlinableExpressionFunctionFinder.h @@ -53,11 +53,7 @@ class InlinableExpressionFunctionFinder: public ASTWalker void operator()(FunctionDefinition const& _function) override; private: - void checkAllowed(YulName _name) - { - if (m_disallowedIdentifiers.count(_name)) - m_foundDisallowedIdentifier = true; - } + void checkAllowed(FunctionName const& _name); bool m_foundDisallowedIdentifier = false; std::set m_disallowedIdentifiers; diff --git a/libyul/optimiser/KnowledgeBase.cpp b/libyul/optimiser/KnowledgeBase.cpp index 731f4b3b8195..ceaa8f06b359 100644 --- a/libyul/optimiser/KnowledgeBase.cpp +++ b/libyul/optimiser/KnowledgeBase.cpp @@ -32,9 +32,11 @@ using namespace solidity; using namespace solidity::yul; -KnowledgeBase::KnowledgeBase(std::map const& _ssaValues): +KnowledgeBase::KnowledgeBase(std::map const& _ssaValues, Dialect const& _dialect): m_valuesAreSSA(true), - m_variableValues([_ssaValues](YulName _var) { return util::valueOrNullptr(_ssaValues, _var); }) + m_variableValues([_ssaValues](YulName _var) { return util::valueOrNullptr(_ssaValues, _var); }), + m_addBuiltinHandle(_dialect.findBuiltin("add")), + m_subBuiltinHandle(_dialect.findBuiltin("sub")) {} bool KnowledgeBase::knownToBeDifferent(YulName _a, YulName _b) @@ -116,9 +118,12 @@ std::optional KnowledgeBase::explore(Expression c return VariableOffset{YulName{}, literal->value.value()}; else if (Identifier const* identifier = std::get_if(&_value)) return explore(identifier->name); - else if (FunctionCall const* f = std::get_if(&_value)) + else if ( + FunctionCall const* f = std::get_if(&_value); + f && isBuiltinFunctionCall(*f) + ) { - if (f->functionName.name == "add"_yulname) + if (std::get(f->functionName).handle == m_addBuiltinHandle) { if (std::optional a = explore(f->arguments[0])) if (std::optional b = explore(f->arguments[1])) @@ -132,7 +137,7 @@ std::optional KnowledgeBase::explore(Expression c return VariableOffset{a->reference, offset}; } } - else if (f->functionName.name == "sub"_yulname) + else if (std::get(f->functionName).handle == m_subBuiltinHandle) if (std::optional a = explore(f->arguments[0])) if (std::optional b = explore(f->arguments[1])) { diff --git a/libyul/optimiser/KnowledgeBase.h b/libyul/optimiser/KnowledgeBase.h index 46ea7b96af66..78daadbc4ee4 100644 --- a/libyul/optimiser/KnowledgeBase.h +++ b/libyul/optimiser/KnowledgeBase.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include #include @@ -62,11 +63,13 @@ class KnowledgeBase public: /// Constructor for arbitrary value callback that allows for variable values /// to change in between calls to functions of this class. - explicit KnowledgeBase(std::function _variableValues): - m_variableValues(std::move(_variableValues)) + explicit KnowledgeBase(std::function _variableValues, Dialect const& _dialect): + m_variableValues(std::move(_variableValues)), + m_addBuiltinHandle(_dialect.findBuiltin("add")), + m_subBuiltinHandle(_dialect.findBuiltin("sub")) {} /// Constructor to use if source code is in SSA form and values are constant. - explicit KnowledgeBase(std::map const& _ssaValues); + explicit KnowledgeBase(std::map const& _ssaValues, Dialect const& _dialect); bool knownToBeDifferent(YulName _a, YulName _b); std::optional differenceIfKnownConstant(YulName _a, YulName _b); @@ -115,6 +118,8 @@ class KnowledgeBase bool m_valuesAreSSA = false; /// Callback to retrieve the current value of a variable. std::function m_variableValues; + std::optional m_addBuiltinHandle; + std::optional m_subBuiltinHandle; /// Offsets for each variable to one representative per group. /// The empty string is the representative of the constant value zero. diff --git a/libyul/optimiser/LoadResolver.cpp b/libyul/optimiser/LoadResolver.cpp index d6961b6f8ca3..36163c6c5f21 100644 --- a/libyul/optimiser/LoadResolver.cpp +++ b/libyul/optimiser/LoadResolver.cpp @@ -56,13 +56,17 @@ void LoadResolver::visit(Expression& _e) { DataFlowAnalyzer::visit(_e); - if (FunctionCall const* funCall = std::get_if(&_e)) + if ( + FunctionCall const* funCall = std::get_if(&_e); + funCall && std::holds_alternative(funCall->functionName) + ) { - if (funCall->functionName.name == m_loadFunctionName[static_cast(StoreLoadLocation::Memory)]) + auto const& builtinHandle = std::get(funCall->functionName).handle; + if (builtinHandle == m_loadFunctionName[static_cast(StoreLoadLocation::Memory)]) tryResolve(_e, StoreLoadLocation::Memory, funCall->arguments); - else if (funCall->functionName.name == m_loadFunctionName[static_cast(StoreLoadLocation::Storage)]) + else if (builtinHandle == m_loadFunctionName[static_cast(StoreLoadLocation::Storage)]) tryResolve(_e, StoreLoadLocation::Storage, funCall->arguments); - else if (!m_containsMSize && funCall->functionName.name.str() == m_dialect.builtin(*m_dialect.hashFunctionHandle()).name) + else if (!m_containsMSize && builtinHandle == m_dialect.hashFunctionHandle()) { Identifier const* start = std::get_if(&funCall->arguments.at(0)); Identifier const* length = std::get_if(&funCall->arguments.at(1)); diff --git a/libyul/optimiser/LoadResolver.h b/libyul/optimiser/LoadResolver.h index b4a496303373..d878076929a6 100644 --- a/libyul/optimiser/LoadResolver.h +++ b/libyul/optimiser/LoadResolver.h @@ -49,7 +49,7 @@ class LoadResolver: public DataFlowAnalyzer private: LoadResolver( Dialect const& _dialect, - std::map _functionSideEffects, + std::map _functionSideEffects, bool _containsMSize, std::optional _expectedExecutionsPerDeployment ): diff --git a/libyul/optimiser/LoopInvariantCodeMotion.cpp b/libyul/optimiser/LoopInvariantCodeMotion.cpp index 2a196a68f956..f2e7d040802e 100644 --- a/libyul/optimiser/LoopInvariantCodeMotion.cpp +++ b/libyul/optimiser/LoopInvariantCodeMotion.cpp @@ -32,7 +32,7 @@ using namespace solidity::yul; void LoopInvariantCodeMotion::run(OptimiserStepContext& _context, Block& _ast) { - std::map functionSideEffects = + std::map functionSideEffects = SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast)); bool containsMSize = MSizeFinder::containsMSize(_context.dialect, _ast); std::set ssaVars = SSAValueTracker::ssaVariables(_ast); diff --git a/libyul/optimiser/LoopInvariantCodeMotion.h b/libyul/optimiser/LoopInvariantCodeMotion.h index c85b87a01c32..d03a91eeeba9 100644 --- a/libyul/optimiser/LoopInvariantCodeMotion.h +++ b/libyul/optimiser/LoopInvariantCodeMotion.h @@ -49,7 +49,7 @@ class LoopInvariantCodeMotion: public ASTModifier explicit LoopInvariantCodeMotion( Dialect const& _dialect, std::set const& _ssaVariables, - std::map const& _functionSideEffects, + std::map const& _functionSideEffects, bool _containsMSize ): m_containsMSize(_containsMSize), @@ -69,7 +69,7 @@ class LoopInvariantCodeMotion: public ASTModifier bool m_containsMSize = true; Dialect const& m_dialect; std::set const& m_ssaVariables; - std::map const& m_functionSideEffects; + std::map const& m_functionSideEffects; }; } diff --git a/libyul/optimiser/Metrics.cpp b/libyul/optimiser/Metrics.cpp index 546c465b5c43..04bfe9a7a06d 100644 --- a/libyul/optimiser/Metrics.cpp +++ b/libyul/optimiser/Metrics.cpp @@ -137,7 +137,7 @@ void CodeCost::operator()(FunctionCall const& _funCall) { ASTWalker::operator()(_funCall); - if (auto instruction = toEVMInstruction(m_dialect, _funCall.functionName.name)) + if (auto instruction = toEVMInstruction(m_dialect, _funCall.functionName)) { addInstructionCost(*instruction); return; diff --git a/libyul/optimiser/NameCollector.cpp b/libyul/optimiser/NameCollector.cpp index d6db837f8485..cde9b64f67d1 100644 --- a/libyul/optimiser/NameCollector.cpp +++ b/libyul/optimiser/NameCollector.cpp @@ -22,6 +22,7 @@ #include #include +#include using namespace solidity; using namespace solidity::yul; @@ -55,25 +56,25 @@ void ReferencesCounter::operator()(Identifier const& _identifier) void ReferencesCounter::operator()(FunctionCall const& _funCall) { - ++m_references[_funCall.functionName.name]; + ++m_references[functionNameToHandle(_funCall.functionName)]; ASTWalker::operator()(_funCall); } -std::map ReferencesCounter::countReferences(Block const& _block) +std::map ReferencesCounter::countReferences(Block const& _block) { ReferencesCounter counter; counter(_block); return std::move(counter.m_references); } -std::map ReferencesCounter::countReferences(FunctionDefinition const& _function) +std::map ReferencesCounter::countReferences(FunctionDefinition const& _function) { ReferencesCounter counter; counter(_function); return std::move(counter.m_references); } -std::map ReferencesCounter::countReferences(Expression const& _expression) +std::map ReferencesCounter::countReferences(Expression const& _expression) { ReferencesCounter counter; counter.visit(_expression); diff --git a/libyul/optimiser/NameCollector.h b/libyul/optimiser/NameCollector.h index 0fc2e311ca50..2adc73ced8e3 100644 --- a/libyul/optimiser/NameCollector.h +++ b/libyul/optimiser/NameCollector.h @@ -76,12 +76,12 @@ class ReferencesCounter: public ASTWalker void operator()(Identifier const& _identifier) override; void operator()(FunctionCall const& _funCall) override; - static std::map countReferences(Block const& _block); - static std::map countReferences(FunctionDefinition const& _function); - static std::map countReferences(Expression const& _expression); + static std::map countReferences(Block const& _block); + static std::map countReferences(FunctionDefinition const& _function); + static std::map countReferences(Expression const& _expression); private: - std::map m_references; + std::map m_references; }; /** diff --git a/libyul/optimiser/NameDisplacer.cpp b/libyul/optimiser/NameDisplacer.cpp index c68d52f9a7bb..7430c26c3046 100644 --- a/libyul/optimiser/NameDisplacer.cpp +++ b/libyul/optimiser/NameDisplacer.cpp @@ -54,7 +54,8 @@ void NameDisplacer::operator()(FunctionDefinition& _function) void NameDisplacer::operator()(FunctionCall& _funCall) { - checkAndReplace(_funCall.functionName.name); + if (std::holds_alternative(_funCall.functionName)) + checkAndReplace(std::get(_funCall.functionName).name); ASTModifier::operator()(_funCall); } diff --git a/libyul/optimiser/NameSimplifier.cpp b/libyul/optimiser/NameSimplifier.cpp index 6f3f1760a852..0572a9003523 100644 --- a/libyul/optimiser/NameSimplifier.cpp +++ b/libyul/optimiser/NameSimplifier.cpp @@ -67,8 +67,11 @@ void NameSimplifier::operator()(Identifier& _identifier) void NameSimplifier::operator()(FunctionCall& _funCall) { // The visitor on its own does not visit the function name. - if (!m_context.dialect.findBuiltin(_funCall.functionName.name.str())) - (*this)(_funCall.functionName); + if (!isBuiltinFunctionCall(_funCall)) + { + yulAssert(std::holds_alternative(_funCall.functionName)); + (*this)(std::get(_funCall.functionName)); + } ASTModifier::operator()(_funCall); } diff --git a/libyul/optimiser/OptimizerUtilities.cpp b/libyul/optimiser/OptimizerUtilities.cpp index 001ff147d4b5..8fc225bee0a4 100644 --- a/libyul/optimiser/OptimizerUtilities.cpp +++ b/libyul/optimiser/OptimizerUtilities.cpp @@ -23,8 +23,9 @@ #include -#include #include +#include +#include #include #include @@ -60,11 +61,11 @@ bool yul::isRestrictedIdentifier(Dialect const& _dialect, YulName const& _identi return _identifier.empty() || hasLeadingOrTrailingDot(_identifier.str()) || TokenTraits::isYulKeyword(_identifier.str()) || _dialect.reservedIdentifier(_identifier.str()); } -std::optional yul::toEVMInstruction(Dialect const& _dialect, YulName const& _name) +std::optional yul::toEVMInstruction(Dialect const& _dialect, FunctionName const& _name) { if (auto const* dialect = dynamic_cast(&_dialect)) - if (std::optional const builtinHandle = dialect->findBuiltin(_name.str())) - return dialect->builtin(*builtinHandle).instruction; + if (BuiltinFunctionForEVM const* builtin = resolveBuiltinFunctionForEVM(_name, *dialect)) + return builtin->instruction; return std::nullopt; } diff --git a/libyul/optimiser/OptimizerUtilities.h b/libyul/optimiser/OptimizerUtilities.h index 911a1be0a812..430834d8304b 100644 --- a/libyul/optimiser/OptimizerUtilities.h +++ b/libyul/optimiser/OptimizerUtilities.h @@ -48,7 +48,7 @@ void removeEmptyBlocks(Block& _block); bool isRestrictedIdentifier(Dialect const& _dialect, YulName const& _identifier); /// Helper function that returns the instruction, if the `_name` is a BuiltinFunction -std::optional toEVMInstruction(Dialect const& _dialect, YulName const& _name); +std::optional toEVMInstruction(Dialect const& _dialect, FunctionName const& _name); /// Helper function that returns the EVM version from a dialect. /// It returns the default EVM version if dialect is not an EVMDialect. diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index 1698ac5eef7a..a505126ae9f1 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -40,7 +41,7 @@ using namespace solidity::yul; SideEffectsCollector::SideEffectsCollector( Dialect const& _dialect, Expression const& _expression, - std::map const* _functionSideEffects + std::map const* _functionSideEffects ): SideEffectsCollector(_dialect, _functionSideEffects) { @@ -56,7 +57,7 @@ SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Statement co SideEffectsCollector::SideEffectsCollector( Dialect const& _dialect, Block const& _ast, - std::map const* _functionSideEffects + std::map const* _functionSideEffects ): SideEffectsCollector(_dialect, _functionSideEffects) { @@ -66,7 +67,7 @@ SideEffectsCollector::SideEffectsCollector( SideEffectsCollector::SideEffectsCollector( Dialect const& _dialect, ForLoop const& _ast, - std::map const* _functionSideEffects + std::map const* _functionSideEffects ): SideEffectsCollector(_dialect, _functionSideEffects) { @@ -77,11 +78,11 @@ void SideEffectsCollector::operator()(FunctionCall const& _functionCall) { ASTWalker::operator()(_functionCall); - YulName functionName = _functionCall.functionName.name; - if (std::optional builtinHandle = m_dialect.findBuiltin(functionName.str())) - m_sideEffects += m_dialect.builtin(*builtinHandle).sideEffects; - else if (m_functionSideEffects && m_functionSideEffects->count(functionName)) - m_sideEffects += m_functionSideEffects->at(functionName); + FunctionHandle functionHandle = functionNameToHandle(_functionCall.functionName); + if (BuiltinFunction const* builtin = resolveBuiltinFunction(_functionCall.functionName, m_dialect)) + m_sideEffects += builtin->sideEffects; + else if (m_functionSideEffects && m_functionSideEffects->count(functionHandle)) + m_sideEffects += m_functionSideEffects->at(functionHandle); else m_sideEffects += SideEffects::worst(); } @@ -111,12 +112,12 @@ void MSizeFinder::operator()(FunctionCall const& _functionCall) { ASTWalker::operator()(_functionCall); - if (std::optional builtinHandle = m_dialect.findBuiltin(_functionCall.functionName.name.str())) - if (m_dialect.builtin(*builtinHandle).isMSize) + if (BuiltinFunction const* builtin = resolveBuiltinFunction(_functionCall.functionName, m_dialect)) + if (builtin->isMSize) m_msizeFound = true; } -std::map SideEffectsPropagator::sideEffects( +std::map SideEffectsPropagator::sideEffects( Dialect const& _dialect, CallGraph const& _directCallGraph ) @@ -127,8 +128,16 @@ std::map SideEffectsPropagator::sideEffects( // In the future, we should refine that, because the property // is actually a bit different from "not movable". - std::map ret; - for (auto const& function: _directCallGraph.functionsWithLoops + _directCallGraph.recursiveFunctions()) + std::map ret; + for (auto const& function: _directCallGraph.functionsWithLoops) + { + ret[function].movable = false; + ret[function].canBeRemoved = false; + ret[function].canBeRemovedIfNoMSize = false; + ret[function].cannotLoop = false; + } + + for (auto const& function: _directCallGraph.recursiveFunctions()) { ret[function].movable = false; ret[function].canBeRemoved = false; @@ -138,20 +147,20 @@ std::map SideEffectsPropagator::sideEffects( for (auto const& call: _directCallGraph.functionCalls) { - YulName funName = call.first; + FunctionHandle funName = call.first; SideEffects sideEffects; - auto _visit = [&, visited = std::set{}](YulName _function, auto&& _recurse) mutable { + auto _visit = [&, visited = std::set{}](FunctionHandle _function, auto&& _recurse) mutable { if (!visited.insert(_function).second) return; if (sideEffects == SideEffects::worst()) return; - if (std::optional builtinHandle = _dialect.findBuiltin(_function.str())) + if (BuiltinHandle const* builtinHandle = std::get_if(&_function)) sideEffects += _dialect.builtin(*builtinHandle).sideEffects; else { if (ret.count(_function)) sideEffects += ret[_function]; - for (YulName callee: _directCallGraph.functionCalls.at(_function)) + for (FunctionHandle const& callee: _directCallGraph.functionCalls.at(_function)) _recurse(callee, _recurse); } }; @@ -228,10 +237,13 @@ bool TerminationFinder::containsNonContinuingFunctionCall(Expression const& _exp if (containsNonContinuingFunctionCall(arg)) return true; - if (std::optional const builtinHandle = m_dialect.findBuiltin(functionCall->functionName.name.str())) - return !m_dialect.builtin(*builtinHandle).controlFlowSideEffects.canContinue; - else if (m_functionSideEffects && m_functionSideEffects->count(functionCall->functionName.name)) - return !m_functionSideEffects->at(functionCall->functionName.name).canContinue; + if (BuiltinFunction const* builtin = resolveBuiltinFunction(functionCall->functionName, m_dialect)) + return !builtin->controlFlowSideEffects.canContinue; + + yulAssert(std::holds_alternative(functionCall->functionName)); + auto const& name = std::get(functionCall->functionName).name; + if (m_functionSideEffects && m_functionSideEffects->count(name)) + return !m_functionSideEffects->at(name).canContinue; } return false; } diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index e97fbd0c31a5..9fdeb78d031e 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -42,23 +42,23 @@ class SideEffectsCollector: public ASTWalker public: explicit SideEffectsCollector( Dialect const& _dialect, - std::map const* _functionSideEffects = nullptr + std::map const* _functionSideEffects = nullptr ): m_dialect(_dialect), m_functionSideEffects(_functionSideEffects) {} SideEffectsCollector( Dialect const& _dialect, Expression const& _expression, - std::map const* _functionSideEffects = nullptr + std::map const* _functionSideEffects = nullptr ); SideEffectsCollector(Dialect const& _dialect, Statement const& _statement); SideEffectsCollector( Dialect const& _dialect, Block const& _ast, - std::map const* _functionSideEffects = nullptr + std::map const* _functionSideEffects = nullptr ); SideEffectsCollector( Dialect const& _dialect, ForLoop const& _ast, - std::map const* _functionSideEffects = nullptr + std::map const* _functionSideEffects = nullptr ); using ASTWalker::operator(); @@ -117,7 +117,7 @@ class SideEffectsCollector: public ASTWalker private: Dialect const& m_dialect; - std::map const* m_functionSideEffects = nullptr; + std::map const* m_functionSideEffects = nullptr; SideEffects m_sideEffects; }; @@ -130,7 +130,7 @@ class SideEffectsCollector: public ASTWalker class SideEffectsPropagator { public: - static std::map sideEffects( + static std::map sideEffects( Dialect const& _dialect, CallGraph const& _directCallGraph ); @@ -195,7 +195,7 @@ class MovableChecker: public SideEffectsCollector public: explicit MovableChecker( Dialect const& _dialect, - std::map const* _functionSideEffects = nullptr + std::map const* _functionSideEffects = nullptr ): SideEffectsCollector(_dialect, _functionSideEffects) {} MovableChecker(Dialect const& _dialect, Expression const& _expression); diff --git a/libyul/optimiser/SimplificationRules.cpp b/libyul/optimiser/SimplificationRules.cpp index 0fcd35bda1ed..8a7549cc125a 100644 --- a/libyul/optimiser/SimplificationRules.cpp +++ b/libyul/optimiser/SimplificationRules.cpp @@ -79,9 +79,9 @@ std::optional const*>> { if (std::holds_alternative(_expr)) if (auto const* dialect = dynamic_cast(&_dialect)) - if (std::optional const builtinHandle = dialect->findBuiltin(std::get(_expr).functionName.name.str())) - if (auto const& instruction = dialect->builtin(*builtinHandle).instruction) - return std::make_pair(*instruction, &std::get(_expr).arguments); + if (auto const* builtin = resolveBuiltinFunctionForEVM(std::get(_expr).functionName, *dialect)) + if (builtin->instruction) + return std::make_pair(*builtin->instruction, &std::get(_expr).arguments); return {}; } @@ -234,7 +234,7 @@ evmasm::Instruction Pattern::instruction() const return m_instruction; } -Expression Pattern::toExpression(langutil::DebugData::ConstPtr const& _debugData, langutil::EVMVersion _evmVersion) const +Expression Pattern::toExpression(langutil::DebugData::ConstPtr const& _debugData, EVMDialect const& _dialect) const { if (matchGroup()) return ASTCopier().translate(matchGroupValue()); @@ -247,12 +247,18 @@ Expression Pattern::toExpression(langutil::DebugData::ConstPtr const& _debugData { std::vector arguments; for (auto const& arg: m_arguments) - arguments.emplace_back(arg.toExpression(_debugData, _evmVersion)); + arguments.emplace_back(arg.toExpression(_debugData, _dialect)); - std::string name = util::toLower(instructionInfo(m_instruction, _evmVersion).name); + if (!m_instructionBuiltinHandle) + { + std::string name = util::toLower(instructionInfo(m_instruction, _dialect.evmVersion()).name); + std::optional handle = _dialect.findBuiltin(name); + yulAssert(handle); + m_instructionBuiltinHandle = *handle; + } return FunctionCall{_debugData, - Identifier{_debugData, YulName{name}}, + BuiltinName{_debugData, *m_instructionBuiltinHandle}, std::move(arguments) }; } diff --git a/libyul/optimiser/SimplificationRules.h b/libyul/optimiser/SimplificationRules.h index 9362ee8557ec..2d48464ca62f 100644 --- a/libyul/optimiser/SimplificationRules.h +++ b/libyul/optimiser/SimplificationRules.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -38,8 +39,9 @@ namespace solidity::yul { -struct Dialect; struct AssignedValue; +struct Dialect; +class EVMDialect; class Pattern; using DebugData = langutil::DebugData; @@ -133,13 +135,14 @@ class Pattern /// Turns this pattern into an actual expression. Should only be called /// for patterns resulting from an action, i.e. with match groups assigned. - Expression toExpression(langutil::DebugData::ConstPtr const& _debugData, langutil::EVMVersion _evmVersion) const; + Expression toExpression(langutil::DebugData::ConstPtr const& _debugData, EVMDialect const& _dialect) const; private: Expression const& matchGroupValue() const; PatternKind m_kind = PatternKind::Any; evmasm::Instruction m_instruction; ///< Only valid if m_kind is Operation + std::optional mutable m_instructionBuiltinHandle; ///< Builtin handle cache for instructions std::shared_ptr m_data; ///< Only valid if m_kind is Constant std::vector m_arguments; unsigned m_matchGroup = 0; diff --git a/libyul/optimiser/StackLimitEvader.cpp b/libyul/optimiser/StackLimitEvader.cpp index 2dc32d78759a..766cd4928447 100644 --- a/libyul/optimiser/StackLimitEvader.cpp +++ b/libyul/optimiser/StackLimitEvader.cpp @@ -57,7 +57,7 @@ namespace */ struct MemoryOffsetAllocator { - uint64_t run(YulName _function = YulName{}) + uint64_t run(FunctionHandle _function = YulName{}) { if (slotsRequiredForFunction.count(_function)) return slotsRequiredForFunction[_function]; @@ -65,14 +65,17 @@ struct MemoryOffsetAllocator // Assign to zero early to guard against recursive calls. slotsRequiredForFunction[_function] = 0; + if (!std::holds_alternative(_function)) + return 0; + uint64_t requiredSlots = 0; - if (callGraph.count(_function)) - for (YulName child: callGraph.at(_function)) + if (callGraph.count(std::get(_function))) + for (FunctionHandle const& child: callGraph.at(std::get(_function))) requiredSlots = std::max(run(child), requiredSlots); - if (auto const* unreachables = util::valueOrNullptr(unreachableVariables, _function)) + if (auto const* unreachables = util::valueOrNullptr(unreachableVariables, std::get(_function))) { - if (FunctionDefinition const* functionDefinition = util::valueOrDefault(functionDefinitions, _function, nullptr, util::allow_copy)) + if (FunctionDefinition const* functionDefinition = util::valueOrDefault(functionDefinitions, std::get(_function), nullptr, util::allow_copy)) if ( size_t totalArgCount = functionDefinition->returnVariables.size() + functionDefinition->parameters.size(); totalArgCount > 16 @@ -99,14 +102,14 @@ struct MemoryOffsetAllocator /// An empty variable name means that the function has too many arguments or return variables. std::map> const& unreachableVariables; /// The graph of immediate function calls of all functions. - std::map> const& callGraph; + std::map> const& callGraph; /// Maps the name of each user-defined function to its definition. std::map const& functionDefinitions; /// Maps variable names to the memory slot the respective variable is assigned. std::map slotAllocations{}; /// Maps function names to the number of memory slots the respective function requires. - std::map slotsRequiredForFunction{}; + std::map slotsRequiredForFunction{}; }; u256 literalArgumentValue(FunctionCall const& _call) @@ -181,7 +184,7 @@ void StackLimitEvader::run( "StackLimitEvader can only be run on objects using the EVMDialect with object access." ); - std::vector memoryGuardCalls = findFunctionCalls(_astRoot, "memoryguard"_yulname); + std::vector memoryGuardCalls = findFunctionCalls(_astRoot, "memoryguard", *evmDialect); // Do not optimise, if no ``memoryguard`` call is found. if (memoryGuardCalls.empty()) return; @@ -197,9 +200,12 @@ void StackLimitEvader::run( CallGraph callGraph = CallGraphGenerator::callGraph(_astRoot); // We cannot move variables in recursive functions to fixed memory offsets. - for (YulName function: callGraph.recursiveFunctions()) - if (_unreachableVariables.count(function)) + for (FunctionHandle function: callGraph.recursiveFunctions()) + { + yulAssert(std::holds_alternative(function), "Builtins are not recursive."); + if (_unreachableVariables.count(std::get(function))) return; + } std::map functionDefinitions = allFunctionDefinitions(_astRoot); @@ -210,7 +216,7 @@ void StackLimitEvader::run( StackToMemoryMover::run(_context, reservedMemory, memoryOffsetAllocator.slotAllocations, requiredSlots, _astRoot); reservedMemory += 32 * requiredSlots; - for (FunctionCall* memoryGuardCall: findFunctionCalls(_astRoot, "memoryguard"_yulname)) + for (FunctionCall* memoryGuardCall: findFunctionCalls(_astRoot, "memoryguard", *evmDialect)) { Literal* literal = std::get_if(&memoryGuardCall->arguments.front()); yulAssert(literal && literal->kind == LiteralKind::Number, ""); diff --git a/libyul/optimiser/StackToMemoryMover.cpp b/libyul/optimiser/StackToMemoryMover.cpp index e75472f068f5..15df64b3f252 100644 --- a/libyul/optimiser/StackToMemoryMover.cpp +++ b/libyul/optimiser/StackToMemoryMover.cpp @@ -47,7 +47,7 @@ std::vector generateMemoryStore( std::vector result; result.emplace_back(ExpressionStatement{_debugData, FunctionCall{ _debugData, - Identifier{_debugData, YulName{_dialect.builtin(*memoryStoreFunctionHandle).name}}, + BuiltinName{_debugData, *memoryStoreFunctionHandle}, { Literal{_debugData, LiteralKind::Number, _mpos}, std::move(_value) @@ -62,7 +62,7 @@ FunctionCall generateMemoryLoad(Dialect const& _dialect, langutil::DebugData::Co yulAssert(memoryLoadHandle); return FunctionCall{ _debugData, - Identifier{_debugData, YulName{_dialect.builtin(*memoryLoadHandle).name}}, { + BuiltinName{_debugData, *memoryLoadHandle}, { Literal{ _debugData, LiteralKind::Number, @@ -223,13 +223,16 @@ void StackToMemoryMover::operator()(Block& _block) { FunctionCall const* functionCall = std::get_if(_stmt.value.get()); yulAssert(functionCall, ""); - if (m_context.dialect.findBuiltin(functionCall->functionName.name.str())) + if (isBuiltinFunctionCall(*functionCall)) rhsMemorySlots = std::vector>(_lhsVars.size(), std::nullopt); else + { + yulAssert(std::holds_alternative(functionCall->functionName)); rhsMemorySlots = - m_functionReturnVariables.at(functionCall->functionName.name) | + m_functionReturnVariables.at(std::get(functionCall->functionName).name) | ranges::views::transform(m_memoryOffsetTracker) | ranges::to>>; + } } else rhsMemorySlots = std::vector>(_lhsVars.size(), std::nullopt); diff --git a/libyul/optimiser/SyntacticalEquality.cpp b/libyul/optimiser/SyntacticalEquality.cpp index 73b4d5083c54..e233b57e9e2d 100644 --- a/libyul/optimiser/SyntacticalEquality.cpp +++ b/libyul/optimiser/SyntacticalEquality.cpp @@ -25,6 +25,7 @@ #include #include +#include using namespace solidity; using namespace solidity::yul; @@ -54,6 +55,14 @@ bool SyntacticallyEqual::expressionEqual(FunctionCall const& _lhs, FunctionCall }); } +bool SyntacticallyEqual::expressionEqual(FunctionName const& _lhs, FunctionName const& _rhs) +{ + return std::visit(util::GenericVisitor{ + [&](BuiltinName const& _builtin) { return std::holds_alternative(_rhs) && _builtin.handle == std::get(_rhs).handle; }, + [&](Identifier const& _identifier) { return std::holds_alternative(_rhs) && expressionEqual(_identifier, std::get(_rhs)); }, + }, _lhs); +} + bool SyntacticallyEqual::expressionEqual(Identifier const& _lhs, Identifier const& _rhs) { auto lhsIt = m_identifiersLHS.find(_lhs.name); diff --git a/libyul/optimiser/SyntacticalEquality.h b/libyul/optimiser/SyntacticalEquality.h index 1fd888f7af8c..0db52c9690df 100644 --- a/libyul/optimiser/SyntacticalEquality.h +++ b/libyul/optimiser/SyntacticalEquality.h @@ -48,6 +48,7 @@ class SyntacticallyEqual bool expressionEqual(FunctionCall const& _lhs, FunctionCall const& _rhs); bool expressionEqual(Identifier const& _lhs, Identifier const& _rhs); + bool expressionEqual(FunctionName const& _lhs, FunctionName const& _rhs); bool expressionEqual(Literal const& _lhs, Literal const& _rhs); bool statementEqual(ExpressionStatement const& _lhs, ExpressionStatement const& _rhs); diff --git a/libyul/optimiser/UnusedAssignEliminator.cpp b/libyul/optimiser/UnusedAssignEliminator.cpp index eb639f8a6c3f..6de2ebf4cacd 100644 --- a/libyul/optimiser/UnusedAssignEliminator.cpp +++ b/libyul/optimiser/UnusedAssignEliminator.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -79,10 +80,13 @@ void UnusedAssignEliminator::operator()(FunctionCall const& _functionCall) UnusedStoreBase::operator()(_functionCall); ControlFlowSideEffects sideEffects; - if (std::optional const builtinHandle = m_dialect.findBuiltin(_functionCall.functionName.name.str())) - sideEffects = m_dialect.builtin(*builtinHandle).controlFlowSideEffects; + if (BuiltinFunction const* builtin = resolveBuiltinFunction(_functionCall.functionName, m_dialect)) + sideEffects = builtin->controlFlowSideEffects; else - sideEffects = m_controlFlowSideEffects.at(_functionCall.functionName.name); + { + yulAssert(std::holds_alternative(_functionCall.functionName)); + sideEffects = m_controlFlowSideEffects.at(std::get(_functionCall.functionName).name); + } if (!sideEffects.canContinue) // We do not return from the current function, so it is OK to also diff --git a/libyul/optimiser/UnusedPruner.cpp b/libyul/optimiser/UnusedPruner.cpp index 9f6b88412f9c..91568f4d65be 100644 --- a/libyul/optimiser/UnusedPruner.cpp +++ b/libyul/optimiser/UnusedPruner.cpp @@ -43,7 +43,7 @@ UnusedPruner::UnusedPruner( Dialect const& _dialect, Block& _ast, bool _allowMSizeOptimization, - std::map const* _functionSideEffects, + std::map const* _functionSideEffects, std::set const& _externallyUsedFunctions ): m_dialect(_dialect), @@ -94,7 +94,7 @@ void UnusedPruner::operator()(Block& _block) else if (varDecl.variables.size() == 1 && m_dialect.discardFunctionHandle()) statement = ExpressionStatement{varDecl.debugData, FunctionCall{ varDecl.debugData, - {varDecl.debugData, YulName{m_dialect.builtin(*m_dialect.discardFunctionHandle()).name}}, + BuiltinName{varDecl.debugData, *m_dialect.discardFunctionHandle()}, {*std::move(varDecl.value)} }}; } @@ -121,7 +121,7 @@ void UnusedPruner::runUntilStabilised( Dialect const& _dialect, Block& _ast, bool _allowMSizeOptimization, - std::map const* _functionSideEffects, + std::map const* _functionSideEffects, std::set const& _externallyUsedFunctions ) { @@ -146,7 +146,7 @@ void UnusedPruner::runUntilStabilisedOnFullAST( std::set const& _externallyUsedFunctions ) { - std::map functionSideEffects = + std::map functionSideEffects = SideEffectsPropagator::sideEffects(_dialect, CallGraphGenerator::callGraph(_ast)); bool allowMSizeOptimization = !MSizeFinder::containsMSize(_dialect, _ast); runUntilStabilised(_dialect, _ast, allowMSizeOptimization, &functionSideEffects, _externallyUsedFunctions); @@ -157,7 +157,7 @@ bool UnusedPruner::used(YulName _name) const return m_references.count(_name) && m_references.at(_name) > 0; } -void UnusedPruner::subtractReferences(std::map const& _subtrahend) +void UnusedPruner::subtractReferences(std::map const& _subtrahend) { for (auto const& ref: _subtrahend) { diff --git a/libyul/optimiser/UnusedPruner.h b/libyul/optimiser/UnusedPruner.h index 040e7ce43b39..37d26bbb0c85 100644 --- a/libyul/optimiser/UnusedPruner.h +++ b/libyul/optimiser/UnusedPruner.h @@ -52,7 +52,6 @@ class UnusedPruner: public ASTModifier static constexpr char const* name{"UnusedPruner"}; static void run(OptimiserStepContext& _context, Block& _ast); - using ASTModifier::operator(); void operator()(Block& _block) override; @@ -64,7 +63,7 @@ class UnusedPruner: public ASTModifier Dialect const& _dialect, Block& _ast, bool _allowMSizeOptimization, - std::map const* _functionSideEffects = nullptr, + std::map const* _functionSideEffects = nullptr, std::set const& _externallyUsedFunctions = {} ); @@ -83,18 +82,18 @@ class UnusedPruner: public ASTModifier Dialect const& _dialect, Block& _ast, bool _allowMSizeOptimization, - std::map const* _functionSideEffects = nullptr, + std::map const* _functionSideEffects = nullptr, std::set const& _externallyUsedFunctions = {} ); bool used(YulName _name) const; - void subtractReferences(std::map const& _subtrahend); + void subtractReferences(std::map const& _subtrahend); Dialect const& m_dialect; bool m_allowMSizeOptimization = false; - std::map const* m_functionSideEffects = nullptr; + std::map const* m_functionSideEffects = nullptr; bool m_shouldRunAgain = false; - std::map m_references; + std::map m_references; }; } diff --git a/libyul/optimiser/UnusedStoreEliminator.cpp b/libyul/optimiser/UnusedStoreEliminator.cpp index 7c68d577bb74..6f2f1ee938f8 100644 --- a/libyul/optimiser/UnusedStoreEliminator.cpp +++ b/libyul/optimiser/UnusedStoreEliminator.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -45,7 +46,7 @@ using namespace solidity::yul; void UnusedStoreEliminator::run(OptimiserStepContext& _context, Block& _ast) { - std::map functionSideEffects = SideEffectsPropagator::sideEffects( + std::map functionSideEffects = SideEffectsPropagator::sideEffects( _context.dialect, CallGraphGenerator::callGraph(_ast) ); @@ -81,7 +82,7 @@ void UnusedStoreEliminator::run(OptimiserStepContext& _context, Block& _ast) UnusedStoreEliminator::UnusedStoreEliminator( Dialect const& _dialect, - std::map const& _functionSideEffects, + std::map const& _functionSideEffects, std::map _controlFlowSideEffects, std::map const& _ssaValues, bool _ignoreMemory @@ -89,9 +90,9 @@ UnusedStoreEliminator::UnusedStoreEliminator( UnusedStoreBase(_dialect), m_ignoreMemory(_ignoreMemory), m_functionSideEffects(_functionSideEffects), - m_controlFlowSideEffects(_controlFlowSideEffects), + m_controlFlowSideEffects(std::move(_controlFlowSideEffects)), m_ssaValues(_ssaValues), - m_knowledgeBase(_ssaValues) + m_knowledgeBase(_ssaValues, _dialect) {} void UnusedStoreEliminator::operator()(FunctionCall const& _functionCall) @@ -102,10 +103,13 @@ void UnusedStoreEliminator::operator()(FunctionCall const& _functionCall) applyOperation(op); ControlFlowSideEffects sideEffects; - if (std::optional const builtinHandle = m_dialect.findBuiltin(_functionCall.functionName.name.str())) - sideEffects = m_dialect.builtin(*builtinHandle).controlFlowSideEffects; + if (auto builtin = resolveBuiltinFunction(_functionCall.functionName, m_dialect)) + sideEffects = builtin->controlFlowSideEffects; else - sideEffects = m_controlFlowSideEffects.at(_functionCall.functionName.name); + { + yulAssert(std::holds_alternative(_functionCall.functionName)); + sideEffects = m_controlFlowSideEffects.at(std::get(_functionCall.functionName).name); + } if (sideEffects.canTerminate) markActiveAsUsed(Location::Storage); @@ -141,7 +145,7 @@ void UnusedStoreEliminator::visit(Statement const& _statement) FunctionCall const* funCall = std::get_if(&exprStatement->expression); yulAssert(funCall); - std::optional instruction = toEVMInstruction(m_dialect, funCall->functionName.name); + std::optional instruction = toEVMInstruction(m_dialect, funCall->functionName); if (!instruction) return; @@ -189,7 +193,7 @@ void UnusedStoreEliminator::visit(Statement const& _statement) if ( m_knowledgeBase.knownToBeZero(*startOffset) && lengthCall && - toEVMInstruction(m_dialect, lengthCall->functionName.name) == Instruction::RETURNDATASIZE + toEVMInstruction(m_dialect, lengthCall->functionName) == Instruction::RETURNDATASIZE ) allowReturndatacopyToBeRemoved = true; } @@ -216,14 +220,16 @@ std::vector UnusedStoreEliminator::operationsF { using evmasm::Instruction; - YulName functionName = _functionCall.functionName.name; SideEffects sideEffects; - if (std::optional const builtinHandle = m_dialect.findBuiltin(functionName.str())) - sideEffects = m_dialect.builtin(*builtinHandle).sideEffects; + if (BuiltinFunction const* f = resolveBuiltinFunction(_functionCall.functionName, m_dialect)) + sideEffects = f->sideEffects; else - sideEffects = m_functionSideEffects.at(functionName); + { + yulAssert(std::holds_alternative(_functionCall.functionName)); + sideEffects = m_functionSideEffects.at(std::get(_functionCall.functionName).name); + } - std::optional instruction = toEVMInstruction(m_dialect, functionName); + std::optional instruction = toEVMInstruction(m_dialect, _functionCall.functionName); if (!instruction) { std::vector result; diff --git a/libyul/optimiser/UnusedStoreEliminator.h b/libyul/optimiser/UnusedStoreEliminator.h index f749a229ca11..6da33a9866c6 100644 --- a/libyul/optimiser/UnusedStoreEliminator.h +++ b/libyul/optimiser/UnusedStoreEliminator.h @@ -62,7 +62,7 @@ class UnusedStoreEliminator: public UnusedStoreBase explicit UnusedStoreEliminator( Dialect const& _dialect, - std::map const& _functionSideEffects, + std::map const& _functionSideEffects, std::map _controlFlowSideEffects, std::map const& _ssaValues, bool _ignoreMemory @@ -122,7 +122,7 @@ class UnusedStoreEliminator: public UnusedStoreBase std::optional identifierNameIfSSA(Expression const& _expression) const; bool const m_ignoreMemory; - std::map const& m_functionSideEffects; + std::map const& m_functionSideEffects; std::map m_controlFlowSideEffects; std::map const& m_ssaValues; diff --git a/test/libsolidity/MemoryGuardTest.cpp b/test/libsolidity/MemoryGuardTest.cpp index a98c7a323015..b797564b7234 100644 --- a/test/libsolidity/MemoryGuardTest.cpp +++ b/test/libsolidity/MemoryGuardTest.cpp @@ -59,12 +59,13 @@ TestCase::TestResult MemoryGuardTest::run(std::ostream& _stream, std::string con m_obtainedResult.clear(); for (std::string contractName: compiler().contractNames()) { + auto const& dialect = EVMDialect::strictAssemblyForEVMObjects(CommonOptions::get().evmVersion(), CommonOptions::get().eofVersion()); ErrorList errors; std::optional const& ir = compiler().yulIR(contractName); solAssert(ir); auto [object, analysisInfo] = yul::test::parse( *ir, - EVMDialect::strictAssemblyForEVMObjects(CommonOptions::get().evmVersion(), CommonOptions::get().eofVersion()), + dialect, errors ); @@ -78,7 +79,8 @@ TestCase::TestResult MemoryGuardTest::run(std::ostream& _stream, std::string con auto handleObject = [&](std::string const& _kind, Object const& _object) { m_obtainedResult += contractName + "(" + _kind + ") " + (findFunctionCalls( _object.code()->root(), - "memoryguard"_yulname + "memoryguard", + dialect ).empty() ? "false" : "true") + "\n"; }; handleObject("creation", *object); diff --git a/test/libyul/ControlFlowGraphTest.cpp b/test/libyul/ControlFlowGraphTest.cpp index 0aaaea58105c..bd0bf0a802d2 100644 --- a/test/libyul/ControlFlowGraphTest.cpp +++ b/test/libyul/ControlFlowGraphTest.cpp @@ -64,8 +64,9 @@ static std::string variableSlotToString(VariableSlot const& _slot) class ControlFlowGraphPrinter { public: - ControlFlowGraphPrinter(std::ostream& _stream): - m_stream(_stream) + ControlFlowGraphPrinter(std::ostream& _stream, Dialect const& _dialect): + m_stream(_stream), + m_dialect(_dialect) { } void operator()(CFG::BasicBlock const& _block, bool _isMainEntry = true) @@ -133,7 +134,7 @@ class ControlFlowGraphPrinter m_stream << _call.function.get().name.str() << ": "; }, [&](CFG::BuiltinCall const& _call) { - m_stream << _call.functionCall.get().functionName.name.str() << ": "; + m_stream << _call.builtin.get().name << ": "; }, [&](CFG::Assignment const& _assignment) { m_stream << "Assignment("; @@ -141,7 +142,7 @@ class ControlFlowGraphPrinter m_stream << "): "; } }, operation.operation); - m_stream << stackToString(operation.input) << " => " << stackToString(operation.output) << "\\l\\\n"; + m_stream << stackToString(operation.input, m_dialect) << " => " << stackToString(operation.output, m_dialect) << "\\l\\\n"; } m_stream << "\"];\n"; std::visit(util::GenericVisitor{ @@ -163,7 +164,7 @@ class ControlFlowGraphPrinter { m_stream << "Block" << getBlockId(_block) << " -> Block" << getBlockId(_block) << "Exit;\n"; m_stream << "Block" << getBlockId(_block) << "Exit [label=\"{ "; - m_stream << stackSlotToString(_conditionalJump.condition); + m_stream << stackSlotToString(_conditionalJump.condition, m_dialect); m_stream << "| { <0> Zero | <1> NonZero }}\" shape=Mrecord];\n"; m_stream << "Block" << getBlockId(_block); m_stream << "Exit:0 -> Block" << getBlockId(*_conditionalJump.zero) << ";\n"; @@ -192,6 +193,7 @@ class ControlFlowGraphPrinter return id; } std::ostream& m_stream; + Dialect const& m_dialect; std::map m_blockIds; size_t m_blockCount = 0; std::list m_blocksToPrint; @@ -212,7 +214,7 @@ TestCase::TestResult ControlFlowGraphTest::run(std::ostream& _stream, std::strin std::unique_ptr cfg = ControlFlowGraphBuilder::build(*analysisInfo, *m_dialect, object->code()->root()); output << "digraph CFG {\nnodesep=0.7;\nnode[shape=box];\n\n"; - ControlFlowGraphPrinter printer{output}; + ControlFlowGraphPrinter printer{output, *m_dialect}; printer(*cfg->entry); for (auto function: cfg->functions) printer(cfg->functionInfo.at(function)); diff --git a/test/libyul/FunctionSideEffects.cpp b/test/libyul/FunctionSideEffects.cpp index 46a7c99fea51..204150ad837e 100644 --- a/test/libyul/FunctionSideEffects.cpp +++ b/test/libyul/FunctionSideEffects.cpp @@ -29,6 +29,7 @@ #include #include +#include #include @@ -92,14 +93,20 @@ TestCase::TestResult FunctionSideEffects::run(std::ostream& _stream, std::string if (!obj.hasCode()) BOOST_THROW_EXCEPTION(std::runtime_error("Parsing input failed.")); - std::map functionSideEffects = SideEffectsPropagator::sideEffects( + std::map functionSideEffects = SideEffectsPropagator::sideEffects( dialect, CallGraphGenerator::callGraph(obj.code()->root()) ); std::map functionSideEffectsStr; for (auto const& fun: functionSideEffects) - functionSideEffectsStr[fun.first.str()] = toString(fun.second); + { + auto const& functionNameStr = std::visit(GenericVisitor{ + [](YulName const& _name) { return _name.str(); }, + [&](BuiltinHandle const& _builtin) { return dialect.builtin(_builtin).name; } + }, fun.first); + functionSideEffectsStr[functionNameStr] = toString(fun.second); + } m_obtainedResult.clear(); for (auto const& fun: functionSideEffectsStr) diff --git a/test/libyul/KnowledgeBaseTest.cpp b/test/libyul/KnowledgeBaseTest.cpp index 0e7d411a3d3d..eefeb90109bc 100644 --- a/test/libyul/KnowledgeBaseTest.cpp +++ b/test/libyul/KnowledgeBaseTest.cpp @@ -60,7 +60,7 @@ class KnowledgeBaseTest m_values[name].value = expression; m_object->setCode(std::make_shared(m_dialect, std::move(astRoot))); - return KnowledgeBase([this](YulName _var) { return util::valueOrNullptr(m_values, _var); }); + return KnowledgeBase([this](YulName _var) { return util::valueOrNullptr(m_values, _var); }, m_dialect); } EVMDialect m_dialect{solidity::test::CommonOptions::get().evmVersion(), diff --git a/test/libyul/StackLayoutGeneratorTest.cpp b/test/libyul/StackLayoutGeneratorTest.cpp index 49af089ef82f..b41f7f21e820 100644 --- a/test/libyul/StackLayoutGeneratorTest.cpp +++ b/test/libyul/StackLayoutGeneratorTest.cpp @@ -69,8 +69,8 @@ static std::string variableSlotToString(VariableSlot const& _slot) class StackLayoutPrinter { public: - StackLayoutPrinter(std::ostream& _stream, StackLayout const& _stackLayout): - m_stream(_stream), m_stackLayout(_stackLayout) + StackLayoutPrinter(std::ostream& _stream, StackLayout const& _stackLayout, Dialect const& _dialect): + m_stream(_stream), m_stackLayout(_stackLayout), m_dialect(_dialect) { } void operator()(CFG::BasicBlock const& _block, bool _isMainEntry = true) @@ -104,7 +104,7 @@ class StackLayoutPrinter m_stream << "\\l\\\n"; Stack functionEntryStack = {FunctionReturnLabelSlot{_info.function}}; functionEntryStack += _info.parameters | ranges::views::reverse; - m_stream << stackToString(functionEntryStack) << "\"];\n"; + m_stream << stackToString(functionEntryStack, m_dialect) << "\"];\n"; m_stream << "FunctionEntry_" << _info.function.name.str() << " -> Block" << getBlockId(*_info.entry) << ";\n"; (*this)(*_info.entry, false); } @@ -135,17 +135,17 @@ class StackLayoutPrinter }, entry->exit); auto const& blockInfo = m_stackLayout.blockInfos.at(&_block); - m_stream << stackToString(blockInfo.entryLayout) << "\\l\\\n"; + m_stream << stackToString(blockInfo.entryLayout, m_dialect) << "\\l\\\n"; for (auto const& operation: _block.operations) { auto entryLayout = m_stackLayout.operationEntryLayout.at(&operation); - m_stream << stackToString(m_stackLayout.operationEntryLayout.at(&operation)) << "\\l\\\n"; + m_stream << stackToString(m_stackLayout.operationEntryLayout.at(&operation), m_dialect) << "\\l\\\n"; std::visit(util::GenericVisitor{ [&](CFG::FunctionCall const& _call) { m_stream << _call.function.get().name.str(); }, [&](CFG::BuiltinCall const& _call) { - m_stream << _call.functionCall.get().functionName.name.str(); + m_stream << _call.builtin.get().name; }, [&](CFG::Assignment const& _assignment) { @@ -159,9 +159,9 @@ class StackLayoutPrinter for (size_t i = 0; i < operation.input.size(); ++i) entryLayout.pop_back(); entryLayout += operation.output; - m_stream << stackToString(entryLayout) << "\\l\\\n"; + m_stream << stackToString(entryLayout, m_dialect) << "\\l\\\n"; } - m_stream << stackToString(blockInfo.exitLayout) << "\\l\\\n"; + m_stream << stackToString(blockInfo.exitLayout, m_dialect) << "\\l\\\n"; m_stream << "\"];\n"; std::visit(util::GenericVisitor{ [&](CFG::BasicBlock::MainExit const&) @@ -182,7 +182,7 @@ class StackLayoutPrinter { m_stream << "Block" << getBlockId(_block) << " -> Block" << getBlockId(_block) << "Exit;\n"; m_stream << "Block" << getBlockId(_block) << "Exit [label=\"{ "; - m_stream << stackSlotToString(_conditionalJump.condition); + m_stream << stackSlotToString(_conditionalJump.condition, m_dialect); m_stream << "| { <0> Zero | <1> NonZero }}\" shape=Mrecord];\n"; m_stream << "Block" << getBlockId(_block); m_stream << "Exit:0 -> Block" << getBlockId(*_conditionalJump.zero) << ";\n"; @@ -212,6 +212,7 @@ class StackLayoutPrinter } std::ostream& m_stream; StackLayout const& m_stackLayout; + Dialect const& m_dialect; std::map m_blockIds; size_t m_blockCount = 0; std::list m_blocksToPrint; @@ -238,7 +239,7 @@ TestCase::TestResult StackLayoutGeneratorTest::run(std::ostream& _stream, std::s StackLayout stackLayout = StackLayoutGenerator::run(*cfg, simulateFunctionsWithJumps); output << "digraph CFG {\nnodesep=0.7;\nnode[shape=box];\n\n"; - StackLayoutPrinter printer{output, stackLayout}; + StackLayoutPrinter printer{output, stackLayout, *m_dialect}; printer(*cfg->entry); for (auto function: cfg->functions) printer(cfg->functionInfo.at(function)); diff --git a/test/libyul/StackShufflingTest.cpp b/test/libyul/StackShufflingTest.cpp index 40fdcc5b15f4..baf8e0c8e543 100644 --- a/test/libyul/StackShufflingTest.cpp +++ b/test/libyul/StackShufflingTest.cpp @@ -17,9 +17,13 @@ #include +#include + +#include +#include + #include #include -#include using namespace solidity::util; using namespace solidity::langutil; @@ -137,6 +141,10 @@ StackShufflingTest::StackShufflingTest(std::string const& _filename): TestCase::TestResult StackShufflingTest::run(std::ostream& _stream, std::string const& _linePrefix, bool _formatted) { + auto const& dialect = EVMDialect::strictAssemblyForEVMObjects( + solidity::test::CommonOptions::get().evmVersion(), + solidity::test::CommonOptions::get().eofVersion() + ); if (!parse(m_source)) { AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << std::endl; @@ -149,14 +157,14 @@ TestCase::TestResult StackShufflingTest::run(std::ostream& _stream, std::string m_targetStack, [&](unsigned _swapDepth) // swap { - output << stackToString(m_sourceStack) << std::endl; + output << stackToString(m_sourceStack, dialect) << std::endl; output << "SWAP" << _swapDepth << std::endl; }, [&](StackSlot const& _slot) // dupOrPush { - output << stackToString(m_sourceStack) << std::endl; + output << stackToString(m_sourceStack, dialect) << std::endl; if (canBeFreelyGenerated(_slot)) - output << "PUSH " << stackSlotToString(_slot) << std::endl; + output << "PUSH " << stackSlotToString(_slot, dialect) << std::endl; else { if (auto depth = util::findOffset(m_sourceStack | ranges::views::reverse, _slot)) @@ -166,12 +174,12 @@ TestCase::TestResult StackShufflingTest::run(std::ostream& _stream, std::string } }, [&](){ // pop - output << stackToString(m_sourceStack) << std::endl; + output << stackToString(m_sourceStack, dialect) << std::endl; output << "POP" << std::endl; } ); - output << stackToString(m_sourceStack) << std::endl; + output << stackToString(m_sourceStack, dialect) << std::endl; m_obtainedResult = output.str(); return checkResult(_stream, _linePrefix, _formatted); diff --git a/test/libyul/yulSyntaxTests/assignment_to_builtin.yul b/test/libyul/yulSyntaxTests/assignment_to_builtin.yul new file mode 100644 index 000000000000..80f84c1349c5 --- /dev/null +++ b/test/libyul/yulSyntaxTests/assignment_to_builtin.yul @@ -0,0 +1,6 @@ +{ + function f() -> x {} + add := f() +} +// ---- +// ParserError 6272: (35-37): Cannot assign to builtin function "add". diff --git a/test/libyul/yulSyntaxTests/assignment_to_number.yul b/test/libyul/yulSyntaxTests/assignment_to_number.yul new file mode 100644 index 000000000000..3c4517fdde0c --- /dev/null +++ b/test/libyul/yulSyntaxTests/assignment_to_number.yul @@ -0,0 +1,6 @@ +{ + function f() -> x {} + let 123 := f() +} +// ---- +// ParserError 2314: (35-38): Expected identifier but got 'Number' diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 433ff3f072e3..2709d1b468aa 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -309,42 +309,39 @@ void ExpressionEvaluator::operator()(Identifier const& _identifier) void ExpressionEvaluator::operator()(FunctionCall const& _funCall) { std::vector> const* literalArguments = nullptr; - if (std::optional builtinHandle = m_dialect.findBuiltin(_funCall.functionName.name.str())) - if ( - auto const& args = m_dialect.builtin(*builtinHandle).literalArguments; - !args.empty() - ) - literalArguments = &args; + if (BuiltinFunction const* builtin = resolveBuiltinFunction(_funCall.functionName, m_dialect)) + if (!builtin->literalArguments.empty()) + literalArguments = &builtin->literalArguments; evaluateArgs(_funCall.arguments, literalArguments); if (EVMDialect const* dialect = dynamic_cast(&m_dialect)) { - if (std::optional builtinHandle = dialect->findBuiltin(_funCall.functionName.name.str())) + if (BuiltinFunctionForEVM const* fun = resolveBuiltinFunctionForEVM(_funCall.functionName, *dialect)) { - auto const& fun = dialect->builtin(*builtinHandle); EVMInstructionInterpreter interpreter(dialect->evmVersion(), m_state, m_disableMemoryTrace); - u256 const value = interpreter.evalBuiltin(fun, _funCall.arguments, values()); + u256 const value = interpreter.evalBuiltin(*fun, _funCall.arguments, values()); if ( !m_disableExternalCalls && - fun.instruction && - evmasm::isCallInstruction(*fun.instruction) + fun->instruction && + evmasm::isCallInstruction(*fun->instruction) ) - runExternalCall(*fun.instruction); + runExternalCall(*fun->instruction); setValue(value); return; } } + yulAssert(!isBuiltinFunctionCall(_funCall)); Scope* scope = &m_scope; for (; scope; scope = scope->parent) - if (scope->names.count(_funCall.functionName.name)) + if (scope->names.count(std::get(_funCall.functionName).name)) break; yulAssert(scope, ""); - FunctionDefinition const* fun = scope->names.at(_funCall.functionName.name); + FunctionDefinition const* fun = scope->names.at(std::get(_funCall.functionName).name); yulAssert(fun, "Function not found."); yulAssert(m_values.size() == fun->parameters.size(), ""); std::map variables;