Split inline assembly into loose and strict flavours.

This commit is contained in:
chriseth 2017-12-13 14:40:54 +01:00 committed by Alex Beregszaszi
parent 2548228b36
commit 124190336b
12 changed files with 78 additions and 38 deletions

View File

@ -207,7 +207,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
// Will be re-generated later with correct information // Will be re-generated later with correct information
assembly::AsmAnalysisInfo analysisInfo; assembly::AsmAnalysisInfo analysisInfo;
assembly::AsmAnalyzer(analysisInfo, errorsIgnored, false, resolver).analyze(_inlineAssembly.operations()); assembly::AsmAnalyzer(analysisInfo, errorsIgnored, assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations());
return false; return false;
} }

View File

@ -873,7 +873,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
assembly::AsmAnalyzer analyzer( assembly::AsmAnalyzer analyzer(
*_inlineAssembly.annotation().analysisInfo, *_inlineAssembly.annotation().analysisInfo,
m_errorReporter, m_errorReporter,
false, assembly::AsmFlavour::Loose,
identifierAccess identifierAccess
); );
if (!analyzer.analyze(_inlineAssembly.operations())) if (!analyzer.analyze(_inlineAssembly.operations()))

View File

@ -319,14 +319,19 @@ void CompilerContext::appendInlineAssembly(
ErrorList errors; ErrorList errors;
ErrorReporter errorReporter(errors); ErrorReporter errorReporter(errors);
auto scanner = make_shared<Scanner>(CharStream(_assembly), "--CODEGEN--"); auto scanner = make_shared<Scanner>(CharStream(_assembly), "--CODEGEN--");
auto parserResult = assembly::Parser(errorReporter).parse(scanner); auto parserResult = assembly::Parser(errorReporter, assembly::AsmFlavour::Strict).parse(scanner);
#ifdef SOL_OUTPUT_ASM #ifdef SOL_OUTPUT_ASM
cout << assembly::AsmPrinter()(*parserResult) << endl; cout << assembly::AsmPrinter()(*parserResult) << endl;
#endif #endif
assembly::AsmAnalysisInfo analysisInfo; assembly::AsmAnalysisInfo analysisInfo;
bool analyzerResult = false; bool analyzerResult = false;
if (parserResult) if (parserResult)
analyzerResult = assembly::AsmAnalyzer(analysisInfo, errorReporter, false, identifierAccess.resolve).analyze(*parserResult); analyzerResult = assembly::AsmAnalyzer(
analysisInfo,
errorReporter,
assembly::AsmFlavour::Strict,
identifierAccess.resolve
).analyze(*parserResult);
if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) if (!parserResult || !errorReporter.errors().empty() || !analyzerResult)
{ {
string message = string message =

View File

@ -187,8 +187,8 @@ public:
CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; } CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; }
CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; } CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; }
/// Appends inline assembly. @a _replacements are string-matching replacements that are performed /// Appends inline assembly (strict mode).
/// prior to parsing the inline assembly. /// @a _replacements are string-matching replacements that are performed prior to parsing the inline assembly.
/// @param _localVariables assigns stack positions to variables with the last one being the stack top /// @param _localVariables assigns stack positions to variables with the last one being the stack top
/// @param _system if true, this is a "system-level" assembly where all functions use named labels. /// @param _system if true, this is a "system-level" assembly where all functions use named labels.
void appendInlineAssembly( void appendInlineAssembly(

View File

@ -54,7 +54,7 @@ bool AsmAnalyzer::analyze(Block const& _block)
bool AsmAnalyzer::operator()(Label const& _label) bool AsmAnalyzer::operator()(Label const& _label)
{ {
solAssert(!m_julia, ""); solAssert(m_flavour == AsmFlavour::Loose, "");
m_info.stackHeightInfo[&_label] = m_stackHeight; m_info.stackHeightInfo[&_label] = m_stackHeight;
warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location); warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location);
return true; return true;
@ -62,7 +62,7 @@ bool AsmAnalyzer::operator()(Label const& _label)
bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction) bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction)
{ {
solAssert(!m_julia, ""); solAssert(m_flavour == AsmFlavour::Loose, "");
auto const& info = instructionInfo(_instruction.instruction); auto const& info = instructionInfo(_instruction.instruction);
m_stackHeight += info.ret - info.args; m_stackHeight += info.ret - info.args;
m_info.stackHeightInfo[&_instruction] = m_stackHeight; m_info.stackHeightInfo[&_instruction] = m_stackHeight;
@ -141,7 +141,7 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
{ {
solAssert(!m_julia, ""); solAssert(m_flavour != AsmFlavour::IULIA, "");
bool success = true; bool success = true;
for (auto const& arg: _instr.arguments | boost::adaptors::reversed) for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
if (!expectExpression(arg)) if (!expectExpression(arg))
@ -157,17 +157,18 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement) bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement)
{ {
// size_t initialStackHeight = m_stackHeight; size_t initialStackHeight = m_stackHeight;
bool success = boost::apply_visitor(*this, _statement.expression); bool success = boost::apply_visitor(*this, _statement.expression);
// if (!expectDeposit(0, initialStackHeight, _statement.location)) if (m_flavour != AsmFlavour::Loose)
// success = false; if (!expectDeposit(0, initialStackHeight, _statement.location))
success = false;
m_info.stackHeightInfo[&_statement] = m_stackHeight; m_info.stackHeightInfo[&_statement] = m_stackHeight;
return success; return success;
} }
bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment) bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment)
{ {
solAssert(!m_julia, ""); solAssert(m_flavour == AsmFlavour::Loose, "");
bool success = checkAssignment(_assignment.variableName, size_t(-1)); bool success = checkAssignment(_assignment.variableName, size_t(-1));
m_info.stackHeightInfo[&_assignment] = m_stackHeight; m_info.stackHeightInfo[&_assignment] = m_stackHeight;
return success; return success;
@ -507,7 +508,7 @@ Scope& AsmAnalyzer::scope(Block const* _block)
} }
void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location) void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location)
{ {
if (!m_julia) if (m_flavour != AsmFlavour::IULIA)
return; return;
if (!builtinTypes.count(type)) if (!builtinTypes.count(type))

View File

@ -54,9 +54,9 @@ public:
explicit AsmAnalyzer( explicit AsmAnalyzer(
AsmAnalysisInfo& _analysisInfo, AsmAnalysisInfo& _analysisInfo,
ErrorReporter& _errorReporter, ErrorReporter& _errorReporter,
bool _julia = false, AsmFlavour _flavour = AsmFlavour::Loose,
julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver() julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver()
): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_julia(_julia) {} ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_flavour(_flavour) {}
bool analyze(assembly::Block const& _block); bool analyze(assembly::Block const& _block);
@ -97,7 +97,7 @@ private:
std::set<Scope::Variable const*> m_activeVariables; std::set<Scope::Variable const*> m_activeVariables;
AsmAnalysisInfo& m_info; AsmAnalysisInfo& m_info;
ErrorReporter& m_errorReporter; ErrorReporter& m_errorReporter;
bool m_julia = false; AsmFlavour m_flavour = AsmFlavour::Loose;
}; };
} }

View File

@ -53,6 +53,13 @@ struct TypedName;
using Expression = boost::variant<FunctionalInstruction, FunctionCall, Identifier, Literal>; using Expression = boost::variant<FunctionalInstruction, FunctionCall, Identifier, Literal>;
using Statement = boost::variant<ExpressionStatement, Instruction, Label, StackAssignment, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>; using Statement = boost::variant<ExpressionStatement, Instruction, Label, StackAssignment, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>;
enum class AsmFlavour
{
Loose, // no types, EVM instructions as function, jumps and direct stack manipulations
Strict, // no types, EVM instructions as functions, but no jumps and no direct stack manipulations
IULIA // same as Strict mode with types
};
} }
} }
} }

View File

@ -103,14 +103,14 @@ assembly::Statement Parser::parseStatement()
return parseForLoop(); return parseForLoop();
case Token::Assign: case Token::Assign:
{ {
if (m_julia) if (m_flavour != AsmFlavour::Loose)
break; break;
assembly::StackAssignment assignment = createWithLocation<assembly::StackAssignment>(); assembly::StackAssignment assignment = createWithLocation<assembly::StackAssignment>();
advance(); advance();
expectToken(Token::Colon); expectToken(Token::Colon);
assignment.variableName.location = location(); assignment.variableName.location = location();
assignment.variableName.name = currentLiteral(); assignment.variableName.name = currentLiteral();
if (!m_julia && instructions().count(assignment.variableName.name)) if (instructions().count(assignment.variableName.name))
fatalParserError("Identifier expected, got instruction name."); fatalParserError("Identifier expected, got instruction name.");
assignment.location.end = endPosition(); assignment.location.end = endPosition();
expectToken(Token::Identifier); expectToken(Token::Identifier);
@ -170,7 +170,7 @@ assembly::Statement Parser::parseStatement()
if (currentToken() == Token::Assign && peekNextToken() != Token::Colon) if (currentToken() == Token::Assign && peekNextToken() != Token::Colon)
{ {
assembly::Assignment assignment = createWithLocation<assembly::Assignment>(identifier.location); assembly::Assignment assignment = createWithLocation<assembly::Assignment>(identifier.location);
if (!m_julia && instructions().count(identifier.name)) if (m_flavour != AsmFlavour::IULIA && instructions().count(identifier.name))
fatalParserError("Cannot use instruction names for identifier names."); fatalParserError("Cannot use instruction names for identifier names.");
advance(); advance();
assignment.variableNames.emplace_back(identifier); assignment.variableNames.emplace_back(identifier);
@ -181,7 +181,7 @@ assembly::Statement Parser::parseStatement()
else else
{ {
// label // label
if (m_julia) if (m_flavour != AsmFlavour::Loose)
fatalParserError("Labels are not supported."); fatalParserError("Labels are not supported.");
Label label = createWithLocation<Label>(identifier.location); Label label = createWithLocation<Label>(identifier.location);
label.name = identifier.name; label.name = identifier.name;
@ -189,7 +189,7 @@ assembly::Statement Parser::parseStatement()
} }
} }
default: default:
if (m_julia) if (m_flavour != AsmFlavour::Loose)
fatalParserError("Call or assignment expected."); fatalParserError("Call or assignment expected.");
break; break;
} }
@ -247,13 +247,17 @@ assembly::ForLoop Parser::parseForLoop()
assembly::Expression Parser::parseExpression() assembly::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(Instruction))
{ {
Instruction const& instr = boost::get<Instruction>(operation); Instruction const& instr = boost::get<Instruction>(operation);
// Enforce functional notation for instructions requiring multiple arguments. // Enforce functional notation for instructions requiring multiple arguments.
int args = instructionInfo(instr.instruction).args; int args = instructionInfo(instr.instruction).args;
if (args > 0 && currentToken() != Token::LParen) bool requireFunctionalNotation = (args > 0 || m_flavour != AsmFlavour::Loose);
if (requireFunctionalNotation && currentToken() != Token::LParen)
fatalParserError(string( fatalParserError(string(
"Expected token \"(\" (\"" + "Expected token \"(\" (\"" +
instructionNames().at(instr.instruction) + instructionNames().at(instr.instruction) +
@ -278,6 +282,7 @@ assembly::Expression Parser::parseExpression()
else if (operation.type() == typeid(Instruction)) else if (operation.type() == typeid(Instruction))
{ {
// Instructions not taking arguments are allowed as expressions. // Instructions not taking arguments are allowed as expressions.
solAssert(m_flavour == AsmFlavour::Loose, "");
Instruction& instr = boost::get<Instruction>(operation); Instruction& instr = boost::get<Instruction>(operation);
return FunctionalInstruction{std::move(instr.location), instr.instruction, {}}; return FunctionalInstruction{std::move(instr.location), instr.instruction, {}};
} }
@ -351,7 +356,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation()
else else
literal = currentLiteral(); literal = currentLiteral();
// first search the set of instructions. // first search the set of instructions.
if (!m_julia && instructions().count(literal)) if (m_flavour != AsmFlavour::IULIA && instructions().count(literal))
{ {
dev::solidity::Instruction const& instr = instructions().at(literal); dev::solidity::Instruction const& instr = instructions().at(literal);
ret = Instruction{location(), instr}; ret = Instruction{location(), instr};
@ -392,7 +397,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation()
"" ""
}; };
advance(); advance();
if (m_julia) if (m_flavour == AsmFlavour::IULIA)
{ {
expectToken(Token::Colon); expectToken(Token::Colon);
literal.location.end = endPosition(); literal.location.end = endPosition();
@ -405,7 +410,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation()
} }
default: default:
fatalParserError( fatalParserError(
m_julia ? m_flavour == AsmFlavour::IULIA ?
"Literal or identifier expected." : "Literal or identifier expected." :
"Literal, identifier or instruction expected." "Literal, identifier or instruction expected."
); );
@ -475,7 +480,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
if (_initialOp.type() == typeid(Instruction)) if (_initialOp.type() == typeid(Instruction))
{ {
solAssert(!m_julia, "Instructions are invalid in JULIA"); solAssert(m_flavour != AsmFlavour::IULIA, "Instructions are invalid in JULIA");
Instruction& instruction = boost::get<Instruction>(_initialOp); Instruction& instruction = boost::get<Instruction>(_initialOp);
FunctionalInstruction ret; FunctionalInstruction ret;
ret.instruction = instruction.instruction; ret.instruction = instruction.instruction;
@ -546,7 +551,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
} }
else else
fatalParserError( fatalParserError(
m_julia ? m_flavour == AsmFlavour::IULIA ?
"Function name expected." : "Function name expected." :
"Assembly instruction or function name required in front of \"(\")" "Assembly instruction or function name required in front of \"(\")"
); );
@ -559,7 +564,7 @@ TypedName Parser::parseTypedName()
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
TypedName typedName = createWithLocation<TypedName>(); TypedName typedName = createWithLocation<TypedName>();
typedName.name = expectAsmIdentifier(); typedName.name = expectAsmIdentifier();
if (m_julia) if (m_flavour == AsmFlavour::IULIA)
{ {
expectToken(Token::Colon); expectToken(Token::Colon);
typedName.location.end = endPosition(); typedName.location.end = endPosition();
@ -571,7 +576,7 @@ TypedName Parser::parseTypedName()
string Parser::expectAsmIdentifier() string Parser::expectAsmIdentifier()
{ {
string name = currentLiteral(); string name = currentLiteral();
if (m_julia) if (m_flavour == AsmFlavour::IULIA)
{ {
switch (currentToken()) switch (currentToken())
{ {

View File

@ -37,7 +37,8 @@ namespace assembly
class Parser: public ParserBase class Parser: public ParserBase
{ {
public: public:
explicit Parser(ErrorReporter& _errorReporter, bool _julia = false): ParserBase(_errorReporter), m_julia(_julia) {} explicit Parser(ErrorReporter& _errorReporter, AsmFlavour _flavour = AsmFlavour::Loose):
ParserBase(_errorReporter), m_flavour(_flavour) {}
/// Parses an inline assembly block starting with `{` and ending with `}`. /// Parses an inline assembly block starting with `{` and ending with `}`.
/// @returns an empty shared pointer on error. /// @returns an empty shared pointer on error.
@ -70,6 +71,9 @@ protected:
assembly::Expression parseExpression(); assembly::Expression parseExpression();
static std::map<std::string, dev::solidity::Instruction> const& instructions(); static std::map<std::string, dev::solidity::Instruction> const& instructions();
static std::map<dev::solidity::Instruction, std::string> const& instructionNames(); static std::map<dev::solidity::Instruction, std::string> const& instructionNames();
/// Parses an elementary operation, i.e. a literal, identifier or instruction.
/// This will parse instructions even in strict mode as part of the full parser
/// for FunctionalInstruction.
ElementaryOperation parseElementaryOperation(); ElementaryOperation parseElementaryOperation();
VariableDeclaration parseVariableDeclaration(); VariableDeclaration parseVariableDeclaration();
FunctionDefinition parseFunctionDefinition(); FunctionDefinition parseFunctionDefinition();
@ -80,7 +84,7 @@ protected:
static bool isValidNumberLiteral(std::string const& _literal); static bool isValidNumberLiteral(std::string const& _literal);
private: private:
bool m_julia = false; AsmFlavour m_flavour = AsmFlavour::Loose;
}; };
} }

View File

@ -38,6 +38,23 @@ using namespace std;
using namespace dev; using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
namespace
{
assembly::AsmFlavour languageToAsmFlavour(AssemblyStack::Language _language)
{
switch (_language)
{
case AssemblyStack::Language::Assembly:
return assembly::AsmFlavour::Loose;
case AssemblyStack::Language::JULIA:
return assembly::AsmFlavour::IULIA;
}
solAssert(false, "");
return assembly::AsmFlavour::IULIA;
}
}
Scanner const& AssemblyStack::scanner() const Scanner const& AssemblyStack::scanner() const
{ {
@ -50,7 +67,7 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string
m_errors.clear(); m_errors.clear();
m_analysisSuccessful = false; m_analysisSuccessful = false;
m_scanner = make_shared<Scanner>(CharStream(_source), _sourceName); m_scanner = make_shared<Scanner>(CharStream(_source), _sourceName);
m_parserResult = assembly::Parser(m_errorReporter, m_language == Language::JULIA).parse(m_scanner); m_parserResult = assembly::Parser(m_errorReporter, languageToAsmFlavour(m_language)).parse(m_scanner);
if (!m_errorReporter.errors().empty()) if (!m_errorReporter.errors().empty())
return false; return false;
solAssert(m_parserResult, ""); solAssert(m_parserResult, "");
@ -72,7 +89,7 @@ bool AssemblyStack::analyze(assembly::Block const& _block, Scanner const* _scann
bool AssemblyStack::analyzeParsed() bool AssemblyStack::analyzeParsed()
{ {
m_analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); m_analysisInfo = make_shared<assembly::AsmAnalysisInfo>();
assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, m_language == Language::JULIA); assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, languageToAsmFlavour(m_language));
m_analysisSuccessful = analyzer.analyze(*m_parserResult); m_analysisSuccessful = analyzer.analyze(*m_parserResult);
return m_analysisSuccessful; return m_analysisSuccessful;
} }

View File

@ -52,15 +52,16 @@ void dev::julia::test::printErrors(ErrorList const& _errors, Scanner const& _sca
pair<shared_ptr<Block>, shared_ptr<assembly::AsmAnalysisInfo>> dev::julia::test::parse(string const& _source, bool _julia) pair<shared_ptr<Block>, shared_ptr<assembly::AsmAnalysisInfo>> dev::julia::test::parse(string const& _source, bool _julia)
{ {
auto flavour = _julia ? assembly::AsmFlavour::IULIA : assembly::AsmFlavour::Strict;
ErrorList errors; ErrorList errors;
ErrorReporter errorReporter(errors); ErrorReporter errorReporter(errors);
auto scanner = make_shared<Scanner>(CharStream(_source), ""); auto scanner = make_shared<Scanner>(CharStream(_source), "");
auto parserResult = assembly::Parser(errorReporter, _julia).parse(scanner); auto parserResult = assembly::Parser(errorReporter, flavour).parse(scanner);
if (parserResult) if (parserResult)
{ {
BOOST_REQUIRE(errorReporter.errors().empty()); BOOST_REQUIRE(errorReporter.errors().empty());
auto analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); auto analysisInfo = make_shared<assembly::AsmAnalysisInfo>();
assembly::AsmAnalyzer analyzer(*analysisInfo, errorReporter, _julia); assembly::AsmAnalyzer analyzer(*analysisInfo, errorReporter, flavour);
if (analyzer.analyze(*parserResult)) if (analyzer.analyze(*parserResult))
{ {
BOOST_REQUIRE(errorReporter.errors().empty()); BOOST_REQUIRE(errorReporter.errors().empty());

View File

@ -52,11 +52,11 @@ bool parse(string const& _source, ErrorReporter& errorReporter)
try try
{ {
auto scanner = make_shared<Scanner>(CharStream(_source)); auto scanner = make_shared<Scanner>(CharStream(_source));
auto parserResult = assembly::Parser(errorReporter, true).parse(scanner); auto parserResult = assembly::Parser(errorReporter, assembly::AsmFlavour::IULIA).parse(scanner);
if (parserResult) if (parserResult)
{ {
assembly::AsmAnalysisInfo analysisInfo; assembly::AsmAnalysisInfo analysisInfo;
return (assembly::AsmAnalyzer(analysisInfo, errorReporter, true)).analyze(*parserResult); return (assembly::AsmAnalyzer(analysisInfo, errorReporter, assembly::AsmFlavour::IULIA)).analyze(*parserResult);
} }
} }
catch (FatalError const&) catch (FatalError const&)