Merge pull request #7560 from ethereum/yulLeave

[Yul] leave statement.
This commit is contained in:
chriseth 2019-10-30 11:15:53 +01:00 committed by GitHub
commit 432b158890
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 448 additions and 78 deletions

View File

@ -20,6 +20,7 @@ Language Features:
* Introduce syntax for array slices and implement them for dynamic calldata arrays.
* Introduce ``push()`` for dynamic storage arrays. It returns a reference to the newly allocated element, if applicable.
* Modify ``push(element)`` for dynamic storage arrays such that it does not return the new length anymore.
* Yul: Introduce ``leave`` statement that exits the current function.
Compiler Features:

View File

@ -54,6 +54,7 @@ New Features
============
* The :ref:`try/catch statement <try-catch>` allows you to react on failed external calls.
* Yul and Inline Assembly have a new statement called ``leave`` that exits the current function.
Deprecated Elements

View File

@ -530,6 +530,9 @@ the other two are blocks. If the initializing part
declares any variables, the scope of these variables is extended into the
body (including the condition and the post-iteration part).
The ``break`` and ``continue`` statements can be used to exit the loop
or skip to the post-part, respectively.
The following example computes the sum of an area in memory.
.. code::
@ -571,6 +574,11 @@ statement.
If you call a function that returns multiple values, you have to assign
them to a tuple using ``a, b := f(x)`` or ``let a, b := f(x)``.
The ``leave`` statement can be used to exit the current function. It
works like the ``return`` statement in other languages just that it does
not take a value to return, it just exits the functions and the function
will return whatever values are currently assigned to the return variable(s).
The following example implements the power function by square-and-multiply.
.. code::
@ -769,6 +777,7 @@ Grammar::
AssemblyFor |
'break' |
'continue' |
'leave' |
SubAssembly
AssemblyExpression = AssemblyCall | Identifier | AssemblyLiteral
AssemblyLiteral = NumberLiteral | StringLiteral | HexLiteral

View File

@ -100,7 +100,8 @@ Grammar::
Expression |
Switch |
ForLoop |
BreakContinue
BreakContinue |
Leave
FunctionDefinition =
'function' Identifier '(' TypedIdentifierList? ')'
( '->' TypedIdentifierList )? Block
@ -122,6 +123,7 @@ Grammar::
'for' Block Expression Block Block
BreakContinue =
'break' | 'continue'
Leave = 'leave'
FunctionCall =
Identifier '(' ( Expression ( ',' Expression )* )? ')'
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9.]*
@ -167,6 +169,7 @@ In all other situations, expressions have to evaluate to exactly one value.
The ``continue`` and ``break`` statements can only be used inside loop bodies
and have to be in the same function as the loop (or both have to be at the
top level).
The ``leave`` statement can only be used inside a function.
The condition part of the for-loop has to evaluate to exactly one value.
Functions cannot be defined inside for loop init blocks.
@ -214,7 +217,7 @@ The two state objects are the global state object
blockchain) and the local state object (the state of local variables, i.e. a
segment of the stack in the EVM).
If the AST node is a statement, E returns the two state objects and a "mode",
which is used for the ``break`` and ``continue`` statements.
which is used for the ``break``, ``continue`` and ``leave`` statements.
If the AST node is an expression, E returns the two state objects and
as many values as the expression evaluates to.
@ -255,12 +258,13 @@ We will use a destructuring notation for the AST nodes.
G, L2, regular
E(G, L, <for { i1, ..., in } condition post body>: ForLoop) =
if n >= 1:
let G1, L1, mode = E(G, L, i1, ..., in)
// mode has to be regular due to the syntactic restrictions
let G2, L2, mode = E(G1, L1, for {} condition post body)
// mode has to be regular due to the syntactic restrictions
let L3 be the restriction of L2 to only variables of L
G2, L3, regular
let G1, L, mode = E(G, L, i1, ..., in)
// mode has to be regular or leave due to the syntactic restrictions
if mode is leave then
G1, L1 restricted to variables of L, leave
otherwise
let G2, L2, mode = E(G1, L1, for {} condition post body)
G2, L2 restricted to variables of L, mode
else:
let G1, L1, v = E(G, L, condition)
if v is false:
@ -269,13 +273,20 @@ We will use a destructuring notation for the AST nodes.
let G2, L2, mode = E(G1, L, body)
if mode is break:
G2, L2, regular
otherwise if mode is leave:
G2, L2, leave
else:
G3, L3, mode = E(G2, L2, post)
E(G3, L3, for {} condition post body)
if mode is leave:
G2, L3, leave
otherwise
E(G3, L3, for {} condition post body)
E(G, L, break: BreakContinue) =
G, L, break
E(G, L, continue: BreakContinue) =
G, L, continue
E(G, L, leave: Leave) =
G, L, leave
E(G, L, <if condition body>: If) =
let G0, L0, v = E(G, L, condition)
if v is true:

View File

@ -108,6 +108,9 @@ public:
void operator()(yul::Continue const&)
{
}
void operator()(yul::Leave const&)
{
}
void operator()(yul::Block const& _block)
{
for (auto const& s: _block.statements)

View File

@ -132,10 +132,7 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
return m_context.functionCollector()->createFunction(functionName, [&]() {
Whiskers t(R"(
function <functionName>(<params>) <returns> {
for { let return_flag := 1 } return_flag {} {
<body>
break
}
<body>
}
)");
t("functionName", functionName);

View File

@ -296,7 +296,7 @@ void IRGeneratorForStatements::endVisit(Return const& _return)
expressionAsType(*value, *types.front()) <<
"\n";
}
m_code << "return_flag := 0\n" << "break\n";
m_code << "leave\n";
}
void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation)
@ -1349,7 +1349,7 @@ void IRGeneratorForStatements::generateLoop(
m_code << "for {\n";
if (_initExpression)
_initExpression->accept(*this);
m_code << "} return_flag {\n";
m_code << "} 1 {\n";
if (_loopExpression)
_loopExpression->accept(*this);
m_code << "}\n";
@ -1373,8 +1373,6 @@ void IRGeneratorForStatements::generateLoop(
_body.accept(*this);
m_code << "}\n";
// Bubble up the return condition.
m_code << "if iszero(return_flag) { break }\n";
}
Type const& IRGeneratorForStatements::type(Expression const& _expression)

View File

@ -519,6 +519,12 @@ bool AsmAnalyzer::operator()(Continue const& _continue)
return true;
}
bool AsmAnalyzer::operator()(Leave const& _leave)
{
m_info.stackHeightInfo[&_leave] = m_stackHeight;
return true;
}
bool AsmAnalyzer::operator()(Block const& _block)
{
bool success = true;

View File

@ -93,6 +93,7 @@ public:
bool operator()(ForLoop const& _forLoop);
bool operator()(Break const&);
bool operator()(Continue const&);
bool operator()(Leave const&);
bool operator()(Block const& _block);
private:

View File

@ -78,6 +78,8 @@ struct ForLoop { langutil::SourceLocation location; Block pre; std::unique_ptr<E
struct Break { langutil::SourceLocation location; };
/// Continue statement (valid within for loop)
struct Continue { langutil::SourceLocation location; };
/// Leave statement (valid within function)
struct Leave { langutil::SourceLocation location; };
struct LocationExtractor: boost::static_visitor<langutil::SourceLocation>
{

View File

@ -42,12 +42,13 @@ struct Case;
struct ForLoop;
struct Break;
struct Continue;
struct Leave;
struct ExpressionStatement;
struct Block;
struct TypedName;
using Expression = boost::variant<FunctionalInstruction, FunctionCall, Identifier, Literal>;
using Statement = boost::variant<ExpressionStatement, Instruction, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Break, Continue, Block>;
using Statement = boost::variant<ExpressionStatement, Instruction, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Break, Continue, Leave, Block>;
}

View File

@ -144,6 +144,16 @@ Statement Parser::parseStatement()
m_scanner->next();
return stmt;
}
case Token::Identifier:
if (currentLiteral() == "leave")
{
Statement stmt{createWithLocation<Leave>()};
if (!m_insideFunction)
m_errorReporter.syntaxError(location(), "Keyword \"leave\" can only be used inside a function.");
m_scanner->next();
return stmt;
}
break;
default:
break;
}
@ -439,7 +449,10 @@ FunctionDefinition Parser::parseFunctionDefinition()
expectToken(Token::Comma);
}
}
bool preInsideFunction = m_insideFunction;
m_insideFunction = true;
funDef.body = parseBlock();
m_insideFunction = preInsideFunction;
funDef.location.end = funDef.body.location.end;
m_currentForLoopComponent = outerForLoopComponent;

View File

@ -98,6 +98,7 @@ protected:
private:
Dialect const& m_dialect;
ForLoopComponent m_currentForLoopComponent = ForLoopComponent::None;
bool m_insideFunction = false;
};
}

View File

@ -227,6 +227,11 @@ string AsmPrinter::operator()(Continue const&) const
return "continue";
}
string AsmPrinter::operator()(Leave const&) const
{
return "leave";
}
string AsmPrinter::operator()(Block const& _block) const
{
if (_block.statements.empty())

View File

@ -50,6 +50,7 @@ public:
std::string operator()(ForLoop const& _forLoop) const;
std::string operator()(Break const& _break) const;
std::string operator()(Continue const& _continue) const;
std::string operator()(Leave const& _continue) const;
std::string operator()(Block const& _block) const;
private:

View File

@ -63,6 +63,7 @@ public:
bool operator()(ForLoop const& _forLoop);
bool operator()(Break const&) { return true; }
bool operator()(Continue const&) { return true; }
bool operator()(Leave const&) { return true; }
bool operator()(Block const& _block);
private:

View File

@ -490,6 +490,9 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
m_assembly.appendConstant(u256(0));
}
m_context->functionExitPoints.push(
CodeTransformContext::JumpInfo{m_assembly.newLabelId(), m_assembly.stackHeight()}
);
try
{
CodeTransform(
@ -518,6 +521,9 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
stackError(std::move(error), height);
}
m_assembly.appendLabel(m_context->functionExitPoints.top().label);
m_context->functionExitPoints.pop();
{
// The stack layout here is:
// <return label>? <arguments...> <return values...>
@ -643,6 +649,17 @@ void CodeTransform::operator()(Continue const& _continue)
checkStackHeight(&_continue);
}
void CodeTransform::operator()(Leave const& _leave)
{
yulAssert(!m_context->functionExitPoints.empty(), "Invalid leave-statement. Requires surrounding function in code generation.");
m_assembly.setSourceLocation(_leave.location);
Context::JumpInfo const& jump = m_context->functionExitPoints.top();
m_assembly.appendJumpTo(jump.label, appendPopUntil(jump.targetStackHeight));
checkStackHeight(&_leave);
}
void CodeTransform::operator()(Block const& _block)
{
Scope* originalScope = m_scope;

View File

@ -73,6 +73,7 @@ struct CodeTransformContext
};
std::stack<ForLoopLabels> forLoopStack;
std::stack<JumpInfo> functionExitPoints;
};
/**
@ -185,6 +186,7 @@ public:
void operator()(ForLoop const&);
void operator()(Break const&);
void operator()(Continue const&);
void operator()(Leave const&);
void operator()(Block const& _block);
private:

View File

@ -42,10 +42,11 @@ struct If;
struct Loop;
struct Break;
struct BreakIf;
struct Return;
using Expression = boost::variant<
Literal, StringLiteral, LocalVariable, GlobalVariable,
FunctionCall, BuiltinCall, LocalAssignment, GlobalAssignment,
Block, If, Loop, Break, BreakIf
Block, If, Loop, Break, BreakIf, Return
>;
struct Literal { uint64_t value; };
@ -65,6 +66,7 @@ struct If {
};
struct Loop { std::string labelName; std::vector<Expression> statements; };
struct Break { Label label; };
struct Return {};
struct BreakIf { Label label; std::unique_ptr<Expression> condition; };
struct VariableDeclaration { std::string variableName; };

View File

@ -257,6 +257,11 @@ wasm::Expression EWasmCodeTransform::operator()(Continue const&)
return wasm::Break{wasm::Label{m_breakContinueLabelNames.top().second}};
}
wasm::Expression EWasmCodeTransform::operator()(Leave const&)
{
return wasm::Return{};
}
wasm::Expression EWasmCodeTransform::operator()(Block const& _block)
{
return wasm::Block{{}, visit(_block.statements)};

View File

@ -52,6 +52,7 @@ public:
wasm::Expression operator()(yul::ForLoop const&);
wasm::Expression operator()(yul::Break const&);
wasm::Expression operator()(yul::Continue const&);
wasm::Expression operator()(yul::Leave const&);
wasm::Expression operator()(yul::Block const& _block);
private:

View File

@ -128,6 +128,11 @@ string EWasmToText::operator()(wasm::BreakIf const& _break)
return "(br_if $" + _break.label.name + " " + visit(*_break.condition) + ")\n";
}
string EWasmToText::operator()(wasm::Return const&)
{
return "(return)\n";
}
string EWasmToText::operator()(wasm::Block const& _block)
{
string label = _block.labelName.empty() ? "" : " $" + _block.labelName;

View File

@ -49,6 +49,7 @@ public:
std::string operator()(wasm::If const& _if);
std::string operator()(wasm::Loop const& _loop);
std::string operator()(wasm::Break const& _break);
std::string operator()(wasm::Return const& _return);
std::string operator()(wasm::BreakIf const& _break);
std::string operator()(wasm::Block const& _block);

View File

@ -136,6 +136,11 @@ Statement ASTCopier::operator()(Continue const& _continue)
return Continue{ _continue };
}
Statement ASTCopier::operator()(Leave const& _leave)
{
return Leave{_leave};
}
Statement ASTCopier::operator ()(Block const& _block)
{
return translate(_block);

View File

@ -58,6 +58,7 @@ public:
virtual Statement operator()(ForLoop const&) = 0;
virtual Statement operator()(Break const&) = 0;
virtual Statement operator()(Continue const&) = 0;
virtual Statement operator()(Leave const&) = 0;
virtual Statement operator()(Block const& _block) = 0;
};
@ -83,6 +84,7 @@ public:
Statement operator()(ForLoop const&) override;
Statement operator()(Break const&) override;
Statement operator()(Continue const&) override;
Statement operator()(Leave const&) override;
Statement operator()(Block const& _block) override;
virtual Expression translate(Expression const& _expression);

View File

@ -169,6 +169,10 @@ void ASTModifier::operator()(Continue&)
{
}
void ASTModifier::operator()(Leave&)
{
}
void ASTModifier::operator()(Block& _block)
{
walkVector(_block.statements);

View File

@ -56,6 +56,7 @@ public:
virtual void operator()(ForLoop const&);
virtual void operator()(Break const&) {}
virtual void operator()(Continue const&) {}
virtual void operator()(Leave const&) {}
virtual void operator()(Block const& _block);
virtual void visit(Statement const& _st);
@ -91,6 +92,7 @@ public:
virtual void operator()(ForLoop&);
virtual void operator()(Break&);
virtual void operator()(Continue&);
virtual void operator()(Leave&);
virtual void operator()(Block& _block);
virtual void visit(Statement& _st);

View File

@ -173,6 +173,11 @@ void BlockHasher::operator()(Continue const& _continue)
ASTWalker::operator()(_continue);
}
void BlockHasher::operator()(Leave const& _leave)
{
hash64(compileTimeLiteralHash("Leave"));
ASTWalker::operator()(_leave);
}
void BlockHasher::operator()(Block const& _block)
{

View File

@ -60,6 +60,7 @@ public:
void operator()(ForLoop const&) override;
void operator()(Break const&) override;
void operator()(Continue const&) override;
void operator()(Leave const&) override;
void operator()(Block const& _block) override;
static std::map<Block const*, uint64_t> run(Block const& _block);

View File

@ -136,6 +136,13 @@ void ControlFlowSimplifier::operator()(Block& _block)
simplify(_block.statements);
}
void ControlFlowSimplifier::operator()(FunctionDefinition& _funDef)
{
ASTModifier::operator()(_funDef);
if (!_funDef.body.statements.empty() && _funDef.body.statements.back().type() == typeid(Leave))
_funDef.body.statements.pop_back();
}
void ControlFlowSimplifier::visit(Statement& _st)
{
if (_st.type() == typeid(ForLoop))
@ -159,7 +166,10 @@ void ControlFlowSimplifier::visit(Statement& _st)
isTerminating = true;
--m_numBreakStatements;
}
else if (controlFlow == TerminationFinder::ControlFlow::Terminate)
else if (
controlFlow == TerminationFinder::ControlFlow::Terminate ||
controlFlow == TerminationFinder::ControlFlow::Leave
)
isTerminating = true;
if (isTerminating && m_numContinueStatements == 0 && m_numBreakStatements == 0)

View File

@ -34,6 +34,7 @@ struct OptimiserStepContext;
* - replace switch with only default case with pop(expression) and body
* - replace switch with const expr with matching case body
* - replace ``for`` with terminating control flow and without other break/continue by ``if``
* - remove ``leave`` at the end of a function.
*
* None of these operations depend on the data flow. The StructuralSimplifier
* performs similar tasks that do depend on data flow.
@ -55,6 +56,7 @@ public:
void operator()(Break&) override { ++m_numBreakStatements; }
void operator()(Continue&) override { ++m_numContinueStatements; }
void operator()(Block& _block) override;
void operator()(FunctionDefinition& _funDef) override;
void visit(Statement& _st) override;

View File

@ -161,6 +161,10 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun)
}
ASTModifier::operator()(_fun);
// Note that the contents of return variables, storage and memory at this point
// might be incorrect due to the fact that the DataFlowAnalyzer ignores the ``leave``
// statement.
popScope();
m_value.swap(value);
swap(m_references, references);

View File

@ -64,6 +64,10 @@ struct SideEffects;
* older version of the other and thus overlapping contents would have been deleted already
* at the point of assignment.
*
* The DataFlowAnalyzer currently does not deal with the ``leave`` statement. This is because
* it only matters at the end of a function body, which is a point in the code a derived class
* can not easily deal with.
*
* Prerequisite: Disambiguator, ForLoopInitRewriter.
*/
class DataFlowAnalyzer: public ASTModifier

View File

@ -35,7 +35,7 @@ struct OptimiserStepContext;
* Optimisation stage that removes unreachable code
*
* Unreachable code is any code within a block which is preceded by a
* return, invalid, break, continue, selfdestruct or revert.
* leave, return, invalid, break, continue, selfdestruct or revert.
*
* Function definitions are retained as they might be called by earlier
* code and thus are considered reachable.

View File

@ -26,6 +26,7 @@
#include <libyul/optimiser/OptimizerUtilities.h>
#include <libyul/optimiser/Metrics.h>
#include <libyul/optimiser/SSAValueTracker.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/Exceptions.h>
#include <libyul/AsmData.h>
@ -62,6 +63,8 @@ FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser):
continue;
FunctionDefinition& fun = boost::get<FunctionDefinition>(statement);
m_functions[fun.name] = &fun;
if (LeaveFinder::containsLeave(fun))
m_noInlineFunctions.insert(fun.name);
// Always inline functions that are only called once.
if (references[fun.name] == 1)
m_singleUse.emplace(fun.name);
@ -94,7 +97,7 @@ bool FullInliner::shallInline(FunctionCall const& _funCall, YulString _callSite)
if (!calledFunction)
return false;
if (recursive(*calledFunction))
if (m_noInlineFunctions.count(_funCall.functionName.name) || recursive(*calledFunction))
return false;
// Inline really, really tiny functions

View File

@ -102,6 +102,8 @@ private:
/// we store pointers to functions.
Block& m_ast;
std::map<YulString, FunctionDefinition*> m_functions;
/// Functions not to be inlined (because they contain the ``leave`` statement).
std::set<YulString> m_noInlineFunctions;
/// Names of functions to always inline.
std::set<YulString> m_singleUse;
/// Variables that are constants (used for inlining heuristic)

View File

@ -70,7 +70,8 @@ void CodeSize::visit(Statement const& _statement)
else if (
_statement.type() == typeid(If) ||
_statement.type() == typeid(Break) ||
_statement.type() == typeid(Continue)
_statement.type() == typeid(Continue) ||
_statement.type() == typeid(Leave)
)
m_size += 2;
else if (_statement.type() == typeid(ForLoop))

View File

@ -104,12 +104,17 @@ void RedundantAssignEliminator::operator()(Switch const& _switch)
void RedundantAssignEliminator::operator()(FunctionDefinition const& _functionDefinition)
{
std::set<YulString> outerDeclaredVariables;
std::set<YulString> outerReturnVariables;
TrackedAssignments outerAssignments;
ForLoopInfo forLoopInfo;
swap(m_declaredVariables, outerDeclaredVariables);
swap(m_returnVariables, outerReturnVariables);
swap(m_assignments, outerAssignments);
swap(m_forLoopInfo, forLoopInfo);
for (auto const& retParam: _functionDefinition.returnVariables)
m_returnVariables.insert(retParam.name);
(*this)(_functionDefinition.body);
for (auto const& param: _functionDefinition.parameters)
@ -118,6 +123,7 @@ void RedundantAssignEliminator::operator()(FunctionDefinition const& _functionDe
finalize(retParam.name, State::Used);
swap(m_declaredVariables, outerDeclaredVariables);
swap(m_returnVariables, outerReturnVariables);
swap(m_assignments, outerAssignments);
swap(m_forLoopInfo, forLoopInfo);
}
@ -200,6 +206,12 @@ void RedundantAssignEliminator::operator()(Continue const&)
m_assignments.clear();
}
void RedundantAssignEliminator::operator()(Leave const&)
{
for (YulString name: m_returnVariables)
changeUndecidedTo(name, State::Used);
}
void RedundantAssignEliminator::operator()(Block const& _block)
{
set<YulString> outerDeclaredVariables;

View File

@ -91,6 +91,8 @@ struct Dialect;
* For switch statements that have a "default"-case, there is no control-flow
* part that skips the switch.
*
* At ``leave`` statements, all return variables are set to "used".
*
* When a variable goes out of scope, all statements still in the "undecided"
* state are changed to "unused", unless the variable is the return
* parameter of a function - there, the state changes to "used".
@ -125,6 +127,7 @@ public:
void operator()(ForLoop const&) override;
void operator()(Break const&) override;
void operator()(Continue const&) override;
void operator()(Leave const&) override;
void operator()(Block const& _block) override;
private:
@ -163,6 +166,7 @@ private:
Dialect const* m_dialect;
std::set<YulString> m_declaredVariables;
std::set<YulString> m_returnVariables;
std::set<Assignment const*> m_pendingRemovals;
TrackedAssignments m_assignments;

View File

@ -173,6 +173,8 @@ TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement cons
return ControlFlow::Break;
else if (_statement.type() == typeid(Continue))
return ControlFlow::Continue;
else if (_statement.type() == typeid(Leave))
return ControlFlow::Leave;
else
return ControlFlow::FlowOut;
}

View File

@ -113,6 +113,31 @@ private:
bool m_msizeFound = false;
};
/**
* Class that can be used to find out if the given function contains the ``leave`` statement.
*
* Returns true even in the case where the function definition contains another function definition
* that contains the leave statement.
*/
class LeaveFinder: public ASTWalker
{
public:
static bool containsLeave(FunctionDefinition const& _fun)
{
LeaveFinder f;
f(_fun);
return f.m_leaveFound;
}
using ASTWalker::operator();
void operator()(Leave const&) { m_leaveFound = true; }
private:
LeaveFinder() = default;
bool m_leaveFound = false;
};
/**
* Specific AST walker that determines whether an expression is movable
* and collects the referenced variables.
@ -148,12 +173,13 @@ private:
class TerminationFinder
{
public:
enum class ControlFlow { FlowOut, Break, Continue, Terminate };
// TODO check all uses of TerminationFinder!
enum class ControlFlow { FlowOut, Break, Continue, Terminate, Leave };
TerminationFinder(Dialect const& _dialect): m_dialect(_dialect) {}
/// @returns the index of the first statement in the provided sequence
/// that is an unconditional ``break``, ``continue`` or a
/// that is an unconditional ``break``, ``continue``, ``leave`` or a
/// call to a terminating builtin function.
/// If control flow can continue at the end of the list,
/// returns `FlowOut` and ``size_t(-1)``.

View File

@ -57,6 +57,7 @@ public:
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(Leave const&, Leave const&) { return true; }
bool statementEqual(Block const& _lhs, Block const& _rhs);
private:
bool statementEqual(Instruction const& _lhs, Instruction const& _rhs);

View File

@ -11,10 +11,7 @@ object \"C_6\" {
codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))
return(0, datasize(\"C_6_deployed\"))
function fun_f_5()
{
for { let return_flag := 1 } return_flag { }
{ break }
}
{ }
}
object \"C_6_deployed\" {
code {
@ -48,10 +45,7 @@ object \"C_6\" {
mstore(64, newFreePtr)
}
function fun_f_5()
{
for { let return_flag := 1 } return_flag { }
{ break }
}
{ }
function shift_right_224_unsigned(value) -> newValue
{ newValue := shr(224, value) }
}

View File

@ -19,10 +19,7 @@ object \"C_6\" {
function fun_f_5() {
for { let return_flag := 1 } return_flag {} {
break
}
}
}
@ -70,10 +67,7 @@ object \"C_6\" {
}
function fun_f_5() {
for { let return_flag := 1 } return_flag {} {
break
}
}
function shift_right_224_unsigned(value) -> newValue {

View File

@ -31,7 +31,7 @@ object "object" {
Binary representation:
60056032565b505050505050505050505050505050601a6032565b5050505050505050505050505050508082555050609a565b60006000600060006000600060006000600060006000600060006000600060006001808155806002558060035580600455806005558060065580600755806008558060095580600a5580600b5580600c5580600d5550909192939495969798999a9b9c9d9e9f565b
60056032565b505050505050505050505050505050601a6032565b5050505050505050505050505050508082555050609b565b60006000600060006000600060006000600060006000600060006000600060006001808155806002558060035580600455806005558060065580600755806008558060095580600a5580600b5580600c5580600d55505b909192939495969798999a9b9c9d9e9f565b
Text representation:
/* "yul_stack_opt/input.sol":495:500 */
@ -181,6 +181,7 @@ tag_2:
sstore
pop
/* "yul_stack_opt/input.sol":85:423 */
tag_5:
swap1
swap2
swap3

View File

@ -0,0 +1,14 @@
contract C {
function f() public pure returns (uint w) {
assembly {
function f() -> t {
t := 2
leave
t := 9
}
w := f()
}
}
}
// ----
// f() -> 2

View File

@ -0,0 +1,10 @@
contract C {
function f() public pure {
assembly {
function f() {
leave
}
}
}
}
// ----

View File

@ -0,0 +1,9 @@
contract C {
function f() public pure {
assembly {
leave
}
}
}
// ----
// SyntaxError: (63-68): Keyword "leave" can only be used inside a function.

View File

@ -171,7 +171,7 @@ BOOST_AUTO_TEST_CASE(function_trivial)
function f() { }
})";
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0x5 JUMP JUMPDEST JUMP JUMPDEST "
"PUSH1 0x6 JUMP JUMPDEST JUMPDEST JUMP JUMPDEST "
);
}
@ -181,8 +181,8 @@ BOOST_AUTO_TEST_CASE(function_retparam)
function f() -> x, y { }
})";
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0xB JUMP "
"JUMPDEST PUSH1 0x0 PUSH1 0x0 SWAP1 SWAP2 JUMP "
"PUSH1 0xC JUMP "
"JUMPDEST PUSH1 0x0 PUSH1 0x0 JUMPDEST SWAP1 SWAP2 JUMP "
"JUMPDEST "
);
}
@ -192,7 +192,7 @@ BOOST_AUTO_TEST_CASE(function_params)
string in = R"({
function f(a, b) { }
})";
BOOST_CHECK_EQUAL(assemble(in), "PUSH1 0x7 JUMP JUMPDEST POP POP JUMP JUMPDEST ");
BOOST_CHECK_EQUAL(assemble(in), "PUSH1 0x8 JUMP JUMPDEST JUMPDEST POP POP JUMP JUMPDEST ");
}
BOOST_AUTO_TEST_CASE(function_params_and_retparams)
@ -205,7 +205,7 @@ BOOST_AUTO_TEST_CASE(function_params_and_retparams)
// layout for a function is still fixed, even though parameters
// can be re-used.
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0x10 JUMP JUMPDEST PUSH1 0x0 PUSH1 0x0 SWAP5 POP SWAP5 SWAP3 POP POP POP JUMP JUMPDEST "
"PUSH1 0x11 JUMP JUMPDEST PUSH1 0x0 PUSH1 0x0 JUMPDEST SWAP5 POP SWAP5 SWAP3 POP POP POP JUMP JUMPDEST "
);
}
@ -215,12 +215,12 @@ BOOST_AUTO_TEST_CASE(function_params_and_retparams_partly_unused)
function f(a, b, c, d) -> x, y { b := 3 let s := 9 y := 2 mstore(s, y) }
})";
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0x1E JUMP "
"PUSH1 0x1F JUMP "
"JUMPDEST PUSH1 0x0 PUSH1 0x0 "
"PUSH1 0x3 SWAP4 POP "
"PUSH1 0x9 PUSH1 0x2 SWAP2 POP "
"DUP2 DUP2 MSTORE "
"POP SWAP5 POP SWAP5 SWAP3 POP POP POP JUMP "
"POP JUMPDEST SWAP5 POP SWAP5 SWAP3 POP POP POP JUMP "
"JUMPDEST "
);
}
@ -237,12 +237,12 @@ BOOST_AUTO_TEST_CASE(function_with_body_embedded)
})";
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0x3 PUSH1 "
"0x16 JUMP "
"0x17 JUMP "
"JUMPDEST PUSH1 0x0 " // start of f, initialize t
"DUP2 POP " // let x := a
"PUSH1 0x3 SWAP2 POP "
"DUP2 SWAP1 POP "
"SWAP3 SWAP2 POP POP JUMP "
"JUMPDEST SWAP3 SWAP2 POP POP JUMP "
"JUMPDEST PUSH1 0x7 SWAP1 "
"POP POP "
);
@ -257,9 +257,9 @@ BOOST_AUTO_TEST_CASE(function_call)
})";
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0x9 PUSH1 0x2 PUSH1 0x1 PUSH1 0xD JUMP "
"JUMPDEST PUSH1 0x15 JUMP " // jump over f
"JUMPDEST PUSH1 0x0 SWAP3 SWAP2 POP POP JUMP " // f
"JUMPDEST PUSH1 0x1F PUSH1 0x4 PUSH1 0x3 PUSH1 0xD JUMP "
"JUMPDEST PUSH1 0x16 JUMP " // jump over f
"JUMPDEST PUSH1 0x0 JUMPDEST SWAP3 SWAP2 POP POP JUMP " // f
"JUMPDEST PUSH1 0x20 PUSH1 0x4 PUSH1 0x3 PUSH1 0xD JUMP "
"JUMPDEST SWAP1 POP POP "
);
}
@ -277,15 +277,15 @@ BOOST_AUTO_TEST_CASE(functions_multi_return)
let unused := 7
})";
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0x13 JUMP "
"JUMPDEST PUSH1 0x0 SWAP3 SWAP2 POP POP JUMP " // f
"JUMPDEST PUSH1 0x0 PUSH1 0x0 SWAP1 SWAP2 JUMP " // g
"JUMPDEST PUSH1 0x1D PUSH1 0x2 PUSH1 0x1 PUSH1 0x3 JUMP " // f(1, 2)
"JUMPDEST PUSH1 0x27 PUSH1 0x4 PUSH1 0x3 PUSH1 0x3 JUMP " // f(3, 4)
"PUSH1 0x15 JUMP "
"JUMPDEST PUSH1 0x0 JUMPDEST SWAP3 SWAP2 POP POP JUMP " // f
"JUMPDEST PUSH1 0x0 PUSH1 0x0 JUMPDEST SWAP1 SWAP2 JUMP " // g
"JUMPDEST PUSH1 0x1F PUSH1 0x2 PUSH1 0x1 PUSH1 0x3 JUMP " // f(1, 2)
"JUMPDEST PUSH1 0x29 PUSH1 0x4 PUSH1 0x3 PUSH1 0x3 JUMP " // f(3, 4)
"JUMPDEST SWAP1 POP " // assignment to x
"POP " // remove x
"PUSH1 0x30 PUSH1 0xB JUMP " // g()
"JUMPDEST PUSH1 0x36 PUSH1 0xB JUMP " // g()
"PUSH1 0x32 PUSH1 0xC JUMP " // g()
"JUMPDEST PUSH1 0x38 PUSH1 0xC JUMP " // g()
"JUMPDEST SWAP2 POP SWAP2 POP " // assignments
"POP POP " // removal of y and z
"PUSH1 0x7 POP "
@ -299,9 +299,9 @@ BOOST_AUTO_TEST_CASE(reuse_slots_function)
let a, b, c, d := f() let x1 := 2 let y1 := 3 mstore(x1, a) mstore(y1, c)
})";
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0x11 JUMP "
"JUMPDEST PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 SWAP1 SWAP2 SWAP3 SWAP4 JUMP "
"JUMPDEST PUSH1 0x17 PUSH1 0x3 JUMP "
"PUSH1 0x12 JUMP "
"JUMPDEST PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 JUMPDEST SWAP1 SWAP2 SWAP3 SWAP4 JUMP "
"JUMPDEST PUSH1 0x18 PUSH1 0x3 JUMP "
// Stack: a b c d
"JUMPDEST POP " // d is unused
// Stack: a b c
@ -327,9 +327,9 @@ BOOST_AUTO_TEST_CASE(reuse_slots_function_with_gaps)
BOOST_CHECK_EQUAL(assemble(in),
"PUSH1 0x5 PUSH1 0x6 PUSH1 0x7 "
"DUP2 DUP4 MSTORE "
"PUSH1 0x1A JUMP " // jump across function
"JUMPDEST PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 SWAP1 SWAP2 SWAP3 SWAP4 JUMP "
"JUMPDEST PUSH1 0x20 PUSH1 0xC JUMP "
"PUSH1 0x1B JUMP " // jump across function
"JUMPDEST PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 JUMPDEST SWAP1 SWAP2 SWAP3 SWAP4 JUMP "
"JUMPDEST PUSH1 0x21 PUSH1 0xC JUMP "
// stack: x1 x2 x3 a b c d
"JUMPDEST SWAP6 POP " // move d into x1
// stack: d x2 x3 a b c

View File

@ -12,10 +12,12 @@ object "Contract" {
// jump(tag_1)
// tag_2:
// /* "source":46:48 */
// tag_3:
// jump
// /* "source":53:68 */
// tag_3:
// tag_4:
// /* "source":66:68 */
// tag_5:
// jump
// tag_1:
// /* "source":83:84 */
@ -24,5 +26,5 @@ object "Contract" {
// 0x00
// /* "source":73:85 */
// sstore
// Bytecode: 6007565b565b565b6001600055
// Opcodes: PUSH1 0x7 JUMP JUMPDEST JUMP JUMPDEST JUMP JUMPDEST PUSH1 0x1 PUSH1 0x0 SSTORE
// Bytecode: 6009565b5b565b5b565b6001600055
// Opcodes: PUSH1 0x9 JUMP JUMPDEST JUMPDEST JUMP JUMPDEST JUMPDEST JUMP JUMPDEST PUSH1 0x1 PUSH1 0x0 SSTORE

View File

@ -0,0 +1,20 @@
{
function f() -> x, y
{
for { x := 0 } lt(x, 10) { x := add(x, 1) } {
if eq(x, 5) { y := 1 leave }
}
x := 9
}
{
let a, b := f()
sstore(a, b)
}
}
// ====
// EVMVersion: >=constantinople
// ----
// Trace:
// Memory dump:
// Storage dump:
// 0000000000000000000000000000000000000000000000000000000000000005: 0000000000000000000000000000000000000000000000000000000000000001

View File

@ -0,0 +1,18 @@
{
function f() -> x
{
for { leave x := 2 } eq(x, 0) { } {
}
}
{
let a := f()
sstore(a, 7)
}
}
// ====
// EVMVersion: >=constantinople
// ----
// Trace:
// Memory dump:
// Storage dump:
// 0000000000000000000000000000000000000000000000000000000000000000: 0000000000000000000000000000000000000000000000000000000000000007

View File

@ -0,0 +1,19 @@
{
function f() -> x { x := 7 leave }
function g() -> x { leave x := 7 }
function h() -> x { if x { leave } }
}
// ====
// step: controlFlowSimplifier
// ----
// {
// function f() -> x
// { x := 7 }
// function g() -> x_1
// {
// leave
// x_1 := 7
// }
// function h() -> x_2
// { if x_2 { leave } }
// }

View File

@ -0,0 +1,36 @@
{
function f() -> x {
for {
let a := 20
}
lt(a, 40)
{
a := add(a, 2)
}
{
a := a
leave
mstore(0, a)
a := add(a, 10)
}
x := 9
}
pop(f())
}
// ====
// step: deadCodeEliminator
// ----
// {
// function f() -> x
// {
// let a := 20
// for { } lt(a, 40) { a := add(a, 2) }
// {
// a := a
// leave
// }
// x := 9
// }
// pop(f())
// }

View File

@ -0,0 +1,22 @@
{
function g() -> x { x := 8 leave }
function f(a) { a := g() }
let a1 := calldataload(0)
f(a1)
}
// ====
// step: fullInliner
// ----
// {
// {
// let a_2 := calldataload(0)
// a_2 := g()
// }
// function g() -> x
// {
// x := 8
// leave
// }
// function f(a)
// { a := g() }
// }

View File

@ -0,0 +1,40 @@
{
function f(a, b) -> x {
let t
a := 2
x := 2
t := 2
if b { leave }
a := 8
x := 8
t := 8
}
function g(a, b) -> x {
let t
a := 2
x := 2
t := 2
if b { }
a := 8
x := 8
t := 8
}
}
// ====
// step: redundantAssignEliminator
// ----
// {
// function f(a, b) -> x
// {
// let t
// x := 2
// if b { leave }
// x := 8
// }
// function g(a_1, b_2) -> x_3
// {
// let t_4
// if b_2 { }
// x_3 := 8
// }
// }

View File

@ -124,30 +124,43 @@ void Interpreter::operator()(ForLoop const& _forLoop)
solAssert(_forLoop.condition, "");
openScope();
ScopeGuard g([this]{ closeScope(); });
for (auto const& statement: _forLoop.pre.statements)
{
visit(statement);
if (m_state.controlFlowState == ControlFlowState::Leave)
return;
}
while (evaluate(*_forLoop.condition) != 0)
{
m_state.loopState = LoopState::Default;
m_state.controlFlowState = ControlFlowState::Default;
(*this)(_forLoop.body);
if (m_state.loopState == LoopState::Break)
if (m_state.controlFlowState == ControlFlowState::Break || m_state.controlFlowState == ControlFlowState::Leave)
break;
m_state.loopState = LoopState::Default;
m_state.controlFlowState = ControlFlowState::Default;
(*this)(_forLoop.post);
if (m_state.controlFlowState == ControlFlowState::Leave)
break;
}
m_state.loopState = LoopState::Default;
closeScope();
if (m_state.controlFlowState != ControlFlowState::Leave)
m_state.controlFlowState = ControlFlowState::Default;
}
void Interpreter::operator()(Break const&)
{
m_state.loopState = LoopState::Break;
m_state.controlFlowState = ControlFlowState::Break;
}
void Interpreter::operator()(Continue const&)
{
m_state.loopState = LoopState::Continue;
m_state.controlFlowState = ControlFlowState::Continue;
}
void Interpreter::operator()(Leave const&)
{
m_state.controlFlowState = ControlFlowState::Leave;
}
void Interpreter::operator()(Block const& _block)
@ -171,7 +184,7 @@ void Interpreter::operator()(Block const& _block)
for (auto const& statement: _block.statements)
{
visit(statement);
if (m_state.loopState != LoopState::Default)
if (m_state.controlFlowState != ControlFlowState::Default)
break;
}
@ -245,8 +258,10 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall)
for (size_t i = 0; i < fun->returnVariables.size(); ++i)
variables[fun->returnVariables.at(i).name] = 0;
m_state.controlFlowState = ControlFlowState::Default;
Interpreter interpreter(m_state, m_dialect, variables, functionScopes);
interpreter(fun->body);
m_state.controlFlowState = ControlFlowState::Default;
m_values.clear();
for (auto const& retVar: fun->returnVariables)

View File

@ -53,11 +53,12 @@ class TraceLimitReached: public InterpreterTerminatedGeneric
{
};
enum class LoopState
enum class ControlFlowState
{
Default,
Continue,
Break,
Leave
};
struct InterpreterState
@ -89,7 +90,7 @@ struct InterpreterState
size_t maxTraceSize = 0;
size_t maxSteps = 0;
size_t numSteps = 0;
LoopState loopState = LoopState::Default;
ControlFlowState controlFlowState = ControlFlowState::Default;
void dumpTraceAndState(std::ostream& _out) const;
};
@ -121,6 +122,7 @@ public:
void operator()(ForLoop const&) override;
void operator()(Break const&) override;
void operator()(Continue const&) override;
void operator()(Leave const&) override;
void operator()(Block const& _block) override;
std::vector<std::string> const& trace() const { return m_state.trace; }