Refactor to abstract assembly to JULIA.

This commit is contained in:
chriseth 2017-04-28 18:15:18 +02:00
parent 41ee2cefbb
commit e2b21e1c96
7 changed files with 216 additions and 76 deletions

View File

@ -0,0 +1,88 @@
/*
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/>.
*/
/**
* @date 2017
* Abstract assembly interface, subclasses of which are to be used with the generic
* bytecode generator.
*/
#pragma once
#include <libdevcore/CommonData.h>
#include <functional>
namespace dev
{
struct SourceLocation;
namespace solidity
{
enum class Instruction: uint8_t;
namespace assembly
{
struct Instruction;
struct Identifier;
}
}
namespace julia
{
class AbstractAssembly
{
public:
virtual ~AbstractAssembly() {}
/// Set a new source location valid starting from the next instruction.
virtual void setSourceLocation(SourceLocation const& _location) = 0;
/// Retrieve the current height of the stack. This does not have to be zero
/// at the beginning.
virtual int stackHeight() const = 0;
/// Append an EVM instruction.
virtual void appendInstruction(solidity::Instruction _instruction) = 0;
/// Append a constant.
virtual void appendConstant(u256 const& _constant) = 0;
/// Append a label.
virtual void appendLabel(size_t _labelId) = 0;
/// Append a label reference.
virtual void appendLabelReference(size_t _labelId) = 0;
/// Generate a new unique label.
virtual size_t newLabel() = 0;
/// Append a reference to a to-be-linked symobl.
virtual void appendLinkerSymbol(std::string const& _name) = 0;
};
enum class IdentifierContext { LValue, RValue };
/// Object that is used to resolve references and generate code for access to identifiers external
/// to inline assembly (not used in standalone assembly mode).
struct ExternalIdentifierAccess
{
using Resolver = std::function<size_t(solidity::assembly::Identifier const&, IdentifierContext)>;
/// 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;
using CodeGenerator = std::function<void(solidity::assembly::Identifier const&, IdentifierContext, julia::AbstractAssembly&)>;
/// Generate code for retrieving the value (rvalue context) or storing the value (lvalue context)
/// of an identifier. The code should be appended to the assembly. In rvalue context, the value is supposed
/// to be put onto the stack, in lvalue context, the value is assumed to be at the top of the stack.
CodeGenerator generateCode;
};
}
}

View File

@ -264,7 +264,7 @@ void CompilerContext::appendInlineAssembly(
assembly = &replacedAssembly;
}
unsigned startStackHeight = stackHeight();
int startStackHeight = stackHeight();
assembly::ExternalIdentifierAccess identifierAccess;
identifierAccess.resolve = [&](
@ -278,26 +278,26 @@ void CompilerContext::appendInlineAssembly(
identifierAccess.generateCode = [&](
assembly::Identifier const& _identifier,
assembly::IdentifierContext _context,
eth::Assembly& _assembly
julia::AbstractAssembly& _assembly
)
{
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
solAssert(it != _localVariables.end(), "");
unsigned stackDepth = _localVariables.end() - it;
int stackDiff = _assembly.deposit() - startStackHeight + stackDepth;
int stackDepth = _localVariables.end() - it;
int stackDiff = _assembly.stackHeight() - startStackHeight + stackDepth;
if (_context == assembly::IdentifierContext::LValue)
stackDiff -= 1;
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
errinfo_comment("Stack too deep, try removing local variables.")
errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.")
);
if (_context == assembly::IdentifierContext::RValue)
_assembly.append(dupInstruction(stackDiff));
_assembly.appendInstruction(dupInstruction(stackDiff));
else
{
_assembly.append(swapInstruction(stackDiff));
_assembly.append(Instruction::POP);
_assembly.appendInstruction(swapInstruction(stackDiff));
_assembly.appendInstruction(Instruction::POP);
}
};

View File

@ -530,7 +530,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
return size_t(-1);
return ref->second.valueSize;
};
identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext _context, eth::Assembly& _assembly)
identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext _context, julia::AbstractAssembly& _assembly)
{
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
solAssert(ref != _inlineAssembly.annotation().externalReferences.end(), "");
@ -538,21 +538,25 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
solAssert(!!decl, "");
if (_context == assembly::IdentifierContext::RValue)
{
int const depositBefore = _assembly.deposit();
int const depositBefore = _assembly.stackHeight();
solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
{
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
functionDef = &m_context.resolveVirtualFunction(*functionDef);
_assembly.append(m_context.functionEntryLabel(*functionDef).pushTag());
auto functionEntryLabel = m_context.functionEntryLabel(*functionDef).pushTag();
solAssert(functionEntryLabel.data() <= std::numeric_limits<size_t>::max(), "");
_assembly.appendLabelReference(size_t(functionEntryLabel.data()));
// If there is a runtime context, we have to merge both labels into the same
// stack slot in case we store it in storage.
if (CompilerContext* rtc = m_context.runtimeContext())
{
_assembly.append(u256(1) << 32);
_assembly.append(Instruction::MUL);
_assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub()));
_assembly.append(Instruction::OR);
_assembly.appendConstant(u256(1) << 32);
_assembly.appendInstruction(Instruction::MUL);
auto runtimeEntryLabel = rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub());
solAssert(runtimeEntryLabel.data() <= std::numeric_limits<size_t>::max(), "");
_assembly.appendLabelReference(size_t(runtimeEntryLabel.data()));
_assembly.appendInstruction(Instruction::OR);
}
}
else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
@ -570,7 +574,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
}
else if (m_context.isLocalVariable(decl))
{
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable);
int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable);
if (ref->second.isSlot || ref->second.isOffset)
{
solAssert(variable->type()->dataStoredIn(DataLocation::Storage), "");
@ -587,7 +591,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
// only slot, offset is zero
if (ref->second.isOffset)
{
_assembly.append(u256(0));
_assembly.appendConstant(u256(0));
return;
}
}
@ -601,7 +605,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
errinfo_comment("Stack too deep, try removing local variables.")
);
solAssert(variable->type()->sizeOnStack() == 1, "");
_assembly.append(dupInstruction(stackDiff));
_assembly.appendInstruction(dupInstruction(stackDiff));
}
else
solAssert(false, "");
@ -610,11 +614,11 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
{
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
solAssert(contract->isLibrary(), "");
_assembly.appendLibraryAddress(contract->fullyQualifiedName());
_assembly.appendLinkerSymbol(contract->fullyQualifiedName());
}
else
solAssert(false, "Invalid declaration type.");
solAssert(_assembly.deposit() - depositBefore == int(ref->second.valueSize), "");
solAssert(_assembly.stackHeight() - depositBefore == int(ref->second.valueSize), "");
}
else
{
@ -626,15 +630,15 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
"Can only assign to stack variables in inline assembly."
);
solAssert(variable->type()->sizeOnStack() == 1, "");
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - 1;
int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable) - 1;
if (stackDiff > 16 || stackDiff < 1)
BOOST_THROW_EXCEPTION(
CompilerError() <<
errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
);
_assembly.append(swapInstruction(stackDiff));
_assembly.append(Instruction::POP);
_assembly.appendInstruction(swapInstruction(stackDiff));
_assembly.appendInstruction(Instruction::POP);
}
};
solAssert(_inlineAssembly.annotation().analysisInfo, "");

View File

@ -32,6 +32,8 @@
#include <libevmasm/SourceLocation.h>
#include <libevmasm/Instruction.h>
#include <libjulia/backends/AbstractAssembly.h>
#include <libdevcore/CommonIO.h>
#include <boost/range/adaptor/reversed.hpp>
@ -48,24 +50,61 @@ using namespace dev::solidity::assembly;
struct GeneratorState
{
GeneratorState(ErrorList& _errors, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly):
errors(_errors), info(_analysisInfo), assembly(_assembly) {}
GeneratorState(ErrorList& _errors, AsmAnalysisInfo& _analysisInfo):
errors(_errors), info(_analysisInfo) {}
size_t newLabelId()
ErrorList& errors;
AsmAnalysisInfo info;
};
class EthAssemblyAdapter: public julia::AbstractAssembly
{
public:
EthAssemblyAdapter(eth::Assembly& _assembly):
m_assembly(_assembly)
{
return assemblyTagToIdentifier(assembly.newTag());
}
virtual void setSourceLocation(SourceLocation const& _location) override
{
m_assembly.setSourceLocation(_location);
}
virtual int stackHeight() const override { return m_assembly.deposit(); }
virtual void appendInstruction(solidity::Instruction _instruction) override
{
m_assembly.append(_instruction);
}
virtual void appendConstant(u256 const& _constant) override
{
m_assembly.append(_constant);
}
/// Append a label.
virtual void appendLabel(size_t _labelId) override
{
m_assembly.append(eth::AssemblyItem(eth::Tag, _labelId));
}
/// Append a label reference.
virtual void appendLabelReference(size_t _labelId) override
{
m_assembly.append(eth::AssemblyItem(eth::PushTag, _labelId));
}
virtual size_t newLabel() override
{
return assemblyTagToIdentifier(m_assembly.newTag());
}
virtual void appendLinkerSymbol(std::string const& _linkerSymbol) override
{
m_assembly.appendLibraryAddress(_linkerSymbol);
}
size_t assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const
private:
size_t assemblyTagToIdentifier(eth::AssemblyItem const& _tag)
{
u256 id = _tag.data();
solAssert(id <= std::numeric_limits<size_t>::max(), "Tag id too large.");
return size_t(id);
}
ErrorList& errors;
AsmAnalysisInfo info;
eth::Assembly& assembly;
eth::Assembly& m_assembly;
};
class CodeTransform: public boost::static_visitor<>
@ -76,81 +115,84 @@ public:
/// @param _identifierAccess used to resolve identifiers external to the inline assembly
explicit CodeTransform(
GeneratorState& _state,
julia::AbstractAssembly& _assembly,
assembly::Block const& _block,
assembly::ExternalIdentifierAccess const& _identifierAccess = assembly::ExternalIdentifierAccess()
): CodeTransform(_state, _block, _identifierAccess, _state.assembly.deposit())
): CodeTransform(_state, _assembly, _block, _identifierAccess, _assembly.stackHeight())
{
}
private:
CodeTransform(
GeneratorState& _state,
julia::AbstractAssembly& _assembly,
assembly::Block const& _block,
assembly::ExternalIdentifierAccess const& _identifierAccess,
int _initialDeposit
int _initialStackHeight
):
m_state(_state),
m_assembly(_assembly),
m_scope(*m_state.info.scopes.at(&_block)),
m_identifierAccess(_identifierAccess),
m_initialDeposit(_initialDeposit)
m_initialStackHeight(_initialStackHeight)
{
int blockStartDeposit = m_state.assembly.deposit();
int blockStartStackHeight = m_assembly.stackHeight();
std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
m_state.assembly.setSourceLocation(_block.location);
m_assembly.setSourceLocation(_block.location);
// pop variables
for (auto const& identifier: m_scope.identifiers)
if (identifier.second.type() == typeid(Scope::Variable))
m_state.assembly.append(solidity::Instruction::POP);
m_assembly.appendInstruction(solidity::Instruction::POP);
int deposit = m_state.assembly.deposit() - blockStartDeposit;
int deposit = m_assembly.stackHeight() - blockStartStackHeight;
solAssert(deposit == 0, "Invalid stack height at end of block.");
}
public:
void operator()(assembly::Instruction const& _instruction)
{
m_state.assembly.setSourceLocation(_instruction.location);
m_state.assembly.append(_instruction.instruction);
m_assembly.setSourceLocation(_instruction.location);
m_assembly.appendInstruction(_instruction.instruction);
checkStackHeight(&_instruction);
}
void operator()(assembly::Literal const& _literal)
{
m_state.assembly.setSourceLocation(_literal.location);
m_assembly.setSourceLocation(_literal.location);
if (_literal.kind == assembly::LiteralKind::Number)
m_state.assembly.append(u256(_literal.value));
m_assembly.appendConstant(u256(_literal.value));
else if (_literal.kind == assembly::LiteralKind::Boolean)
{
if (_literal.value == "true")
m_state.assembly.append(u256(1));
m_assembly.appendConstant(u256(1));
else
m_state.assembly.append(u256(0));
m_assembly.appendConstant(u256(0));
}
else
{
solAssert(_literal.value.size() <= 32, "");
m_state.assembly.append(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft)));
m_assembly.appendConstant(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft)));
}
checkStackHeight(&_literal);
}
void operator()(assembly::Identifier const& _identifier)
{
m_state.assembly.setSourceLocation(_identifier.location);
m_assembly.setSourceLocation(_identifier.location);
// First search internals, then externals.
if (m_scope.lookup(_identifier.name, Scope::NonconstVisitor(
[=](Scope::Variable& _var)
{
if (int heightDiff = variableHeightDiff(_var, _identifier.location, false))
m_state.assembly.append(solidity::dupInstruction(heightDiff));
m_assembly.appendInstruction(solidity::dupInstruction(heightDiff));
else
// Store something to balance the stack
m_state.assembly.append(u256(0));
m_assembly.appendConstant(u256(0));
},
[=](Scope::Label& _label)
{
assignLabelIdIfUnset(_label);
m_state.assembly.append(eth::AssemblyItem(eth::PushTag, _label.id));
m_assembly.appendLabelReference(*_label.id);
},
[=](Scope::Function&)
{
@ -164,14 +206,14 @@ public:
m_identifierAccess.generateCode,
"Identifier not found and no external access available."
);
m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_state.assembly);
m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_assembly);
checkStackHeight(&_identifier);
}
void operator()(FunctionalInstruction const& _instr)
{
for (auto it = _instr.arguments.rbegin(); it != _instr.arguments.rend(); ++it)
{
int height = m_state.assembly.deposit();
int height = m_assembly.stackHeight();
boost::apply_visitor(*this, *it);
expectDeposit(1, height);
}
@ -184,31 +226,31 @@ public:
}
void operator()(Label const& _label)
{
m_state.assembly.setSourceLocation(_label.location);
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);
m_state.assembly.append(eth::AssemblyItem(eth::Tag, label.id));
m_assembly.appendLabel(*label.id);
checkStackHeight(&_label);
}
void operator()(assembly::Assignment const& _assignment)
{
m_state.assembly.setSourceLocation(_assignment.location);
m_assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
checkStackHeight(&_assignment);
}
void operator()(FunctionalAssignment const& _assignment)
{
int height = m_state.assembly.deposit();
size_t height = m_assembly.stackHeight();
boost::apply_visitor(*this, *_assignment.value);
expectDeposit(1, height);
m_state.assembly.setSourceLocation(_assignment.location);
m_assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
checkStackHeight(&_assignment);
}
void operator()(assembly::VariableDeclaration const& _varDecl)
{
int height = m_state.assembly.deposit();
size_t height = m_assembly.stackHeight();
boost::apply_visitor(*this, *_varDecl.value);
expectDeposit(1, height);
auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(_varDecl.variable.name));
@ -217,7 +259,7 @@ public:
}
void operator()(assembly::Block const& _block)
{
CodeTransform(m_state, _block, m_identifierAccess, m_initialDeposit);
CodeTransform(m_state, m_assembly, _block, m_identifierAccess, m_initialStackHeight);
checkStackHeight(&_block);
}
void operator()(assembly::FunctionDefinition const&)
@ -233,8 +275,8 @@ private:
{
Scope::Variable const& _var = boost::get<Scope::Variable>(*var);
if (int heightDiff = variableHeightDiff(_var, _location, true))
m_state.assembly.append(solidity::swapInstruction(heightDiff - 1));
m_state.assembly.append(solidity::Instruction::POP);
m_assembly.appendInstruction(solidity::swapInstruction(heightDiff - 1));
m_assembly.appendInstruction(solidity::Instruction::POP);
}
else
{
@ -242,16 +284,16 @@ private:
m_identifierAccess.generateCode,
"Identifier not found and no external access available."
);
m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_state.assembly);
m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_assembly);
}
}
/// 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(Scope::Variable const& _var, SourceLocation const& _location, bool _forSwap)
size_t variableHeightDiff(Scope::Variable const& _var, SourceLocation const& _location, bool _forSwap)
{
int heightDiff = m_state.assembly.deposit() - _var.stackHeight;
size_t heightDiff = m_assembly.stackHeight() - _var.stackHeight;
if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16))
{
//@TODO move this to analysis phase.
@ -268,14 +310,14 @@ private:
void expectDeposit(int _deposit, int _oldHeight)
{
solAssert(m_state.assembly.deposit() == _oldHeight + _deposit, "Invalid stack deposit.");
solAssert(m_assembly.stackHeight() == _oldHeight + _deposit, "Invalid stack deposit.");
}
void checkStackHeight(void const* _astElement)
{
solAssert(m_state.info.stackHeightInfo.count(_astElement), "Stack height for AST element not found.");
solAssert(
m_state.info.stackHeightInfo.at(_astElement) == m_state.assembly.deposit() - m_initialDeposit,
m_state.info.stackHeightInfo.at(_astElement) == m_assembly.stackHeight() - m_initialStackHeight,
"Stack height mismatch between analysis and code generation phase."
);
}
@ -283,15 +325,16 @@ private:
/// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set.
void assignLabelIdIfUnset(Scope::Label& _label)
{
if (_label.id == Scope::Label::unassignedLabelId)
_label.id = m_state.newLabelId();
if (!_label.id)
_label.id.reset(m_assembly.newLabel());
}
GeneratorState& m_state;
julia::AbstractAssembly& m_assembly;
Scope& m_scope;
ExternalIdentifierAccess m_identifierAccess;
int const m_initialDeposit;
int const m_initialStackHeight;
};
eth::Assembly assembly::CodeGenerator::assemble(
@ -301,8 +344,9 @@ eth::Assembly assembly::CodeGenerator::assemble(
)
{
eth::Assembly assembly;
GeneratorState state(m_errors, _analysisInfo, assembly);
CodeTransform(state, _parsedData, _identifierAccess);
GeneratorState state(m_errors, _analysisInfo);
EthAssemblyAdapter assemblyAdapter(assembly);
CodeTransform(state, assemblyAdapter, _parsedData, _identifierAccess);
return assembly;
}
@ -313,6 +357,7 @@ void assembly::CodeGenerator::assemble(
ExternalIdentifierAccess const& _identifierAccess
)
{
GeneratorState state(m_errors, _analysisInfo, _assembly);
CodeTransform(state, _parsedData, _identifierAccess);
GeneratorState state(m_errors, _analysisInfo);
EthAssemblyAdapter assemblyAdapter(_assembly);
CodeTransform(state, assemblyAdapter, _parsedData, _identifierAccess);
}

View File

@ -23,6 +23,7 @@
#include <libsolidity/interface/Exceptions.h>
#include <boost/variant.hpp>
#include <boost/optional.hpp>
#include <functional>
#include <memory>
@ -75,8 +76,7 @@ struct Scope
struct Label
{
size_t id = unassignedLabelId;
static const size_t unassignedLabelId = 0;
boost::optional<size_t> id;
};
struct Function

View File

@ -24,6 +24,8 @@
#include <libsolidity/interface/Exceptions.h>
#include <libjulia/backends/AbstractAssembly.h>
#include <string>
#include <functional>
@ -51,7 +53,7 @@ struct ExternalIdentifierAccess
/// 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;
using CodeGenerator = std::function<void(assembly::Identifier const&, IdentifierContext, eth::Assembly&)>;
using CodeGenerator = std::function<void(assembly::Identifier const&, IdentifierContext, julia::AbstractAssembly&)>;
/// Generate code for retrieving the value (rvalue context) or storing the value (lvalue context)
/// of an identifier. The code should be appended to the assembly. In rvalue context, the value is supposed
/// to be put onto the stack, in lvalue context, the value is assumed to be at the top of the stack.

View File

@ -4,6 +4,7 @@ aux_source_directory(. SRC_LIST)
aux_source_directory(libdevcore SRC_LIST)
aux_source_directory(libevmasm SRC_LIST)
aux_source_directory(libsolidity SRC_LIST)
aux_source_directory(libjulia SRC_LIST)
aux_source_directory(contracts SRC_LIST)
aux_source_directory(liblll SRC_LIST)
aux_source_directory(libjulia SRC_LIST)