mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
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.
This commit is contained in:
parent
51349bdae5
commit
7f19f3d133
43
AST.cpp
43
AST.cpp
@ -167,6 +167,14 @@ void Return::accept(ASTVisitor& _visitor)
|
||||
_visitor.endVisit(*this);
|
||||
}
|
||||
|
||||
void ExpressionStatement::accept(ASTVisitor& _visitor)
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
if (m_expression)
|
||||
m_expression->accept(_visitor);
|
||||
_visitor.endVisit(*this);
|
||||
}
|
||||
|
||||
void VariableDefinition::accept(ASTVisitor& _visitor)
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
@ -255,14 +263,6 @@ TypeError ASTNode::createTypeError(string const& _description)
|
||||
return TypeError() << errinfo_sourceLocation(getLocation()) << errinfo_comment(_description);
|
||||
}
|
||||
|
||||
void Statement::expectType(Expression& _expression, const Type& _expectedType)
|
||||
{
|
||||
_expression.checkTypeRequirements();
|
||||
if (!_expression.getType()->isImplicitlyConvertibleTo(_expectedType))
|
||||
BOOST_THROW_EXCEPTION(_expression.createTypeError("Type not implicitly convertible to expected type."));
|
||||
//@todo provide more information to the exception
|
||||
}
|
||||
|
||||
void Block::checkTypeRequirements()
|
||||
{
|
||||
for (shared_ptr<Statement> const& statement: m_statements)
|
||||
@ -271,7 +271,7 @@ void Block::checkTypeRequirements()
|
||||
|
||||
void IfStatement::checkTypeRequirements()
|
||||
{
|
||||
expectType(*m_condition, BoolType());
|
||||
m_condition->expectType(BoolType());
|
||||
m_trueBody->checkTypeRequirements();
|
||||
if (m_falseBody)
|
||||
m_falseBody->checkTypeRequirements();
|
||||
@ -279,7 +279,7 @@ void IfStatement::checkTypeRequirements()
|
||||
|
||||
void WhileStatement::checkTypeRequirements()
|
||||
{
|
||||
expectType(*m_condition, BoolType());
|
||||
m_condition->expectType(BoolType());
|
||||
m_body->checkTypeRequirements();
|
||||
}
|
||||
|
||||
@ -301,7 +301,7 @@ void Return::checkTypeRequirements()
|
||||
"than in returns declaration."));
|
||||
// this could later be changed such that the paramaters type is an anonymous struct type,
|
||||
// but for now, we only allow one return parameter
|
||||
expectType(*m_expression, *m_returnParameters->getParameters().front()->getType());
|
||||
m_expression->expectType(*m_returnParameters->getParameters().front()->getType());
|
||||
}
|
||||
|
||||
void VariableDefinition::checkTypeRequirements()
|
||||
@ -313,7 +313,7 @@ void VariableDefinition::checkTypeRequirements()
|
||||
if (m_value)
|
||||
{
|
||||
if (m_variable->getType())
|
||||
expectType(*m_value, *m_variable->getType());
|
||||
m_value->expectType(*m_variable->getType());
|
||||
else
|
||||
{
|
||||
// no type declared and no previous assignment, infer the type
|
||||
@ -330,7 +330,7 @@ void Assignment::checkTypeRequirements()
|
||||
m_leftHandSide->checkTypeRequirements();
|
||||
if (!m_leftHandSide->isLvalue())
|
||||
BOOST_THROW_EXCEPTION(createTypeError("Expression has to be an lvalue."));
|
||||
expectType(*m_rightHandSide, *m_leftHandSide->getType());
|
||||
m_rightHandSide->expectType(*m_leftHandSide->getType());
|
||||
m_type = m_leftHandSide->getType();
|
||||
if (m_assigmentOperator != Token::ASSIGN)
|
||||
{
|
||||
@ -340,6 +340,19 @@ void Assignment::checkTypeRequirements()
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionStatement::checkTypeRequirements()
|
||||
{
|
||||
m_expression->checkTypeRequirements();
|
||||
}
|
||||
|
||||
void Expression::expectType(const Type& _expectedType)
|
||||
{
|
||||
checkTypeRequirements();
|
||||
if (!getType()->isImplicitlyConvertibleTo(_expectedType))
|
||||
BOOST_THROW_EXCEPTION(createTypeError("Type not implicitly convertible to expected type."));
|
||||
//@todo provide more information to the exception
|
||||
}
|
||||
|
||||
void UnaryOperation::checkTypeRequirements()
|
||||
{
|
||||
// INC, DEC, ADD, SUB, NOT, BIT_NOT, DELETE
|
||||
@ -411,10 +424,10 @@ void FunctionCall::checkTypeRequirements()
|
||||
BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in function call."));
|
||||
// @todo actually the return type should be an anonymous struct,
|
||||
// but we change it to the type of the first return value until we have structs
|
||||
if (fun.getReturnParameterList()->getParameters().empty())
|
||||
if (fun.getReturnParameters().empty())
|
||||
m_type = make_shared<VoidType>();
|
||||
else
|
||||
m_type = fun.getReturnParameterList()->getParameters().front()->getType();
|
||||
m_type = fun.getReturnParameters().front()->getType();
|
||||
}
|
||||
}
|
||||
|
||||
|
65
AST.h
65
AST.h
@ -144,7 +144,7 @@ public:
|
||||
ASTNode(_location), m_parameters(_parameters) {}
|
||||
virtual void accept(ASTVisitor& _visitor) override;
|
||||
|
||||
std::vector<ASTPointer<VariableDeclaration>> const& getParameters() { return m_parameters; }
|
||||
std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters; }
|
||||
|
||||
private:
|
||||
std::vector<ASTPointer<VariableDeclaration>> m_parameters;
|
||||
@ -167,15 +167,20 @@ public:
|
||||
bool isDeclaredConst() const { return m_isDeclaredConst; }
|
||||
std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); }
|
||||
ParameterList& getParameterList() { return *m_parameters; }
|
||||
std::vector<ASTPointer<VariableDeclaration>> const& getReturnParameters() const { return m_returnParameters->getParameters(); }
|
||||
ASTPointer<ParameterList> const& getReturnParameterList() const { return m_returnParameters; }
|
||||
Block& getBody() { return *m_body; }
|
||||
|
||||
void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); }
|
||||
std::vector<VariableDeclaration const*>const& getLocalVariables() const { return m_localVariables; }
|
||||
private:
|
||||
bool m_isPublic;
|
||||
ASTPointer<ParameterList> m_parameters;
|
||||
bool m_isDeclaredConst;
|
||||
ASTPointer<ParameterList> m_returnParameters;
|
||||
ASTPointer<Block> m_body;
|
||||
|
||||
std::vector<VariableDeclaration const*> m_localVariables;
|
||||
};
|
||||
|
||||
/// Declaration of a variable. This can be used in various places, e.g. in function parameter
|
||||
@ -285,11 +290,6 @@ public:
|
||||
//! This includes checking that operators are applicable to their arguments but also that
|
||||
//! the number of function call arguments matches the number of formal parameters and so forth.
|
||||
virtual void checkTypeRequirements() = 0;
|
||||
|
||||
protected:
|
||||
//! Helper function, check that the inferred type for @a _expression is @a _expectedType or at
|
||||
//! least implicitly convertible to @a _expectedType. If not, throw exception.
|
||||
void expectType(Expression& _expression, Type const& _expectedType);
|
||||
};
|
||||
|
||||
/// Brace-enclosed block containing zero or more statements.
|
||||
@ -318,6 +318,9 @@ public:
|
||||
virtual void accept(ASTVisitor& _visitor) override;
|
||||
virtual void checkTypeRequirements() override;
|
||||
|
||||
Expression& getCondition() const { return *m_condition; }
|
||||
Statement& getTrueStatement() const { return *m_trueBody; }
|
||||
Statement* getFalseStatement() const { return m_falseBody.get(); }
|
||||
private:
|
||||
ASTPointer<Expression> m_condition;
|
||||
ASTPointer<Statement> m_trueBody;
|
||||
@ -342,6 +345,8 @@ public:
|
||||
virtual void accept(ASTVisitor& _visitor) override;
|
||||
virtual void checkTypeRequirements() override;
|
||||
|
||||
Expression& getCondition() const { return *m_condition; }
|
||||
Statement& getBody() const { return *m_body; }
|
||||
private:
|
||||
ASTPointer<Expression> m_condition;
|
||||
ASTPointer<Statement> m_body;
|
||||
@ -372,6 +377,8 @@ public:
|
||||
virtual void checkTypeRequirements() override;
|
||||
|
||||
void setFunctionReturnParameters(ParameterList& _parameters) { m_returnParameters = &_parameters; }
|
||||
ParameterList const& getFunctionReturnParameters() const { assert(m_returnParameters); return *m_returnParameters; }
|
||||
Expression* getExpression() const { return m_expression.get(); }
|
||||
|
||||
private:
|
||||
ASTPointer<Expression> m_expression; //< value to return, optional
|
||||
@ -392,21 +399,54 @@ public:
|
||||
virtual void accept(ASTVisitor& _visitor) override;
|
||||
virtual void checkTypeRequirements() override;
|
||||
|
||||
VariableDeclaration const& getDeclaration() const { return *m_variable; }
|
||||
Expression* getExpression() const { return m_value.get(); }
|
||||
|
||||
private:
|
||||
ASTPointer<VariableDeclaration> m_variable;
|
||||
ASTPointer<Expression> m_value; ///< the assigned value, can be missing
|
||||
};
|
||||
|
||||
/// An expression, i.e. something that has a value (which can also be of type "void" in case
|
||||
/// of function calls).
|
||||
class Expression: public Statement
|
||||
/**
|
||||
* A statement that contains only an expression (i.e. an assignment, function call, ...).
|
||||
*/
|
||||
class ExpressionStatement: public Statement
|
||||
{
|
||||
public:
|
||||
Expression(Location const& _location): Statement(_location), m_isLvalue(false) {}
|
||||
ExpressionStatement(Location const& _location, ASTPointer<Expression> _expression):
|
||||
Statement(_location), m_expression(_expression) {}
|
||||
virtual void accept(ASTVisitor& _visitor) override;
|
||||
virtual void checkTypeRequirements() override;
|
||||
|
||||
Expression& getExpression() const { return *m_expression; }
|
||||
|
||||
private:
|
||||
ASTPointer<Expression> m_expression;
|
||||
};
|
||||
|
||||
/// @}
|
||||
|
||||
/// Expressions
|
||||
/// @{
|
||||
|
||||
/**
|
||||
* An expression, i.e. something that has a value (which can also be of type "void" in case
|
||||
* of some function calls).
|
||||
* @abstract
|
||||
*/
|
||||
class Expression: public ASTNode
|
||||
{
|
||||
public:
|
||||
Expression(Location const& _location): ASTNode(_location), m_isLvalue(false) {}
|
||||
virtual void checkTypeRequirements() = 0;
|
||||
|
||||
std::shared_ptr<Type const> const& getType() const { return m_type; }
|
||||
bool isLvalue() const { return m_isLvalue; }
|
||||
|
||||
/// Helper function, infer the type via @ref checkTypeRequirements and then check that it
|
||||
/// is implicitly convertible to @a _expectedType. If not, throw exception.
|
||||
void expectType(Type const& _expectedType);
|
||||
|
||||
protected:
|
||||
//! Inferred type of the expression, only filled after a call to checkTypeRequirements().
|
||||
std::shared_ptr<Type const> m_type;
|
||||
@ -415,11 +455,6 @@ protected:
|
||||
bool m_isLvalue;
|
||||
};
|
||||
|
||||
/// @}
|
||||
|
||||
/// Expressions
|
||||
/// @{
|
||||
|
||||
/// Assignment, can also be a compound assignment.
|
||||
/// Examples: (a = 7 + 8) or (a *= 2)
|
||||
class Assignment: public Expression
|
||||
|
@ -53,6 +53,7 @@ class Continue;
|
||||
class Break;
|
||||
class Return;
|
||||
class VariableDefinition;
|
||||
class ExpressionStatement;
|
||||
class Expression;
|
||||
class Assignment;
|
||||
class UnaryOperation;
|
||||
|
@ -171,6 +171,13 @@ bool ASTPrinter::visit(VariableDefinition& _node)
|
||||
return goDeeper();
|
||||
}
|
||||
|
||||
bool ASTPrinter::visit(ExpressionStatement& _node)
|
||||
{
|
||||
writeLine("ExpressionStatement");
|
||||
printSourcePart(_node);
|
||||
return goDeeper();
|
||||
}
|
||||
|
||||
bool ASTPrinter::visit(Expression& _node)
|
||||
{
|
||||
writeLine("Expression");
|
||||
@ -358,6 +365,11 @@ void ASTPrinter::endVisit(VariableDefinition&)
|
||||
m_indentation--;
|
||||
}
|
||||
|
||||
void ASTPrinter::endVisit(ExpressionStatement&)
|
||||
{
|
||||
m_indentation--;
|
||||
}
|
||||
|
||||
void ASTPrinter::endVisit(Expression&)
|
||||
{
|
||||
m_indentation--;
|
||||
|
@ -58,6 +58,7 @@ public:
|
||||
bool visit(Break& _node) override;
|
||||
bool visit(Return& _node) override;
|
||||
bool visit(VariableDefinition& _node) override;
|
||||
bool visit(ExpressionStatement& _node) override;
|
||||
bool visit(Expression& _node) override;
|
||||
bool visit(Assignment& _node) override;
|
||||
bool visit(UnaryOperation& _node) override;
|
||||
@ -89,6 +90,7 @@ public:
|
||||
void endVisit(Break&) override;
|
||||
void endVisit(Return&) override;
|
||||
void endVisit(VariableDefinition&) override;
|
||||
void endVisit(ExpressionStatement&) override;
|
||||
void endVisit(Expression&) override;
|
||||
void endVisit(Assignment&) override;
|
||||
void endVisit(UnaryOperation&) override;
|
||||
|
@ -58,6 +58,7 @@ public:
|
||||
virtual bool visit(Break&) { return true; }
|
||||
virtual bool visit(Return&) { return true; }
|
||||
virtual bool visit(VariableDefinition&) { return true; }
|
||||
virtual bool visit(ExpressionStatement&) { return true; }
|
||||
virtual bool visit(Expression&) { return true; }
|
||||
virtual bool visit(Assignment&) { return true; }
|
||||
virtual bool visit(UnaryOperation&) { return true; }
|
||||
@ -89,6 +90,7 @@ public:
|
||||
virtual void endVisit(Break&) { }
|
||||
virtual void endVisit(Return&) { }
|
||||
virtual void endVisit(VariableDefinition&) { }
|
||||
virtual void endVisit(ExpressionStatement&) { }
|
||||
virtual void endVisit(Expression&) { }
|
||||
virtual void endVisit(Assignment&) { }
|
||||
virtual void endVisit(UnaryOperation&) { }
|
||||
|
@ -16,8 +16,8 @@ file(GLOB HEADERS "*.h")
|
||||
|
||||
include_directories(..)
|
||||
|
||||
target_link_libraries(${EXECUTABLE} devcore)
|
||||
target_link_libraries(${EXECUTABLE} evmface)
|
||||
# @todo we only depend on Assembly, not on all of lll
|
||||
target_link_libraries(${EXECUTABLE} evmface devcore lll)
|
||||
|
||||
install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib )
|
||||
install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} )
|
||||
|
555
Compiler.cpp
555
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)
|
||||
bytes Compiler::compile(ContractDefinition& _contract)
|
||||
{
|
||||
assert(m_labelPositions.find(_label) == m_labelPositions.end());
|
||||
m_labelPositions[_label] = _position;
|
||||
Compiler compiler;
|
||||
compiler.compileContract(_contract);
|
||||
return compiler.m_context.getAssembledBytecode();
|
||||
}
|
||||
|
||||
uint32_t CompilerContext::getLabelPosition(uint32_t _label) const
|
||||
void Compiler::compileContract(ContractDefinition& _contract)
|
||||
{
|
||||
auto iter = m_labelPositions.find(_label);
|
||||
assert(iter != m_labelPositions.end());
|
||||
return iter->second;
|
||||
m_context = CompilerContext(); // clear it just in case
|
||||
|
||||
//@todo constructor
|
||||
//@todo register state variables
|
||||
|
||||
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
|
||||
m_context.addFunction(*function);
|
||||
appendFunctionSelector(_contract.getDefinedFunctions());
|
||||
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
|
||||
function->accept(*this);
|
||||
}
|
||||
|
||||
void ExpressionCompiler::compile(Expression& _expression)
|
||||
void Compiler::appendFunctionSelector(std::vector<ASTPointer<FunctionDefinition>> const&)
|
||||
{
|
||||
m_assemblyItems.clear();
|
||||
_expression.accept(*this);
|
||||
// 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.
|
||||
}
|
||||
|
||||
bytes ExpressionCompiler::getAssembledBytecode() const
|
||||
bool Compiler::visit(FunctionDefinition& _function)
|
||||
{
|
||||
bytes assembled;
|
||||
assembled.reserve(m_assemblyItems.size());
|
||||
//@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
|
||||
|
||||
// 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);
|
||||
}
|
||||
m_context.startNewFunction();
|
||||
m_returnTag = m_context.newTag();
|
||||
m_breakTags.clear();
|
||||
m_continueTags.clear();
|
||||
|
||||
for (AssemblyItem const& item: m_assemblyItems)
|
||||
{
|
||||
if (item.getType() == AssemblyItem::Type::LABELREF)
|
||||
assembled.push_back(m_context.getLabelPosition(item.getLabel()));
|
||||
m_context << m_context.getFunctionEntryLabel(_function);
|
||||
|
||||
// stack upon entry: [return address] [arg0] [arg1] ... [argn]
|
||||
// reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp]
|
||||
|
||||
unsigned const numArguments = _function.getParameters().size();
|
||||
unsigned const numReturnValues = _function.getReturnParameters().size();
|
||||
unsigned const numLocalVariables = _function.getLocalVariables().size();
|
||||
|
||||
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);
|
||||
|
||||
_function.getBody().accept(*this);
|
||||
|
||||
m_context << m_returnTag;
|
||||
|
||||
// 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);
|
||||
|
||||
while (stackLayout.back() != int(stackLayout.size() - 1))
|
||||
if (stackLayout.back() < 0)
|
||||
{
|
||||
m_context << eth::Instruction::POP;
|
||||
stackLayout.pop_back();
|
||||
}
|
||||
else
|
||||
assembled.push_back(item.getData());
|
||||
}
|
||||
{
|
||||
m_context << eth::swapInstruction(stackLayout.size() - stackLayout.back() - 1);
|
||||
swap(stackLayout[stackLayout.back()], stackLayout.back());
|
||||
}
|
||||
|
||||
return assembled;
|
||||
}
|
||||
m_context << eth::Instruction::JUMP;
|
||||
|
||||
AssemblyItems ExpressionCompiler::compileExpression(CompilerContext& _context,
|
||||
Expression& _expression)
|
||||
{
|
||||
ExpressionCompiler compiler(_context);
|
||||
compiler.compile(_expression);
|
||||
return compiler.getAssemblyItems();
|
||||
}
|
||||
|
||||
bool ExpressionCompiler::visit(Assignment& _assignment)
|
||||
{
|
||||
m_currentLValue = nullptr;
|
||||
_assignment.getLeftHandSide().accept(*this);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
storeInLValue(_assignment);
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation)
|
||||
bool Compiler::visit(IfStatement& _ifStatement)
|
||||
{
|
||||
//@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
|
||||
}
|
||||
}
|
||||
|
||||
bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation)
|
||||
{
|
||||
Expression& leftExpression = _binaryOperation.getLeftExpression();
|
||||
Expression& rightExpression = _binaryOperation.getRightExpression();
|
||||
Type const& resultType = *_binaryOperation.getType();
|
||||
Token::Value const op = _binaryOperation.getOperator();
|
||||
|
||||
if (op == Token::AND || op == Token::OR)
|
||||
{
|
||||
// special case: short-circuiting
|
||||
appendAndOrOperatorCode(_binaryOperation);
|
||||
}
|
||||
else if (Token::isCompareOp(op))
|
||||
{
|
||||
leftExpression.accept(*this);
|
||||
rightExpression.accept(*this);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// do not visit the child nodes, we already did that explicitly
|
||||
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::endVisit(FunctionCall& _functionCall)
|
||||
bool Compiler::visit(WhileStatement& _whileStatement)
|
||||
{
|
||||
if (_functionCall.isTypeConversion())
|
||||
eth::AssemblyItem loopStart = m_context.newTag();
|
||||
eth::AssemblyItem loopEnd = m_context.newTag();
|
||||
m_continueTags.push_back(loopStart);
|
||||
m_breakTags.push_back(loopEnd);
|
||||
|
||||
m_context << loopStart;
|
||||
ExpressionCompiler::compileExpression(m_context, _whileStatement.getCondition());
|
||||
m_context << eth::Instruction::NOT;
|
||||
m_context.appendConditionalJumpTo(loopEnd);
|
||||
|
||||
_whileStatement.getBody().accept(*this);
|
||||
|
||||
m_context.appendJumpTo(loopStart);
|
||||
m_context << loopEnd;
|
||||
|
||||
m_continueTags.pop_back();
|
||||
m_breakTags.pop_back();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Compiler::visit(Continue&)
|
||||
{
|
||||
assert(!m_continueTags.empty());
|
||||
m_context.appendJumpTo(m_continueTags.back());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Compiler::visit(Break&)
|
||||
{
|
||||
assert(!m_breakTags.empty());
|
||||
m_context.appendJumpTo(m_breakTags.back());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Compiler::visit(Return& _return)
|
||||
{
|
||||
//@todo modifications are needed to make this work with functions returning multiple values
|
||||
if (Expression* expression = _return.getExpression())
|
||||
{
|
||||
//@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());
|
||||
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;
|
||||
}
|
||||
else
|
||||
m_context.appendJumpTo(m_returnTag);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Compiler::visit(VariableDefinition& _variableDefinition)
|
||||
{
|
||||
if (Expression* expression = _variableDefinition.getExpression())
|
||||
{
|
||||
//@todo: arguments are already on the stack
|
||||
// push return label (below arguments?)
|
||||
// jump to function label
|
||||
// discard all but the first function return argument
|
||||
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;
|
||||
}
|
||||
|
||||
void ExpressionCompiler::endVisit(MemberAccess&)
|
||||
bool Compiler::visit(ExpressionStatement& _expressionStatement)
|
||||
{
|
||||
|
||||
Expression& expression = _expressionStatement.getExpression();
|
||||
ExpressionCompiler::compileExpression(m_context, expression);
|
||||
if (expression.getType()->getCategory() != Type::Category::VOID)
|
||||
m_context << eth::Instruction::POP;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExpressionCompiler::endVisit(IndexAccess&)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void ExpressionCompiler::endVisit(Literal& _literal)
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionCompiler::cleanHigherOrderBitsIfNeeded(const Type& _typeOnStack, const Type& _targetType)
|
||||
{
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator)
|
||||
{
|
||||
switch (_operator)
|
||||
{
|
||||
case Token::SHL:
|
||||
assert(false); //@todo
|
||||
break;
|
||||
case Token::SAR:
|
||||
assert(false); //@todo
|
||||
break;
|
||||
default:
|
||||
assert(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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
appendSwap(stackPos);
|
||||
append(eth::Instruction::POP);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned ExpressionCompiler::stackPositionOfLValue() const
|
||||
{
|
||||
return 8; // @todo ask the context and track stack changes due to m_assemblyItems
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
139
Compiler.h
139
Compiler.h
@ -20,133 +20,40 @@
|
||||
* Solidity AST to EVM bytecode compiler.
|
||||
*/
|
||||
|
||||
#include <libevmface/Instruction.h>
|
||||
#include <libsolidity/ASTVisitor.h>
|
||||
#include <libsolidity/Types.h>
|
||||
#include <libsolidity/Token.h>
|
||||
#include <libsolidity/CompilerUtilities.h>
|
||||
|
||||
namespace dev {
|
||||
namespace solidity {
|
||||
|
||||
/// A single item of compiled code that can be assembled to a single byte value in the final
|
||||
/// bytecode. Its main purpose is to inject jump labels and label references into the opcode stream,
|
||||
/// which can be resolved in the final step.
|
||||
class AssemblyItem
|
||||
class Compiler: private ASTVisitor
|
||||
{
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
CODE, //< m_data is opcode, m_label is empty.
|
||||
DATA, //< m_data is actual data, m_label is empty
|
||||
LABEL, //< m_data is JUMPDEST opcode, m_label is id of label
|
||||
LABELREF //< m_data is empty, m_label is id of label
|
||||
};
|
||||
|
||||
explicit AssemblyItem(eth::Instruction _instruction) : m_type(Type::CODE), m_data(byte(_instruction)) {}
|
||||
explicit AssemblyItem(byte _data): m_type(Type::DATA), m_data(_data) {}
|
||||
|
||||
/// Factory functions
|
||||
static AssemblyItem labelRef(uint32_t _label) { return AssemblyItem(Type::LABELREF, 0, _label); }
|
||||
static AssemblyItem label(uint32_t _label) { return AssemblyItem(Type::LABEL, byte(eth::Instruction::JUMPDEST), _label); }
|
||||
|
||||
Type getType() const { return m_type; }
|
||||
byte getData() const { return m_data; }
|
||||
uint32_t getLabel() const { return m_label; }
|
||||
/// Compile the given contract and return the EVM bytecode.
|
||||
static bytes compile(ContractDefinition& _contract);
|
||||
|
||||
private:
|
||||
AssemblyItem(Type _type, byte _data, uint32_t _label): m_type(_type), m_data(_data), m_label(_label) {}
|
||||
Compiler(): m_returnTag(m_context.newTag()) {}
|
||||
|
||||
Type m_type;
|
||||
byte m_data; //< data to be written to the bytecode stream (or filled by a label if this is a LABELREF)
|
||||
uint32_t m_label; //< the id of a label either referenced or defined by this item
|
||||
void compileContract(ContractDefinition& _contract);
|
||||
void appendFunctionSelector(const std::vector<ASTPointer<FunctionDefinition> >& _functions);
|
||||
|
||||
virtual bool visit(FunctionDefinition& _function) override;
|
||||
virtual bool visit(IfStatement& _ifStatement) override;
|
||||
virtual bool visit(WhileStatement& _whileStatement) override;
|
||||
virtual bool visit(Continue& _continue) override;
|
||||
virtual bool visit(Break& _break) override;
|
||||
virtual bool visit(Return& _return) override;
|
||||
virtual bool visit(VariableDefinition& _variableDefinition) override;
|
||||
virtual bool visit(ExpressionStatement& _expressionStatement) override;
|
||||
|
||||
bytes getAssembledBytecode() { return m_context.getAssembledBytecode(); }
|
||||
|
||||
CompilerContext m_context;
|
||||
std::vector<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement
|
||||
std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement
|
||||
eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement
|
||||
};
|
||||
|
||||
using AssemblyItems = std::vector<AssemblyItem>;
|
||||
|
||||
|
||||
/// Context to be shared by all units that compile the same contract. Its current usage only
|
||||
/// concerns dispensing unique jump label IDs and storing their actual positions in the bytecode
|
||||
/// stream.
|
||||
class CompilerContext
|
||||
{
|
||||
public:
|
||||
CompilerContext(): m_nextLabel(0) {}
|
||||
uint32_t dispenseNewLabel() { return m_nextLabel++; }
|
||||
void setLabelPosition(uint32_t _label, uint32_t _position);
|
||||
uint32_t getLabelPosition(uint32_t _label) const;
|
||||
|
||||
private:
|
||||
uint32_t m_nextLabel;
|
||||
|
||||
std::map<uint32_t, uint32_t> m_labelPositions;
|
||||
};
|
||||
|
||||
/// Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream
|
||||
/// of EVM instructions. It needs a compiler context that is the same for the whole compilation
|
||||
/// unit.
|
||||
class ExpressionCompiler: public ASTVisitor
|
||||
{
|
||||
public:
|
||||
ExpressionCompiler(CompilerContext& _compilerContext): m_currentLValue(nullptr), m_context(_compilerContext) {}
|
||||
|
||||
/// Compile the given expression and (re-)populate the assembly item list.
|
||||
void compile(Expression& _expression);
|
||||
AssemblyItems const& getAssemblyItems() const { return m_assemblyItems; }
|
||||
bytes getAssembledBytecode() const;
|
||||
|
||||
/// Compile the given expression and return the assembly items right away.
|
||||
static AssemblyItems compileExpression(CompilerContext& _context, Expression& _expression);
|
||||
|
||||
private:
|
||||
virtual bool visit(Assignment& _assignment) override;
|
||||
virtual void endVisit(UnaryOperation& _unaryOperation) override;
|
||||
virtual bool visit(BinaryOperation& _binaryOperation) override;
|
||||
virtual void endVisit(FunctionCall& _functionCall) override;
|
||||
virtual void endVisit(MemberAccess& _memberAccess) override;
|
||||
virtual void endVisit(IndexAccess& _indexAccess) override;
|
||||
virtual void endVisit(Identifier& _identifier) override;
|
||||
virtual void endVisit(Literal& _literal) override;
|
||||
|
||||
/// Appends code to remove dirty higher order bits in case of an implicit promotion to a wider type.
|
||||
void cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _targetType);
|
||||
|
||||
///@{
|
||||
///@name Append code for various operator types
|
||||
void appendAndOrOperatorCode(BinaryOperation& _binaryOperation);
|
||||
void appendCompareOperatorCode(Token::Value _operator, Type const& _type);
|
||||
void appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type);
|
||||
|
||||
void appendArithmeticOperatorCode(Token::Value _operator, Type const& _type);
|
||||
void appendBitOperatorCode(Token::Value _operator);
|
||||
void appendShiftOperatorCode(Token::Value _operator);
|
||||
/// @}
|
||||
|
||||
/// Appends a JUMPI instruction to a new label and returns the label
|
||||
uint32_t appendConditionalJump();
|
||||
void appendPush(unsigned _number);
|
||||
void appendDup(unsigned _number);
|
||||
void appendSwap(unsigned _number);
|
||||
|
||||
/// Append elements to the current instruction list.
|
||||
void append(eth::Instruction const& _instruction) { m_assemblyItems.push_back(AssemblyItem(_instruction)); }
|
||||
void append(byte _value) { m_assemblyItems.push_back(AssemblyItem(_value)); }
|
||||
void append(bytes const& _data);
|
||||
void appendLabelref(byte _label) { m_assemblyItems.push_back(AssemblyItem::labelRef(_label)); }
|
||||
void appendLabel(byte _label) { m_assemblyItems.push_back(AssemblyItem::label(_label)); }
|
||||
|
||||
/// Stores the value on top of the stack in the current lvalue and copies that value to the
|
||||
/// top of the stack again
|
||||
void storeInLValue(Expression const& _expression);
|
||||
/// The same as storeInLValue but do not again retrieve the value to the top of the stack.
|
||||
void moveToLValue(Expression const& _expression);
|
||||
/// Returns the position of @a m_currentLValue in the stack, where 0 is the top of the stack.
|
||||
unsigned stackPositionOfLValue() const;
|
||||
|
||||
Declaration* m_currentLValue;
|
||||
AssemblyItems m_assemblyItems;
|
||||
CompilerContext& m_context;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
60
CompilerUtilities.cpp
Normal file
60
CompilerUtilities.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
|
||||
cpp-ethereum 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.
|
||||
|
||||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @author Christian <c@ethdev.com>
|
||||
* @date 2014
|
||||
* Utilities for the solidity compiler.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include <numeric>
|
||||
#include <libsolidity/AST.h>
|
||||
#include <libsolidity/Compiler.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace dev {
|
||||
namespace solidity {
|
||||
|
||||
void CompilerContext::initializeLocalVariables(unsigned _numVariables)
|
||||
{
|
||||
if (_numVariables > 0)
|
||||
{
|
||||
*this << u256(0);
|
||||
for (unsigned i = 1; i < _numVariables; ++i)
|
||||
*this << eth::Instruction::DUP1;
|
||||
m_asm.adjustDeposit(-_numVariables);
|
||||
}
|
||||
}
|
||||
|
||||
int CompilerContext::getStackPositionOfVariable(const Declaration& _declaration)
|
||||
{
|
||||
auto res = find(begin(m_localVariables), end(m_localVariables), &_declaration);
|
||||
assert(res != m_localVariables.end());
|
||||
return end(m_localVariables) - res - 1 + m_asm.deposit();
|
||||
}
|
||||
|
||||
eth::AssemblyItem CompilerContext::getFunctionEntryLabel(const FunctionDefinition& _function) const
|
||||
{
|
||||
auto res = m_functionEntryLabels.find(&_function);
|
||||
assert(res != m_functionEntryLabels.end());
|
||||
return res->second.tag();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
83
CompilerUtilities.h
Normal file
83
CompilerUtilities.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
|
||||
cpp-ethereum 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.
|
||||
|
||||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @author Christian <c@ethdev.com>
|
||||
* @date 2014
|
||||
* Utilities for the solidity compiler.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libevmface/Instruction.h>
|
||||
#include <liblll/Assembly.h>
|
||||
#include <libsolidity/Types.h>
|
||||
|
||||
namespace dev {
|
||||
namespace solidity {
|
||||
|
||||
|
||||
/**
|
||||
* Context to be shared by all units that compile the same contract.
|
||||
* It stores the generated bytecode and the position of identifiers in memory and on the stack.
|
||||
*/
|
||||
class CompilerContext
|
||||
{
|
||||
public:
|
||||
CompilerContext() {}
|
||||
|
||||
void startNewFunction() { m_localVariables.clear(); m_asm.setDeposit(0); }
|
||||
void initializeLocalVariables(unsigned _numVariables);
|
||||
void addVariable(VariableDeclaration const& _declaration) { m_localVariables.push_back(&_declaration); }
|
||||
/// Returns the distance of the given local variable from the top of the stack.
|
||||
int getStackPositionOfVariable(Declaration const& _declaration);
|
||||
|
||||
void addFunction(FunctionDefinition const& _function) { m_functionEntryLabels.insert(std::make_pair(&_function, m_asm.newTag())); }
|
||||
eth::AssemblyItem getFunctionEntryLabel(FunctionDefinition const& _function) const;
|
||||
|
||||
void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); }
|
||||
|
||||
/// Appends a JUMPI instruction to a new tag and @returns the tag
|
||||
eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); }
|
||||
/// Appends a JUMPI instruction to @a _tag
|
||||
CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJumpI(_tag); return *this; }
|
||||
/// Appends a JUMP to a new tag and @returns the tag
|
||||
eth::AssemblyItem appendJump() { return m_asm.appendJump().tag(); }
|
||||
/// Appends a JUMP to a specific tag
|
||||
CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; }
|
||||
/// Appends pushing of a new tag and @returns the new tag.
|
||||
eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); }
|
||||
/// @returns a new tag without pushing any opcodes or data
|
||||
eth::AssemblyItem newTag() { return m_asm.newTag(); }
|
||||
|
||||
/// Append elements to the current instruction list and adjust @a m_stackOffset.
|
||||
CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; }
|
||||
CompilerContext& operator<<(eth::Instruction _instruction) { m_asm.append(_instruction); return *this; }
|
||||
CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; }
|
||||
CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; }
|
||||
|
||||
bytes getAssembledBytecode() { return m_asm.assemble(); }
|
||||
private:
|
||||
eth::Assembly m_asm;
|
||||
|
||||
/// Offsets of local variables on the stack.
|
||||
std::vector<Declaration const*> m_localVariables;
|
||||
/// Labels pointing to the entry points of funcitons.
|
||||
std::map<FunctionDefinition const*, eth::AssemblyItem> m_functionEntryLabels;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
408
ExpressionCompiler.cpp
Normal file
408
ExpressionCompiler.cpp
Normal file
@ -0,0 +1,408 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
|
||||
cpp-ethereum 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.
|
||||
|
||||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @author Christian <c@ethdev.com>
|
||||
* @date 2014
|
||||
* Solidity AST to EVM bytecode compiler for expressions.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include <numeric>
|
||||
#include <libsolidity/AST.h>
|
||||
#include <libsolidity/ExpressionCompiler.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace dev {
|
||||
namespace solidity {
|
||||
|
||||
void ExpressionCompiler::compileExpression(CompilerContext& _context, Expression& _expression)
|
||||
{
|
||||
ExpressionCompiler compiler(_context);
|
||||
_expression.accept(compiler);
|
||||
}
|
||||
|
||||
bool ExpressionCompiler::visit(Assignment& _assignment)
|
||||
{
|
||||
m_currentLValue = nullptr;
|
||||
|
||||
Expression& rightHandSide = _assignment.getRightHandSide();
|
||||
rightHandSide.accept(*this);
|
||||
Type const& resultType = *_assignment.getType();
|
||||
cleanHigherOrderBitsIfNeeded(*rightHandSide.getType(), resultType);
|
||||
_assignment.getLeftHandSide().accept(*this);
|
||||
|
||||
Token::Value op = _assignment.getAssignmentOperator();
|
||||
if (op != Token::ASSIGN)
|
||||
{
|
||||
// compound assignment
|
||||
m_context << eth::Instruction::SWAP1;
|
||||
appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), resultType);
|
||||
}
|
||||
else
|
||||
m_context << eth::Instruction::POP; //@todo do not retrieve the value in the first place
|
||||
|
||||
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: // !
|
||||
m_context << eth::Instruction::NOT;
|
||||
break;
|
||||
case Token::BIT_NOT: // ~
|
||||
m_context << eth::Instruction::BNOT;
|
||||
break;
|
||||
case Token::DELETE: // delete
|
||||
{
|
||||
// a -> a xor a (= 0).
|
||||
// @todo semantics change for complex types
|
||||
m_context << eth::Instruction::DUP1 << eth::Instruction::XOR;
|
||||
storeInLValue(_unaryOperation);
|
||||
break;
|
||||
}
|
||||
case Token::INC: // ++ (pre- or postfix)
|
||||
case Token::DEC: // -- (pre- or postfix)
|
||||
if (!_unaryOperation.isPrefixOperation())
|
||||
m_context << eth::Instruction::DUP1;
|
||||
m_context << u256(1);
|
||||
if (_unaryOperation.getOperator() == Token::INC)
|
||||
m_context << eth::Instruction::ADD;
|
||||
else
|
||||
m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; // @todo avoid the swap
|
||||
if (_unaryOperation.isPrefixOperation())
|
||||
storeInLValue(_unaryOperation);
|
||||
else
|
||||
moveToLValue(_unaryOperation);
|
||||
break;
|
||||
case Token::ADD: // +
|
||||
// unary add, so basically no-op
|
||||
break;
|
||||
case Token::SUB: // -
|
||||
m_context << u256(0) << eth::Instruction::SUB;
|
||||
break;
|
||||
default:
|
||||
assert(false); // invalid operation
|
||||
}
|
||||
}
|
||||
|
||||
bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation)
|
||||
{
|
||||
Expression& leftExpression = _binaryOperation.getLeftExpression();
|
||||
Expression& rightExpression = _binaryOperation.getRightExpression();
|
||||
Type const& resultType = *_binaryOperation.getType();
|
||||
Token::Value const op = _binaryOperation.getOperator();
|
||||
|
||||
if (op == Token::AND || op == Token::OR)
|
||||
{
|
||||
// special case: short-circuiting
|
||||
appendAndOrOperatorCode(_binaryOperation);
|
||||
}
|
||||
else if (Token::isCompareOp(op))
|
||||
{
|
||||
leftExpression.accept(*this);
|
||||
rightExpression.accept(*this);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// do not visit the child nodes, we already did that explicitly
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExpressionCompiler::visit(FunctionCall& _functionCall)
|
||||
{
|
||||
if (_functionCall.isTypeConversion())
|
||||
{
|
||||
//@todo we only have integers and bools for now which cannot be explicitly converted
|
||||
assert(_functionCall.getArguments().size() == 1);
|
||||
Expression& firstArgument = *_functionCall.getArguments().front();
|
||||
firstArgument.accept(*this);
|
||||
cleanHigherOrderBitsIfNeeded(*firstArgument.getType(), *_functionCall.getType());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Calling convention: Caller pushes return address and arguments
|
||||
// Callee removes them and pushes return values
|
||||
m_currentLValue = nullptr;
|
||||
_functionCall.getExpression().accept(*this);
|
||||
FunctionDefinition const* function = dynamic_cast<FunctionDefinition*>(m_currentLValue);
|
||||
assert(function);
|
||||
|
||||
eth::AssemblyItem returnLabel = m_context.pushNewTag();
|
||||
std::vector<ASTPointer<Expression>> const& arguments = _functionCall.getArguments();
|
||||
assert(arguments.size() == function->getParameters().size());
|
||||
for (unsigned i = 0; i < arguments.size(); ++i)
|
||||
{
|
||||
arguments[i]->accept(*this);
|
||||
cleanHigherOrderBitsIfNeeded(*arguments[i]->getType(),
|
||||
*function->getParameters()[i]->getType());
|
||||
}
|
||||
|
||||
m_context.appendJumpTo(m_context.getFunctionEntryLabel(*function));
|
||||
m_context << returnLabel;
|
||||
|
||||
// callee adds return parameters, but removes arguments and return label
|
||||
m_context.adjustStackOffset(function->getReturnParameters().size() - arguments.size() - 1);
|
||||
|
||||
// @todo for now, the return value of a function is its first return value, so remove
|
||||
// all others
|
||||
for (unsigned i = 1; i < function->getReturnParameters().size(); ++i)
|
||||
m_context << eth::Instruction::POP;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExpressionCompiler::endVisit(MemberAccess&)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ExpressionCompiler::endVisit(IndexAccess&)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ExpressionCompiler::endVisit(Identifier& _identifier)
|
||||
{
|
||||
m_currentLValue = _identifier.getReferencedDeclaration();
|
||||
switch (_identifier.getType()->getCategory())
|
||||
{
|
||||
case Type::Category::BOOL:
|
||||
case Type::Category::INTEGER:
|
||||
case Type::Category::REAL:
|
||||
{
|
||||
//@todo we also have to check where to retrieve them from once we add storage variables
|
||||
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."));
|
||||
m_context << eth::dupInstruction(stackPos + 1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionCompiler::endVisit(Literal& _literal)
|
||||
{
|
||||
switch (_literal.getType()->getCategory())
|
||||
{
|
||||
case Type::Category::INTEGER:
|
||||
case Type::Category::BOOL:
|
||||
m_context << _literal.getType()->literalValue(_literal);
|
||||
break;
|
||||
default:
|
||||
assert(false); // @todo
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionCompiler::cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _targetType)
|
||||
{
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation)
|
||||
{
|
||||
Token::Value const op = _binaryOperation.getOperator();
|
||||
assert(op == Token::OR || op == Token::AND);
|
||||
|
||||
_binaryOperation.getLeftExpression().accept(*this);
|
||||
m_context << eth::Instruction::DUP1;
|
||||
if (op == Token::AND)
|
||||
m_context << eth::Instruction::NOT;
|
||||
eth::AssemblyItem endLabel = m_context.appendConditionalJump();
|
||||
_binaryOperation.getRightExpression().accept(*this);
|
||||
m_context << endLabel;
|
||||
}
|
||||
|
||||
void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type)
|
||||
{
|
||||
if (_operator == Token::EQ || _operator == Token::NE)
|
||||
{
|
||||
m_context << eth::Instruction::EQ;
|
||||
if (_operator == Token::NE)
|
||||
m_context << eth::Instruction::NOT;
|
||||
}
|
||||
else
|
||||
{
|
||||
IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
|
||||
assert(type);
|
||||
bool const isSigned = type->isSigned();
|
||||
|
||||
// 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:
|
||||
m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT)
|
||||
<< eth::Instruction::NOT;
|
||||
break;
|
||||
case Token::LTE:
|
||||
m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT)
|
||||
<< eth::Instruction::NOT;
|
||||
break;
|
||||
case Token::GT:
|
||||
m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT);
|
||||
break;
|
||||
case Token::LT:
|
||||
m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
|
||||
{
|
||||
IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
|
||||
assert(type);
|
||||
bool const isSigned = type->isSigned();
|
||||
|
||||
switch (_operator)
|
||||
{
|
||||
case Token::ADD:
|
||||
m_context << eth::Instruction::ADD;
|
||||
break;
|
||||
case Token::SUB:
|
||||
m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB;
|
||||
break;
|
||||
case Token::MUL:
|
||||
m_context << eth::Instruction::MUL;
|
||||
break;
|
||||
case Token::DIV:
|
||||
m_context << (isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV);
|
||||
break;
|
||||
case Token::MOD:
|
||||
m_context << (isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator)
|
||||
{
|
||||
switch (_operator)
|
||||
{
|
||||
case Token::BIT_OR:
|
||||
m_context << eth::Instruction::OR;
|
||||
break;
|
||||
case Token::BIT_AND:
|
||||
m_context << eth::Instruction::AND;
|
||||
break;
|
||||
case Token::BIT_XOR:
|
||||
m_context << eth::Instruction::XOR;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator)
|
||||
{
|
||||
switch (_operator)
|
||||
{
|
||||
case Token::SHL:
|
||||
assert(false); //@todo
|
||||
break;
|
||||
case Token::SAR:
|
||||
assert(false); //@todo
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionCompiler::storeInLValue(Expression const& _expression)
|
||||
{
|
||||
moveToLValue(_expression);
|
||||
unsigned stackPos = stackPositionOfLValue();
|
||||
if (stackPos > 16)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation())
|
||||
<< errinfo_comment("Stack too deep."));
|
||||
m_context << eth::dupInstruction(stackPos + 1);
|
||||
}
|
||||
|
||||
void ExpressionCompiler::moveToLValue(Expression const& _expression)
|
||||
{
|
||||
unsigned stackPos = stackPositionOfLValue();
|
||||
if (stackPos > 16)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation())
|
||||
<< errinfo_comment("Stack too deep."));
|
||||
else if (stackPos > 0)
|
||||
m_context << eth::swapInstruction(stackPos) << eth::Instruction::POP;
|
||||
}
|
||||
|
||||
unsigned ExpressionCompiler::stackPositionOfLValue() const
|
||||
{
|
||||
assert(m_currentLValue);
|
||||
return m_context.getStackPositionOfVariable(*m_currentLValue);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
79
ExpressionCompiler.h
Normal file
79
ExpressionCompiler.h
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
|
||||
cpp-ethereum 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.
|
||||
|
||||
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @author Christian <c@ethdev.com>
|
||||
* @date 2014
|
||||
* Solidity AST to EVM bytecode compiler for expressions.
|
||||
*/
|
||||
|
||||
#include <libsolidity/ASTVisitor.h>
|
||||
#include <libsolidity/CompilerUtilities.h>
|
||||
|
||||
namespace dev {
|
||||
namespace solidity {
|
||||
|
||||
/// Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream
|
||||
/// of EVM instructions. It needs a compiler context that is the same for the whole compilation
|
||||
/// unit.
|
||||
class ExpressionCompiler: private ASTVisitor
|
||||
{
|
||||
public:
|
||||
/// Compile the given @a _expression into the @a _context.
|
||||
static void compileExpression(CompilerContext& _context, Expression& _expression);
|
||||
|
||||
/// Appends code to remove dirty higher order bits in case of an implicit promotion to a wider type.
|
||||
static void cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _targetType);
|
||||
|
||||
private:
|
||||
ExpressionCompiler(CompilerContext& _compilerContext): m_currentLValue(nullptr), m_context(_compilerContext) {}
|
||||
|
||||
virtual bool visit(Assignment& _assignment) override;
|
||||
virtual void endVisit(UnaryOperation& _unaryOperation) override;
|
||||
virtual bool visit(BinaryOperation& _binaryOperation) override;
|
||||
virtual bool visit(FunctionCall& _functionCall) override;
|
||||
virtual void endVisit(MemberAccess& _memberAccess) override;
|
||||
virtual void endVisit(IndexAccess& _indexAccess) override;
|
||||
virtual void endVisit(Identifier& _identifier) override;
|
||||
virtual void endVisit(Literal& _literal) override;
|
||||
|
||||
///@{
|
||||
///@name Append code for various operator types
|
||||
void appendAndOrOperatorCode(BinaryOperation& _binaryOperation);
|
||||
void appendCompareOperatorCode(Token::Value _operator, Type const& _type);
|
||||
void appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type);
|
||||
|
||||
void appendArithmeticOperatorCode(Token::Value _operator, Type const& _type);
|
||||
void appendBitOperatorCode(Token::Value _operator);
|
||||
void appendShiftOperatorCode(Token::Value _operator);
|
||||
/// @}
|
||||
|
||||
/// Stores the value on top of the stack in the current lvalue and copies that value to the
|
||||
/// top of the stack again
|
||||
void storeInLValue(Expression const& _expression);
|
||||
/// The same as storeInLValue but do not again retrieve the value to the top of the stack.
|
||||
void moveToLValue(Expression const& _expression);
|
||||
/// Returns the position of @a m_currentLValue in the stack, where 0 is the top of the stack.
|
||||
unsigned stackPositionOfLValue() const;
|
||||
void adjustStackOffset(eth::Instruction _instruction);
|
||||
|
||||
Declaration* m_currentLValue;
|
||||
CompilerContext& m_context;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -55,6 +55,20 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
|
||||
m_currentScope = &m_scopes[function.get()];
|
||||
function->getBody().checkTypeRequirements();
|
||||
}
|
||||
m_currentScope = &m_scopes[nullptr];
|
||||
}
|
||||
|
||||
Declaration* NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const
|
||||
{
|
||||
auto iterator = m_scopes.find(_scope);
|
||||
if (iterator == end(m_scopes))
|
||||
return nullptr;
|
||||
return iterator->second.resolveName(_name, false);
|
||||
}
|
||||
|
||||
Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive)
|
||||
{
|
||||
return m_currentScope->resolveName(_name, _recursive);
|
||||
}
|
||||
|
||||
void NameAndTypeResolver::reset()
|
||||
@ -63,13 +77,7 @@ void NameAndTypeResolver::reset()
|
||||
m_currentScope = nullptr;
|
||||
}
|
||||
|
||||
Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive)
|
||||
{
|
||||
return m_currentScope->resolveName(_name, _recursive);
|
||||
}
|
||||
|
||||
|
||||
DeclarationRegistrationHelper::DeclarationRegistrationHelper(map<ASTNode*, Scope>& _scopes,
|
||||
DeclarationRegistrationHelper::DeclarationRegistrationHelper(map<ASTNode const*, Scope>& _scopes,
|
||||
ASTNode& _astRoot):
|
||||
m_scopes(_scopes), m_currentScope(&m_scopes[nullptr])
|
||||
{
|
||||
@ -101,27 +109,33 @@ void DeclarationRegistrationHelper::endVisit(StructDefinition&)
|
||||
bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function)
|
||||
{
|
||||
registerDeclaration(_function, true);
|
||||
m_currentFunction = &_function;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(FunctionDefinition&)
|
||||
{
|
||||
m_currentFunction = nullptr;
|
||||
closeCurrentScope();
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(VariableDefinition& _variableDefinition)
|
||||
{
|
||||
// Register the local variables with the function
|
||||
// This does not fit here perfectly, but it saves us another AST visit.
|
||||
assert(m_currentFunction);
|
||||
m_currentFunction->addLocalVariable(_variableDefinition.getDeclaration());
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration)
|
||||
{
|
||||
registerDeclaration(_declaration, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(VariableDeclaration&)
|
||||
{
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _node)
|
||||
{
|
||||
map<ASTNode*, Scope>::iterator iter;
|
||||
map<ASTNode const*, Scope>::iterator iter;
|
||||
bool newlyAdded;
|
||||
tie(iter, newlyAdded) = m_scopes.emplace(&_node, Scope(m_currentScope));
|
||||
assert(newlyAdded);
|
||||
|
@ -41,6 +41,14 @@ public:
|
||||
NameAndTypeResolver() {}
|
||||
|
||||
void resolveNamesAndTypes(ContractDefinition& _contract);
|
||||
|
||||
/// Resolves the given @a _name inside the scope @a _scope. If @a _scope is omitted,
|
||||
/// the global scope is used (i.e. the one containing only the contract).
|
||||
/// @returns a pointer to the declaration on success or nullptr on failure.
|
||||
Declaration* resolveName(ASTString const& _name, Declaration const* _scope = nullptr) const;
|
||||
|
||||
/// Resolves a name in the "current" scope. Should only be called during the initial
|
||||
/// resolving phase.
|
||||
Declaration* getNameFromCurrentScope(ASTString const& _name, bool _recursive = true);
|
||||
|
||||
private:
|
||||
@ -48,7 +56,7 @@ private:
|
||||
|
||||
//! Maps nodes declaring a scope to scopes, i.e. ContractDefinition, FunctionDeclaration and
|
||||
//! StructDefinition (@todo not yet implemented), where nullptr denotes the global scope.
|
||||
std::map<ASTNode*, Scope> m_scopes;
|
||||
std::map<ASTNode const*, Scope> m_scopes;
|
||||
|
||||
Scope* m_currentScope;
|
||||
};
|
||||
@ -58,7 +66,7 @@ private:
|
||||
class DeclarationRegistrationHelper: private ASTVisitor
|
||||
{
|
||||
public:
|
||||
DeclarationRegistrationHelper(std::map<ASTNode*, Scope>& _scopes, ASTNode& _astRoot);
|
||||
DeclarationRegistrationHelper(std::map<ASTNode const*, Scope>& _scopes, ASTNode& _astRoot);
|
||||
|
||||
private:
|
||||
bool visit(ContractDefinition& _contract);
|
||||
@ -67,15 +75,16 @@ private:
|
||||
void endVisit(StructDefinition& _struct);
|
||||
bool visit(FunctionDefinition& _function);
|
||||
void endVisit(FunctionDefinition& _function);
|
||||
void endVisit(VariableDefinition& _variableDefinition);
|
||||
bool visit(VariableDeclaration& _declaration);
|
||||
void endVisit(VariableDeclaration& _declaration);
|
||||
|
||||
void enterNewSubScope(ASTNode& _node);
|
||||
void closeCurrentScope();
|
||||
void registerDeclaration(Declaration& _declaration, bool _opensScope);
|
||||
|
||||
std::map<ASTNode*, Scope>& m_scopes;
|
||||
std::map<ASTNode const*, Scope>& m_scopes;
|
||||
Scope* m_currentScope;
|
||||
FunctionDefinition* m_currentFunction;
|
||||
};
|
||||
|
||||
//! Resolves references to declarations (of variables and types) and also establishes the link
|
||||
|
21
Parser.cpp
21
Parser.cpp
@ -266,9 +266,11 @@ ASTPointer<Statement> Parser::parseStatement()
|
||||
// starting from here, all statements must be terminated by a semicolon
|
||||
case Token::CONTINUE:
|
||||
statement = ASTNodeFactory(*this).createNode<Continue>();
|
||||
m_scanner->next();
|
||||
break;
|
||||
case Token::BREAK:
|
||||
statement = ASTNodeFactory(*this).createNode<Break>();
|
||||
m_scanner->next();
|
||||
break;
|
||||
case Token::RETURN:
|
||||
{
|
||||
@ -283,9 +285,9 @@ ASTPointer<Statement> Parser::parseStatement()
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// distinguish between variable definition (and potentially assignment) and expressions
|
||||
// distinguish between variable definition (and potentially assignment) and expression statement
|
||||
// (which include assignments to other expressions and pre-declared variables)
|
||||
// We have a variable definition if we ge a keyword that specifies a type name, or
|
||||
// We have a variable definition if we get a keyword that specifies a type name, or
|
||||
// in the case of a user-defined type, we have two identifiers following each other.
|
||||
if (m_scanner->getCurrentToken() == Token::MAPPING ||
|
||||
m_scanner->getCurrentToken() == Token::VAR ||
|
||||
@ -293,8 +295,8 @@ ASTPointer<Statement> Parser::parseStatement()
|
||||
(m_scanner->getCurrentToken() == Token::IDENTIFIER &&
|
||||
m_scanner->peekNextToken() == Token::IDENTIFIER))
|
||||
statement = parseVariableDefinition();
|
||||
else // "ordinary" expression
|
||||
statement = parseExpression();
|
||||
else // "ordinary" expression statement
|
||||
statement = parseExpressionStatement();
|
||||
}
|
||||
expectToken(Token::SEMICOLON);
|
||||
return statement;
|
||||
@ -349,6 +351,14 @@ ASTPointer<VariableDefinition> Parser::parseVariableDefinition()
|
||||
return nodeFactory.createNode<VariableDefinition>(variable, value);
|
||||
}
|
||||
|
||||
ASTPointer<ExpressionStatement> Parser::parseExpressionStatement()
|
||||
{
|
||||
ASTNodeFactory nodeFactory(*this);
|
||||
ASTPointer<Expression> expression = parseExpression();
|
||||
nodeFactory.setEndPositionFromNode(expression);
|
||||
return nodeFactory.createNode<ExpressionStatement>(expression);
|
||||
}
|
||||
|
||||
ASTPointer<Expression> Parser::parseExpression()
|
||||
{
|
||||
ASTNodeFactory nodeFactory(*this);
|
||||
@ -453,8 +463,7 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
|
||||
{
|
||||
case Token::TRUE_LITERAL:
|
||||
case Token::FALSE_LITERAL:
|
||||
expression = nodeFactory.createNode<Literal>(token, ASTPointer<ASTString>());
|
||||
m_scanner->next();
|
||||
expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance());
|
||||
break;
|
||||
case Token::NUMBER:
|
||||
case Token::STRING_LITERAL:
|
||||
|
1
Parser.h
1
Parser.h
@ -58,6 +58,7 @@ private:
|
||||
ASTPointer<IfStatement> parseIfStatement();
|
||||
ASTPointer<WhileStatement> parseWhileStatement();
|
||||
ASTPointer<VariableDefinition> parseVariableDefinition();
|
||||
ASTPointer<ExpressionStatement> parseExpressionStatement();
|
||||
ASTPointer<Expression> parseExpression();
|
||||
ASTPointer<Expression> parseBinaryExpression(int _minPrecedence = 4);
|
||||
ASTPointer<Expression> parseUnaryExpression();
|
||||
|
18
Types.cpp
18
Types.cpp
@ -159,14 +159,12 @@ std::string IntegerType::toString() const
|
||||
return prefix + dev::toString(m_bits);
|
||||
}
|
||||
|
||||
bytes IntegerType::literalToBigEndian(const Literal& _literal) const
|
||||
u256 IntegerType::literalValue(const Literal& _literal) const
|
||||
{
|
||||
bigint value(_literal.getValue());
|
||||
if (!isSigned() && value < 0)
|
||||
return bytes(); // @todo this should already be caught by "smallestTypeforLiteral"
|
||||
//@todo check that the number of bits is correct
|
||||
//@todo does "toCompactBigEndian" work for signed numbers?
|
||||
return toCompactBigEndian(value);
|
||||
//@todo check that the number is not too large
|
||||
//@todo does this work for signed numbers?
|
||||
return u256(value);
|
||||
}
|
||||
|
||||
bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
@ -182,14 +180,14 @@ bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
return isImplicitlyConvertibleTo(_convertTo);
|
||||
}
|
||||
|
||||
bytes BoolType::literalToBigEndian(const Literal& _literal) const
|
||||
u256 BoolType::literalValue(const Literal& _literal) const
|
||||
{
|
||||
if (_literal.getToken() == Token::TRUE_LITERAL)
|
||||
return bytes(1, 1);
|
||||
return u256(1);
|
||||
else if (_literal.getToken() == Token::FALSE_LITERAL)
|
||||
return bytes(1, 0);
|
||||
return u256(0);
|
||||
else
|
||||
return NullBytes;
|
||||
assert(false);
|
||||
}
|
||||
|
||||
bool ContractType::operator==(const Type& _other) const
|
||||
|
6
Types.h
6
Types.h
@ -69,7 +69,7 @@ public:
|
||||
virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); }
|
||||
|
||||
virtual std::string toString() const = 0;
|
||||
virtual bytes literalToBigEndian(Literal const&) const { return NullBytes; }
|
||||
virtual u256 literalValue(Literal const&) const { assert(false); }
|
||||
};
|
||||
|
||||
/// Any kind of integer type including hash and address.
|
||||
@ -94,7 +94,7 @@ public:
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
|
||||
virtual std::string toString() const override;
|
||||
virtual bytes literalToBigEndian(Literal const& _literal) const override;
|
||||
virtual u256 literalValue(Literal const& _literal) const override;
|
||||
|
||||
int getNumBits() const { return m_bits; }
|
||||
bool isHash() const { return m_modifier == Modifier::HASH || m_modifier == Modifier::ADDRESS; }
|
||||
@ -122,7 +122,7 @@ public:
|
||||
}
|
||||
|
||||
virtual std::string toString() const override { return "bool"; }
|
||||
virtual bytes literalToBigEndian(Literal const& _literal) const override;
|
||||
virtual u256 literalValue(Literal const& _literal) const override;
|
||||
};
|
||||
|
||||
/// The type of a contract instance, there is one distinct type for each contract definition.
|
||||
|
Loading…
Reference in New Issue
Block a user