mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Add EVM opcodes as builtins.
This commit is contained in:
parent
003c170989
commit
46d9df7574
@ -169,6 +169,7 @@ Statement Parser::parseStatement()
|
|||||||
// literal,
|
// literal,
|
||||||
// identifier (might turn into label or functional assignment)
|
// identifier (might turn into label or functional assignment)
|
||||||
ElementaryOperation elementary(parseElementaryOperation());
|
ElementaryOperation elementary(parseElementaryOperation());
|
||||||
|
|
||||||
switch (currentToken())
|
switch (currentToken())
|
||||||
{
|
{
|
||||||
case Token::LParen:
|
case Token::LParen:
|
||||||
@ -243,10 +244,18 @@ Statement Parser::parseStatement()
|
|||||||
fatalParserError("Call or assignment expected.");
|
fatalParserError("Call or assignment expected.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elementary.type() == typeid(Identifier))
|
if (elementary.type() == typeid(Identifier))
|
||||||
{
|
{
|
||||||
Expression expr = boost::get<Identifier>(elementary);
|
Identifier& identifier = boost::get<Identifier>(elementary);
|
||||||
return ExpressionStatement{locationOf(expr), expr};
|
// Fallback from builtin function to Instruction for loose assembly.
|
||||||
|
if (
|
||||||
|
m_dialect.flavour == AsmFlavour::Loose &&
|
||||||
|
instructions().count(identifier.name.str())
|
||||||
|
)
|
||||||
|
return Instruction{identifier.location, instructions().at(identifier.name.str())};
|
||||||
|
else
|
||||||
|
return ExpressionStatement{identifier.location, { move(identifier) }};
|
||||||
}
|
}
|
||||||
else if (elementary.type() == typeid(Literal))
|
else if (elementary.type() == typeid(Literal))
|
||||||
{
|
{
|
||||||
@ -307,12 +316,13 @@ ForLoop Parser::parseForLoop()
|
|||||||
Expression Parser::parseExpression()
|
Expression Parser::parseExpression()
|
||||||
{
|
{
|
||||||
RecursionGuard recursionGuard(*this);
|
RecursionGuard recursionGuard(*this);
|
||||||
// In strict mode, this might parse a plain Instruction, but
|
|
||||||
// it will be converted to a FunctionalInstruction inside
|
|
||||||
// parseCall below.
|
|
||||||
ElementaryOperation operation = parseElementaryOperation();
|
ElementaryOperation operation = parseElementaryOperation();
|
||||||
if (operation.type() == typeid(Instruction))
|
if (operation.type() == typeid(FunctionCall))
|
||||||
|
return parseCall(std::move(operation));
|
||||||
|
else if (operation.type() == typeid(Instruction))
|
||||||
{
|
{
|
||||||
|
solAssert(m_dialect.flavour == AsmFlavour::Loose, "");
|
||||||
Instruction const& instr = boost::get<Instruction>(operation);
|
Instruction const& instr = boost::get<Instruction>(operation);
|
||||||
// Disallow instructions returning multiple values (and DUP/SWAP) as expression.
|
// Disallow instructions returning multiple values (and DUP/SWAP) as expression.
|
||||||
if (
|
if (
|
||||||
@ -394,8 +404,14 @@ Parser::ElementaryOperation Parser::parseElementaryOperation()
|
|||||||
literal = YulString{currentLiteral()};
|
literal = YulString{currentLiteral()};
|
||||||
// first search the set of builtins, then the instructions.
|
// first search the set of builtins, then the instructions.
|
||||||
if (m_dialect.builtin(literal))
|
if (m_dialect.builtin(literal))
|
||||||
ret = Identifier{location(), literal};
|
{
|
||||||
else if (m_dialect.flavour != AsmFlavour::Yul && instructions().count(literal.str()))
|
// For builtins we already check here that they are followed by `(`.
|
||||||
|
ret = FunctionCall{location(), Identifier{location(), literal}, {}};
|
||||||
|
advance();
|
||||||
|
expectToken(Token::LParen, false);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
else if (m_dialect.flavour == AsmFlavour::Loose && instructions().count(literal.str()))
|
||||||
{
|
{
|
||||||
dev::eth::Instruction const& instr = instructions().at(literal.str());
|
dev::eth::Instruction const& instr = instructions().at(literal.str());
|
||||||
ret = Instruction{location(), instr};
|
ret = Instruction{location(), instr};
|
||||||
@ -582,11 +598,16 @@ Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
|
|||||||
expectToken(Token::RParen);
|
expectToken(Token::RParen);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
else if (_initialOp.type() == typeid(Identifier))
|
else if (_initialOp.type() == typeid(Identifier) || _initialOp.type() == typeid(FunctionCall))
|
||||||
{
|
{
|
||||||
FunctionCall ret;
|
FunctionCall ret;
|
||||||
ret.functionName = std::move(boost::get<Identifier>(_initialOp));
|
if (_initialOp.type() == typeid(Identifier))
|
||||||
ret.location = ret.functionName.location;
|
{
|
||||||
|
ret.functionName = std::move(boost::get<Identifier>(_initialOp));
|
||||||
|
ret.location = ret.functionName.location;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ret = std::move(boost::get<FunctionCall>(_initialOp));
|
||||||
expectToken(Token::LParen);
|
expectToken(Token::LParen);
|
||||||
while (currentToken() != Token::RParen)
|
while (currentToken() != Token::RParen)
|
||||||
{
|
{
|
||||||
|
@ -56,7 +56,7 @@ public:
|
|||||||
static std::map<std::string, dev::eth::Instruction> const& instructions();
|
static std::map<std::string, dev::eth::Instruction> const& instructions();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
using ElementaryOperation = boost::variant<Instruction, Literal, Identifier>;
|
using ElementaryOperation = boost::variant<Instruction, Literal, Identifier, FunctionCall>;
|
||||||
|
|
||||||
/// Creates an inline assembly node with the given source location.
|
/// Creates an inline assembly node with the given source location.
|
||||||
template <class T> T createWithLocation(langutil::SourceLocation const& _loc = {}) const
|
template <class T> T createWithLocation(langutil::SourceLocation const& _loc = {}) const
|
||||||
@ -81,9 +81,8 @@ protected:
|
|||||||
/// Parses a functional expression that has to push exactly one stack element
|
/// Parses a functional expression that has to push exactly one stack element
|
||||||
Expression parseExpression();
|
Expression parseExpression();
|
||||||
static std::map<dev::eth::Instruction, std::string> const& instructionNames();
|
static std::map<dev::eth::Instruction, std::string> const& instructionNames();
|
||||||
/// Parses an elementary operation, i.e. a literal, identifier or instruction.
|
/// Parses an elementary operation, i.e. a literal, identifier, instruction or
|
||||||
/// This will parse instructions even in strict mode as part of the full parser
|
/// builtin functian call (only the name).
|
||||||
/// for FunctionalInstruction.
|
|
||||||
ElementaryOperation parseElementaryOperation();
|
ElementaryOperation parseElementaryOperation();
|
||||||
VariableDeclaration parseVariableDeclaration();
|
VariableDeclaration parseVariableDeclaration();
|
||||||
FunctionDefinition parseFunctionDefinition();
|
FunctionDefinition parseFunctionDefinition();
|
||||||
|
@ -51,7 +51,9 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
|
|||||||
f.parameters.resize(info.args);
|
f.parameters.resize(info.args);
|
||||||
f.returns.resize(info.ret);
|
f.returns.resize(info.ret);
|
||||||
f.movable = eth::SemanticInformation::movable(_instruction);
|
f.movable = eth::SemanticInformation::movable(_instruction);
|
||||||
|
f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction);
|
||||||
f.literalArguments = false;
|
f.literalArguments = false;
|
||||||
|
f.instruction = _instruction;
|
||||||
f.generateCode = [_instruction](
|
f.generateCode = [_instruction](
|
||||||
FunctionCall const&,
|
FunctionCall const&,
|
||||||
AbstractAssembly& _assembly,
|
AbstractAssembly& _assembly,
|
||||||
@ -83,20 +85,22 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
|
|||||||
f.movable = _movable;
|
f.movable = _movable;
|
||||||
f.literalArguments = _literalArguments;
|
f.literalArguments = _literalArguments;
|
||||||
f.sideEffectFree = _sideEffectFree;
|
f.sideEffectFree = _sideEffectFree;
|
||||||
|
f.instruction = {};
|
||||||
f.generateCode = std::move(_generateCode);
|
f.generateCode = std::move(_generateCode);
|
||||||
return {name, f};
|
return {name, f};
|
||||||
}
|
}
|
||||||
|
|
||||||
map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVersion, bool _objectAccess)
|
map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVersion, bool _objectAccess, bool _evmOpcodes)
|
||||||
{
|
{
|
||||||
map<YulString, BuiltinFunctionForEVM> builtins;
|
map<YulString, BuiltinFunctionForEVM> builtins;
|
||||||
for (auto const& instr: Parser::instructions())
|
if (_evmOpcodes)
|
||||||
if (
|
for (auto const& instr: Parser::instructions())
|
||||||
!dev::eth::isDupInstruction(instr.second) &&
|
if (
|
||||||
!dev::eth::isSwapInstruction(instr.second) &&
|
!dev::eth::isDupInstruction(instr.second) &&
|
||||||
_evmVersion.hasOpcode(instr.second)
|
!dev::eth::isSwapInstruction(instr.second) &&
|
||||||
)
|
_evmVersion.hasOpcode(instr.second)
|
||||||
builtins.emplace(createEVMFunction(instr.first, instr.second));
|
)
|
||||||
|
builtins.emplace(createEVMFunction(instr.first, instr.second));
|
||||||
|
|
||||||
if (_objectAccess)
|
if (_objectAccess)
|
||||||
{
|
{
|
||||||
@ -161,7 +165,7 @@ EVMDialect::EVMDialect(AsmFlavour _flavour, bool _objectAccess, langutil::EVMVer
|
|||||||
Dialect{_flavour},
|
Dialect{_flavour},
|
||||||
m_objectAccess(_objectAccess),
|
m_objectAccess(_objectAccess),
|
||||||
m_evmVersion(_evmVersion),
|
m_evmVersion(_evmVersion),
|
||||||
m_functions(createBuiltins(_evmVersion, _objectAccess))
|
m_functions(createBuiltins(_evmVersion, _objectAccess, _flavour != AsmFlavour::Loose))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ struct BuiltinContext
|
|||||||
|
|
||||||
struct BuiltinFunctionForEVM: BuiltinFunction
|
struct BuiltinFunctionForEVM: BuiltinFunction
|
||||||
{
|
{
|
||||||
|
boost::optional<dev::eth::Instruction> instruction;
|
||||||
/// Function to generate code for the given function call and append it to the abstract
|
/// Function to generate code for the given function call and append it to the abstract
|
||||||
/// assembly. The fourth parameter is called to visit (and generate code for) the arguments
|
/// assembly. The fourth parameter is called to visit (and generate code for) the arguments
|
||||||
/// from right to left.
|
/// from right to left.
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include <libyul/optimiser/ASTCopier.h>
|
#include <libyul/optimiser/ASTCopier.h>
|
||||||
#include <libyul/optimiser/Semantics.h>
|
#include <libyul/optimiser/Semantics.h>
|
||||||
#include <libyul/optimiser/SyntacticalEquality.h>
|
#include <libyul/optimiser/SyntacticalEquality.h>
|
||||||
|
#include <libyul/backends/evm/EVMDialect.h>
|
||||||
#include <libyul/AsmData.h>
|
#include <libyul/AsmData.h>
|
||||||
#include <libyul/Utilities.h>
|
#include <libyul/Utilities.h>
|
||||||
|
|
||||||
@ -41,14 +42,14 @@ SimplificationRule<yul::Pattern> const* SimplificationRules::findFirstMatch(
|
|||||||
map<YulString, Expression const*> const& _ssaValues
|
map<YulString, Expression const*> const& _ssaValues
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (_expr.type() != typeid(FunctionalInstruction))
|
auto instruction = instructionAndArguments(_dialect, _expr);
|
||||||
|
if (!instruction)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
static SimplificationRules rules;
|
static SimplificationRules rules;
|
||||||
assertThrow(rules.isInitialized(), OptimizerException, "Rule list not properly initialized.");
|
assertThrow(rules.isInitialized(), OptimizerException, "Rule list not properly initialized.");
|
||||||
|
|
||||||
FunctionalInstruction const& instruction = boost::get<FunctionalInstruction>(_expr);
|
for (auto const& rule: rules.m_rules[uint8_t(instruction->first)])
|
||||||
for (auto const& rule: rules.m_rules[uint8_t(instruction.instruction)])
|
|
||||||
{
|
{
|
||||||
rules.resetMatchGroups();
|
rules.resetMatchGroups();
|
||||||
if (rule.pattern.matches(_expr, _dialect, _ssaValues))
|
if (rule.pattern.matches(_expr, _dialect, _ssaValues))
|
||||||
@ -63,6 +64,20 @@ bool SimplificationRules::isInitialized() const
|
|||||||
return !m_rules[uint8_t(dev::eth::Instruction::ADD)].empty();
|
return !m_rules[uint8_t(dev::eth::Instruction::ADD)].empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::optional<std::pair<dev::eth::Instruction, vector<Expression> const*>>
|
||||||
|
SimplificationRules::instructionAndArguments(Dialect const& _dialect, Expression const& _expr)
|
||||||
|
{
|
||||||
|
if (_expr.type() == typeid(FunctionalInstruction))
|
||||||
|
return make_pair(boost::get<FunctionalInstruction>(_expr).instruction, &boost::get<FunctionalInstruction>(_expr).arguments);
|
||||||
|
else if (_expr.type() == typeid(FunctionCall))
|
||||||
|
if (auto const* dialect = dynamic_cast<EVMDialect const*>(&_dialect))
|
||||||
|
if (auto const* builtin = dialect->builtin(boost::get<FunctionCall>(_expr).functionName.name))
|
||||||
|
if (builtin->instruction)
|
||||||
|
return make_pair(*builtin->instruction, &boost::get<FunctionCall>(_expr).arguments);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void SimplificationRules::addRules(vector<SimplificationRule<Pattern>> const& _rules)
|
void SimplificationRules::addRules(vector<SimplificationRule<Pattern>> const& _rules)
|
||||||
{
|
{
|
||||||
for (auto const& r: _rules)
|
for (auto const& r: _rules)
|
||||||
@ -139,14 +154,12 @@ bool Pattern::matches(
|
|||||||
}
|
}
|
||||||
else if (m_kind == PatternKind::Operation)
|
else if (m_kind == PatternKind::Operation)
|
||||||
{
|
{
|
||||||
if (expr->type() != typeid(FunctionalInstruction))
|
auto instrAndArgs = SimplificationRules::instructionAndArguments(_dialect, *expr);
|
||||||
|
if (!instrAndArgs || m_instruction != instrAndArgs->first)
|
||||||
return false;
|
return false;
|
||||||
FunctionalInstruction const& instr = boost::get<FunctionalInstruction>(*expr);
|
assertThrow(m_arguments.size() == instrAndArgs->second->size(), OptimizerException, "");
|
||||||
if (m_instruction != instr.instruction)
|
|
||||||
return false;
|
|
||||||
assertThrow(m_arguments.size() == instr.arguments.size(), OptimizerException, "");
|
|
||||||
for (size_t i = 0; i < m_arguments.size(); ++i)
|
for (size_t i = 0; i < m_arguments.size(); ++i)
|
||||||
if (!m_arguments[i].matches(instr.arguments.at(i), _dialect, _ssaValues))
|
if (!m_arguments[i].matches(instrAndArgs->second->at(i), _dialect, _ssaValues))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include <libyul/AsmData.h>
|
#include <libyul/AsmData.h>
|
||||||
|
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -55,6 +56,10 @@ public:
|
|||||||
/// Checks whether the rulelist is non-empty. This is usually enforced
|
/// Checks whether the rulelist is non-empty. This is usually enforced
|
||||||
/// by the constructor, but we had some issues with static initialization.
|
/// by the constructor, but we had some issues with static initialization.
|
||||||
bool isInitialized() const;
|
bool isInitialized() const;
|
||||||
|
|
||||||
|
static boost::optional<std::pair<dev::eth::Instruction, std::vector<Expression> const*>>
|
||||||
|
instructionAndArguments(Dialect const& _dialect, Expression const& _expr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addRules(std::vector<dev::eth::SimplificationRule<Pattern>> const& _rules);
|
void addRules(std::vector<dev::eth::SimplificationRule<Pattern>> const& _rules);
|
||||||
void addRule(dev::eth::SimplificationRule<Pattern> const& _rule);
|
void addRule(dev::eth::SimplificationRule<Pattern> const& _rule);
|
||||||
|
@ -516,11 +516,11 @@ BOOST_AUTO_TEST_CASE(no_opcodes_in_strict)
|
|||||||
{
|
{
|
||||||
BOOST_CHECK(successParse("{ pop(callvalue) }"));
|
BOOST_CHECK(successParse("{ pop(callvalue) }"));
|
||||||
BOOST_CHECK(successParse("{ callvalue pop }"));
|
BOOST_CHECK(successParse("{ callvalue pop }"));
|
||||||
CHECK_STRICT_ERROR("{ pop(callvalue) }", ParserError, "Non-functional instructions are not allowed in this context.");
|
CHECK_STRICT_ERROR("{ pop(callvalue) }", ParserError, "Expected '(' but got ')'");
|
||||||
CHECK_STRICT_ERROR("{ callvalue pop }", ParserError, "Call or assignment expected");
|
CHECK_STRICT_ERROR("{ callvalue pop }", ParserError, "Expected '(' but got identifier");
|
||||||
SUCCESS_STRICT("{ pop(callvalue()) }");
|
SUCCESS_STRICT("{ pop(callvalue()) }");
|
||||||
BOOST_CHECK(successParse("{ switch callvalue case 0 {} }"));
|
BOOST_CHECK(successParse("{ switch callvalue case 0 {} }"));
|
||||||
CHECK_STRICT_ERROR("{ switch callvalue case 0 {} }", ParserError, "Non-functional instructions are not allowed in this context.");
|
CHECK_STRICT_ERROR("{ switch callvalue case 0 {} }", ParserError, "Expected '(' but got reserved keyword 'case'");
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(no_labels_in_strict)
|
BOOST_AUTO_TEST_CASE(no_labels_in_strict)
|
||||||
@ -546,7 +546,6 @@ BOOST_AUTO_TEST_CASE(no_dup_swap_in_strict)
|
|||||||
BOOST_CHECK(successParse("{ dup2 pop }"));
|
BOOST_CHECK(successParse("{ dup2 pop }"));
|
||||||
CHECK_STRICT_ERROR("{ dup2 pop }", ParserError, "Call or assignment expected.");
|
CHECK_STRICT_ERROR("{ dup2 pop }", ParserError, "Call or assignment expected.");
|
||||||
CHECK_PARSE_ERROR("{ switch dup1 case 0 {} }", ParserError, "Instruction \"dup1\" not allowed in this context");
|
CHECK_PARSE_ERROR("{ switch dup1 case 0 {} }", ParserError, "Instruction \"dup1\" not allowed in this context");
|
||||||
CHECK_STRICT_ERROR("{ switch dup1 case 0 {} }", ParserError, "Instruction \"dup1\" not allowed in this context");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
Loading…
Reference in New Issue
Block a user