[Yul] Adds break/continue statements and some general tests for for-loop syntax.

This commit is contained in:
Christian Parpart 2019-03-04 15:38:05 +01:00 committed by Christian Parpart
parent 4704ef843d
commit 05e2d362c8
24 changed files with 277 additions and 10 deletions

View File

@ -94,6 +94,12 @@ public:
(*this)(_for.body);
(*this)(_for.post);
}
void operator()(yul::Break const&)
{
}
void operator()(yul::Continue const&)
{
}
void operator()(yul::Block const& _block)
{
for (auto const& s: _block.statements)

View File

@ -25,6 +25,7 @@
#include <libyul/AsmScope.h>
#include <libyul/AsmAnalysisInfo.h>
#include <libyul/Utilities.h>
#include <libyul/Exceptions.h>
#include <liblangutil/ErrorReporter.h>
@ -33,6 +34,7 @@
#include <memory>
#include <functional>
#include <utility>
using namespace std;
using namespace dev;
@ -472,7 +474,7 @@ bool AsmAnalyzer::operator()(ForLoop const& _for)
{
solAssert(_for.condition, "");
Scope* originalScope = m_currentScope;
Scope* outerScope = m_currentScope;
bool success = true;
if (!(*this)(_for.pre))
@ -485,18 +487,37 @@ bool AsmAnalyzer::operator()(ForLoop const& _for)
if (!expectExpression(*_for.condition))
success = false;
m_stackHeight--;
// backup outer for-loop & create new state
auto outerForLoop = m_currentForLoop;
m_currentForLoop = &_for;
if (!(*this)(_for.body))
success = false;
if (!(*this)(_for.post))
success = false;
m_stackHeight -= scope(&_for.pre).numberOfVariables();
m_info.stackHeightInfo[&_for] = m_stackHeight;
m_currentScope = originalScope;
m_currentScope = outerScope;
m_currentForLoop = outerForLoop;
return success;
}
bool AsmAnalyzer::operator()(Break const& _break)
{
m_info.stackHeightInfo[&_break] = m_stackHeight;
return true;
}
bool AsmAnalyzer::operator()(Continue const& _continue)
{
m_info.stackHeightInfo[&_continue] = m_stackHeight;
return true;
}
bool AsmAnalyzer::operator()(Block const& _block)
{
bool success = true;

View File

@ -34,6 +34,7 @@
#include <boost/optional.hpp>
#include <functional>
#include <list>
#include <memory>
namespace langutil
@ -93,6 +94,8 @@ public:
bool operator()(If const& _if);
bool operator()(Switch const& _switch);
bool operator()(ForLoop const& _forLoop);
bool operator()(Break const&);
bool operator()(Continue const&);
bool operator()(Block const& _block);
private:
@ -124,6 +127,7 @@ private:
langutil::EVMVersion m_evmVersion;
std::shared_ptr<Dialect> m_dialect;
boost::optional<langutil::Error::Type> m_errorTypeForLoose;
ForLoop const* m_currentForLoop = nullptr;
};
}

View File

@ -78,6 +78,10 @@ struct Case { langutil::SourceLocation location; std::unique_ptr<Literal> value;
/// Switch statement
struct Switch { langutil::SourceLocation location; std::unique_ptr<Expression> expression; std::vector<Case> cases; };
struct ForLoop { langutil::SourceLocation location; Block pre; std::unique_ptr<Expression> condition; Block post; Block body; };
/// Break statement (valid within for loop)
struct Break { langutil::SourceLocation location; };
/// Continue statement (valid within for loop)
struct Continue { langutil::SourceLocation location; };
struct LocationExtractor: boost::static_visitor<langutil::SourceLocation>
{

View File

@ -41,12 +41,14 @@ struct If;
struct Switch;
struct Case;
struct ForLoop;
struct Break;
struct Continue;
struct ExpressionStatement;
struct Block;
struct TypedName;
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, Break, Continue, Block>;
}

View File

@ -23,6 +23,7 @@
#include <libyul/AsmParser.h>
#include <liblangutil/Scanner.h>
#include <liblangutil/ErrorReporter.h>
#include <libdevcore/Common.h>
#include <boost/algorithm/string.hpp>
@ -104,6 +105,32 @@ Statement Parser::parseStatement()
}
case Token::For:
return parseForLoop();
case Token::Break:
if (m_insideForLoopBody)
{
auto stmt = Statement{ createWithLocation<Break>() };
m_scanner->next();
return stmt;
}
else
{
m_errorReporter.syntaxError(location(), "Keyword break outside for-loop body is not allowed.");
m_scanner->next();
return {};
}
case Token::Continue:
if (m_insideForLoopBody)
{
auto stmt = Statement{ createWithLocation<Continue>() };
m_scanner->next();
return stmt;
}
else
{
m_errorReporter.syntaxError(location(), "Keyword continue outside for-loop body is not allowed.");
m_scanner->next();
return {};
}
case Token::Assign:
{
if (m_dialect->flavour != AsmFlavour::Loose)
@ -243,13 +270,19 @@ Case Parser::parseCase()
ForLoop Parser::parseForLoop()
{
bool outerForLoopBody = m_insideForLoopBody;
m_insideForLoopBody = false;
RecursionGuard recursionGuard(*this);
ForLoop forLoop = createWithLocation<ForLoop>();
expectToken(Token::For);
forLoop.pre = parseBlock();
forLoop.condition = make_unique<Expression>(parseExpression());
forLoop.post = parseBlock();
m_insideForLoopBody = true;
forLoop.body = parseBlock();
m_insideForLoopBody = outerForLoopBody;
forLoop.location.end = forLoop.body.location.end;
return forLoop;
}
@ -455,6 +488,9 @@ VariableDeclaration Parser::parseVariableDeclaration()
FunctionDefinition Parser::parseFunctionDefinition()
{
RecursionGuard recursionGuard(*this);
auto outerForLoopBody = m_insideForLoopBody;
m_insideForLoopBody = false;
ScopeGuard restoreInsideForLoopBody{[&]() { m_insideForLoopBody = outerForLoopBody; }};
FunctionDefinition funDef = createWithLocation<FunctionDefinition>();
expectToken(Token::Function);
funDef.name = expectAsmIdentifier();

View File

@ -39,7 +39,7 @@ class Parser: public langutil::ParserBase
{
public:
explicit Parser(langutil::ErrorReporter& _errorReporter, std::shared_ptr<Dialect> _dialect):
ParserBase(_errorReporter), m_dialect(std::move(_dialect)) {}
ParserBase(_errorReporter), m_dialect(std::move(_dialect)), m_insideForLoopBody{false} {}
/// Parses an inline assembly block starting with `{` and ending with `}`.
/// @param _reuseScanner if true, do check for end of input after the `}`.
@ -87,6 +87,7 @@ protected:
private:
std::shared_ptr<Dialect> m_dialect;
bool m_insideForLoopBody;
};
}

View File

@ -224,6 +224,16 @@ string AsmPrinter::operator()(ForLoop const& _forLoop) const
return out;
}
string AsmPrinter::operator()(Break const&) const
{
return "break";
}
string AsmPrinter::operator()(Continue const&) const
{
return "continue";
}
string AsmPrinter::operator()(Block const& _block) const
{
if (_block.statements.empty())

View File

@ -50,6 +50,8 @@ public:
std::string operator()(If const& _if) const;
std::string operator()(Switch const& _switch) const;
std::string operator()(ForLoop const& _forLoop) const;
std::string operator()(Break const& _break) const;
std::string operator()(Continue const& _continue) const;
std::string operator()(Block const& _block) const;
private:

View File

@ -63,6 +63,8 @@ public:
bool operator()(If const& _if);
bool operator()(Switch const& _switch);
bool operator()(ForLoop const& _forLoop);
bool operator()(Break const&) { return true; }
bool operator()(Continue const&) { return true; }
bool operator()(Block const& _block);
private:

View File

@ -69,7 +69,6 @@ void VariableReferenceCounter::operator()(ForLoop const& _forLoop)
m_scope = originalScope;
}
void VariableReferenceCounter::operator()(Block const& _block)
{
Scope* originalScope = m_scope;
@ -92,7 +91,6 @@ void VariableReferenceCounter::increaseRefIfFound(YulString _variableName)
));
}
CodeTransform::CodeTransform(
AbstractAssembly& _assembly,
AsmAnalysisInfo& _analysisInfo,
@ -605,11 +603,9 @@ void CodeTransform::operator()(ForLoop const& _forLoop)
visitStatements(_forLoop.pre.statements);
// TODO: When we implement break and continue, the labels and the stack heights at that point
// have to be stored in a stack.
AbstractAssembly::LabelID loopStart = m_assembly.newLabelId();
AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId();
AbstractAssembly::LabelID postPart = m_assembly.newLabelId();
AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId();
m_assembly.setSourceLocation(_forLoop.location);
m_assembly.appendLabel(loopStart);
@ -619,6 +615,8 @@ void CodeTransform::operator()(ForLoop const& _forLoop)
m_assembly.appendInstruction(solidity::Instruction::ISZERO);
m_assembly.appendJumpToIf(loopEnd);
int const stackHeightBody = m_assembly.stackHeight();
m_context->forLoopStack.emplace(Context::ForLoopLabels{ {postPart, stackHeightBody}, {loopEnd, stackHeightBody} });
(*this)(_forLoop.body);
m_assembly.setSourceLocation(_forLoop.location);
@ -631,7 +629,19 @@ void CodeTransform::operator()(ForLoop const& _forLoop)
m_assembly.appendLabel(loopEnd);
finalizeBlock(_forLoop.pre, stackStartHeight);
m_context->forLoopStack.pop();
m_scope = originalScope;
checkStackHeight(&_forLoop);
}
void CodeTransform::operator()(Break const&)
{
yulAssert(false, "Code generation for break statement in Yul is not implemented yet.");
}
void CodeTransform::operator()(Continue const&)
{
yulAssert(false, "Code generation for continue statement in Yul is not implemented yet.");
}
void CodeTransform::operator()(Block const& _block)

View File

@ -30,6 +30,8 @@
#include <boost/variant.hpp>
#include <boost/optional.hpp>
#include <stack>
namespace langutil
{
class ErrorReporter;
@ -57,6 +59,20 @@ struct CodeTransformContext
std::map<Scope::Function const*, AbstractAssembly::LabelID> functionEntryIDs;
std::map<Scope::Variable const*, int> variableStackHeights;
std::map<Scope::Variable const*, unsigned> variableReferences;
struct JumpInfo
{
AbstractAssembly::LabelID label; ///< Jump's LabelID to jump to.
int targetStackHeight; ///< Stack height after the jump.
};
struct ForLoopLabels
{
JumpInfo post; ///< Jump info for jumping to post branch.
JumpInfo done; ///< Jump info for jumping to done branch.
};
std::stack<ForLoopLabels> forLoopStack;
};
/**
@ -166,6 +182,8 @@ public:
void operator()(Switch const& _switch);
void operator()(FunctionDefinition const&);
void operator()(ForLoop const&);
void operator()(Break const&);
void operator()(Continue const&);
void operator()(Block const& _block);
private:

View File

@ -138,6 +138,15 @@ Statement ASTCopier::operator()(ForLoop const& _forLoop)
translate(_forLoop.body)
};
}
Statement ASTCopier::operator()(Break const& _break)
{
return Break{ _break };
}
Statement ASTCopier::operator()(Continue const& _continue)
{
return Continue{ _continue };
}
Statement ASTCopier::operator ()(Block const& _block)
{

View File

@ -58,6 +58,8 @@ public:
virtual Statement operator()(Switch const& _switch) = 0;
virtual Statement operator()(FunctionDefinition const&) = 0;
virtual Statement operator()(ForLoop const&) = 0;
virtual Statement operator()(Break const&) = 0;
virtual Statement operator()(Continue const&) = 0;
virtual Statement operator()(Block const& _block) = 0;
};
@ -83,6 +85,8 @@ public:
Statement operator()(Switch const& _switch) override;
Statement operator()(FunctionDefinition const&) override;
Statement operator()(ForLoop const&) override;
Statement operator()(Break const&) override;
Statement operator()(Continue const&) override;
Statement operator()(Block const& _block) override;
virtual Expression translate(Expression const& _expression);

View File

@ -161,6 +161,14 @@ void ASTModifier::operator()(ForLoop& _for)
(*this)(_for.body);
}
void ASTModifier::operator()(Break&)
{
}
void ASTModifier::operator()(Continue&)
{
}
void ASTModifier::operator()(Block& _block)
{
walkVector(_block.statements);

View File

@ -56,6 +56,8 @@ public:
virtual void operator()(Switch const& _switch);
virtual void operator()(FunctionDefinition const&);
virtual void operator()(ForLoop const&);
virtual void operator()(Break const&) {}
virtual void operator()(Continue const&) {}
virtual void operator()(Block const& _block);
virtual void visit(Statement const& _st);
@ -91,6 +93,8 @@ public:
virtual void operator()(Switch& _switch);
virtual void operator()(FunctionDefinition&);
virtual void operator()(ForLoop&);
virtual void operator()(Break&);
virtual void operator()(Continue&);
virtual void operator()(Block& _block);
virtual void visit(Statement& _st);

View File

@ -130,6 +130,16 @@ void DataFlowAnalyzer::operator()(ForLoop& _for)
popScope();
}
void DataFlowAnalyzer::operator()(Break&)
{
yulAssert(false, "Not implemented yet.");
}
void DataFlowAnalyzer::operator()(Continue&)
{
yulAssert(false, "Not implemented yet.");
}
void DataFlowAnalyzer::operator()(Block& _block)
{
size_t numScopes = m_variableScopes.size();

View File

@ -53,6 +53,8 @@ public:
void operator()(Switch& _switch) override;
void operator()(FunctionDefinition&) override;
void operator()(ForLoop&) override;
void operator()(Break& _continue) override;
void operator()(Continue& _continue) override;
void operator()(Block& _block) override;
protected:

View File

@ -149,6 +149,16 @@ void RedundantAssignEliminator::operator()(ForLoop const& _forLoop)
join(zeroRuns);
}
void RedundantAssignEliminator::operator()(Break const&)
{
yulAssert(false, "Not implemented yet.");
}
void RedundantAssignEliminator::operator()(Continue const&)
{
yulAssert(false, "Not implemented yet.");
}
void RedundantAssignEliminator::operator()(Block const& _block)
{
// This will set all variables that are declared in this

View File

@ -112,6 +112,8 @@ public:
void operator()(Switch const& _switch) override;
void operator()(FunctionDefinition const&) override;
void operator()(ForLoop const&) override;
void operator()(Break const&) override;
void operator()(Continue const&) override;
void operator()(Block const& _block) override;
static void run(Dialect const& _dialect, Block& _ast);

View File

@ -55,6 +55,8 @@ public:
bool statementEqual(Switch const& _lhs, Switch const& _rhs);
bool switchCaseEqual(Case const& _lhs, Case const& _rhs);
bool statementEqual(ForLoop const& _lhs, ForLoop const& _rhs);
bool statementEqual(Break const&, Break const&) { return true; }
bool statementEqual(Continue const&, Continue const&) { return true; }
bool statementEqual(Block const& _lhs, Block const& _rhs);
private:
bool statementEqual(Instruction const& _lhs, Instruction const& _rhs);

View File

@ -295,6 +295,74 @@ BOOST_AUTO_TEST_CASE(if_statement)
BOOST_CHECK(successParse("{ function f() -> x:bool {} if f() { let b:bool := f() } }"));
}
BOOST_AUTO_TEST_CASE(for_statement)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
BOOST_CHECK(successParse("{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1)} {} }", dialect));
}
BOOST_AUTO_TEST_CASE(for_statement_break)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
BOOST_CHECK(successParse("{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1)} {break} }", dialect));
}
BOOST_AUTO_TEST_CASE(for_statement_break_init)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
CHECK_ERROR_DIALECT(
"{ for {let i := 0 break} iszero(eq(i, 10)) {i := add(i, 1)} {} }",
SyntaxError,
"Keyword break outside for-loop body is not allowed.",
dialect);
}
BOOST_AUTO_TEST_CASE(for_statement_break_post)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
CHECK_ERROR_DIALECT(
"{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1) break} {} }",
SyntaxError,
"Keyword break outside for-loop body is not allowed.",
dialect);
}
BOOST_AUTO_TEST_CASE(for_statement_nested_break)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
CHECK_ERROR_DIALECT(
"{ for {let i := 0} iszero(eq(i, 10)) {} { function f() { break } } }",
SyntaxError,
"Keyword break outside for-loop body is not allowed.",
dialect);
}
BOOST_AUTO_TEST_CASE(for_statement_continue)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
BOOST_CHECK(successParse("{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1)} {continue} }", dialect));
}
BOOST_AUTO_TEST_CASE(for_statement_continue_fail_init)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
CHECK_ERROR_DIALECT(
"{ for {let i := 0 continue} iszero(eq(i, 10)) {i := add(i, 1)} {} }",
SyntaxError,
"Keyword continue outside for-loop body is not allowed.",
dialect);
}
BOOST_AUTO_TEST_CASE(for_statement_continue_fail_post)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
CHECK_ERROR_DIALECT(
"{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1) continue} {} }",
SyntaxError,
"Keyword continue outside for-loop body is not allowed.",
dialect);
}
BOOST_AUTO_TEST_CASE(if_statement_invalid)
{
CHECK_ERROR("{ if let x:u256 {} }", ParserError, "Literal or identifier expected.");

View File

@ -105,12 +105,27 @@ void Interpreter::operator()(ForLoop const& _forLoop)
visit(statement);
while (evaluate(*_forLoop.condition) != 0)
{
m_state.loopState = LoopState::Default;
(*this)(_forLoop.body);
if (m_state.loopState == LoopState::Break)
break;
(*this)(_forLoop.post);
}
m_state.loopState = LoopState::Default;
closeScope();
}
void Interpreter::operator()(Break const&)
{
m_state.loopState = LoopState::Break;
}
void Interpreter::operator()(Continue const&)
{
m_state.loopState = LoopState::Continue;
}
void Interpreter::operator()(Block const& _block)
{
openScope();
@ -122,7 +137,14 @@ void Interpreter::operator()(Block const& _block)
m_functions[funDef.name] = &funDef;
m_scopes.back().insert(funDef.name);
}
ASTWalker::operator()(_block);
for (auto const& statement: _block.statements)
{
visit(statement);
if (m_state.loopState != LoopState::Default)
break;
}
closeScope();
}

View File

@ -39,6 +39,13 @@ class InterpreterTerminated: dev::Exception
{
};
enum class LoopState
{
Default,
Continue,
Break,
};
struct InterpreterState
{
dev::bytes calldata;
@ -65,6 +72,7 @@ struct InterpreterState
std::vector<std::string> trace;
/// This is actually an input parameter that more or less limits the runtime.
size_t maxTraceSize = 0;
LoopState loopState = LoopState::Default;
};
/**
@ -90,6 +98,8 @@ public:
void operator()(Switch const& _switch) override;
void operator()(FunctionDefinition const&) override;
void operator()(ForLoop const&) override;
void operator()(Break const&) override;
void operator()(Continue const&) override;
void operator()(Block const& _block) override;
std::vector<std::string> const& trace() const { return m_state.trace; }