aboutsummaryrefslogtreecommitdiffstats
path: root/Compiler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Compiler.cpp')
-rw-r--r--Compiler.cpp509
1 files changed, 123 insertions, 386 deletions
diff --git a/Compiler.cpp b/Compiler.cpp
index dbb38324..fea88560 100644
--- a/Compiler.cpp
+++ b/Compiler.cpp
@@ -17,455 +17,192 @@
/**
* @author Christian <c@ethdev.com>
* @date 2014
- * Solidity AST to EVM bytecode compiler.
+ * Solidity compiler.
*/
-#include <cassert>
-#include <utility>
+#include <algorithm>
#include <libsolidity/AST.h>
#include <libsolidity/Compiler.h>
+#include <libsolidity/ExpressionCompiler.h>
+using namespace std;
namespace dev {
namespace solidity {
-
-void CompilerContext::setLabelPosition(uint32_t _label, uint32_t _position)
-{
- assert(m_labelPositions.find(_label) == m_labelPositions.end());
- m_labelPositions[_label] = _position;
-}
-
-uint32_t CompilerContext::getLabelPosition(uint32_t _label) const
-{
- auto iter = m_labelPositions.find(_label);
- assert(iter != m_labelPositions.end());
- return iter->second;
-}
-
-void ExpressionCompiler::compile(Expression& _expression)
+bytes Compiler::compile(ContractDefinition& _contract)
{
- m_assemblyItems.clear();
- _expression.accept(*this);
+ Compiler compiler;
+ compiler.compileContract(_contract);
+ return compiler.m_context.getAssembledBytecode();
}
-bytes ExpressionCompiler::getAssembledBytecode() const
+void Compiler::compileContract(ContractDefinition& _contract)
{
- bytes assembled;
- assembled.reserve(m_assemblyItems.size());
+ m_context = CompilerContext(); // clear it just in case
- // resolve label references
- for (uint32_t pos = 0; pos < m_assemblyItems.size(); ++pos)
- {
- AssemblyItem const& item = m_assemblyItems[pos];
- if (item.getType() == AssemblyItem::Type::LABEL)
- m_context.setLabelPosition(item.getLabel(), pos + 1);
- }
+ //@todo constructor
+ //@todo register state variables
- for (AssemblyItem const& item: m_assemblyItems)
- {
- if (item.getType() == AssemblyItem::Type::LABELREF)
- assembled.push_back(m_context.getLabelPosition(item.getLabel()));
- else
- assembled.push_back(item.getData());
- }
-
- return assembled;
+ for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
+ m_context.addFunction(*function);
+ appendFunctionSelector(_contract.getDefinedFunctions());
+ for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
+ function->accept(*this);
}
-AssemblyItems ExpressionCompiler::compileExpression(CompilerContext& _context,
- Expression& _expression)
+void Compiler::appendFunctionSelector(std::vector<ASTPointer<FunctionDefinition>> const&)
{
- ExpressionCompiler compiler(_context);
- compiler.compile(_expression);
- return compiler.getAssemblyItems();
+ // filter public functions, and sort by name. Then select function from first byte,
+ // unpack arguments from calldata, push to stack and jump. Pack return values to
+ // output and return.
}
-bool ExpressionCompiler::visit(Assignment& _assignment)
+bool Compiler::visit(FunctionDefinition& _function)
{
- m_currentLValue = nullptr;
- _assignment.getLeftHandSide().accept(*this);
+ //@todo to simplify this, the colling convention could by changed such that
+ // caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn]
+ // although note that this reduces the size of the visible stack
- Expression& rightHandSide = _assignment.getRightHandSide();
- Token::Value op = _assignment.getAssignmentOperator();
- if (op != Token::ASSIGN)
- {
- // compound assignment
- rightHandSide.accept(*this);
- Type const& resultType = *_assignment.getType();
- cleanHigherOrderBitsIfNeeded(*rightHandSide.getType(), resultType);
- appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), resultType);
- }
- else
- {
- append(eth::Instruction::POP); //@todo do not retrieve the value in the first place
- rightHandSide.accept(*this);
- }
+ m_context.startNewFunction();
+ m_returnTag = m_context.newTag();
+ m_breakTags.clear();
+ m_continueTags.clear();
- storeInLValue(_assignment);
- return false;
-}
-
-void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation)
-{
- //@todo type checking and creating code for an operator should be in the same place:
- // the operator should know how to convert itself and to which types it applies, so
- // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that
- // represents the operator
- switch (_unaryOperation.getOperator())
- {
- case Token::NOT: // !
- append(eth::Instruction::NOT);
- break;
- case Token::BIT_NOT: // ~
- append(eth::Instruction::BNOT);
- break;
- case Token::DELETE: // delete
- {
- // a -> a xor a (= 0).
- // @todo semantics change for complex types
- append(eth::Instruction::DUP1);
- append(eth::Instruction::XOR);
- storeInLValue(_unaryOperation);
- break;
- }
- case Token::INC: // ++ (pre- or postfix)
- case Token::DEC: // -- (pre- or postfix)
- if (!_unaryOperation.isPrefixOperation())
- append(eth::Instruction::DUP1);
- append(eth::Instruction::PUSH1);
- append(1);
- if (_unaryOperation.getOperator() == Token::INC)
- append(eth::Instruction::ADD);
- else
- {
- append(eth::Instruction::SWAP1); //@todo avoid this
- append(eth::Instruction::SUB);
- }
- if (_unaryOperation.isPrefixOperation())
- storeInLValue(_unaryOperation);
- else
- moveToLValue(_unaryOperation);
- break;
- case Token::ADD: // +
- // unary add, so basically no-op
- break;
- case Token::SUB: // -
- append(eth::Instruction::PUSH1);
- append(0);
- append(eth::Instruction::SUB);
- break;
- default:
- assert(false); // invalid operation
- }
-}
+ m_context << m_context.getFunctionEntryLabel(_function);
-bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation)
-{
- Expression& leftExpression = _binaryOperation.getLeftExpression();
- Expression& rightExpression = _binaryOperation.getRightExpression();
- Type const& resultType = *_binaryOperation.getType();
- Token::Value const op = _binaryOperation.getOperator();
+ // stack upon entry: [return address] [arg0] [arg1] ... [argn]
+ // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp]
- if (op == Token::AND || op == Token::OR)
- {
- // special case: short-circuiting
- appendAndOrOperatorCode(_binaryOperation);
- }
- else if (Token::isCompareOp(op))
- {
- leftExpression.accept(*this);
- rightExpression.accept(*this);
+ unsigned const numArguments = _function.getParameters().size();
+ unsigned const numReturnValues = _function.getReturnParameters().size();
+ unsigned const numLocalVariables = _function.getLocalVariables().size();
- // the types to compare have to be the same, but the resulting type is always bool
- assert(*leftExpression.getType() == *rightExpression.getType());
- appendCompareOperatorCode(op, *leftExpression.getType());
- }
- else
- {
- leftExpression.accept(*this);
- cleanHigherOrderBitsIfNeeded(*leftExpression.getType(), resultType);
- rightExpression.accept(*this);
- cleanHigherOrderBitsIfNeeded(*rightExpression.getType(), resultType);
- appendOrdinaryBinaryOperatorCode(op, resultType);
- }
+ for (ASTPointer<VariableDeclaration> const& variable: _function.getParameters() + _function.getReturnParameters())
+ m_context.addVariable(*variable);
+ for (VariableDeclaration const* localVariable: _function.getLocalVariables())
+ m_context.addVariable(*localVariable);
+ m_context.initializeLocalVariables(numReturnValues + numLocalVariables);
- // do not visit the child nodes, we already did that explicitly
- return false;
-}
+ _function.getBody().accept(*this);
-void ExpressionCompiler::endVisit(FunctionCall& _functionCall)
-{
- if (_functionCall.isTypeConversion())
- {
- //@todo we only have integers and bools for now which cannot be explicitly converted
- assert(_functionCall.getArguments().size() == 1);
- cleanHigherOrderBitsIfNeeded(*_functionCall.getArguments().front()->getType(),
- *_functionCall.getType());
- }
- else
- {
- //@todo: arguments are already on the stack
- // push return label (below arguments?)
- // jump to function label
- // discard all but the first function return argument
- }
-}
+ m_context << m_returnTag;
-void ExpressionCompiler::endVisit(MemberAccess&)
-{
+ // Now we need to re-shuffle the stack. For this we keep a record of the stack layout
+ // that shows the target positions of the elements, where "-1" denotes that this element needs
+ // to be removed from the stack.
+ // Note that the fact that the return arguments are of increasing index is vital for this
+ // algorithm to work.
-}
+ vector<int> stackLayout;
+ stackLayout.push_back(numReturnValues); // target of return address
+ stackLayout += vector<int>(numArguments, -1); // discard all arguments
+ for (unsigned i = 0; i < numReturnValues; ++i)
+ stackLayout.push_back(i);
+ stackLayout += vector<int>(numLocalVariables, -1);
-void ExpressionCompiler::endVisit(IndexAccess&)
-{
+ while (stackLayout.back() != int(stackLayout.size() - 1))
+ if (stackLayout.back() < 0)
+ {
+ m_context << eth::Instruction::POP;
+ stackLayout.pop_back();
+ }
+ else
+ {
+ m_context << eth::swapInstruction(stackLayout.size() - stackLayout.back() - 1);
+ swap(stackLayout[stackLayout.back()], stackLayout.back());
+ }
-}
+ m_context << eth::Instruction::JUMP;
-void ExpressionCompiler::endVisit(Identifier& _identifier)
-{
- m_currentLValue = _identifier.getReferencedDeclaration();
- unsigned stackPos = stackPositionOfLValue();
- if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory
- BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_identifier.getLocation())
- << errinfo_comment("Stack too deep."));
- appendDup(stackPos + 1);
+ return false;
}
-void ExpressionCompiler::endVisit(Literal& _literal)
+bool Compiler::visit(IfStatement& _ifStatement)
{
- switch (_literal.getType()->getCategory())
- {
- case Type::Category::INTEGER:
- case Type::Category::BOOL:
- {
- bytes value = _literal.getType()->literalToBigEndian(_literal);
- assert(value.size() <= 32);
- assert(!value.empty());
- appendPush(value.size());
- append(value);
- break;
- }
- default:
- assert(false); // @todo
- }
+ ExpressionCompiler::compileExpression(m_context, _ifStatement.getCondition());
+ eth::AssemblyItem trueTag = m_context.appendConditionalJump();
+ if (_ifStatement.getFalseStatement())
+ _ifStatement.getFalseStatement()->accept(*this);
+ eth::AssemblyItem endTag = m_context.appendJump();
+ m_context << trueTag;
+ _ifStatement.getTrueStatement().accept(*this);
+ m_context << endTag;
+ return false;
}
-void ExpressionCompiler::cleanHigherOrderBitsIfNeeded(const Type& _typeOnStack, const Type& _targetType)
+bool Compiler::visit(WhileStatement& _whileStatement)
{
- // If the type of one of the operands is extended, we need to remove all
- // higher-order bits that we might have ignored in previous operations.
- // @todo: store in the AST whether the operand might have "dirty" higher
- // order bits
-
- if (_typeOnStack == _targetType)
- return;
- if (_typeOnStack.getCategory() == Type::Category::INTEGER &&
- _targetType.getCategory() == Type::Category::INTEGER)
- {
- //@todo
- }
- else
- {
- // If we get here, there is either an implementation missing to clean higher oder bits
- // for non-integer types that are explicitly convertible or we got here in error.
- assert(!_typeOnStack.isExplicitlyConvertibleTo(_targetType));
- assert(false); // these types should not be convertible.
- }
-}
+ eth::AssemblyItem loopStart = m_context.newTag();
+ eth::AssemblyItem loopEnd = m_context.newTag();
+ m_continueTags.push_back(loopStart);
+ m_breakTags.push_back(loopEnd);
-void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation)
-{
- Token::Value const op = _binaryOperation.getOperator();
- assert(op == Token::OR || op == Token::AND);
-
- _binaryOperation.getLeftExpression().accept(*this);
- append(eth::Instruction::DUP1);
- if (op == Token::AND)
- append(eth::Instruction::NOT);
- uint32_t endLabel = appendConditionalJump();
- _binaryOperation.getRightExpression().accept(*this);
- appendLabel(endLabel);
-}
+ m_context << loopStart;
+ ExpressionCompiler::compileExpression(m_context, _whileStatement.getCondition());
+ m_context << eth::Instruction::NOT;
+ m_context.appendConditionalJumpTo(loopEnd);
-void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type)
-{
- if (_operator == Token::EQ || _operator == Token::NE)
- {
- append(eth::Instruction::EQ);
- if (_operator == Token::NE)
- append(eth::Instruction::NOT);
- }
- else
- {
- IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
- assert(type);
- bool const isSigned = type->isSigned();
+ _whileStatement.getBody().accept(*this);
- // note that EVM opcodes compare like "stack[0] < stack[1]",
- // but our left value is at stack[1], so everyhing is reversed.
- switch (_operator)
- {
- case Token::GTE:
- append(isSigned ? eth::Instruction::SGT : eth::Instruction::GT);
- append(eth::Instruction::NOT);
- break;
- case Token::LTE:
- append(isSigned ? eth::Instruction::SLT : eth::Instruction::LT);
- append(eth::Instruction::NOT);
- break;
- case Token::GT:
- append(isSigned ? eth::Instruction::SLT : eth::Instruction::LT);
- break;
- case Token::LT:
- append(isSigned ? eth::Instruction::SGT : eth::Instruction::GT);
- break;
- default:
- assert(false);
- }
- }
-}
+ m_context.appendJumpTo(loopStart);
+ m_context << loopEnd;
-void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type)
-{
- if (Token::isArithmeticOp(_operator))
- appendArithmeticOperatorCode(_operator, _type);
- else if (Token::isBitOp(_operator))
- appendBitOperatorCode(_operator);
- else if (Token::isShiftOp(_operator))
- appendShiftOperatorCode(_operator);
- else
- assert(false); // unknown binary operator
+ m_continueTags.pop_back();
+ m_breakTags.pop_back();
+ return false;
}
-void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
+bool Compiler::visit(Continue&)
{
- IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
- assert(type);
- bool const isSigned = type->isSigned();
-
- switch (_operator)
- {
- case Token::ADD:
- append(eth::Instruction::ADD);
- break;
- case Token::SUB:
- append(eth::Instruction::SWAP1);
- append(eth::Instruction::SUB);
- break;
- case Token::MUL:
- append(eth::Instruction::MUL);
- break;
- case Token::DIV:
- append(isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV);
- break;
- case Token::MOD:
- append(isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD);
- break;
- default:
- assert(false);
- }
+ assert(!m_continueTags.empty());
+ m_context.appendJumpTo(m_continueTags.back());
+ return false;
}
-void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator)
+bool Compiler::visit(Break&)
{
- switch (_operator)
- {
- case Token::BIT_OR:
- append(eth::Instruction::OR);
- break;
- case Token::BIT_AND:
- append(eth::Instruction::AND);
- break;
- case Token::BIT_XOR:
- append(eth::Instruction::XOR);
- break;
- default:
- assert(false);
- }
+ assert(!m_breakTags.empty());
+ m_context.appendJumpTo(m_breakTags.back());
+ return false;
}
-void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator)
+bool Compiler::visit(Return& _return)
{
- switch (_operator)
+ //@todo modifications are needed to make this work with functions returning multiple values
+ if (Expression* expression = _return.getExpression())
{
- case Token::SHL:
- assert(false); //@todo
- break;
- case Token::SAR:
- assert(false); //@todo
- break;
- default:
- assert(false);
+ ExpressionCompiler::compileExpression(m_context, *expression);
+ VariableDeclaration const& firstVariable = *_return.getFunctionReturnParameters().getParameters().front();
+ ExpressionCompiler::cleanHigherOrderBitsIfNeeded(*expression->getType(), *firstVariable.getType());
+ int stackPosition = m_context.getStackPositionOfVariable(firstVariable);
+ m_context << eth::swapInstruction(stackPosition) << eth::Instruction::POP;
}
+ m_context.appendJumpTo(m_returnTag);
+ return false;
}
-uint32_t ExpressionCompiler::appendConditionalJump()
-{
- uint32_t label = m_context.dispenseNewLabel();
- append(eth::Instruction::PUSH1);
- appendLabelref(label);
- append(eth::Instruction::JUMPI);
- return label;
-}
-
-void ExpressionCompiler::appendPush(unsigned _number)
-{
- assert(1 <= _number && _number <= 32);
- append(eth::Instruction(unsigned(eth::Instruction::PUSH1) + _number - 1));
-}
-
-void ExpressionCompiler::appendDup(unsigned _number)
-{
- assert(1 <= _number && _number <= 16);
- append(eth::Instruction(unsigned(eth::Instruction::DUP1) + _number - 1));
-}
-
-void ExpressionCompiler::appendSwap(unsigned _number)
-{
- assert(1 <= _number && _number <= 16);
- append(eth::Instruction(unsigned(eth::Instruction::SWAP1) + _number - 1));
-}
-
-void ExpressionCompiler::append(bytes const& _data)
-{
- m_assemblyItems.reserve(m_assemblyItems.size() + _data.size());
- for (byte b: _data)
- append(b);
-}
-
-void ExpressionCompiler::storeInLValue(Expression const& _expression)
-{
- assert(m_currentLValue);
- moveToLValue(_expression);
- unsigned stackPos = stackPositionOfLValue();
- if (stackPos > 16)
- BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation())
- << errinfo_comment("Stack too deep."));
- if (stackPos >= 1)
- appendDup(stackPos);
-}
-
-void ExpressionCompiler::moveToLValue(Expression const& _expression)
+bool Compiler::visit(VariableDefinition& _variableDefinition)
{
- assert(m_currentLValue);
- unsigned stackPos = stackPositionOfLValue();
- if (stackPos > 16)
- BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation())
- << errinfo_comment("Stack too deep."));
- else if (stackPos > 0)
+ if (Expression* expression = _variableDefinition.getExpression())
{
- appendSwap(stackPos);
- append(eth::Instruction::POP);
+ ExpressionCompiler::compileExpression(m_context, *expression);
+ ExpressionCompiler::cleanHigherOrderBitsIfNeeded(*expression->getType(),
+ *_variableDefinition.getDeclaration().getType());
+ int stackPosition = m_context.getStackPositionOfVariable(_variableDefinition.getDeclaration());
+ m_context << eth::swapInstruction(stackPosition) << eth::Instruction::POP;
}
+ return false;
}
-unsigned ExpressionCompiler::stackPositionOfLValue() const
+bool Compiler::visit(ExpressionStatement& _expressionStatement)
{
- return 8; // @todo ask the context and track stack changes due to m_assemblyItems
+ Expression& expression = _expressionStatement.getExpression();
+ ExpressionCompiler::compileExpression(m_context, expression);
+ if (expression.getType()->getCategory() != Type::Category::VOID)
+ m_context << eth::Instruction::POP;
+ return false;
}
-
-
}
}