Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Treat builtins separately in Yul AST #15347

Merged
merged 6 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions libsolidity/analysis/ControlFlowBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <libsolidity/analysis/ControlFlowBuilder.h>
#include <libsolidity/ast/ASTUtils.h>
#include <libyul/AST.h>
#include <libyul/Utilities.h>
#include <libyul/backends/evm/EVMDialect.h>

using namespace solidity::langutil;
Expand Down Expand Up @@ -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();
}
}
Expand Down
9 changes: 5 additions & 4 deletions libsolidity/analysis/ViewPureChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@

#include <libsolidity/analysis/ViewPureChecker.h>
#include <libsolidity/ast/ExperimentalFeatures.h>
#include <libyul/AST.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/AST.h>
#include <libyul/Utilities.h>
#include <liblangutil/ErrorReporter.h>
#include <libevmasm/SemanticInformation.h>

Expand Down Expand Up @@ -66,9 +67,9 @@ class AssemblyViewPureChecker
void operator()(yul::FunctionCall const& _funCall)
{
if (yul::EVMDialect const* dialect = dynamic_cast<decltype(dialect)>(&m_dialect))
if (std::optional<yul::BuiltinHandle> 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);
Expand Down
4 changes: 3 additions & 1 deletion libsolidity/ast/ASTJsonImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,9 @@ ASTPointer<InlineAssembly> ASTJsonImporter::createInlineAssembly(Json const& _no
flags->emplace_back(std::make_shared<ASTString>(flag.get<std::string>()));
}
}
std::shared_ptr<yul::AST> operations = std::make_shared<yul::AST>(yul::AsmJsonImporter(dialect, m_sourceNames).createAST(member(_node, "AST")));
std::shared_ptr<yul::AST> operations = std::make_shared<yul::AST>(
yul::AsmJsonImporter(dialect, m_sourceNames).createAST(member(_node, "AST"))
);
return createASTNode<InlineAssembly>(
_node,
nullOrASTString(_node, "documentation"),
Expand Down
11 changes: 10 additions & 1 deletion libyul/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#pragma once

#include <libyul/ASTForward.h>
#include <libyul/Builtins.h>
#include <libyul/YulName.h>

#include <liblangutil/DebugData.h>
Expand Down Expand Up @@ -71,14 +72,17 @@ 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.
///
/// Multiple assignment ("x, y := f()"), where the left hand side variables each occupy
/// 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<Identifier> variableNames; std::unique_ptr<Expression> value; };
struct FunctionCall { langutil::DebugData::ConstPtr debugData; Identifier functionName; std::vector<Expression> arguments; };
struct FunctionCall { langutil::DebugData::ConstPtr debugData; FunctionName functionName; std::vector<Expression> 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
Expand Down Expand Up @@ -114,6 +118,11 @@ class AST
Block m_root;
};

bool constexpr isBuiltinFunctionCall(FunctionCall const& _functionCall) noexcept
{
return std::holds_alternative<BuiltinName>(_functionCall.functionName);
}


/// Extracts the IR source location from a Yul node.
template <class T> inline langutil::SourceLocation nativeLocationOf(T const& _node)
Expand Down
9 changes: 9 additions & 0 deletions libyul/ASTForward.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
namespace solidity::yul
{

class YulString;
using YulName = YulString;

enum class LiteralKind;
class LiteralValue;
struct Literal;
Expand All @@ -46,11 +49,17 @@ struct Continue;
struct Leave;
struct ExpressionStatement;
struct Block;
struct BuiltinName;
struct BuiltinHandle;
class AST;

struct NameWithDebugData;

using Expression = std::variant<FunctionCall, Identifier, Literal>;
using FunctionName = std::variant<Identifier, BuiltinName>;
/// 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<YulName, BuiltinHandle>;
using Statement = std::variant<ExpressionStatement, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Break, Continue, Leave, Block>;

}
48 changes: 26 additions & 22 deletions libyul/AsmAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t> numParameters;
std::optional<size_t> numReturns;
std::vector<std::optional<LiteralKind>> const* literalArguments = nullptr;

if (std::optional<BuiltinHandle> 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),
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -372,10 +370,11 @@ size_t AsmAnalyzer::operator()(FunctionCall const& _funCall)
}
}))
{
yulAssert(std::holds_alternative<Identifier>(_funCall.functionName));
if (m_resolver)
// We found a local reference, make sure there is no external reference.
m_resolver(
_funCall.functionName,
std::get<Identifier>(_funCall.functionName),
yul::IdentifierContext::NonExternal,
m_currentScope->insideFunction()
);
Expand All @@ -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.");
}
Expand All @@ -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--)
Expand All @@ -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<Literal>(arg);
Expand Down Expand Up @@ -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))
Expand All @@ -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<Literal>(arg);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion libyul/AsmAnalysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 12 additions & 2 deletions libyul/AsmJsonConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
* Converts inline assembly AST to JSON format
*/

#include <libyul/AST.h>
#include <libyul/AsmJsonConverter.h>

#include <libyul/AST.h>
#include <libyul/Dialect.h>
#include <libyul/Exceptions.h>
#include <libyul/Utilities.h>
#include <libsolutil/CommonData.h>
Expand Down Expand Up @@ -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");
Expand All @@ -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;
}
Expand Down
5 changes: 4 additions & 1 deletion libyul/AsmJsonConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ class AsmJsonConverter: public boost::static_visitor<Json>
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<size_t> _sourceIndex): m_sourceIndex(_sourceIndex) {}
AsmJsonConverter(Dialect const& _dialect, std::optional<size_t> _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;
Expand All @@ -68,6 +70,7 @@ class AsmJsonConverter: public boost::static_visitor<Json>
template <class T>
Json vectorOfVariantsToJson(std::vector<T> const& vec) const;

Dialect const& m_dialect;
std::optional<size_t> const m_sourceIndex;
};

Expand Down
13 changes: 12 additions & 1 deletion libyul/AsmJsonImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
*/

#include <libyul/AsmJsonImporter.h>

#include <libyul/AST.h>
#include <libyul/Dialect.h>
#include <libyul/Exceptions.h>
#include <libyul/Utilities.h>

Expand Down Expand Up @@ -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<std::string>();
if (std::optional<BuiltinHandle> builtinHandle = m_dialect.findBuiltin(name))
{
auto builtin = createAsmNode<BuiltinName>(functionNameNode);
builtin.handle = *builtinHandle;
functionCall.functionName = builtin;
}
else
functionCall.functionName = createIdentifier(functionNameNode);

return functionCall;
}
Expand Down
Loading