Skip to content

Commit

Permalink
Merge pull request #15520 from ethereum/builtin_handles
Browse files Browse the repository at this point in the history
Introduce Yul builtin handles for its dialects
  • Loading branch information
clonker authored Oct 21, 2024
2 parents ecc32ec + 6a2631e commit abc46f3
Show file tree
Hide file tree
Showing 46 changed files with 581 additions and 391 deletions.
9 changes: 5 additions & 4 deletions libsolidity/analysis/ControlFlowBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -582,13 +582,14 @@ void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
solAssert(m_currentNode && m_inlineAssembly, "");
yul::ASTWalker::operator()(_functionCall);

if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
if (auto const& builtinHandle = m_inlineAssembly->dialect().findBuiltin(_functionCall.functionName.name.str()))
{
if (builtinFunction->controlFlowSideEffects.canTerminate)
auto const& builtinFunction = m_inlineAssembly->dialect().builtin(*builtinHandle);
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();
}
}
Expand Down
6 changes: 3 additions & 3 deletions libsolidity/analysis/ViewPureChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ class AssemblyViewPureChecker
void operator()(yul::FunctionCall const& _funCall)
{
if (yul::EVMDialect const* dialect = dynamic_cast<decltype(dialect)>(&m_dialect))
if (yul::BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name))
if (fun->instruction)
checkInstruction(nativeLocationOf(_funCall), *fun->instruction);
if (std::optional<yul::BuiltinHandle> builtinHandle = dialect->findBuiltin(_funCall.functionName.name.str()))
if (auto const& instruction = dialect->builtin(*builtinHandle).instruction)
checkInstruction(nativeLocationOf(_funCall), *instruction);

for (auto const& arg: _funCall.arguments)
std::visit(*this, arg);
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/codegen/ir/IRGeneratorForStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ struct CopyTranslate: public yul::ASTCopier
// from the Yul dialect we are compiling to. So we are assuming here that the builtin
// functions are identical. This should not be a problem for now since everything
// is EVM anyway.
if (m_dialect.builtin(_name))
if (m_dialect.findBuiltin(_name.str()))
return _name;
else
return yul::YulName{"usr$" + _name.str()};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ struct CopyTranslate: public yul::ASTCopier

yul::YulName translateIdentifier(yul::YulName _name) override
{
if (m_dialect.builtin(_name))
if (m_dialect.findBuiltin(_name.str()))
return _name;
else
return yul::YulName{"usr$" + _name.str()};
Expand Down
29 changes: 16 additions & 13 deletions libyul/AsmAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ size_t AsmAnalyzer::operator()(FunctionCall const& _funCall)
std::optional<size_t> numReturns;
std::vector<std::optional<LiteralKind>> const* literalArguments = nullptr;

if (BuiltinFunction const* f = m_dialect.builtin(_funCall.functionName.name))
if (std::optional<BuiltinHandle> handle = m_dialect.findBuiltin(_funCall.functionName.name.str()))
{
if (_funCall.functionName.name == "selfdestruct"_yulname)
m_errorReporter.warning(
Expand Down Expand Up @@ -327,13 +327,14 @@ 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."
);

numParameters = f->numParameters;
numReturns = f->numReturns;
if (!f->literalArguments.empty())
literalArguments = &f->literalArguments;
BuiltinFunction const& f = m_dialect.builtin(*handle);
numParameters = f.numParameters;
numReturns = f.numReturns;
if (!f.literalArguments.empty())
literalArguments = &f.literalArguments;

validateInstructions(_funCall);
m_sideEffects += f->sideEffects;
m_sideEffects += f.sideEffects;
}
else if (m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{
[&](Scope::Variable const&)
Expand Down Expand Up @@ -628,7 +629,7 @@ void AsmAnalyzer::expectValidIdentifier(YulName _identifier, SourceLocation cons
"\"" + _identifier.str() + "\" is not a valid identifier (contains consecutive dots)."
);

if (m_dialect.reservedIdentifier(_identifier))
if (m_dialect.reservedIdentifier(_identifier.str()))
m_errorReporter.declarationError(
5017_error,
_location,
Expand All @@ -639,14 +640,16 @@ void AsmAnalyzer::expectValidIdentifier(YulName _identifier, SourceLocation cons
bool AsmAnalyzer::validateInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location)
{
// NOTE: This function uses the default EVM version instead of the currently selected one.
auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}, std::nullopt).builtin(YulName(_instructionIdentifier));
if (builtin && builtin->instruction.has_value())
return validateInstructions(builtin->instruction.value(), _location);
auto const& defaultEVMDialect = EVMDialect::strictAssemblyForEVM(EVMVersion{}, std::nullopt);
auto const builtinHandle = defaultEVMDialect.findBuiltin(_instructionIdentifier);
if (builtinHandle && defaultEVMDialect.builtin(*builtinHandle).instruction.has_value())
return validateInstructions(*defaultEVMDialect.builtin(*builtinHandle).instruction, _location);

// TODO: Change `prague()` to `EVMVersion{}` once EOF gets deployed
auto const eofBuiltin = EVMDialect::strictAssemblyForEVM(EVMVersion::prague(), 1).builtin(YulName(_instructionIdentifier));
if (eofBuiltin && eofBuiltin->instruction.has_value())
return validateInstructions(eofBuiltin->instruction.value(), _location);
auto const& eofDialect = EVMDialect::strictAssemblyForEVM(EVMVersion::prague(), 1);
auto const eofBuiltinHandle = eofDialect.findBuiltin(_instructionIdentifier);
if (eofBuiltinHandle && eofDialect.builtin(*eofBuiltinHandle).instruction.has_value())
return validateInstructions(*eofDialect.builtin(*eofBuiltinHandle).instruction, _location);

return false;
}
Expand Down
15 changes: 8 additions & 7 deletions libyul/AsmParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ Statement Parser::parseStatement()

auto const& identifier = std::get<Identifier>(elementary);

if (m_dialect.builtin(identifier.name))
if (m_dialect.findBuiltin(identifier.name.str()))
fatalParserError(6272_error, "Cannot assign to builtin function \"" + identifier.name.str() + "\".");

assignment.variableNames.emplace_back(identifier);
Expand Down Expand Up @@ -515,7 +515,7 @@ Expression Parser::parseExpression(bool _unlimitedLiteralArgument)
{
if (currentToken() == Token::LParen)
return parseCall(std::move(operation));
if (m_dialect.builtin(_identifier.name))
if (m_dialect.findBuiltin(_identifier.name.str()))
fatalParserError(
7104_error,
nativeLocationOf(_identifier),
Expand Down Expand Up @@ -676,10 +676,11 @@ FunctionCall Parser::parseCall(std::variant<Literal, Identifier>&& _initialOp)
FunctionCall ret;
ret.functionName = std::move(std::get<Identifier>(_initialOp));
ret.debugData = ret.functionName.debugData;
auto const isUnlimitedLiteralArgument = [f=m_dialect.builtin(ret.functionName.name)](size_t const index) {
if (f && index < f->literalArguments.size())
return f->literalArgument(index).has_value();
return false;
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();
};
size_t argumentIndex {0};
expectToken(Token::LParen);
Expand Down Expand Up @@ -718,7 +719,7 @@ NameWithDebugData Parser::parseNameWithDebugData()
YulName Parser::expectAsmIdentifier()
{
YulName name{currentLiteral()};
if (currentToken() == Token::Identifier && m_dialect.builtin(name))
if (currentToken() == Token::Identifier && m_dialect.findBuiltin(name.str()))
fatalParserError(5568_error, "Cannot use builtin function name \"" + name.str() + "\" as identifier name.");
// NOTE: We keep the expectation here to ensure the correct source location for the error above.
expectToken(Token::Identifier);
Expand Down
35 changes: 35 additions & 0 deletions libyul/Builtins.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

#pragma once

#include <cstddef>

namespace solidity::yul
{

/// Handle to reference a builtin function in the AST
struct BuiltinHandle
{
size_t id;

bool operator==(BuiltinHandle const& _other) const { return id == _other.id; }
bool operator<(BuiltinHandle const& _other) const { return id < _other.id; }
};

}
1 change: 1 addition & 0 deletions libyul/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ add_library(yul
AsmParser.h
AsmPrinter.cpp
AsmPrinter.h
Builtins.h
YulStack.h
YulStack.cpp
CompilabilityChecker.cpp
Expand Down
4 changes: 2 additions & 2 deletions libyul/ControlFlowSideEffectsCollector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,8 @@ ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(Func

ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(FunctionCall const& _call) const
{
if (auto const* builtin = m_dialect.builtin(_call.functionName.name))
return builtin->controlFlowSideEffects;
if (std::optional<BuiltinHandle> builtinHandle = m_dialect.findBuiltin(_call.functionName.name.str()))
return m_dialect.builtin(*builtinHandle).controlFlowSideEffects;
else
return m_functionSideEffects.at(m_functionReferences.at(&_call));
}
Expand Down
40 changes: 23 additions & 17 deletions libyul/Dialect.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@

#pragma once

#include <libyul/YulName.h>
#include <libyul/Builtins.h>
#include <libyul/ControlFlowSideEffects.h>
#include <libyul/Exceptions.h>
#include <libyul/SideEffects.h>
#include <libyul/YulString.h>

#include <vector>
#include <set>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

namespace solidity::yul
{
Expand All @@ -38,7 +41,7 @@ struct Literal;

struct BuiltinFunction
{
YulName name;
std::string name;
size_t numParameters;
size_t numReturns;
SideEffects sideEffects;
Expand All @@ -60,26 +63,29 @@ struct Dialect
Dialect(Dialect const&) = delete;
Dialect& operator=(Dialect const&) = delete;

/// @returns the builtin function of the given name or a nullptr if it is not a builtin function.
virtual BuiltinFunction const* builtin(YulName /*_name*/) const { return nullptr; }
/// Finds a builtin by name and returns the corresponding handle.
/// @returns Builtin handle or null if the name does not match any builtin in the dialect.
virtual std::optional<BuiltinHandle> findBuiltin(std::string_view /*_name*/) const { return std::nullopt; }

/// Retrieves the description of a builtin function by its handle.
/// Note that handles are dialect-specific and can be used only with a dialect that created them.
virtual BuiltinFunction const& builtin(BuiltinHandle const&) const { yulAssert(false); }

/// @returns true if the identifier is reserved. This includes the builtins too.
virtual bool reservedIdentifier(YulName _name) const { return builtin(_name) != nullptr; }
virtual bool reservedIdentifier(std::string_view _name) const { return findBuiltin(_name).has_value(); }

virtual BuiltinFunction const* discardFunction() const { return nullptr; }
virtual BuiltinFunction const* equalityFunction() const { return nullptr; }
virtual BuiltinFunction const* booleanNegationFunction() const { return nullptr; }
virtual std::optional<BuiltinHandle> discardFunctionHandle() const { return std::nullopt; }
virtual std::optional<BuiltinHandle> equalityFunctionHandle() const { return std::nullopt; }
virtual std::optional<BuiltinHandle> booleanNegationFunctionHandle() const { return std::nullopt; }

virtual BuiltinFunction const* memoryStoreFunction() const { return nullptr; }
virtual BuiltinFunction const* memoryLoadFunction() const { return nullptr; }
virtual BuiltinFunction const* storageStoreFunction() const { return nullptr; }
virtual BuiltinFunction const* storageLoadFunction() const { return nullptr; }
virtual YulName hashFunction() const { return YulName{}; }
virtual std::optional<BuiltinHandle> memoryStoreFunctionHandle() const { return std::nullopt; }
virtual std::optional<BuiltinHandle> memoryLoadFunctionHandle() const { return std::nullopt; }
virtual std::optional<BuiltinHandle> storageStoreFunctionHandle() const { return std::nullopt; }
virtual std::optional<BuiltinHandle> storageLoadFunctionHandle() const { return std::nullopt; }
virtual std::optional<BuiltinHandle> hashFunctionHandle() const { return std::nullopt; }

Literal zeroLiteral() const;

virtual std::set<YulName> fixedFunctionNames() const { return {}; }

Dialect() = default;
virtual ~Dialect() = default;
};
Expand Down
2 changes: 1 addition & 1 deletion libyul/YulControlFlowGraphExporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ Json YulControlFlowGraphExporter::toJson(Json& _ret, SSACFG const& _cfg, SSACFG:
if (!builtinArgsJson.empty())
opJson["builtinArgs"] = builtinArgsJson;

opJson["op"] = _call.builtin.get().name.str();
opJson["op"] = _call.builtin.get().name;
},
}, _operation.kind);

Expand Down
16 changes: 10 additions & 6 deletions libyul/backends/evm/ConstantOptimiser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,11 @@ struct MiniEVMInterpreter

u256 operator()(FunctionCall const& _funCall)
{
BuiltinFunctionForEVM const* fun = m_dialect.builtin(_funCall.functionName.name);
yulAssert(fun, "Expected builtin function.");
yulAssert(fun->instruction, "Expected EVM instruction.");
return eval(*fun->instruction, _funCall.arguments);
std::optional<BuiltinHandle> 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);
}
u256 operator()(Literal const& _literal)
{
Expand Down Expand Up @@ -195,7 +196,9 @@ Representation RepresentationFinder::represent(
Identifier{m_debugData, _instruction},
{ASTCopier{}.translate(*_argument.expression)}
});
repr.cost = _argument.cost + m_meter.instructionCosts(*m_dialect.builtin(_instruction)->instruction);
repr.cost = _argument.cost + m_meter.instructionCosts(
*m_dialect.builtin(*m_dialect.findBuiltin(_instruction.str())).instruction
);
return repr;
}

Expand All @@ -211,7 +214,8 @@ Representation RepresentationFinder::represent(
Identifier{m_debugData, _instruction},
{ASTCopier{}.translate(*_arg1.expression), ASTCopier{}.translate(*_arg2.expression)}
});
repr.cost = m_meter.instructionCosts(*m_dialect.builtin(_instruction)->instruction) + _arg1.cost + _arg2.cost;
repr.cost = m_meter.instructionCosts(
*m_dialect.builtin(*m_dialect.findBuiltin(_instruction.str())).instruction) + _arg1.cost + _arg2.cost;
return repr;
}

Expand Down
18 changes: 10 additions & 8 deletions libyul/backends/evm/ControlFlowGraphBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,8 @@ void ControlFlowGraphBuilder::operator()(Switch const& _switch)
CFG::Assignment{_switch.debugData, {ghostVarSlot}}
});

BuiltinFunction const* equalityBuiltin = m_dialect.equalityFunction();
yulAssert(equalityBuiltin, "");
std::optional<BuiltinHandle> const& equalityBuiltinHandle = m_dialect.equalityFunctionHandle();
yulAssert(equalityBuiltinHandle);

// Artificially generate:
// eq(<literal>, <ghostVariable>)
Expand All @@ -360,10 +360,11 @@ void ControlFlowGraphBuilder::operator()(Switch const& _switch)
yul::Identifier{{}, "eq"_yulname},
{*_case.value, Identifier{{}, ghostVariableName}}
});
BuiltinFunction const& equalityBuiltin = m_dialect.builtin(*equalityBuiltinHandle);
CFG::Operation& operation = m_currentBlock->operations.emplace_back(CFG::Operation{
Stack{ghostVarSlot, LiteralSlot{_case.value->value.value(), debugDataOf(*_case.value)}},
Stack{TemporarySlot{ghostCall, 0}},
CFG::BuiltinCall{debugDataOf(_case), *equalityBuiltin, ghostCall, 2},
CFG::BuiltinCall{debugDataOf(_case), equalityBuiltin, ghostCall, 2},
});
return operation.output.front();
};
Expand Down Expand Up @@ -516,24 +517,25 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal

Stack const* output = nullptr;
bool canContinue = true;
if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name))
if (std::optional<BuiltinHandle> const& builtinHandle = m_dialect.findBuiltin(_call.functionName.name.str()))
{
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<Stack>,
// operation
std::move(builtinCall)
}).output;
canContinue = builtin->controlFlowSideEffects.canContinue;
canContinue = builtin.controlFlowSideEffects.canContinue;
}
else
{
Expand Down
7 changes: 4 additions & 3 deletions libyul/backends/evm/EVMCodeTransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,14 @@ void CodeTransform::operator()(FunctionCall const& _call)
yulAssert(m_scope, "");

m_assembly.setSourceLocation(originLocationOf(_call));
if (BuiltinFunctionForEVM const* builtin = m_dialect.builtin(_call.functionName.name))
if (std::optional<BuiltinHandle> builtinHandle = m_dialect.findBuiltin(_call.functionName.name.str()))
{
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
{
Expand Down
Loading

0 comments on commit abc46f3

Please sign in to comment.