mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #2225 from ethereum/julia-for
Implement for statement in assembly parser / printer / code generator
This commit is contained in:
commit
0c75afb2c1
@ -8,6 +8,8 @@ Features:
|
||||
* Inline Assembly: Present proper error message when not supplying enough arguments to a functional
|
||||
instruction.
|
||||
* Inline Assembly: introduce ``keccak256`` as an opcode. ``sha3`` is still a valid alias.
|
||||
* Inline Assembly: ``for`` and ``switch`` statements.
|
||||
* Inline Assembly: function definitions and function calls.
|
||||
|
||||
Bugfixes:
|
||||
* Type Checker: Make UTF8-validation a bit more sloppy to include more valid sequences.
|
||||
|
@ -28,11 +28,8 @@ arising when writing manual assembly by the following features:
|
||||
* access to external variables: ``function f(uint x) { assembly { x := sub(x, 1) } }``
|
||||
* labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))``
|
||||
* loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }``
|
||||
* switch statements: ``switch x case 0: { y := mul(x, 2) } default: { y := 0 }``
|
||||
* function calls: ``function f(x) -> y { switch x case 0: { y := 1 } default: { y := mul(x, f(sub(x, 1))) } }``
|
||||
|
||||
.. note::
|
||||
Of the above, loops, function calls and switch statements are not yet implemented.
|
||||
* switch statements: ``switch x case 0 { y := mul(x, 2) } default { y := 0 }``
|
||||
* function calls: ``function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }``
|
||||
|
||||
We now want to describe the inline assembly language in detail.
|
||||
|
||||
@ -501,9 +498,6 @@ is performed by replacing the variable's value on the stack by the new value.
|
||||
Switch
|
||||
------
|
||||
|
||||
.. note::
|
||||
Switch is not yet implemented.
|
||||
|
||||
You can use a switch statement as a very basic version of "if/else".
|
||||
It takes the value of an expression and compares it to several constants.
|
||||
The branch corresponding to the matching constant is taken. Contrary to the
|
||||
@ -516,10 +510,10 @@ case called ``default``.
|
||||
assembly {
|
||||
let x := 0
|
||||
switch calldataload(4)
|
||||
case 0: {
|
||||
case 0 {
|
||||
x := calldataload(0x24)
|
||||
}
|
||||
default: {
|
||||
default {
|
||||
x := calldataload(0x44)
|
||||
}
|
||||
sstore(0, div(x, 2))
|
||||
@ -531,13 +525,10 @@ case does require them.
|
||||
Loops
|
||||
-----
|
||||
|
||||
.. note::
|
||||
Loops are not yet implemented.
|
||||
|
||||
Assembly supports a simple for-style loop. For-style loops have
|
||||
a header containing an initializing part, a condition and a post-iteration
|
||||
part. The condition has to be a functional-style expression, while
|
||||
the other two can also be blocks. If the initializing part is a block that
|
||||
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).
|
||||
|
||||
@ -555,9 +546,6 @@ The following example computes the sum of an area in memory.
|
||||
Functions
|
||||
---------
|
||||
|
||||
.. note::
|
||||
Functions are not yet implemented.
|
||||
|
||||
Assembly allows the definition of low-level functions. These take their
|
||||
arguments (and a return PC) from the stack and also put the results onto the
|
||||
stack. Calling a function looks the same way as executing a functional-style
|
||||
@ -569,7 +557,7 @@ defined outside of that function. There is no explicit ``return``
|
||||
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)``.
|
||||
them to a tuple using ``a, b := f(x)`` or ``let a, b := f(x)``.
|
||||
|
||||
The following example implements the power function by square-and-multiply.
|
||||
|
||||
@ -578,12 +566,12 @@ The following example implements the power function by square-and-multiply.
|
||||
assembly {
|
||||
function power(base, exponent) -> result {
|
||||
switch exponent
|
||||
0: { result := 1 }
|
||||
1: { result := base }
|
||||
default: {
|
||||
case 0 { result := 1 }
|
||||
case 1 { result := base }
|
||||
default {
|
||||
result := power(mul(base, base), div(exponent, 2))
|
||||
switch mod(exponent, 2)
|
||||
1: { result := mul(base, result) }
|
||||
case 1 { result := mul(base, result) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -703,13 +691,13 @@ The following assembly will be generated::
|
||||
mstore(0x40, 0x60) // store the "free memory pointer"
|
||||
// function dispatcher
|
||||
switch div(calldataload(0), exp(2, 226))
|
||||
case 0xb3de648b: {
|
||||
case 0xb3de648b {
|
||||
let (r) = f(calldataload(4))
|
||||
let ret := $allocate(0x20)
|
||||
mstore(ret, r)
|
||||
return(ret, 0x20)
|
||||
}
|
||||
default: { revert(0, 0) }
|
||||
default { revert(0, 0) }
|
||||
// memory allocator
|
||||
function $allocate(size) -> pos {
|
||||
pos := mload(0x40)
|
||||
@ -860,8 +848,8 @@ Grammar::
|
||||
AssemblyAssignment = '=:' Identifier
|
||||
LabelDefinition = Identifier ':'
|
||||
AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase*
|
||||
( 'default' ':' AssemblyBlock )?
|
||||
AssemblyCase = 'case' FunctionalAssemblyExpression ':' AssemblyBlock
|
||||
( 'default' AssemblyBlock )?
|
||||
AssemblyCase = 'case' FunctionalAssemblyExpression AssemblyBlock
|
||||
AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')'
|
||||
( '->' '(' IdentifierList ')' )? AssemblyBlock
|
||||
AssemblyFor = 'for' ( AssemblyBlock | FunctionalAssemblyExpression)
|
||||
|
@ -33,26 +33,6 @@ using namespace dev::julia;
|
||||
using namespace dev::solidity;
|
||||
using namespace dev::solidity::assembly;
|
||||
|
||||
void CodeTransform::run(Block const& _block)
|
||||
{
|
||||
m_scope = m_info.scopes.at(&_block).get();
|
||||
|
||||
int blockStartStackHeight = m_assembly.stackHeight();
|
||||
std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
|
||||
|
||||
m_assembly.setSourceLocation(_block.location);
|
||||
|
||||
// pop variables
|
||||
for (auto const& identifier: m_scope->identifiers)
|
||||
if (identifier.second.type() == typeid(Scope::Variable))
|
||||
m_assembly.appendInstruction(solidity::Instruction::POP);
|
||||
|
||||
int deposit = m_assembly.stackHeight() - blockStartStackHeight;
|
||||
solAssert(deposit == 0, "Invalid stack height at end of block.");
|
||||
checkStackHeight(&_block);
|
||||
}
|
||||
|
||||
|
||||
void CodeTransform::operator()(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(m_scope, "");
|
||||
@ -315,7 +295,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
||||
}
|
||||
|
||||
CodeTransform(m_assembly, m_info, m_evm15, m_identifierAccess, localStackAdjustment, m_context)
|
||||
.run(_function.body);
|
||||
(_function.body);
|
||||
|
||||
{
|
||||
// The stack layout here is:
|
||||
@ -358,9 +338,54 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
||||
checkStackHeight(&_function);
|
||||
}
|
||||
|
||||
void CodeTransform::operator()(ForLoop const& _forLoop)
|
||||
{
|
||||
Scope* originalScope = m_scope;
|
||||
// We start with visiting the block, but not finalizing it.
|
||||
m_scope = m_info.scopes.at(&_forLoop.pre).get();
|
||||
int stackStartHeight = m_assembly.stackHeight();
|
||||
|
||||
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();
|
||||
|
||||
m_assembly.setSourceLocation(_forLoop.location);
|
||||
m_assembly.appendLabel(loopStart);
|
||||
|
||||
visitExpression(*_forLoop.condition);
|
||||
m_assembly.setSourceLocation(_forLoop.location);
|
||||
m_assembly.appendInstruction(solidity::Instruction::ISZERO);
|
||||
m_assembly.appendJumpToIf(loopEnd);
|
||||
|
||||
(*this)(_forLoop.body);
|
||||
|
||||
m_assembly.setSourceLocation(_forLoop.location);
|
||||
m_assembly.appendLabel(postPart);
|
||||
|
||||
(*this)(_forLoop.post);
|
||||
|
||||
m_assembly.setSourceLocation(_forLoop.location);
|
||||
m_assembly.appendJumpTo(loopStart);
|
||||
m_assembly.appendLabel(loopEnd);
|
||||
|
||||
finalizeBlock(_forLoop.pre, stackStartHeight);
|
||||
m_scope = originalScope;
|
||||
}
|
||||
|
||||
void CodeTransform::operator()(Block const& _block)
|
||||
{
|
||||
CodeTransform(m_assembly, m_info, m_evm15, m_identifierAccess, m_stackAdjustment, m_context).run(_block);
|
||||
Scope* originalScope = m_scope;
|
||||
m_scope = m_info.scopes.at(&_block).get();
|
||||
|
||||
int blockStartStackHeight = m_assembly.stackHeight();
|
||||
visitStatements(_block.statements);
|
||||
|
||||
finalizeBlock(_block, blockStartStackHeight);
|
||||
m_scope = originalScope;
|
||||
}
|
||||
|
||||
AbstractAssembly::LabelID CodeTransform::labelFromIdentifier(Identifier const& _identifier)
|
||||
@ -401,6 +426,26 @@ void CodeTransform::visitExpression(Statement const& _expression)
|
||||
expectDeposit(1, height);
|
||||
}
|
||||
|
||||
void CodeTransform::visitStatements(vector<Statement> const& _statements)
|
||||
{
|
||||
for (auto const& statement: _statements)
|
||||
boost::apply_visitor(*this, statement);
|
||||
}
|
||||
|
||||
void CodeTransform::finalizeBlock(Block const& _block, int blockStartStackHeight)
|
||||
{
|
||||
m_assembly.setSourceLocation(_block.location);
|
||||
|
||||
// pop variables
|
||||
solAssert(m_info.scopes.at(&_block).get() == m_scope, "");
|
||||
for (size_t i = 0; i < m_scope->numberOfVariables(); ++i)
|
||||
m_assembly.appendInstruction(solidity::Instruction::POP);
|
||||
|
||||
int deposit = m_assembly.stackHeight() - blockStartStackHeight;
|
||||
solAssert(deposit == 0, "Invalid stack height at end of block.");
|
||||
checkStackHeight(&_block);
|
||||
}
|
||||
|
||||
void CodeTransform::generateAssignment(Identifier const& _variableName)
|
||||
{
|
||||
solAssert(m_scope, "");
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <libjulia/backends/evm/EVMAssembly.h>
|
||||
|
||||
#include <libsolidity/inlineasm/AsmScope.h>
|
||||
#include <libsolidity/inlineasm/AsmDataForward.h>
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
@ -32,21 +33,6 @@ namespace solidity
|
||||
class ErrorReporter;
|
||||
namespace assembly
|
||||
{
|
||||
struct Literal;
|
||||
struct Block;
|
||||
struct Switch;
|
||||
struct Label;
|
||||
struct FunctionalInstruction;
|
||||
struct Assignment;
|
||||
struct VariableDeclaration;
|
||||
struct Instruction;
|
||||
struct Identifier;
|
||||
struct StackAssignment;
|
||||
struct FunctionDefinition;
|
||||
struct FunctionCall;
|
||||
|
||||
using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, Block>;
|
||||
|
||||
struct AsmAnalysisInfo;
|
||||
}
|
||||
}
|
||||
@ -75,9 +61,6 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/// Processes the block and appends the resulting code to the assembly.
|
||||
void run(solidity::assembly::Block const& _block);
|
||||
|
||||
protected:
|
||||
struct Context
|
||||
{
|
||||
@ -115,6 +98,7 @@ public:
|
||||
void operator()(solidity::assembly::VariableDeclaration const& _varDecl);
|
||||
void operator()(solidity::assembly::Switch const& _switch);
|
||||
void operator()(solidity::assembly::FunctionDefinition const&);
|
||||
void operator()(solidity::assembly::ForLoop const&);
|
||||
void operator()(solidity::assembly::Block const& _block);
|
||||
|
||||
private:
|
||||
@ -126,6 +110,12 @@ private:
|
||||
/// Generates code for an expression that is supposed to return a single value.
|
||||
void visitExpression(solidity::assembly::Statement const& _expression);
|
||||
|
||||
void visitStatements(std::vector<solidity::assembly::Statement> const& _statements);
|
||||
|
||||
/// Pops all variables declared in the block and checks that the stack height is equal
|
||||
/// to @a _blackStartStackHeight.
|
||||
void finalizeBlock(solidity::assembly::Block const& _block, int _blockStartStackHeight);
|
||||
|
||||
void generateAssignment(solidity::assembly::Identifier const& _variableName);
|
||||
|
||||
/// Determines the stack height difference to the given variables. Throws
|
||||
|
@ -310,6 +310,33 @@ bool AsmAnalyzer::operator()(Switch const& _switch)
|
||||
return success;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(assembly::ForLoop const& _for)
|
||||
{
|
||||
Scope* originalScope = m_currentScope;
|
||||
|
||||
bool success = true;
|
||||
if (!(*this)(_for.pre))
|
||||
success = false;
|
||||
// The block was closed already, but we re-open it again and stuff the
|
||||
// condition, the body and the post part inside.
|
||||
m_stackHeight += scope(&_for.pre).numberOfVariables();
|
||||
m_currentScope = &scope(&_for.pre);
|
||||
|
||||
if (!expectExpression(*_for.condition))
|
||||
success = false;
|
||||
m_stackHeight--;
|
||||
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;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(Block const& _block)
|
||||
{
|
||||
bool success = true;
|
||||
@ -322,9 +349,7 @@ bool AsmAnalyzer::operator()(Block const& _block)
|
||||
if (!boost::apply_visitor(*this, s))
|
||||
success = false;
|
||||
|
||||
for (auto const& identifier: scope(&_block).identifiers)
|
||||
if (identifier.second.type() == typeid(Scope::Variable))
|
||||
--m_stackHeight;
|
||||
m_stackHeight -= scope(&_block).numberOfVariables();
|
||||
|
||||
int const stackDiff = m_stackHeight - initialStackHeight;
|
||||
if (stackDiff != 0)
|
||||
|
@ -26,6 +26,8 @@
|
||||
|
||||
#include <libjulia/backends/evm/AbstractAssembly.h>
|
||||
|
||||
#include <libsolidity/inlineasm/AsmDataForward.h>
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
#include <functional>
|
||||
@ -39,20 +41,6 @@ class ErrorReporter;
|
||||
namespace assembly
|
||||
{
|
||||
|
||||
struct Literal;
|
||||
struct Block;
|
||||
struct Label;
|
||||
struct FunctionalInstruction;
|
||||
struct Assignment;
|
||||
struct VariableDeclaration;
|
||||
struct Instruction;
|
||||
struct Identifier;
|
||||
struct StackAssignment;
|
||||
struct FunctionDefinition;
|
||||
struct FunctionCall;
|
||||
struct Switch;
|
||||
using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, Block>;
|
||||
|
||||
struct AsmAnalysisInfo;
|
||||
|
||||
/**
|
||||
@ -83,6 +71,7 @@ public:
|
||||
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
|
||||
bool operator()(assembly::FunctionCall const& _functionCall);
|
||||
bool operator()(assembly::Switch const& _switch);
|
||||
bool operator()(assembly::ForLoop const& _forLoop);
|
||||
bool operator()(assembly::Block const& _block);
|
||||
|
||||
private:
|
||||
|
@ -20,6 +20,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/inlineasm/AsmDataForward.h>
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
#include <map>
|
||||
@ -33,23 +35,8 @@ namespace solidity
|
||||
namespace assembly
|
||||
{
|
||||
|
||||
struct Literal;
|
||||
struct Block;
|
||||
struct Label;
|
||||
struct FunctionalInstruction;
|
||||
struct Assignment;
|
||||
struct VariableDeclaration;
|
||||
struct Instruction;
|
||||
struct Identifier;
|
||||
struct StackAssignment;
|
||||
struct FunctionDefinition;
|
||||
struct FunctionCall;
|
||||
struct Switch;
|
||||
|
||||
struct Scope;
|
||||
|
||||
using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, Block>;
|
||||
|
||||
struct AsmAnalysisInfo
|
||||
{
|
||||
using StackHeightInfo = std::map<void const*, int>;
|
||||
|
@ -141,5 +141,5 @@ void assembly::CodeGenerator::assemble(
|
||||
)
|
||||
{
|
||||
EthAssemblyAdapter assemblyAdapter(_assembly);
|
||||
julia::CodeTransform(assemblyAdapter, _analysisInfo, false, _identifierAccess).run(_parsedData);
|
||||
julia::CodeTransform(assemblyAdapter, _analysisInfo, false, _identifierAccess)(_parsedData);
|
||||
}
|
||||
|
@ -22,10 +22,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
#include <libsolidity/inlineasm/AsmDataForward.h>
|
||||
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libevmasm/SourceLocation.h>
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
@ -38,23 +41,6 @@ using Type = std::string;
|
||||
struct TypedName { SourceLocation location; std::string name; Type type; };
|
||||
using TypedNameList = std::vector<TypedName>;
|
||||
|
||||
/// What follows are the AST nodes for assembly.
|
||||
|
||||
struct Instruction;
|
||||
struct Literal;
|
||||
struct Label;
|
||||
struct StackAssignment;
|
||||
struct Identifier;
|
||||
struct Assignment;
|
||||
struct VariableDeclaration;
|
||||
struct FunctionalInstruction;
|
||||
struct FunctionDefinition;
|
||||
struct FunctionCall;
|
||||
struct Switch;
|
||||
struct Block;
|
||||
|
||||
using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, Block>;
|
||||
|
||||
/// Direct EVM instruction (except PUSHi and JUMPDEST)
|
||||
struct Instruction { SourceLocation location; solidity::Instruction instruction; };
|
||||
/// Literal number or string (up to 32 bytes)
|
||||
@ -82,6 +68,7 @@ struct FunctionDefinition { SourceLocation location; std::string name; TypedName
|
||||
struct Case { SourceLocation location; std::shared_ptr<Literal> value; Block body; };
|
||||
/// Switch statement
|
||||
struct Switch { SourceLocation location; std::shared_ptr<Statement> expression; std::vector<Case> cases; };
|
||||
struct ForLoop { SourceLocation location; Block pre; std::shared_ptr<Statement> condition; Block post; Block body; };
|
||||
|
||||
struct LocationExtractor: boost::static_visitor<SourceLocation>
|
||||
{
|
||||
|
52
libsolidity/inlineasm/AsmDataForward.h
Normal file
52
libsolidity/inlineasm/AsmDataForward.h
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @author Christian <c@ethdev.com>
|
||||
* @date 2016
|
||||
* Forward declaration of classes for inline assembly / JULIA AST
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace assembly
|
||||
{
|
||||
|
||||
struct Instruction;
|
||||
struct Literal;
|
||||
struct Label;
|
||||
struct StackAssignment;
|
||||
struct Identifier;
|
||||
struct Assignment;
|
||||
struct VariableDeclaration;
|
||||
struct FunctionalInstruction;
|
||||
struct FunctionDefinition;
|
||||
struct FunctionCall;
|
||||
struct Switch;
|
||||
struct ForLoop;
|
||||
struct Block;
|
||||
|
||||
using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, ForLoop, Block>;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -87,6 +87,8 @@ assembly::Statement Parser::parseStatement()
|
||||
_switch.location.end = _switch.cases.back().body.location.end;
|
||||
return _switch;
|
||||
}
|
||||
case Token::For:
|
||||
return parseForLoop();
|
||||
case Token::Assign:
|
||||
{
|
||||
if (m_julia)
|
||||
@ -171,6 +173,20 @@ assembly::Case Parser::parseCase()
|
||||
return _case;
|
||||
}
|
||||
|
||||
assembly::ForLoop Parser::parseForLoop()
|
||||
{
|
||||
ForLoop forLoop = createWithLocation<ForLoop>();
|
||||
expectToken(Token::For);
|
||||
forLoop.pre = parseBlock();
|
||||
forLoop.condition = make_shared<Statement>(parseExpression());
|
||||
if (forLoop.condition->type() == typeid(assembly::Instruction))
|
||||
fatalParserError("Instructions are not supported as conditions for the for statement.");
|
||||
forLoop.post = parseBlock();
|
||||
forLoop.body = parseBlock();
|
||||
forLoop.location.end = forLoop.body.location.end;
|
||||
return forLoop;
|
||||
}
|
||||
|
||||
assembly::Statement Parser::parseExpression()
|
||||
{
|
||||
Statement operation = parseElementaryOperation(true);
|
||||
|
@ -63,6 +63,7 @@ protected:
|
||||
Block parseBlock();
|
||||
Statement parseStatement();
|
||||
Case parseCase();
|
||||
ForLoop parseForLoop();
|
||||
/// Parses a functional expression that has to push exactly one stack element
|
||||
Statement parseExpression();
|
||||
static std::map<std::string, dev::solidity::Instruction> const& instructions();
|
||||
|
@ -181,6 +181,19 @@ string AsmPrinter::operator()(Switch const& _switch)
|
||||
return out;
|
||||
}
|
||||
|
||||
string AsmPrinter::operator()(assembly::ForLoop const& _forLoop)
|
||||
{
|
||||
string out = "for ";
|
||||
out += (*this)(_forLoop.pre);
|
||||
out += "\n";
|
||||
out += boost::apply_visitor(*this, *_forLoop.condition);
|
||||
out += "\n";
|
||||
out += (*this)(_forLoop.post);
|
||||
out += "\n";
|
||||
out += (*this)(_forLoop.body);
|
||||
return out;
|
||||
}
|
||||
|
||||
string AsmPrinter::operator()(Block const& _block)
|
||||
{
|
||||
if (_block.statements.empty())
|
||||
|
@ -22,6 +22,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/inlineasm/AsmDataForward.h>
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
namespace dev
|
||||
@ -30,18 +32,6 @@ namespace solidity
|
||||
{
|
||||
namespace assembly
|
||||
{
|
||||
struct Instruction;
|
||||
struct Literal;
|
||||
struct Identifier;
|
||||
struct FunctionalInstruction;
|
||||
struct Label;
|
||||
struct StackAssignment;
|
||||
struct Assignment;
|
||||
struct VariableDeclaration;
|
||||
struct FunctionDefinition;
|
||||
struct FunctionCall;
|
||||
struct Switch;
|
||||
struct Block;
|
||||
|
||||
class AsmPrinter: public boost::static_visitor<std::string>
|
||||
{
|
||||
@ -59,6 +49,7 @@ public:
|
||||
std::string operator()(assembly::FunctionDefinition const& _functionDefinition);
|
||||
std::string operator()(assembly::FunctionCall const& _functionCall);
|
||||
std::string operator()(assembly::Switch const& _switch);
|
||||
std::string operator()(assembly::ForLoop const& _forLoop);
|
||||
std::string operator()(assembly::Block const& _block);
|
||||
|
||||
private:
|
||||
|
@ -80,6 +80,15 @@ bool Scope::exists(string const& _name)
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t Scope::numberOfVariables() const
|
||||
{
|
||||
size_t count = 0;
|
||||
for (auto const& identifier: identifiers)
|
||||
if (identifier.second.type() == typeid(Scope::Variable))
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
bool Scope::insideFunction() const
|
||||
{
|
||||
for (Scope const* s = this; s; s = s->superScope)
|
||||
|
@ -109,6 +109,8 @@ struct Scope
|
||||
/// across function and assembly boundaries).
|
||||
bool exists(std::string const& _name);
|
||||
|
||||
/// @returns the number of variables directly registered inside the scope.
|
||||
size_t numberOfVariables() const;
|
||||
/// @returns true if this scope is inside a function.
|
||||
bool insideFunction() const;
|
||||
|
||||
|
@ -111,6 +111,26 @@ bool ScopeFiller::operator()(Switch const& _switch)
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ScopeFiller::operator()(ForLoop const& _forLoop)
|
||||
{
|
||||
Scope* originalScope = m_currentScope;
|
||||
|
||||
bool success = true;
|
||||
if (!(*this)(_forLoop.pre))
|
||||
success = false;
|
||||
m_currentScope = &scope(&_forLoop.pre);
|
||||
if (!boost::apply_visitor(*this, *_forLoop.condition))
|
||||
success = false;
|
||||
if (!(*this)(_forLoop.body))
|
||||
success = false;
|
||||
if (!(*this)(_forLoop.post))
|
||||
success = false;
|
||||
|
||||
m_currentScope = originalScope;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ScopeFiller::operator()(Block const& _block)
|
||||
{
|
||||
bool success = true;
|
||||
|
@ -20,6 +20,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/inlineasm/AsmDataForward.h>
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
#include <functional>
|
||||
@ -35,19 +37,6 @@ namespace assembly
|
||||
{
|
||||
|
||||
struct TypedName;
|
||||
struct Literal;
|
||||
struct Block;
|
||||
struct Label;
|
||||
struct FunctionalInstruction;
|
||||
struct Assignment;
|
||||
struct VariableDeclaration;
|
||||
struct Instruction;
|
||||
struct Identifier;
|
||||
struct StackAssignment;
|
||||
struct FunctionDefinition;
|
||||
struct FunctionCall;
|
||||
struct Switch;
|
||||
|
||||
struct Scope;
|
||||
struct AsmAnalysisInfo;
|
||||
|
||||
@ -71,6 +60,7 @@ public:
|
||||
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
|
||||
bool operator()(assembly::FunctionCall const&) { return true; }
|
||||
bool operator()(assembly::Switch const& _switch);
|
||||
bool operator()(assembly::ForLoop const& _forLoop);
|
||||
bool operator()(assembly::Block const& _block);
|
||||
|
||||
private:
|
||||
|
@ -100,7 +100,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
|
||||
{
|
||||
MachineAssemblyObject object;
|
||||
julia::EVMAssembly assembly(true);
|
||||
julia::CodeTransform(assembly, *m_analysisInfo, true).run(*m_parserResult);
|
||||
julia::CodeTransform(assembly, *m_analysisInfo, true)(*m_parserResult);
|
||||
object.bytecode = make_shared<eth::LinkerObject>(assembly.finalize());
|
||||
/// TOOD: fill out text representation
|
||||
return object;
|
||||
|
@ -269,6 +269,7 @@ BOOST_AUTO_TEST_CASE(switch_invalid_expression)
|
||||
{
|
||||
CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected.");
|
||||
CHECK_PARSE_ERROR("{ switch calldatasize default {} }", ParserError, "Instructions are not supported as expressions for switch.");
|
||||
CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", ParserError, "Instruction \"mstore\" not allowed in this context");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(switch_default_before_case)
|
||||
@ -291,6 +292,41 @@ BOOST_AUTO_TEST_CASE(switch_invalid_body)
|
||||
CHECK_PARSE_ERROR("{ switch 42 case 1 mul case 2 {} default {} }", ParserError, "Expected token LBrace got 'Identifier'");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(for_statement)
|
||||
{
|
||||
BOOST_CHECK(successParse("{ for {} 1 {} {} }"));
|
||||
BOOST_CHECK(successParse("{ for { let i := 1 } lt(i, 5) { i := add(i, 1) } {} }"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(for_invalid_expression)
|
||||
{
|
||||
CHECK_PARSE_ERROR("{ for {} {} {} {} }", ParserError, "Literal, identifier or instruction expected.");
|
||||
CHECK_PARSE_ERROR("{ for 1 1 {} {} }", ParserError, "Expected token LBrace got 'Number'");
|
||||
CHECK_PARSE_ERROR("{ for {} 1 1 {} }", ParserError, "Expected token LBrace got 'Number'");
|
||||
CHECK_PARSE_ERROR("{ for {} 1 {} 1 }", ParserError, "Expected token LBrace got 'Number'");
|
||||
CHECK_PARSE_ERROR("{ for {} calldatasize {} {} }", ParserError, "Instructions are not supported as conditions for the for statement.");
|
||||
CHECK_PARSE_ERROR("{ for {} mstore(1, 1) {} {} }", ParserError, "Instruction \"mstore\" not allowed in this context");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(for_visibility)
|
||||
{
|
||||
BOOST_CHECK(successParse("{ for { let i := 1 } i { pop(i) } { pop(i) } }"));
|
||||
CHECK_PARSE_ERROR("{ for {} i { let i := 1 } {} }", DeclarationError, "Identifier not found");
|
||||
CHECK_PARSE_ERROR("{ for {} 1 { let i := 1 } { pop(i) } }", DeclarationError, "Identifier not found");
|
||||
CHECK_PARSE_ERROR("{ for {} 1 { pop(i) } { let i := 1 } }", DeclarationError, "Identifier not found");
|
||||
CHECK_PARSE_ERROR("{ for { pop(i) } 1 { let i := 1 } {} }", DeclarationError, "Identifier not found");
|
||||
CHECK_PARSE_ERROR("{ for { pop(i) } 1 { } { let i := 1 } }", DeclarationError, "Identifier not found");
|
||||
CHECK_PARSE_ERROR("{ for {} i {} { let i := 1 } }", DeclarationError, "Identifier not found");
|
||||
CHECK_PARSE_ERROR("{ for {} 1 { pop(i) } { let i := 1 } }", DeclarationError, "Identifier not found");
|
||||
CHECK_PARSE_ERROR("{ for { let x := 1 } 1 { let x := 1 } {} }", DeclarationError, "Variable name x already taken in this scope");
|
||||
CHECK_PARSE_ERROR("{ for { let x := 1 } 1 {} { let x := 1 } }", DeclarationError, "Variable name x already taken in this scope");
|
||||
CHECK_PARSE_ERROR("{ let x := 1 for { let x := 1 } 1 {} {} }", DeclarationError, "Variable name x already taken in this scope");
|
||||
CHECK_PARSE_ERROR("{ let x := 1 for {} 1 { let x := 1 } {} }", DeclarationError, "Variable name x already taken in this scope");
|
||||
CHECK_PARSE_ERROR("{ let x := 1 for {} 1 {} { let x := 1 } }", DeclarationError, "Variable name x already taken in this scope");
|
||||
// Check that body and post are not sub-scopes of each other.
|
||||
BOOST_CHECK(successParse("{ for {} 1 { let x := 1 } { let x := 1 } }"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(blocks)
|
||||
{
|
||||
BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }"));
|
||||
@ -409,6 +445,11 @@ BOOST_AUTO_TEST_CASE(print_switch)
|
||||
parsePrintCompare("{\n switch 42\n case 1 {\n }\n case 2 {\n }\n default {\n }\n}");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(print_for)
|
||||
{
|
||||
parsePrintCompare("{\n let ret := 5\n for {\n let i := 1\n }\n lt(i, 15)\n {\n i := add(i, 1)\n }\n {\n ret := mul(ret, i)\n }\n}");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(function_definitions_multiple_args)
|
||||
{
|
||||
parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> x, y\n {\n }\n}");
|
||||
@ -534,6 +575,13 @@ BOOST_AUTO_TEST_CASE(switch_statement)
|
||||
BOOST_CHECK(successAssemble("{ let a := 2 switch calldataload(0) case 1 { a := 1 } case 2 { a := 5 } }"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(for_statement)
|
||||
{
|
||||
BOOST_CHECK(successAssemble("{ for {} 1 {} {} }"));
|
||||
BOOST_CHECK(successAssemble("{ let x := calldatasize() for { let i := 0} lt(i, x) { i := add(i, 1) } { mstore(i, 2) } }"));
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(large_constant)
|
||||
{
|
||||
auto source = R"({
|
||||
|
@ -7685,6 +7685,55 @@ BOOST_AUTO_TEST_CASE(inline_assembly_recursion)
|
||||
BOOST_CHECK(callContractFunction("f(uint256)", u256(4)) == encodeArgs(u256(24)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(inline_assembly_for)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract C {
|
||||
function f(uint a) returns (uint b) {
|
||||
assembly {
|
||||
function fac(n) -> nf {
|
||||
nf := 1
|
||||
for { let i := n } gt(i, 0) { i := sub(i, 1) } {
|
||||
nf := mul(nf, i)
|
||||
}
|
||||
}
|
||||
b := fac(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
compileAndRun(sourceCode, 0, "C");
|
||||
BOOST_CHECK(callContractFunction("f(uint256)", u256(0)) == encodeArgs(u256(1)));
|
||||
BOOST_CHECK(callContractFunction("f(uint256)", u256(1)) == encodeArgs(u256(1)));
|
||||
BOOST_CHECK(callContractFunction("f(uint256)", u256(2)) == encodeArgs(u256(2)));
|
||||
BOOST_CHECK(callContractFunction("f(uint256)", u256(3)) == encodeArgs(u256(6)));
|
||||
BOOST_CHECK(callContractFunction("f(uint256)", u256(4)) == encodeArgs(u256(24)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(inline_assembly_for2)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract C {
|
||||
uint st;
|
||||
function f(uint a) returns (uint b, uint c, uint d) {
|
||||
st = 0;
|
||||
assembly {
|
||||
function sideeffect(r) -> x { sstore(0, add(sload(0), r)) x := 1}
|
||||
for { let i := a } eq(i, sideeffect(2)) { d := add(d, 3) } {
|
||||
b := i
|
||||
i := 0
|
||||
}
|
||||
}
|
||||
c = st;
|
||||
}
|
||||
}
|
||||
)";
|
||||
compileAndRun(sourceCode, 0, "C");
|
||||
BOOST_CHECK(callContractFunction("f(uint256)", u256(0)) == encodeArgs(u256(0), u256(2), u256(0)));
|
||||
BOOST_CHECK(callContractFunction("f(uint256)", u256(1)) == encodeArgs(u256(1), u256(4), u256(3)));
|
||||
BOOST_CHECK(callContractFunction("f(uint256)", u256(2)) == encodeArgs(u256(0), u256(2), u256(0)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(index_access_with_type_conversion)
|
||||
{
|
||||
// Test for a bug where higher order bits cleanup was not done for array index access.
|
||||
|
Loading…
Reference in New Issue
Block a user