Merge pull request #2304 from ethereum/evm15asm

Implementation of EVM 1.5 backend
This commit is contained in:
Alex Beregszaszi 2017-06-09 11:23:40 +01:00 committed by GitHub
commit 76667fed4f
24 changed files with 1047 additions and 194 deletions

View File

@ -85,6 +85,13 @@ enum class Instruction: uint8_t
DIFFICULTY, ///< get the block's difficulty
GASLIMIT, ///< get the block's gas limit
JUMPTO = 0x4a, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
JUMPIF, ///< conditionally alter the program counter -- not part of Instructions.cpp
JUMPV, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
JUMPSUB, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
JUMPSUBV, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
RETURNSUB, ///< return to subroutine jumped from -- not part of Instructions.cpp
POP = 0x50, ///< remove item from stack
MLOAD, ///< load word from memory
MSTORE, ///< save word to memory
@ -97,6 +104,8 @@ enum class Instruction: uint8_t
MSIZE, ///< get the size of active memory
GAS, ///< get the amount of available gas
JUMPDEST, ///< set a potential jump destination
BEGINSUB, ///< set a potential jumpsub destination -- not part of Instructions.cpp
BEGINDATA, ///< begine the data section -- not part of Instructions.cpp
PUSH1 = 0x60, ///< place 1 byte item on stack
PUSH2, ///< place 2 byte item on stack

View File

@ -41,6 +41,9 @@ struct Identifier;
namespace julia
{
///
/// Assembly class that abstracts both the libevmasm assembly and the new julia evm assembly.
///
class AbstractAssembly
{
public:
@ -66,6 +69,26 @@ public:
/// Append a reference to a to-be-linked symobl.
/// Currently, we assume that the value is always a 20 byte number.
virtual void appendLinkerSymbol(std::string const& _name) = 0;
/// Append a jump instruction.
/// @param _stackDiffAfter the stack adjustment after this instruction.
/// This is helpful to stack height analysis if there is no continuing control flow.
virtual void appendJump(int _stackDiffAfter) = 0;
/// Append a jump-to-immediate operation.
/// @param _stackDiffAfter the stack adjustment after this instruction.
virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter = 0) = 0;
/// Append a jump-to-if-immediate operation.
virtual void appendJumpToIf(LabelID _labelId) = 0;
/// Start a subroutine identified by @a _labelId that takes @a _arguments
/// stack slots as arguments.
virtual void appendBeginsub(LabelID _labelId, int _arguments) = 0;
/// Call a subroutine identified by @a _labelId, taking @a _arguments from the
/// stack upon call and putting @a _returns arguments onto the stack upon return.
virtual void appendJumpsub(LabelID _labelId, int _arguments, int _returns) = 0;
/// Return from a subroutine.
/// @param _stackDiffAfter the stack adjustment after this instruction.
virtual void appendReturnsub(int _returns, int _stackDiffAfter = 0) = 0;
};
enum class IdentifierContext { LValue, RValue };
@ -74,7 +97,7 @@ enum class IdentifierContext { LValue, RValue };
/// to inline assembly (not used in standalone assembly mode).
struct ExternalIdentifierAccess
{
using Resolver = std::function<size_t(solidity::assembly::Identifier const&, IdentifierContext)>;
using Resolver = std::function<size_t(solidity::assembly::Identifier const&, IdentifierContext, bool /*_crossesFunctionBoundary*/)>;
/// Resolve a an external reference given by the identifier in the given context.
/// @returns the size of the value (number of stack slots) or size_t(-1) if not found.
Resolver resolve;

View File

@ -0,0 +1,175 @@
/*
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/>.
*/
/**
* Assembly interface for EVM and EVM1.5.
*/
#include <libjulia/backends/evm/EVMAssembly.h>
#include <libevmasm/Instruction.h>
#include <libsolidity/interface/Utils.h>
using namespace std;
using namespace dev;
using namespace julia;
namespace
{
/// Size of labels in bytes. Four-byte labels are required by some EVM1.5 instructions.
size_t constexpr labelReferenceSize = 4;
}
void EVMAssembly::setSourceLocation(SourceLocation const&)
{
// Ignored for now;
}
void EVMAssembly::appendInstruction(solidity::Instruction _instr)
{
m_bytecode.push_back(byte(_instr));
m_stackHeight += solidity::instructionInfo(_instr).ret - solidity::instructionInfo(_instr).args;
}
void EVMAssembly::appendConstant(u256 const& _constant)
{
bytes data = toCompactBigEndian(_constant, 1);
appendInstruction(solidity::pushInstruction(data.size()));
m_bytecode += data;
}
void EVMAssembly::appendLabel(LabelID _labelId)
{
setLabelToCurrentPosition(_labelId);
appendInstruction(solidity::Instruction::JUMPDEST);
}
void EVMAssembly::appendLabelReference(LabelID _labelId)
{
solAssert(!m_evm15, "Cannot use plain label references in EMV1.5 mode.");
// @TODO we now always use labelReferenceSize for all labels, it could be shortened
// for some of them.
appendInstruction(solidity::pushInstruction(labelReferenceSize));
m_labelReferences[m_bytecode.size()] = _labelId;
m_bytecode += bytes(labelReferenceSize);
}
EVMAssembly::LabelID EVMAssembly::newLabelId()
{
m_labelPositions[m_nextLabelId] = size_t(-1);
return m_nextLabelId++;
}
void EVMAssembly::appendLinkerSymbol(string const&)
{
solAssert(false, "Linker symbols not yet implemented.");
}
void EVMAssembly::appendJump(int _stackDiffAfter)
{
solAssert(!m_evm15, "Plain JUMP used for EVM 1.5");
appendInstruction(solidity::Instruction::JUMP);
m_stackHeight += _stackDiffAfter;
}
void EVMAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter)
{
if (m_evm15)
{
m_bytecode.push_back(byte(solidity::Instruction::JUMPTO));
appendLabelReferenceInternal(_labelId);
m_stackHeight += _stackDiffAfter;
}
else
{
appendLabelReference(_labelId);
appendJump(_stackDiffAfter);
}
}
void EVMAssembly::appendJumpToIf(LabelID _labelId)
{
if (m_evm15)
{
m_bytecode.push_back(byte(solidity::Instruction::JUMPIF));
appendLabelReferenceInternal(_labelId);
m_stackHeight--;
}
else
{
appendLabelReference(_labelId);
appendInstruction(solidity::Instruction::JUMPI);
}
}
void EVMAssembly::appendBeginsub(LabelID _labelId, int _arguments)
{
solAssert(m_evm15, "BEGINSUB used for EVM 1.0");
solAssert(_arguments >= 0, "");
setLabelToCurrentPosition(_labelId);
m_bytecode.push_back(byte(solidity::Instruction::BEGINSUB));
m_stackHeight += _arguments;
}
void EVMAssembly::appendJumpsub(LabelID _labelId, int _arguments, int _returns)
{
solAssert(m_evm15, "JUMPSUB used for EVM 1.0");
solAssert(_arguments >= 0 && _returns >= 0, "");
m_bytecode.push_back(byte(solidity::Instruction::JUMPSUB));
appendLabelReferenceInternal(_labelId);
m_stackHeight += _returns - _arguments;
}
void EVMAssembly::appendReturnsub(int _returns, int _stackDiffAfter)
{
solAssert(m_evm15, "RETURNSUB used for EVM 1.0");
solAssert(_returns >= 0, "");
m_bytecode.push_back(byte(solidity::Instruction::RETURNSUB));
m_stackHeight += _stackDiffAfter - _returns;
}
eth::LinkerObject EVMAssembly::finalize()
{
for (auto const& ref: m_labelReferences)
{
size_t referencePos = ref.first;
solAssert(m_labelPositions.count(ref.second), "");
size_t labelPos = m_labelPositions.at(ref.second);
solAssert(labelPos != size_t(-1), "Undefined but allocated label used.");
solAssert(m_bytecode.size() >= 4 && referencePos <= m_bytecode.size() - 4, "");
solAssert(uint64_t(labelPos) < (uint64_t(1) << (8 * labelReferenceSize)), "");
for (size_t i = 0; i < labelReferenceSize; i++)
m_bytecode[referencePos + i] = byte((labelPos >> (8 * (labelReferenceSize - i - 1))) & 0xff);
}
eth::LinkerObject obj;
obj.bytecode = m_bytecode;
return obj;
}
void EVMAssembly::setLabelToCurrentPosition(LabelID _labelId)
{
solAssert(m_labelPositions.count(_labelId), "Label not found.");
solAssert(m_labelPositions[_labelId] == size_t(-1), "Label already set.");
m_labelPositions[_labelId] = m_bytecode.size();
}
void EVMAssembly::appendLabelReferenceInternal(LabelID _labelId)
{
m_labelReferences[m_bytecode.size()] = _labelId;
m_bytecode += bytes(labelReferenceSize);
}

View File

@ -0,0 +1,90 @@
/*
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/>.
*/
/**
* Assembly interface for EVM and EVM1.5.
*/
#pragma once
#include <libjulia/backends/evm/AbstractAssembly.h>
#include <libevmasm/LinkerObject.h>
#include <map>
namespace dev
{
namespace julia
{
class EVMAssembly: public AbstractAssembly
{
public:
explicit EVMAssembly(bool _evm15 = false): m_evm15(_evm15) { }
virtual ~EVMAssembly() {}
/// Set a new source location valid starting from the next instruction.
virtual void setSourceLocation(SourceLocation const& _location) override;
/// Retrieve the current height of the stack. This does not have to be zero
/// at the beginning.
virtual int stackHeight() const override { return m_stackHeight; }
/// Append an EVM instruction.
virtual void appendInstruction(solidity::Instruction _instruction) override;
/// Append a constant.
virtual void appendConstant(u256 const& _constant) override;
/// Append a label.
virtual void appendLabel(LabelID _labelId) override;
/// Append a label reference.
virtual void appendLabelReference(LabelID _labelId) override;
/// Generate a new unique label.
virtual LabelID newLabelId() override;
/// Append a reference to a to-be-linked symobl.
/// Currently, we assume that the value is always a 20 byte number.
virtual void appendLinkerSymbol(std::string const& _name) override;
/// Append a jump instruction.
/// @param _stackDiffAfter the stack adjustment after this instruction.
virtual void appendJump(int _stackDiffAfter) override;
/// Append a jump-to-immediate operation.
virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override;
/// Append a jump-to-if-immediate operation.
virtual void appendJumpToIf(LabelID _labelId) override;
/// Start a subroutine.
virtual void appendBeginsub(LabelID _labelId, int _arguments) override;
/// Call a subroutine.
virtual void appendJumpsub(LabelID _labelId, int _arguments, int _returns) override;
/// Return from a subroutine.
virtual void appendReturnsub(int _returns, int _stackDiffAfter) override;
/// Resolves references inside the bytecode and returns the linker object.
eth::LinkerObject finalize();
private:
void setLabelToCurrentPosition(AbstractAssembly::LabelID _labelId);
void appendLabelReferenceInternal(AbstractAssembly::LabelID _labelId);
bool m_evm15 = false; ///< if true, switch to evm1.5 mode
LabelID m_nextLabelId = 0;
int m_stackHeight = 0;
bytes m_bytecode;
std::map<LabelID, size_t> m_labelPositions;
std::map<size_t, LabelID> m_labelReferences;
};
}
}

View File

@ -25,179 +25,155 @@
#include <libsolidity/interface/Utils.h>
#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace dev;
using namespace dev::julia;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
CodeTransform::CodeTransform(
ErrorReporter& _errorReporter,
AbstractAssembly& _assembly,
Block const& _block,
AsmAnalysisInfo& _analysisInfo,
ExternalIdentifierAccess const& _identifierAccess,
int _initialStackHeight
):
m_errorReporter(_errorReporter),
m_assembly(_assembly),
m_info(_analysisInfo),
m_scope(*_analysisInfo.scopes.at(&_block)),
m_identifierAccess(_identifierAccess),
m_initialStackHeight(_initialStackHeight)
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)
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.");
}
void CodeTransform::operator()(const FunctionDefinition&)
{
solAssert(false, "Function definition not removed during desugaring phase.");
}
void CodeTransform::generateAssignment(Identifier const& _variableName, SourceLocation const& _location)
{
auto var = m_scope.lookup(_variableName.name);
if (var)
{
Scope::Variable const& _var = boost::get<Scope::Variable>(*var);
if (int heightDiff = variableHeightDiff(_var, _location, true))
m_assembly.appendInstruction(solidity::swapInstruction(heightDiff - 1));
m_assembly.appendInstruction(solidity::Instruction::POP);
}
else
{
solAssert(
m_identifierAccess.generateCode,
"Identifier not found and no external access available."
);
m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_assembly);
}
}
int CodeTransform::variableHeightDiff(solidity::assembly::Scope::Variable const& _var, SourceLocation const& _location, bool _forSwap)
{
int heightDiff = m_assembly.stackHeight() - _var.stackHeight;
if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16))
{
//@TODO move this to analysis phase.
m_errorReporter.typeError(
_location,
"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")"
);
return 0;
}
else
return heightDiff;
}
void CodeTransform::expectDeposit(int _deposit, int _oldHeight)
{
solAssert(m_assembly.stackHeight() == _oldHeight + _deposit, "Invalid stack deposit.");
}
void CodeTransform::checkStackHeight(void const* _astElement)
{
solAssert(m_info.stackHeightInfo.count(_astElement), "Stack height for AST element not found.");
solAssert(
m_info.stackHeightInfo.at(_astElement) == m_assembly.stackHeight() - m_initialStackHeight,
"Stack height mismatch between analysis and code generation phase."
);
}
void CodeTransform::assignLabelIdIfUnset(Scope::Label& _label)
{
if (!_label.id)
_label.id.reset(m_assembly.newLabelId());
}
void CodeTransform::operator()(Block const& _block)
{
CodeTransform(m_errorReporter, m_assembly, _block, m_info, m_identifierAccess, m_initialStackHeight);
checkStackHeight(&_block);
}
void CodeTransform::operator()(Switch const&)
{
solAssert(false, "Switch not removed during desugaring phase.");
}
void CodeTransform::operator()(VariableDeclaration const& _varDecl)
{
solAssert(m_scope, "");
int expectedItems = _varDecl.variables.size();
int height = m_assembly.stackHeight();
boost::apply_visitor(*this, *_varDecl.value);
expectDeposit(expectedItems, height);
for (auto const& variable: _varDecl.variables)
{
auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(variable.name));
auto& var = boost::get<Scope::Variable>(m_scope->identifiers.at(variable.name));
var.stackHeight = height++;
var.active = true;
}
checkStackHeight(&_varDecl);
}
void CodeTransform::operator()(Assignment const& _assignment)
{
int height = m_assembly.stackHeight();
boost::apply_visitor(*this, *_assignment.value);
expectDeposit(1, height);
visitExpression(*_assignment.value);
m_assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
generateAssignment(_assignment.variableName);
checkStackHeight(&_assignment);
}
void CodeTransform::operator()(StackAssignment const& _assignment)
{
m_assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
generateAssignment(_assignment.variableName);
checkStackHeight(&_assignment);
}
void CodeTransform::operator()(Label const& _label)
{
m_assembly.setSourceLocation(_label.location);
solAssert(m_scope.identifiers.count(_label.name), "");
Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers.at(_label.name));
assignLabelIdIfUnset(label);
solAssert(m_scope, "");
solAssert(m_scope->identifiers.count(_label.name), "");
Scope::Label& label = boost::get<Scope::Label>(m_scope->identifiers.at(_label.name));
assignLabelIdIfUnset(label.id);
m_assembly.appendLabel(*label.id);
checkStackHeight(&_label);
}
void CodeTransform::operator()(FunctionCall const&)
void CodeTransform::operator()(FunctionCall const& _call)
{
solAssert(false, "Function call not removed during desugaring phase.");
solAssert(m_scope, "");
m_assembly.setSourceLocation(_call.location);
EVMAssembly::LabelID returnLabel(-1); // only used for evm 1.0
if (!m_evm15)
{
returnLabel = m_assembly.newLabelId();
m_assembly.appendLabelReference(returnLabel);
m_stackAdjustment++;
}
Scope::Function* function = nullptr;
solAssert(m_scope->lookup(_call.functionName.name, Scope::NonconstVisitor(
[=](Scope::Variable&) { solAssert(false, "Expected function name."); },
[=](Scope::Label&) { solAssert(false, "Expected function name."); },
[&](Scope::Function& _function) { function = &_function; }
)), "Function name not found.");
solAssert(function, "");
solAssert(function->arguments.size() == _call.arguments.size(), "");
for (auto const& arg: _call.arguments | boost::adaptors::reversed)
visitExpression(arg);
m_assembly.setSourceLocation(_call.location);
assignLabelIdIfUnset(function->id);
if (m_evm15)
m_assembly.appendJumpsub(*function->id, function->arguments.size(), function->returns.size());
else
{
m_assembly.appendJumpTo(*function->id, function->returns.size() - function->arguments.size() - 1);
m_assembly.appendLabel(returnLabel);
m_stackAdjustment--;
}
checkStackHeight(&_call);
}
void CodeTransform::operator()(FunctionalInstruction const& _instr)
void CodeTransform::operator()(FunctionalInstruction const& _instruction)
{
for (auto it = _instr.arguments.rbegin(); it != _instr.arguments.rend(); ++it)
if (m_evm15 && (
_instruction.instruction.instruction == solidity::Instruction::JUMP ||
_instruction.instruction.instruction == solidity::Instruction::JUMPI
))
{
int height = m_assembly.stackHeight();
boost::apply_visitor(*this, *it);
expectDeposit(1, height);
bool const isJumpI = _instruction.instruction.instruction == solidity::Instruction::JUMPI;
if (isJumpI)
{
solAssert(_instruction.arguments.size() == 2, "");
visitExpression(_instruction.arguments.at(1));
}
else
{
solAssert(_instruction.arguments.size() == 1, "");
}
m_assembly.setSourceLocation(_instruction.location);
auto label = labelFromIdentifier(boost::get<assembly::Identifier>(_instruction.arguments.at(0)));
if (isJumpI)
m_assembly.appendJumpToIf(label);
else
m_assembly.appendJumpTo(label);
}
(*this)(_instr.instruction);
checkStackHeight(&_instr);
else
{
for (auto const& arg: _instruction.arguments | boost::adaptors::reversed)
visitExpression(arg);
(*this)(_instruction.instruction);
}
checkStackHeight(&_instruction);
}
void CodeTransform::operator()(assembly::Identifier const& _identifier)
{
m_assembly.setSourceLocation(_identifier.location);
// First search internals, then externals.
if (m_scope.lookup(_identifier.name, Scope::NonconstVisitor(
solAssert(m_scope, "");
if (m_scope->lookup(_identifier.name, Scope::NonconstVisitor(
[=](Scope::Variable& _var)
{
if (int heightDiff = variableHeightDiff(_var, _identifier.location, false))
if (int heightDiff = variableHeightDiff(_var, false))
m_assembly.appendInstruction(solidity::dupInstruction(heightDiff));
else
// Store something to balance the stack
@ -205,7 +181,7 @@ void CodeTransform::operator()(assembly::Identifier const& _identifier)
},
[=](Scope::Label& _label)
{
assignLabelIdIfUnset(_label);
assignLabelIdIfUnset(_label.id);
m_assembly.appendLabelReference(*_label.id);
},
[=](Scope::Function&)
@ -246,7 +222,233 @@ void CodeTransform::operator()(assembly::Literal const& _literal)
void CodeTransform::operator()(assembly::Instruction const& _instruction)
{
solAssert(!m_evm15 || _instruction.instruction != solidity::Instruction::JUMP, "Bare JUMP instruction used for EVM1.5");
solAssert(!m_evm15 || _instruction.instruction != solidity::Instruction::JUMPI, "Bare JUMPI instruction used for EVM1.5");
m_assembly.setSourceLocation(_instruction.location);
m_assembly.appendInstruction(_instruction.instruction);
checkStackHeight(&_instruction);
}
void CodeTransform::operator()(Switch const& _switch)
{
//@TODO use JUMPV in EVM1.5?
visitExpression(*_switch.expression);
int expressionHeight = m_assembly.stackHeight();
map<Case const*, AbstractAssembly::LabelID> caseBodies;
AbstractAssembly::LabelID end = m_assembly.newLabelId();
for (Case const& c: _switch.cases)
{
if (c.value)
{
(*this)(*c.value);
m_assembly.setSourceLocation(c.location);
AbstractAssembly::LabelID bodyLabel = m_assembly.newLabelId();
caseBodies[&c] = bodyLabel;
solAssert(m_assembly.stackHeight() == expressionHeight + 1, "");
m_assembly.appendInstruction(solidity::dupInstruction(2));
m_assembly.appendInstruction(solidity::Instruction::EQ);
m_assembly.appendJumpToIf(bodyLabel);
}
else
// default case
(*this)(c.body);
}
m_assembly.setSourceLocation(_switch.location);
m_assembly.appendJumpTo(end);
size_t numCases = caseBodies.size();
for (auto const& c: caseBodies)
{
m_assembly.setSourceLocation(c.first->location);
m_assembly.appendLabel(c.second);
(*this)(c.first->body);
// Avoid useless "jump to next" for the last case.
if (--numCases > 0)
{
m_assembly.setSourceLocation(c.first->location);
m_assembly.appendJumpTo(end);
}
}
m_assembly.setSourceLocation(_switch.location);
m_assembly.appendLabel(end);
m_assembly.appendInstruction(solidity::Instruction::POP);
checkStackHeight(&_switch);
}
void CodeTransform::operator()(FunctionDefinition const& _function)
{
solAssert(m_scope, "");
solAssert(m_scope->identifiers.count(_function.name), "");
Scope::Function& function = boost::get<Scope::Function>(m_scope->identifiers.at(_function.name));
assignLabelIdIfUnset(function.id);
int const localStackAdjustment = m_evm15 ? 0 : 1;
int height = localStackAdjustment;
solAssert(m_info.scopes.at(&_function.body), "");
Scope* varScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get();
solAssert(varScope, "");
for (auto const& v: _function.arguments | boost::adaptors::reversed)
{
auto& var = boost::get<Scope::Variable>(varScope->identifiers.at(v.name));
var.stackHeight = height++;
var.active = true;
}
m_assembly.setSourceLocation(_function.location);
int stackHeightBefore = m_assembly.stackHeight();
AbstractAssembly::LabelID afterFunction = m_assembly.newLabelId();
if (m_evm15)
{
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore);
m_assembly.appendBeginsub(*function.id, _function.arguments.size());
}
else
{
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore + height);
m_assembly.appendLabel(*function.id);
}
m_stackAdjustment += localStackAdjustment;
for (auto const& v: _function.returns)
{
auto& var = boost::get<Scope::Variable>(varScope->identifiers.at(v.name));
var.stackHeight = height++;
var.active = true;
// Preset stack slots for return variables to zero.
m_assembly.appendConstant(u256(0));
}
CodeTransform(m_assembly, m_info, m_evm15, m_identifierAccess, localStackAdjustment)
.run(_function.body);
{
// The stack layout here is:
// <return label>? <arguments...> <return values...>
// But we would like it to be:
// <return values...> <return label>?
// So we have to append some SWAP and POP instructions.
// This vector holds the desired target positions of all stack slots and is
// modified parallel to the actual stack.
vector<int> stackLayout;
if (!m_evm15)
stackLayout.push_back(_function.returns.size()); // Move return label to the top
stackLayout += vector<int>(_function.arguments.size(), -1); // discard all arguments
for (size_t i = 0; i < _function.returns.size(); ++i)
stackLayout.push_back(i); // Move return values down, but keep order.
solAssert(stackLayout.size() <= 17, "Stack too deep");
while (!stackLayout.empty() && stackLayout.back() != int(stackLayout.size() - 1))
if (stackLayout.back() < 0)
{
m_assembly.appendInstruction(solidity::Instruction::POP);
stackLayout.pop_back();
}
else
{
m_assembly.appendInstruction(swapInstruction(stackLayout.size() - stackLayout.back() - 1));
swap(stackLayout[stackLayout.back()], stackLayout.back());
}
for (int i = 0; size_t(i) < stackLayout.size(); ++i)
solAssert(i == stackLayout[i], "Error reshuffling stack.");
}
if (m_evm15)
m_assembly.appendReturnsub(_function.returns.size(), stackHeightBefore);
else
m_assembly.appendJump(stackHeightBefore - _function.returns.size());
m_stackAdjustment -= localStackAdjustment;
m_assembly.appendLabel(afterFunction);
checkStackHeight(&_function);
}
void CodeTransform::operator()(Block const& _block)
{
CodeTransform(m_assembly, m_info, m_evm15, m_identifierAccess, m_stackAdjustment).run(_block);
}
AbstractAssembly::LabelID CodeTransform::labelFromIdentifier(Identifier const& _identifier)
{
AbstractAssembly::LabelID label = AbstractAssembly::LabelID(-1);
if (!m_scope->lookup(_identifier.name, Scope::NonconstVisitor(
[=](Scope::Variable&) { solAssert(false, "Expected label"); },
[&](Scope::Label& _label)
{
assignLabelIdIfUnset(_label.id);
label = *_label.id;
},
[=](Scope::Function&) { solAssert(false, "Expected label"); }
)))
{
solAssert(false, "Identifier not found.");
}
return label;
}
void CodeTransform::visitExpression(Statement const& _expression)
{
int height = m_assembly.stackHeight();
boost::apply_visitor(*this, _expression);
expectDeposit(1, height);
}
void CodeTransform::generateAssignment(Identifier const& _variableName)
{
solAssert(m_scope, "");
auto var = m_scope->lookup(_variableName.name);
if (var)
{
Scope::Variable const& _var = boost::get<Scope::Variable>(*var);
if (int heightDiff = variableHeightDiff(_var, true))
m_assembly.appendInstruction(solidity::swapInstruction(heightDiff - 1));
m_assembly.appendInstruction(solidity::Instruction::POP);
}
else
{
solAssert(
m_identifierAccess.generateCode,
"Identifier not found and no external access available."
);
m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_assembly);
}
}
int CodeTransform::variableHeightDiff(solidity::assembly::Scope::Variable const& _var, bool _forSwap)
{
int heightDiff = m_assembly.stackHeight() - _var.stackHeight;
if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16))
{
solUnimplemented(
"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")"
);
return 0;
}
else
return heightDiff;
}
void CodeTransform::expectDeposit(int _deposit, int _oldHeight)
{
solAssert(m_assembly.stackHeight() == _oldHeight + _deposit, "Invalid stack deposit.");
}
void CodeTransform::checkStackHeight(void const* _astElement)
{
solAssert(m_info.stackHeightInfo.count(_astElement), "Stack height for AST element not found.");
solAssert(
m_info.stackHeightInfo.at(_astElement) == m_assembly.stackHeight() - m_stackAdjustment,
"Stack height mismatch between analysis and code generation phase: Analysis: " +
to_string(m_info.stackHeightInfo.at(_astElement)) +
" code gen: " +
to_string(m_assembly.stackHeight() - m_stackAdjustment)
);
}
void CodeTransform::assignLabelIdIfUnset(boost::optional<AbstractAssembly::LabelID>& _labelId)
{
if (!_labelId)
_labelId.reset(m_assembly.newLabelId());
}

View File

@ -18,12 +18,13 @@
* Common code generator for translating Julia / inline assembly to EVM and EVM1.5.
*/
#include <libjulia/backends/evm/AbstractAssembly.h>
#include <libjulia/backends/evm/EVMAssembly.h>
#include <libsolidity/inlineasm/AsmStack.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <boost/variant.hpp>
#include <boost/optional.hpp>
namespace dev
{
@ -45,37 +46,46 @@ struct StackAssignment;
struct FunctionDefinition;
struct FunctionCall;
using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, Block>;
struct AsmAnalysisInfo;
}
}
namespace julia
{
class EVMAssembly;
class CodeTransform: public boost::static_visitor<>
{
public:
/// Create the code transformer which appends assembly to _assembly as a side-effect
/// of its creation.
/// Create the code transformer.
/// @param _identifierAccess used to resolve identifiers external to the inline assembly
CodeTransform(
solidity::ErrorReporter& _errorReporter,
julia::AbstractAssembly& _assembly,
solidity::assembly::Block const& _block,
solidity::assembly::AsmAnalysisInfo& _analysisInfo,
bool _evm15 = false,
ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
): CodeTransform(_errorReporter, _assembly, _block, _analysisInfo, _identifierAccess, _assembly.stackHeight())
): CodeTransform(_assembly, _analysisInfo, _evm15, _identifierAccess, _assembly.stackHeight())
{
}
private:
/// Processes the block and appends the resulting code to the assembly.
void run(solidity::assembly::Block const& _block);
protected:
CodeTransform(
solidity::ErrorReporter& _errorReporter,
julia::AbstractAssembly& _assembly,
solidity::assembly::Block const& _block,
solidity::assembly::AsmAnalysisInfo& _analysisInfo,
bool _evm15,
ExternalIdentifierAccess const& _identifierAccess,
int _initialStackHeight
);
int _stackAdjustment
):
m_assembly(_assembly),
m_info(_analysisInfo),
m_evm15(_evm15),
m_identifierAccess(_identifierAccess),
m_stackAdjustment(_stackAdjustment)
{}
public:
void operator()(solidity::assembly::Instruction const& _instruction);
@ -87,31 +97,39 @@ public:
void operator()(solidity::assembly::StackAssignment const& _assignment);
void operator()(solidity::assembly::Assignment const& _assignment);
void operator()(solidity::assembly::VariableDeclaration const& _varDecl);
void operator()(solidity::assembly::Block const& _block);
void operator()(solidity::assembly::Switch const& _switch);
void operator()(solidity::assembly::FunctionDefinition const&);
void operator()(solidity::assembly::Block const& _block);
private:
void generateAssignment(solidity::assembly::Identifier const& _variableName, SourceLocation const& _location);
AbstractAssembly::LabelID labelFromIdentifier(solidity::assembly::Identifier const& _identifier);
/// Generates code for an expression that is supposed to return a single value.
void visitExpression(solidity::assembly::Statement const& _expression);
/// Determines the stack height difference to the given variables. Automatically generates
/// errors if it is not yet in scope or the height difference is too large. Returns 0 on
/// errors and the (positive) stack height difference otherwise.
int variableHeightDiff(solidity::assembly::Scope::Variable const& _var, SourceLocation const& _location, bool _forSwap);
void generateAssignment(solidity::assembly::Identifier const& _variableName);
/// Determines the stack height difference to the given variables. Throws
/// if it is not yet in scope or the height difference is too large. Returns
/// the (positive) stack height difference otherwise.
int variableHeightDiff(solidity::assembly::Scope::Variable const& _var, bool _forSwap);
void expectDeposit(int _deposit, int _oldHeight);
void checkStackHeight(void const* _astElement);
/// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set.
void assignLabelIdIfUnset(solidity::assembly::Scope::Label& _label);
/// Assigns the label's or function's id to a value taken from eth::Assembly if it has not yet been set.
void assignLabelIdIfUnset(boost::optional<AbstractAssembly::LabelID>& _labelId);
solidity::ErrorReporter& m_errorReporter;
julia::AbstractAssembly& m_assembly;
solidity::assembly::AsmAnalysisInfo& m_info;
solidity::assembly::Scope& m_scope;
solidity::assembly::Scope* m_scope = nullptr;
bool m_evm15 = false;
ExternalIdentifierAccess m_identifierAccess;
int const m_initialStackHeight;
/// Adjustment between the stack height as determined during the analysis phase
/// and the stack height in the assembly. This is caused by an initial stack being present
/// for inline assembly and different stack heights depending on the EVM backend used
/// (EVM 1.0 or 1.5).
int m_stackAdjustment = 0;
};
}

View File

@ -169,7 +169,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
ErrorList errors;
ErrorReporter errorsIgnored(errors);
julia::ExternalIdentifierAccess::Resolver resolver =
[&](assembly::Identifier const& _identifier, julia::IdentifierContext) {
[&](assembly::Identifier const& _identifier, julia::IdentifierContext, bool _crossesFunctionBoundary) {
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name);
bool isSlot = boost::algorithm::ends_with(_identifier.name, "_slot");
bool isOffset = boost::algorithm::ends_with(_identifier.name, "_offset");
@ -188,6 +188,12 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
}
if (declarations.size() != 1)
return size_t(-1);
if (auto var = dynamic_cast<VariableDeclaration const*>(declarations.front()))
if (var->isLocalVariable() && _crossesFunctionBoundary)
{
declarationError(_identifier.location, "Cannot access local Solidity variables from inside an inline assembly function.");
return size_t(-1);
}
_inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot;
_inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset;
_inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front();
@ -315,6 +321,12 @@ void ReferencesResolver::fatalTypeError(SourceLocation const& _location, string
m_errorReporter.fatalTypeError(_location, _description);
}
void ReferencesResolver::declarationError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.declarationError(_location, _description);
}
void ReferencesResolver::fatalDeclarationError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;

View File

@ -75,10 +75,13 @@ private:
/// Adds a new error to the list of errors.
void typeError(SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort type checking.
/// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalTypeError(SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort type checking.
/// Adds a new error to the list of errors.
void declarationError(SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalDeclarationError(SourceLocation const& _location, std::string const& _description);
ErrorReporter& m_errorReporter;

View File

@ -632,7 +632,8 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
// We run the resolve step again regardless.
julia::ExternalIdentifierAccess::Resolver identifierAccess = [&](
assembly::Identifier const& _identifier,
julia::IdentifierContext _context
julia::IdentifierContext _context,
bool
)
{
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);

View File

@ -269,7 +269,8 @@ void CompilerContext::appendInlineAssembly(
julia::ExternalIdentifierAccess identifierAccess;
identifierAccess.resolve = [&](
assembly::Identifier const& _identifier,
julia::IdentifierContext
julia::IdentifierContext,
bool
)
{
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);

View File

@ -21,15 +21,20 @@
*/
#include <libsolidity/codegen/ContractCompiler.h>
#include <algorithm>
#include <boost/range/adaptor/reversed.hpp>
#include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/codegen/ExpressionCompiler.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/Assembly.h>
#include <libevmasm/GasMeter.h>
#include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/ExpressionCompiler.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <boost/range/adaptor/reversed.hpp>
#include <algorithm>
using namespace std;
using namespace dev;
using namespace dev::solidity;
@ -519,12 +524,9 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
{
ErrorList errors;
ErrorReporter errorReporter(errors);
assembly::CodeGenerator codeGen(errorReporter);
unsigned startStackHeight = m_context.stackHeight();
julia::ExternalIdentifierAccess identifierAccess;
identifierAccess.resolve = [&](assembly::Identifier const& _identifier, julia::IdentifierContext)
identifierAccess.resolve = [&](assembly::Identifier const& _identifier, julia::IdentifierContext, bool)
{
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
if (ref == _inlineAssembly.annotation().externalReferences.end())
@ -643,13 +645,12 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
}
};
solAssert(_inlineAssembly.annotation().analysisInfo, "");
codeGen.assemble(
assembly::CodeGenerator::assemble(
_inlineAssembly.operations(),
*_inlineAssembly.annotation().analysisInfo,
m_context.nonConstAssembly(),
identifierAccess
);
solAssert(Error::containsOnlyWarnings(errorReporter.errors()), "Code generation for inline assembly with errors requested.");
m_context.setStackOffset(startStackHeight);
return false;
}

View File

@ -25,7 +25,7 @@
#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/interface/Utils.h>
#include <boost/range/adaptor/reversed.hpp>
@ -120,7 +120,10 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
{
size_t stackSize(-1);
if (m_resolver)
stackSize = m_resolver(_identifier, julia::IdentifierContext::RValue);
{
bool insideFunction = m_currentScope->insideFunction();
stackSize = m_resolver(_identifier, julia::IdentifierContext::RValue, insideFunction);
}
if (stackSize == size_t(-1))
{
// Only add an error message if the callback did not do it.
@ -274,8 +277,12 @@ bool AsmAnalyzer::operator()(Switch const& _switch)
{
if (_case.value)
{
if (!expectExpression(*_case.value))
int const initialStackHeight = m_stackHeight;
// We cannot use "expectExpression" here because *_case.value is not a
// Statement and would be converted to a Statement otherwise.
if (!(*this)(*_case.value))
success = false;
expectDeposit(1, initialStackHeight, _case.value->location);
m_stackHeight--;
/// Note: the parser ensures there is only one default case
@ -295,6 +302,7 @@ bool AsmAnalyzer::operator()(Switch const& _switch)
}
m_stackHeight--;
m_info.stackHeightInfo[&_switch] = m_stackHeight;
return success;
}
@ -341,17 +349,24 @@ bool AsmAnalyzer::expectExpression(Statement const& _statement)
int const initialHeight = m_stackHeight;
if (!boost::apply_visitor(*this, _statement))
success = false;
if (m_stackHeight - initialHeight != 1)
if (!expectDeposit(1, initialHeight, locationOf(_statement)))
success = false;
return success;
}
bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location)
{
if (m_stackHeight - _oldHeight != _deposit)
{
m_errorReporter.typeError(
locationOf(_statement),
_location,
"Expected expression to return one item to the stack, but did return " +
boost::lexical_cast<string>(m_stackHeight - initialHeight) +
boost::lexical_cast<string>(m_stackHeight - _oldHeight) +
" items."
);
success = false;
return false;
}
return success;
return true;
}
bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize)
@ -378,7 +393,10 @@ bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t
variableSize = 1;
}
else if (m_resolver)
variableSize = m_resolver(_variable, julia::IdentifierContext::LValue);
{
bool insideFunction = m_currentScope->insideFunction();
variableSize = m_resolver(_variable, julia::IdentifierContext::LValue, insideFunction);
}
if (variableSize == size_t(-1))
{
// Only add message if the callback did not.

View File

@ -20,7 +20,9 @@
#pragma once
#include <libsolidity/inlineasm/AsmStack.h>
#include <libsolidity/interface/Exceptions.h>
#include <libjulia/backends/evm/AbstractAssembly.h>
#include <boost/variant.hpp>
@ -87,6 +89,7 @@ public:
private:
/// Visits the statement and expects it to deposit one item onto the stack.
bool expectExpression(Statement const& _statement);
bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location);
/// Verifies that a variable to be assigned to exists and has the same size
/// as the value, @a _valueSize, unless that is equal to -1.
@ -96,7 +99,7 @@ private:
void expectValidType(std::string const& type, SourceLocation const& _location);
int m_stackHeight = 0;
julia::ExternalIdentifierAccess::Resolver const& m_resolver;
julia::ExternalIdentifierAccess::Resolver m_resolver;
Scope* m_currentScope = nullptr;
AsmAnalysisInfo& m_info;
ErrorReporter& m_errorReporter;

View File

@ -87,13 +87,46 @@ public:
{
m_assembly.appendLibraryAddress(_linkerSymbol);
}
virtual void appendJump(int _stackDiffAfter) override
{
appendInstruction(solidity::Instruction::JUMP);
m_assembly.adjustDeposit(_stackDiffAfter);
}
virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override
{
appendLabelReference(_labelId);
appendJump(_stackDiffAfter);
}
virtual void appendJumpToIf(LabelID _labelId) override
{
appendLabelReference(_labelId);
appendInstruction(solidity::Instruction::JUMPI);
}
virtual void appendBeginsub(LabelID, int) override
{
// TODO we could emulate that, though
solAssert(false, "BEGINSUB not implemented for EVM 1.0");
}
/// Call a subroutine.
virtual void appendJumpsub(LabelID, int, int) override
{
// TODO we could emulate that, though
solAssert(false, "JUMPSUB not implemented for EVM 1.0");
}
/// Return from a subroutine.
virtual void appendReturnsub(int, int) override
{
// TODO we could emulate that, though
solAssert(false, "RETURNSUB not implemented for EVM 1.0");
}
private:
size_t assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const
LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const
{
u256 id = _tag.data();
solAssert(id <= std::numeric_limits<size_t>::max(), "Tag id too large.");
return size_t(id);
solAssert(id <= std::numeric_limits<LabelID>::max(), "Tag id too large.");
return LabelID(id);
}
eth::Assembly& m_assembly;
@ -107,7 +140,7 @@ eth::Assembly assembly::CodeGenerator::assemble(
{
eth::Assembly assembly;
EthAssemblyAdapter assemblyAdapter(assembly);
julia::CodeTransform(m_errorReporter, assemblyAdapter, _parsedData, _analysisInfo, _identifierAccess);
julia::CodeTransform(assemblyAdapter, _analysisInfo, false, _identifierAccess).run(_parsedData);
return assembly;
}
@ -119,5 +152,5 @@ void assembly::CodeGenerator::assemble(
)
{
EthAssemblyAdapter assemblyAdapter(_assembly);
julia::CodeTransform(m_errorReporter, assemblyAdapter, _parsedData, _analysisInfo, _identifierAccess);
julia::CodeTransform(assemblyAdapter, _analysisInfo, false, _identifierAccess).run(_parsedData);
}

View File

@ -34,7 +34,6 @@ class Assembly;
}
namespace solidity
{
class ErrorReporter;
namespace assembly
{
struct Block;
@ -42,24 +41,19 @@ struct Block;
class CodeGenerator
{
public:
CodeGenerator(ErrorReporter& _errorReporter):
m_errorReporter(_errorReporter) {}
/// Performs code generation and @returns the result.
eth::Assembly assemble(
static eth::Assembly assemble(
Block const& _parsedData,
AsmAnalysisInfo& _analysisInfo,
julia::ExternalIdentifierAccess const& _identifierAccess = julia::ExternalIdentifierAccess()
);
/// Performs code generation and appends generated to to _assembly.
void assemble(
static void assemble(
Block const& _parsedData,
AsmAnalysisInfo& _analysisInfo,
eth::Assembly& _assembly,
julia::ExternalIdentifierAccess const& _identifierAccess = julia::ExternalIdentifierAccess()
);
private:
ErrorReporter& m_errorReporter;
};
}

View File

@ -79,3 +79,11 @@ bool Scope::exists(string const& _name)
else
return false;
}
bool Scope::insideFunction() const
{
for (Scope const* s = this; s; s = s->superScope)
if (s->functionScope)
return true;
return false;
}

View File

@ -85,6 +85,7 @@ struct Scope
Function(std::vector<JuliaType> const& _arguments, std::vector<JuliaType> const& _returns): arguments(_arguments), returns(_returns) {}
std::vector<JuliaType> arguments;
std::vector<JuliaType> returns;
boost::optional<LabelID> id;
};
using Identifier = boost::variant<Variable, Label, Function>;
@ -123,6 +124,9 @@ struct Scope
/// across function and assembly boundaries).
bool exists(std::string const& _name);
/// @returns true if this scope is inside a function.
bool insideFunction() const;
Scope* superScope = nullptr;
/// If true, variables from the super scope are not visible here (other identifiers are),
/// but they are still taken into account to prevent shadowing.

View File

@ -66,8 +66,7 @@ eth::Assembly InlineAssemblyStack::assemble()
AsmAnalysisInfo analysisInfo;
AsmAnalyzer analyzer(analysisInfo, m_errorReporter);
solAssert(analyzer.analyze(*m_parserResult), "");
CodeGenerator codeGen(m_errorReporter);
return codeGen.assemble(*m_parserResult, analysisInfo);
return CodeGenerator::assemble(*m_parserResult, analysisInfo);
}
bool InlineAssemblyStack::parseAndAssemble(
@ -87,7 +86,8 @@ bool InlineAssemblyStack::parseAndAssemble(
AsmAnalysisInfo analysisInfo;
AsmAnalyzer analyzer(analysisInfo, errorReporter, false, _identifierAccess.resolve);
solAssert(analyzer.analyze(*parserResult), "");
CodeGenerator(errorReporter).assemble(*parserResult, analysisInfo, _assembly, _identifierAccess);
solAssert(errorReporter.errors().empty(), "");
CodeGenerator::assemble(*parserResult, analysisInfo, _assembly, _identifierAccess);
// At this point, the assembly might be messed up, but we should throw an
// internal compiler error anyway.

View File

@ -30,6 +30,9 @@
#include <libevmasm/Assembly.h>
#include <libjulia/backends/evm/EVMCodeTransform.h>
#include <libjulia/backends/evm/EVMAssembly.h>
using namespace std;
using namespace dev;
using namespace dev::solidity;
@ -73,7 +76,7 @@ bool AssemblyStack::analyzeParsed()
return m_analysisSuccessful;
}
eth::LinkerObject AssemblyStack::assemble(Machine _machine)
eth::LinkerObject AssemblyStack::assemble(Machine _machine) const
{
solAssert(m_analysisSuccessful, "");
solAssert(m_parserResult, "");
@ -83,11 +86,15 @@ eth::LinkerObject AssemblyStack::assemble(Machine _machine)
{
case Machine::EVM:
{
auto assembly = assembly::CodeGenerator(m_errorReporter).assemble(*m_parserResult, *m_analysisInfo);
auto assembly = assembly::CodeGenerator::assemble(*m_parserResult, *m_analysisInfo);
return assembly.assemble();
}
case Machine::EVM15:
solUnimplemented("EVM 1.5 backend is not yet implemented.");
{
julia::EVMAssembly assembly(true);
julia::CodeTransform(assembly, *m_analysisInfo, true).run(*m_parserResult);
return assembly.finalize();
}
case Machine::eWasm:
solUnimplemented("eWasm backend is not yet implemented.");
}
@ -95,7 +102,7 @@ eth::LinkerObject AssemblyStack::assemble(Machine _machine)
return eth::LinkerObject();
}
string AssemblyStack::print()
string AssemblyStack::print() const
{
solAssert(m_parserResult, "");
return assembly::AsmPrinter(m_language == Language::JULIA)(*m_parserResult);

View File

@ -65,13 +65,13 @@ public:
bool analyze(assembly::Block const& _block, Scanner const* _scanner = nullptr);
/// Run the assembly step (should only be called after parseAndAnalyze).
eth::LinkerObject assemble(Machine _machine);
eth::LinkerObject assemble(Machine _machine) const;
/// @returns the errors generated during parsing, analysis (and potentially assembly).
ErrorList const& errors() const { return m_errors; }
/// Pretty-print the input after having parsed it.
std::string print();
std::string print() const;
private:
bool analyzeParsed();

View File

@ -21,11 +21,13 @@
#include "../TestHelper.h"
#include <test/libsolidity/ErrorCheck.h>
#include <libsolidity/inlineasm/AsmParser.h>
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/parsing/Scanner.h>
#include <test/libsolidity/ErrorCheck.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <boost/optional.hpp>
#include <boost/algorithm/string/replace.hpp>

View File

@ -22,7 +22,7 @@
#include "../TestHelper.h"
#include <libsolidity/inlineasm/AsmStack.h>
#include <libsolidity/interface/AssemblyStack.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/AST.h>
@ -47,15 +47,20 @@ namespace test
namespace
{
boost::optional<Error> parseAndReturnFirstError(string const& _source, bool _assemble = false, bool _allowWarnings = true)
boost::optional<Error> parseAndReturnFirstError(
string const& _source,
bool _assemble = false,
bool _allowWarnings = true,
AssemblyStack::Machine _machine = AssemblyStack::Machine::EVM
)
{
assembly::InlineAssemblyStack stack;
AssemblyStack stack;
bool success = false;
try
{
success = stack.parse(std::make_shared<Scanner>(CharStream(_source)));
success = stack.parseAndAnalyze("", _source);
if (success && _assemble)
stack.assemble();
stack.assemble(_machine);
}
catch (FatalError const&)
{
@ -82,14 +87,20 @@ boost::optional<Error> parseAndReturnFirstError(string const& _source, bool _ass
return {};
}
bool successParse(std::string const& _source, bool _assemble = false, bool _allowWarnings = true)
bool successParse(
string const& _source,
bool _assemble = false,
bool _allowWarnings = true,
AssemblyStack::Machine _machine = AssemblyStack::Machine::EVM
)
{
return !parseAndReturnFirstError(_source, _assemble, _allowWarnings);
return !parseAndReturnFirstError(_source, _assemble, _allowWarnings, _machine);
}
bool successAssemble(string const& _source, bool _allowWarnings = true)
{
return successParse(_source, true, _allowWarnings);
return successParse(_source, true, _allowWarnings, AssemblyStack::Machine::EVM) &&
successParse(_source, true, _allowWarnings, AssemblyStack::Machine::EVM15);
}
Error expectError(std::string const& _source, bool _assemble, bool _allowWarnings = false)
@ -102,10 +113,10 @@ Error expectError(std::string const& _source, bool _assemble, bool _allowWarning
void parsePrintCompare(string const& _source)
{
assembly::InlineAssemblyStack stack;
BOOST_REQUIRE(stack.parse(std::make_shared<Scanner>(CharStream(_source))));
AssemblyStack stack;
BOOST_REQUIRE(stack.parseAndAnalyze("", _source));
BOOST_REQUIRE(stack.errors().empty());
BOOST_CHECK_EQUAL(stack.toString(), _source);
BOOST_CHECK_EQUAL(stack.print(), _source);
}
}
@ -376,10 +387,10 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode)
{
string source = "{ let x := \"\\u1bac\" }";
string parsed = "{\n let x := \"\\xe1\\xae\\xac\"\n}";
assembly::InlineAssemblyStack stack;
BOOST_REQUIRE(stack.parse(std::make_shared<Scanner>(CharStream(source))));
AssemblyStack stack;
BOOST_REQUIRE(stack.parseAndAnalyze("", source));
BOOST_REQUIRE(stack.errors().empty());
BOOST_CHECK_EQUAL(stack.toString(), parsed);
BOOST_CHECK_EQUAL(stack.print(), parsed);
parsePrintCompare(parsed);
}
@ -481,6 +492,48 @@ BOOST_AUTO_TEST_CASE(revert)
BOOST_CHECK(successAssemble("{ revert(0, 0) }"));
}
BOOST_AUTO_TEST_CASE(function_calls)
{
BOOST_CHECK(successAssemble("{ function f() {} }"));
BOOST_CHECK(successAssemble("{ function f() { let y := 2 } }"));
BOOST_CHECK(successAssemble("{ function f() -> z { let y := 2 } }"));
BOOST_CHECK(successAssemble("{ function f(a) { let y := 2 } }"));
BOOST_CHECK(successAssemble("{ function f(a) { let y := a } }"));
BOOST_CHECK(successAssemble("{ function f() -> x, y, z {} }"));
BOOST_CHECK(successAssemble("{ function f(x, y, z) {} }"));
BOOST_CHECK(successAssemble("{ function f(a, b) -> x, y, z { y := a } }"));
BOOST_CHECK(successAssemble("{ function f() {} f() }"));
BOOST_CHECK(successAssemble("{ function f() -> x, y { x := 1 y := 2} let a, b := f() }"));
BOOST_CHECK(successAssemble("{ function f(a, b) -> x, y { x := b y := a } let a, b := f(2, 3) }"));
BOOST_CHECK(successAssemble("{ function rec(a) { rec(sub(a, 1)) } rec(2) }"));
BOOST_CHECK(successAssemble("{ let r := 2 function f() -> x, y { x := 1 y := 2} let a, b := f() b := r }"));
BOOST_CHECK(successAssemble("{ function f() { g() } function g() { f() } }"));
}
BOOST_AUTO_TEST_CASE(embedded_functions)
{
BOOST_CHECK(successAssemble("{ function f(r, s) -> x { function g(a) -> b { } x := g(2) } let x := f(2, 3) }"));
}
BOOST_AUTO_TEST_CASE(switch_statement)
{
BOOST_CHECK(successAssemble("{ switch 1 default {} }"));
BOOST_CHECK(successAssemble("{ switch 1 case 1 {} default {} }"));
BOOST_CHECK(successAssemble("{ switch 1 case 1 {} }"));
BOOST_CHECK(successAssemble("{ let a := 3 switch a case 1 { a := 1 } case 2 { a := 5 } a := 9}"));
BOOST_CHECK(successAssemble("{ let a := 2 switch calldataload(0) case 1 { a := 1 } case 2 { a := 5 } }"));
}
BOOST_AUTO_TEST_CASE(large_constant)
{
auto source = R"({
switch mul(1, 2)
case 0x0000000000000000000000000000000000000000000000000000000026121ff0 {
}
})";
BOOST_CHECK(successAssemble(source));
}
BOOST_AUTO_TEST_CASE(keccak256)
{
BOOST_CHECK(successAssemble("{ 0 0 keccak256 pop }"));

View File

@ -7462,6 +7462,33 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_access)
BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_storage_access_inside_function)
{
char const* sourceCode = R"(
contract C {
uint16 x;
uint16 public y;
uint public z;
function f() returns (bool) {
uint off1;
uint off2;
assembly {
function f() -> o1 {
sstore(z_slot, 7)
o1 := y_offset
}
off2 := f()
}
assert(off2 == 2);
return true;
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f()") == encodeArgs(true));
BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_storage_access_via_pointer)
{
char const* sourceCode = R"(
@ -7535,6 +7562,129 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_access)
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(10)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_function_call)
{
char const* sourceCode = R"(
contract C {
function f() {
assembly {
function asmfun(a, b, c) -> x, y, z {
x := a
y := b
z := 7
}
let a1, b1, c1 := asmfun(1, 2, 3)
mstore(0x00, a1)
mstore(0x20, b1)
mstore(0x40, c1)
return(0, 0x60)
}
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(2), u256(7)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_function_call2)
{
char const* sourceCode = R"(
contract C {
function f() {
assembly {
let d := 0x10
function asmfun(a, b, c) -> x, y, z {
x := a
y := b
z := 7
}
let a1, b1, c1 := asmfun(1, 2, 3)
mstore(0x00, a1)
mstore(0x20, b1)
mstore(0x40, c1)
mstore(0x60, d)
return(0, 0x80)
}
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(2), u256(7), u256(0x10)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_embedded_function_call)
{
char const* sourceCode = R"(
contract C {
function f() {
assembly {
let d := 0x10
function asmfun(a, b, c) -> x, y, z {
x := g(a)
function g(r) -> s { s := mul(r, r) }
y := g(b)
z := 7
}
let a1, b1, c1 := asmfun(1, 2, 3)
mstore(0x00, a1)
mstore(0x20, b1)
mstore(0x40, c1)
mstore(0x60, d)
return(0, 0x80)
}
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(4), u256(7), u256(0x10)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_switch)
{
char const* sourceCode = R"(
contract C {
function f(uint a) returns (uint b) {
assembly {
switch a
case 1 { b := 8 }
case 2 { b := 9 }
default { b := 2 }
}
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f(uint256)", u256(0)) == encodeArgs(u256(2)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(1)) == encodeArgs(u256(8)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(2)) == encodeArgs(u256(9)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(3)) == encodeArgs(u256(2)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_recursion)
{
char const* sourceCode = R"(
contract C {
function f(uint a) returns (uint b) {
assembly {
function fac(n) -> nf {
switch n
case 0 { nf := 1 }
case 1 { nf := 1 }
default { nf := mul(n, fac(sub(n, 1))) }
}
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(index_access_with_type_conversion)
{
// Test for a bug where higher order bits cleanup was not done for array index access.

View File

@ -5159,6 +5159,52 @@ BOOST_AUTO_TEST_CASE(inline_assembly_constant_access)
CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly");
}
BOOST_AUTO_TEST_CASE(inline_assembly_local_variable_access_out_of_functions)
{
char const* text = R"(
contract test {
function f() {
uint a;
assembly {
function g() -> x { x := a }
}
}
}
)";
CHECK_ERROR(text, DeclarationError, "Cannot access local Solidity variables from inside an inline assembly function.");
}
BOOST_AUTO_TEST_CASE(inline_assembly_local_variable_access_out_of_functions_storage_ptr)
{
char const* text = R"(
contract test {
uint[] r;
function f() {
uint[] storage a = r;
assembly {
function g() -> x { x := a_offset }
}
}
}
)";
CHECK_ERROR(text, DeclarationError, "Cannot access local Solidity variables from inside an inline assembly function.");
}
BOOST_AUTO_TEST_CASE(inline_assembly_storage_variable_access_out_of_functions)
{
char const* text = R"(
contract test {
uint a;
function f() {
assembly {
function g() -> x { x := a_slot }
}
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(invalid_mobile_type)
{
char const* text = R"(