Add EVM opcodes as builtins.

This commit is contained in:
chriseth 2019-05-16 21:19:50 +02:00
parent 003c170989
commit 46d9df7574
7 changed files with 79 additions and 37 deletions

View File

@ -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;
if (_initialOp.type() == typeid(Identifier))
{
ret.functionName = std::move(boost::get<Identifier>(_initialOp)); ret.functionName = std::move(boost::get<Identifier>(_initialOp));
ret.location = ret.functionName.location; 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)
{ {

View File

@ -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();

View File

@ -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,13 +85,15 @@ 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;
if (_evmOpcodes)
for (auto const& instr: Parser::instructions()) for (auto const& instr: Parser::instructions())
if ( if (
!dev::eth::isDupInstruction(instr.second) && !dev::eth::isDupInstruction(instr.second) &&
@ -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))
{ {
} }

View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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()