aboutsummaryrefslogtreecommitdiffstats
path: root/Compiler.cpp
diff options
context:
space:
mode:
authorChristian <c@ethdev.com>2014-10-30 08:20:32 +0800
committerChristian <c@ethdev.com>2014-10-30 08:25:42 +0800
commit7f19f3d133b74bd7ebc96d18b09e145417b7daac (patch)
treea344a8faf9675882eb42f4f83e57f3a825844dcf /Compiler.cpp
parent51349bdae53e7d495732085c446ff9488473dcc8 (diff)
downloaddexon-solidity-7f19f3d133b74bd7ebc96d18b09e145417b7daac.tar.gz
dexon-solidity-7f19f3d133b74bd7ebc96d18b09e145417b7daac.tar.zst
dexon-solidity-7f19f3d133b74bd7ebc96d18b09e145417b7daac.zip
Contract compiler and also add ExpressionStatement to AST.
ExpressionStatement functions as glue between Statements and Expressions. This way it is possible to detect when the border between statements and expressions is crossed while walking the AST. Note that ExpressionStatement is not the only border, almost every statement can contains expressions.
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;
}
-
-
}
}