diff options
-rw-r--r-- | Compiler.cpp | 13 | ||||
-rw-r--r-- | ExpressionCompiler.cpp | 634 | ||||
-rw-r--r-- | ExpressionCompiler.h | 108 | ||||
-rw-r--r-- | LValue.cpp | 220 | ||||
-rw-r--r-- | LValue.h | 112 |
5 files changed, 554 insertions, 533 deletions
diff --git a/Compiler.cpp b/Compiler.cpp index 23591d1a..2f75d2ea 100644 --- a/Compiler.cpp +++ b/Compiler.cpp @@ -250,7 +250,7 @@ void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters) for (TypePointer const& type: _typeParameters) { CompilerUtils(m_context).copyToStackTop(stackDepth, *type); - ExpressionCompiler::appendTypeConversion(m_context, *type, *type, true); + ExpressionCompiler(m_context, m_optimize).appendTypeConversion(*type, *type, true); bool const c_padToWords = true; dataOffset += CompilerUtils(m_context).storeInMemory(dataOffset, *type, c_padToWords); stackDepth -= type->getSizeOnStack(); @@ -270,7 +270,7 @@ void Compiler::initializeStateVariables(ContractDefinition const& _contract) { for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables()) if (variable->getValue()) - ExpressionCompiler::appendStateVariableInitialization(m_context, *variable); + ExpressionCompiler(m_context, m_optimize).appendStateVariableInitialization(*variable); } bool Compiler::visit(VariableDeclaration const& _variableDeclaration) @@ -283,7 +283,7 @@ bool Compiler::visit(VariableDeclaration const& _variableDeclaration) m_continueTags.clear(); m_context << m_context.getFunctionEntryLabel(_variableDeclaration); - ExpressionCompiler::appendStateVariableAccessor(m_context, _variableDeclaration); + ExpressionCompiler(m_context, m_optimize).appendStateVariableAccessor(_variableDeclaration); return false; } @@ -475,7 +475,7 @@ bool Compiler::visit(Return const& _return) bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_variableDeclarationStatement); + CompilerContext::LocationSetter locationSetter(m_context, &_variableDeclarationStatement); if (Expression const* expression = _variableDeclarationStatement.getExpression()) { compileExpression(*expression, _variableDeclarationStatement.getDeclaration().getType()); @@ -541,9 +541,10 @@ void Compiler::appendModifierOrFunctionCode() void Compiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) { - ExpressionCompiler::compileExpression(m_context, _expression, m_optimize); + ExpressionCompiler expressionCompiler(m_context, m_optimize); + expressionCompiler.compile(_expression); if (_targetType) - ExpressionCompiler::appendTypeConversion(m_context, *_expression.getType(), *_targetType); + expressionCompiler.appendTypeConversion(*_expression.getType(), *_targetType); } } diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp index 94c71fc0..6b9b5167 100644 --- a/ExpressionCompiler.cpp +++ b/ExpressionCompiler.cpp @@ -29,6 +29,7 @@ #include <libsolidity/ExpressionCompiler.h> #include <libsolidity/CompilerContext.h> #include <libsolidity/CompilerUtils.h> +#include <libsolidity/LValue.h> using namespace std; @@ -37,41 +38,167 @@ namespace dev namespace solidity { -void ExpressionCompiler::compileExpression(CompilerContext& _context, Expression const& _expression, bool _optimize) +void ExpressionCompiler::compile(Expression const& _expression) { - ExpressionCompiler compiler(_context, _optimize); - _expression.accept(compiler); + _expression.accept(*this); } -void ExpressionCompiler::appendTypeConversion(CompilerContext& _context, Type const& _typeOnStack, - Type const& _targetType, bool _cleanupNeeded) +void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration const& _varDecl) { - ExpressionCompiler compiler(_context); - compiler.appendTypeConversion(_typeOnStack, _targetType, _cleanupNeeded); -} + if (!_varDecl.getValue()) + return; + solAssert(!!_varDecl.getValue()->getType(), "Type information not available."); + CompilerContext::LocationSetter locationSetter(m_context, &_varDecl); + _varDecl.getValue()->accept(*this); + appendTypeConversion(*_varDecl.getValue()->getType(), *_varDecl.getType(), true); -void ExpressionCompiler::appendStateVariableAccessor(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize) -{ - ExpressionCompiler compiler(_context, _optimize); - compiler.appendStateVariableAccessor(_varDecl); + StorageItem(m_context, _varDecl).storeValue(*_varDecl.getType(), _varDecl.getLocation(), true); } -void ExpressionCompiler::appendStateVariableInitialization(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize) +void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) { - compileExpression(_context, *(_varDecl.getValue()), _optimize); - if (_varDecl.getValue()->getType()) - appendTypeConversion(_context, *(_varDecl.getValue())->getType(), *(_varDecl.getValue())->getType()); + CompilerContext::LocationSetter locationSetter(m_context, &_varDecl); + FunctionType accessorType(_varDecl); + + unsigned length = 0; + TypePointers const& paramTypes = accessorType.getParameterTypes(); + // move arguments to memory + for (TypePointer const& paramType: boost::adaptors::reverse(paramTypes)) + length += CompilerUtils(m_context).storeInMemory(length, *paramType, true); + + // retrieve the position of the variable + m_context << m_context.getStorageLocationOfVariable(_varDecl); + TypePointer returnType = _varDecl.getType(); + + for (TypePointer const& paramType: paramTypes) + { + // move offset to memory + CompilerUtils(m_context).storeInMemory(length); + unsigned argLen = CompilerUtils::getPaddedSize(paramType->getCalldataEncodedSize()); + length -= argLen; + m_context << u256(argLen + 32) << u256(length) << eth::Instruction::SHA3; + + returnType = dynamic_cast<MappingType const&>(*returnType).getValueType(); + } - ExpressionCompiler compiler(_context, _optimize); - compiler.appendStateVariableInitialization(_varDecl); + unsigned retSizeOnStack = 0; + solAssert(accessorType.getReturnParameterTypes().size() >= 1, ""); + if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get())) + { + auto const& names = accessorType.getReturnParameterNames(); + auto const& types = accessorType.getReturnParameterTypes(); + // struct + for (size_t i = 0; i < names.size(); ++i) + { + m_context << eth::Instruction::DUP1 + << structType->getStorageOffsetOfMember(names[i]) + << eth::Instruction::ADD; + StorageItem(m_context, types[i]).retrieveValue(SourceLocation(), true); + solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); + m_context << eth::Instruction::SWAP1; + retSizeOnStack += types[i]->getSizeOnStack(); + } + m_context << eth::Instruction::POP; + } + else + { + // simple value + solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); + StorageItem(m_context, returnType).retrieveValue(SourceLocation(), true); + retSizeOnStack = returnType->getSizeOnStack(); + } + solAssert(retSizeOnStack <= 15, "Stack too deep."); + m_context << eth::dupInstruction(retSizeOnStack + 1) << eth::Instruction::JUMP; } -void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration const& _varDecl) +void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) { - CompilerContext::LocationSetter locationSetter(m_context, &_varDecl); - LValue var = LValue(m_context); - var.fromDeclaration(_varDecl, _varDecl.getValue()->getLocation()); - var.storeValue(*_varDecl.getType(), _varDecl.getLocation()); + // For a type extension, 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 && !_cleanupNeeded) + return; + Type::Category stackTypeCategory = _typeOnStack.getCategory(); + Type::Category targetTypeCategory = _targetType.getCategory(); + + if (stackTypeCategory == Type::Category::String) + { + StaticStringType const& typeOnStack = dynamic_cast<StaticStringType const&>(_typeOnStack); + if (targetTypeCategory == Type::Category::Integer) + { + // conversion from string to hash. no need to clean the high bit + // only to shift right because of opposite alignment + IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType); + solAssert(targetIntegerType.isHash(), "Only conversion between String and Hash is allowed."); + solAssert(targetIntegerType.getNumBits() == typeOnStack.getNumBytes() * 8, "The size should be the same."); + m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + } + else + { + // clear lower-order bytes for conversion to shorter strings - we always clean + solAssert(targetTypeCategory == Type::Category::String, "Invalid type conversion requested."); + StaticStringType const& targetType = dynamic_cast<StaticStringType const&>(_targetType); + if (targetType.getNumBytes() < typeOnStack.getNumBytes()) + { + if (targetType.getNumBytes() == 0) + m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; + else + m_context << (u256(1) << (256 - targetType.getNumBytes() * 8)) + << eth::Instruction::DUP1 << eth::Instruction::SWAP2 + << eth::Instruction::DIV << eth::Instruction::MUL; + } + } + } + else if (stackTypeCategory == Type::Category::Enum) + solAssert(targetTypeCategory == Type::Category::Integer || + targetTypeCategory == Type::Category::Enum, ""); + else if (stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::Contract || + stackTypeCategory == Type::Category::IntegerConstant) + { + if (targetTypeCategory == Type::Category::String && stackTypeCategory == Type::Category::Integer) + { + // conversion from hash to string. no need to clean the high bit + // only to shift left because of opposite alignment + StaticStringType const& targetStringType = dynamic_cast<StaticStringType const&>(_targetType); + IntegerType const& typeOnStack = dynamic_cast<IntegerType const&>(_typeOnStack); + solAssert(typeOnStack.isHash(), "Only conversion between String and Hash is allowed."); + solAssert(typeOnStack.getNumBits() == targetStringType.getNumBytes() * 8, "The size should be the same."); + m_context << (u256(1) << (256 - typeOnStack.getNumBits())) << eth::Instruction::MUL; + } + else if (targetTypeCategory == Type::Category::Enum) + // just clean + appendTypeConversion(_typeOnStack, *_typeOnStack.getRealType(), true); + else + { + solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); + IntegerType addressType(0, IntegerType::Modifier::Address); + IntegerType const& targetType = targetTypeCategory == Type::Category::Integer + ? dynamic_cast<IntegerType const&>(_targetType) : addressType; + if (stackTypeCategory == Type::Category::IntegerConstant) + { + IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack); + // We know that the stack is clean, we only have to clean for a narrowing conversion + // where cleanup is forced. + if (targetType.getNumBits() < constType.getIntegerType()->getNumBits() && _cleanupNeeded) + appendHighBitsCleanup(targetType); + } + else + { + IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer + ? dynamic_cast<IntegerType const&>(_typeOnStack) : addressType; + // Widening: clean up according to source type width + // Non-widening and force: clean up according to target type bits + if (targetType.getNumBits() > typeOnStack.getNumBits()) + appendHighBitsCleanup(typeOnStack); + else if (_cleanupNeeded) + appendHighBitsCleanup(targetType); + } + } + } + else if (_typeOnStack != _targetType) + // All other types should not be convertible to non-equal types. + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid type conversion requested.")); } bool ExpressionCompiler::visit(Assignment const& _assignment) @@ -81,20 +208,20 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) if (_assignment.getType()->isValueType()) appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); _assignment.getLeftHandSide().accept(*this); - solAssert(m_currentLValue.isValid(), "LValue not retrieved."); + solAssert(!!m_currentLValue, "LValue not retrieved."); Token::Value op = _assignment.getAssignmentOperator(); if (op != Token::Assign) // compound assignment { solAssert(_assignment.getType()->isValueType(), "Compound operators not implemented for non-value types."); - if (m_currentLValue.storesReferenceOnStack()) + if (m_currentLValue->storesReferenceOnStack()) m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; - m_currentLValue.retrieveValue(_assignment.getLocation(), true); + m_currentLValue->retrieveValue(_assignment.getLocation(), true); appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType()); - if (m_currentLValue.storesReferenceOnStack()) + if (m_currentLValue->storesReferenceOnStack()) m_context << eth::Instruction::SWAP1; } - m_currentLValue.storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation()); + m_currentLValue->storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation()); m_currentLValue.reset(); return false; } @@ -123,17 +250,17 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) m_context << eth::Instruction::NOT; break; case Token::Delete: // delete - solAssert(m_currentLValue.isValid(), "LValue not retrieved."); - m_currentLValue.setToZero(_unaryOperation.getLocation()); + solAssert(!!m_currentLValue, "LValue not retrieved."); + m_currentLValue->setToZero(_unaryOperation.getLocation()); m_currentLValue.reset(); break; case Token::Inc: // ++ (pre- or postfix) case Token::Dec: // -- (pre- or postfix) - solAssert(m_currentLValue.isValid(), "LValue not retrieved."); - m_currentLValue.retrieveValue(_unaryOperation.getLocation()); + solAssert(!!m_currentLValue, "LValue not retrieved."); + m_currentLValue->retrieveValue(_unaryOperation.getLocation()); if (!_unaryOperation.isPrefixOperation()) { - if (m_currentLValue.storesReferenceOnStack()) + if (m_currentLValue->storesReferenceOnStack()) m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; else m_context << eth::Instruction::DUP1; @@ -145,10 +272,11 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; // @todo avoid the swap // Stack for prefix: [ref] (*ref)+-1 // Stack for postfix: *ref [ref] (*ref)+-1 - if (m_currentLValue.storesReferenceOnStack()) + if (m_currentLValue->storesReferenceOnStack()) m_context << eth::Instruction::SWAP1; - m_currentLValue.storeValue(*_unaryOperation.getType(), _unaryOperation.getLocation(), - !_unaryOperation.isPrefixOperation()); + m_currentLValue->storeValue( + *_unaryOperation.getType(), _unaryOperation.getLocation(), + !_unaryOperation.isPrefixOperation()); m_currentLValue.reset(); break; case Token::Add: // + @@ -179,7 +307,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) else { bool cleanupNeeded = commonType.getCategory() == Type::Category::Integer && - (Token::isCompareOp(c_op) || c_op == Token::Div || c_op == Token::Mod); + (Token::isCompareOp(c_op) || c_op == Token::Div || c_op == Token::Mod); // for commutative operators, push the literal as late as possible to allow improved optimization auto isLiteral = [](Expression const& _e) @@ -505,8 +633,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) { StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.getExpression().getType()); m_context << type.getStorageOffsetOfMember(member) << eth::Instruction::ADD; - m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _memberAccess.getType()); - m_currentLValue.retrieveValueIfLValueNotRequested(_memberAccess); + setLValueToStorageItem(_memberAccess); break; } case Type::Category::Enum: @@ -552,8 +679,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; break; case ArrayType::Location::Storage: - m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _memberAccess.getType()); - m_currentLValue.retrieveValueIfLValueNotRequested(_memberAccess); + setLValueToStorageItem(_memberAccess); break; default: solAssert(false, "Unsupported array location."); @@ -583,8 +709,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) m_context << eth::Instruction::SWAP1; appendTypeMoveToMemory(IntegerType(256)); m_context << u256(0) << eth::Instruction::SHA3; - m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _indexAccess.getType()); - m_currentLValue.retrieveValueIfLValueNotRequested(_indexAccess); + setLValueToStorageItem( _indexAccess); } else if (baseType.getCategory() == Type::Category::Array) { @@ -616,8 +741,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) CompilerUtils(m_context).computeHashStatic(); } m_context << eth::Instruction::ADD; - m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _indexAccess.getType()); - m_currentLValue.retrieveValueIfLValueNotRequested(_indexAccess); + setLValueToStorageItem(_indexAccess); } else solAssert(false, "Index access only allowed for mappings or arrays."); @@ -638,10 +762,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) m_context << m_context.getVirtualFunctionEntryLabel(*functionDef).pushTag(); else if (dynamic_cast<VariableDeclaration const*>(declaration)) - { - m_currentLValue.fromDeclaration(*declaration, _identifier.getLocation()); - m_currentLValue.retrieveValueIfLValueNotRequested(_identifier); - } + setLValueFromDeclaration(*declaration, _identifier); else if (dynamic_cast<ContractDefinition const*>(declaration)) { // no-op @@ -798,96 +919,6 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) } } -void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) -{ - // For a type extension, 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 && !_cleanupNeeded) - return; - Type::Category stackTypeCategory = _typeOnStack.getCategory(); - Type::Category targetTypeCategory = _targetType.getCategory(); - - if (stackTypeCategory == Type::Category::String) - { - StaticStringType const& typeOnStack = dynamic_cast<StaticStringType const&>(_typeOnStack); - if (targetTypeCategory == Type::Category::Integer) - { - // conversion from string to hash. no need to clean the high bit - // only to shift right because of opposite alignment - IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType); - solAssert(targetIntegerType.isHash(), "Only conversion between String and Hash is allowed."); - solAssert(targetIntegerType.getNumBits() == typeOnStack.getNumBytes() * 8, "The size should be the same."); - m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; - } - else - { - // clear lower-order bytes for conversion to shorter strings - we always clean - solAssert(targetTypeCategory == Type::Category::String, "Invalid type conversion requested."); - StaticStringType const& targetType = dynamic_cast<StaticStringType const&>(_targetType); - if (targetType.getNumBytes() < typeOnStack.getNumBytes()) - { - if (targetType.getNumBytes() == 0) - m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; - else - m_context << (u256(1) << (256 - targetType.getNumBytes() * 8)) - << eth::Instruction::DUP1 << eth::Instruction::SWAP2 - << eth::Instruction::DIV << eth::Instruction::MUL; - } - } - } - else if (stackTypeCategory == Type::Category::Enum) - solAssert(targetTypeCategory == Type::Category::Integer || - targetTypeCategory == Type::Category::Enum, ""); - else if (stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::Contract || - stackTypeCategory == Type::Category::IntegerConstant) - { - if (targetTypeCategory == Type::Category::String && stackTypeCategory == Type::Category::Integer) - { - // conversion from hash to string. no need to clean the high bit - // only to shift left because of opposite alignment - StaticStringType const& targetStringType = dynamic_cast<StaticStringType const&>(_targetType); - IntegerType const& typeOnStack = dynamic_cast<IntegerType const&>(_typeOnStack); - solAssert(typeOnStack.isHash(), "Only conversion between String and Hash is allowed."); - solAssert(typeOnStack.getNumBits() == targetStringType.getNumBytes() * 8, "The size should be the same."); - m_context << (u256(1) << (256 - typeOnStack.getNumBits())) << eth::Instruction::MUL; - } - else if (targetTypeCategory == Type::Category::Enum) - // just clean - appendTypeConversion(_typeOnStack, *_typeOnStack.getRealType(), true); - else - { - solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); - IntegerType addressType(0, IntegerType::Modifier::Address); - IntegerType const& targetType = targetTypeCategory == Type::Category::Integer - ? dynamic_cast<IntegerType const&>(_targetType) : addressType; - if (stackTypeCategory == Type::Category::IntegerConstant) - { - IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack); - // We know that the stack is clean, we only have to clean for a narrowing conversion - // where cleanup is forced. - if (targetType.getNumBits() < constType.getIntegerType()->getNumBits() && _cleanupNeeded) - appendHighBitsCleanup(targetType); - } - else - { - IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer - ? dynamic_cast<IntegerType const&>(_typeOnStack) : addressType; - // Widening: clean up according to source type width - // Non-widening and force: clean up according to target type bits - if (targetType.getNumBits() > typeOnStack.getNumBits()) - appendHighBitsCleanup(typeOnStack); - else if (_cleanupNeeded) - appendHighBitsCleanup(targetType); - } - } - } - else if (_typeOnStack != _targetType) - // All other types should not be convertible to non-equal types. - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid type conversion requested.")); -} - void ExpressionCompiler::appendHighBitsCleanup(IntegerType const& _typeOnStack) { if (_typeOnStack.getNumBits() == 256) @@ -998,319 +1029,32 @@ void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, appendTypeMoveToMemory(_expectedType); } -void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) -{ - CompilerContext::LocationSetter locationSetter(m_context, &_varDecl); - FunctionType accessorType(_varDecl); - - unsigned length = 0; - TypePointers const& paramTypes = accessorType.getParameterTypes(); - // move arguments to memory - for (TypePointer const& paramType: boost::adaptors::reverse(paramTypes)) - length += CompilerUtils(m_context).storeInMemory(length, *paramType, true); - - // retrieve the position of the variable - m_context << m_context.getStorageLocationOfVariable(_varDecl); - TypePointer returnType = _varDecl.getType(); - - for (TypePointer const& paramType: paramTypes) - { - // move offset to memory - CompilerUtils(m_context).storeInMemory(length); - unsigned argLen = CompilerUtils::getPaddedSize(paramType->getCalldataEncodedSize()); - length -= argLen; - m_context << u256(argLen + 32) << u256(length) << eth::Instruction::SHA3; - - returnType = dynamic_cast<MappingType const&>(*returnType).getValueType(); - } - - unsigned retSizeOnStack = 0; - solAssert(accessorType.getReturnParameterTypes().size() >= 1, ""); - if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get())) - { - auto const& names = accessorType.getReturnParameterNames(); - auto const& types = accessorType.getReturnParameterTypes(); - // struct - for (size_t i = 0; i < names.size(); ++i) - { - m_context << eth::Instruction::DUP1 - << structType->getStorageOffsetOfMember(names[i]) - << eth::Instruction::ADD; - m_currentLValue = LValue(m_context, LValue::LValueType::Storage, types[i]); - m_currentLValue.retrieveValue(SourceLocation(), true); - solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); - m_context << eth::Instruction::SWAP1; - retSizeOnStack += types[i]->getSizeOnStack(); - } - m_context << eth::Instruction::POP; - } - else - { - // simple value - solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); - m_currentLValue = LValue(m_context, LValue::LValueType::Storage, returnType); - m_currentLValue.retrieveValue(SourceLocation(), true); - retSizeOnStack = returnType->getSizeOnStack(); - } - solAssert(retSizeOnStack <= 15, "Stack too deep."); - m_context << eth::dupInstruction(retSizeOnStack + 1) << eth::Instruction::JUMP; -} - -ExpressionCompiler::LValue::LValue(CompilerContext& _compilerContext, LValueType _type, - TypePointer const& _dataType, unsigned _baseStackOffset): - m_context(&_compilerContext), m_type(_type), m_dataType(_dataType), - m_baseStackOffset(_baseStackOffset) +void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression) { - //@todo change the type cast for arrays - solAssert(m_dataType->getStorageSize() <= numeric_limits<unsigned>::max(), - "The storage size of " + m_dataType->toString() + " should fit in unsigned"); - if (m_type == LValueType::Storage) - m_size = unsigned(m_dataType->getStorageSize()); + solAssert(!m_currentLValue, "Current LValue not reset when trying to set to new one."); + std::unique_ptr<LValue> lvalue; + if (m_context.isLocalVariable(&_declaration)) + lvalue.reset(new StackVariable(m_context, _declaration)); + else if (m_context.isStateVariable(&_declaration)) + lvalue.reset(new StorageItem(m_context, _declaration)); else - m_size = unsigned(m_dataType->getSizeOnStack()); -} - -void ExpressionCompiler::LValue::fromDeclaration(Declaration const& _declaration, SourceLocation const& _location) -{ - if (m_context->isLocalVariable(&_declaration)) - { - m_type = LValueType::Stack; - m_dataType = _declaration.getType(); - m_size = m_dataType->getSizeOnStack(); - m_baseStackOffset = m_context->getBaseStackOffsetOfVariable(_declaration); - } - else if (m_context->isStateVariable(&_declaration)) - { - *m_context << m_context->getStorageLocationOfVariable(_declaration); - m_type = LValueType::Storage; - m_dataType = _declaration.getType(); - solAssert(m_dataType->getStorageSize() <= numeric_limits<unsigned>::max(), - "The storage size of " + m_dataType->toString() + " should fit in an unsigned"); - m_size = unsigned(m_dataType->getStorageSize()); - } + BOOST_THROW_EXCEPTION(InternalCompilerError() + << errinfo_sourceLocation(_expression.getLocation()) + << errinfo_comment("Identifier type not supported or identifier not found.")); + if (_expression.lvalueRequested()) + m_currentLValue = move(lvalue); else - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) - << errinfo_comment("Identifier type not supported or identifier not found.")); -} - -void ExpressionCompiler::LValue::retrieveValue(SourceLocation const& _location, bool _remove) const -{ - switch (m_type) - { - case LValueType::Stack: - { - unsigned stackPos = m_context->baseToCurrentStackOffset(m_baseStackOffset); - if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) - << errinfo_comment("Stack too deep.")); - for (unsigned i = 0; i < m_size; ++i) - *m_context << eth::dupInstruction(stackPos + 1); - break; - } - case LValueType::Storage: - retrieveValueFromStorage(_remove); - break; - case LValueType::Memory: - if (!m_dataType->isValueType()) - break; // no distinction between value and reference for non-value types - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) - << errinfo_comment("Location type not yet implemented.")); - break; - default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) - << errinfo_comment("Unsupported location type.")); - break; - } + lvalue->retrieveValue(_expression.getLocation(), true); } -void ExpressionCompiler::LValue::retrieveValueFromStorage(bool _remove) const +void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) { - if (!m_dataType->isValueType()) - return; // no distinction between value and reference for non-value types - if (!_remove) - *m_context << eth::Instruction::DUP1; - if (m_size == 1) - *m_context << eth::Instruction::SLOAD; + solAssert(!m_currentLValue, "Current LValue not reset when trying to set to new one."); + std::unique_ptr<LValue> lvalue(new StorageItem(m_context, _expression.getType())); + if (_expression.lvalueRequested()) + m_currentLValue = move(lvalue); else - for (unsigned i = 0; i < m_size; ++i) - { - *m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; - if (i + 1 < m_size) - *m_context << u256(1) << eth::Instruction::ADD; - else - *m_context << eth::Instruction::POP; - } -} - -void ExpressionCompiler::LValue::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const -{ - switch (m_type) - { - case LValueType::Stack: - { - unsigned stackDiff = m_context->baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1; - if (stackDiff > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) - << errinfo_comment("Stack too deep.")); - else if (stackDiff > 0) - for (unsigned i = 0; i < m_size; ++i) - *m_context << eth::swapInstruction(stackDiff) << eth::Instruction::POP; - if (!_move) - retrieveValue(_location); - break; - } - case LValueType::Storage: - // stack layout: value value ... value target_ref - if (m_dataType->isValueType()) - { - if (!_move) // copy values - { - if (m_size + 1 > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) - << errinfo_comment("Stack too deep.")); - for (unsigned i = 0; i < m_size; ++i) - *m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1; - } - if (m_size > 0) // store high index value first - *m_context << u256(m_size - 1) << eth::Instruction::ADD; - for (unsigned i = 0; i < m_size; ++i) - { - if (i + 1 >= m_size) - *m_context << eth::Instruction::SSTORE; - else - // stack here: value value ... value value (target_ref+offset) - *m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 - << eth::Instruction::SSTORE - << u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB; - } - } - else - { - solAssert(_sourceType.getCategory() == m_dataType->getCategory(), "Wrong type conversation for assignment."); - if (m_dataType->getCategory() == Type::Category::Array) - { - CompilerUtils(*m_context).copyByteArrayToStorage( - dynamic_cast<ArrayType const&>(*m_dataType), - dynamic_cast<ArrayType const&>(_sourceType)); - if (_move) - *m_context << eth::Instruction::POP; - } - else if (m_dataType->getCategory() == Type::Category::Struct) - { - // stack layout: source_ref target_ref - auto const& structType = dynamic_cast<StructType const&>(*m_dataType); - solAssert(structType == _sourceType, "Struct assignment with conversion."); - for (auto const& member: structType.getMembers()) - { - // assign each member that is not a mapping - TypePointer const& memberType = member.second; - if (memberType->getCategory() == Type::Category::Mapping) - continue; - *m_context << structType.getStorageOffsetOfMember(member.first) - << eth::Instruction::DUP3 << eth::Instruction::DUP2 - << eth::Instruction::ADD; - // stack: source_ref target_ref member_offset source_member_ref - LValue rightHandSide(*m_context, LValueType::Storage, memberType); - rightHandSide.retrieveValue(_location, true); - // stack: source_ref target_ref member_offset source_value... - *m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) - << eth::dupInstruction(2 + memberType->getSizeOnStack()) - << eth::Instruction::ADD; - // stack: source_ref target_ref member_offset source_value... target_member_ref - LValue memberLValue(*m_context, LValueType::Storage, memberType); - memberLValue.storeValue(*memberType, _location, true); - *m_context << eth::Instruction::POP; - } - if (_move) - *m_context << eth::Instruction::POP; - else - *m_context << eth::Instruction::SWAP1; - *m_context << eth::Instruction::POP; - } - else - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) - << errinfo_comment("Invalid non-value type for assignment.")); - } - break; - case LValueType::Memory: - if (!m_dataType->isValueType()) - break; // no distinction between value and reference for non-value types - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) - << errinfo_comment("Location type not yet implemented.")); - break; - default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) - << errinfo_comment("Unsupported location type.")); - break; - } -} - -void ExpressionCompiler::LValue::setToZero(SourceLocation const& _location) const -{ - switch (m_type) - { - case LValueType::Stack: - { - unsigned stackDiff = m_context->baseToCurrentStackOffset(m_baseStackOffset); - if (stackDiff > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) - << errinfo_comment("Stack too deep.")); - solAssert(stackDiff >= m_size - 1, ""); - for (unsigned i = 0; i < m_size; ++i) - *m_context << u256(0) << eth::swapInstruction(stackDiff + 1 - i) - << eth::Instruction::POP; - break; - } - case LValueType::Storage: - if (m_dataType->getCategory() == Type::Category::Array) - CompilerUtils(*m_context).clearByteArray(dynamic_cast<ArrayType const&>(*m_dataType)); - else if (m_dataType->getCategory() == Type::Category::Struct) - { - // stack layout: ref - auto const& structType = dynamic_cast<StructType const&>(*m_dataType); - for (auto const& member: structType.getMembers()) - { - // zero each member that is not a mapping - TypePointer const& memberType = member.second; - if (memberType->getCategory() == Type::Category::Mapping) - continue; - *m_context << structType.getStorageOffsetOfMember(member.first) - << eth::Instruction::DUP2 << eth::Instruction::ADD; - LValue memberValue(*m_context, LValueType::Storage, memberType); - memberValue.setToZero(); - } - *m_context << eth::Instruction::POP; - } - else - { - if (m_size == 0) - *m_context << eth::Instruction::POP; - for (unsigned i = 0; i < m_size; ++i) - if (i + 1 >= m_size) - *m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; - else - *m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE - << u256(1) << eth::Instruction::ADD; - } - break; - case LValueType::Memory: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) - << errinfo_comment("Location type not yet implemented.")); - break; - default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) - << errinfo_comment("Unsupported location type.")); - break; - } -} - -void ExpressionCompiler::LValue::retrieveValueIfLValueNotRequested(Expression const& _expression) -{ - if (!_expression.lvalueRequested()) - { - retrieveValue(_expression.getLocation(), true); - reset(); - } + lvalue->retrieveValue(_expression.getLocation(), true); } } diff --git a/ExpressionCompiler.h b/ExpressionCompiler.h index 2eb8ca20..edb63ad9 100644 --- a/ExpressionCompiler.h +++ b/ExpressionCompiler.h @@ -27,6 +27,7 @@ #include <libdevcore/Common.h> #include <libevmcore/SourceLocation.h> #include <libsolidity/ASTVisitor.h> +#include <libsolidity/LValue.h> namespace dev { namespace eth @@ -50,22 +51,28 @@ class StaticStringType; class ExpressionCompiler: private ASTConstVisitor { public: - /// Compile the given @a _expression into the @a _context. - static void compileExpression(CompilerContext& _context, Expression const& _expression, bool _optimize = false); - - /// Appends code to remove dirty higher order bits in case of an implicit promotion to a wider type. - static void appendTypeConversion(CompilerContext& _context, Type const& _typeOnStack, - Type const& _targetType, bool _cleanupNeeded = false); /// Appends code for a State Variable accessor function static void appendStateVariableAccessor(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize = false); - /// Appends code for a State Variable Initialization function - static void appendStateVariableInitialization(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize = false); - -private: explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimize = false): - m_optimize(_optimize), m_context(_compilerContext), m_currentLValue(m_context) {} + m_optimize(_optimize), m_context(_compilerContext) {} + + /// Compile the given @a _expression and leave its value on the stack. + void compile(Expression const& _expression); + + /// Appends code to set a state variable to its initial value/expression. + void appendStateVariableInitialization(VariableDeclaration const& _varDecl); + + /// Appends code for a State Variable accessor function + void appendStateVariableAccessor(VariableDeclaration const& _varDecl); + + /// Appends an implicit or explicit type conversion. For now this comprises only erasing + /// higher-order bits (@see appendHighBitCleanup) when widening integer. + /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be + /// necessary. + void appendTypeConversion(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false); +private: virtual bool visit(Assignment const& _assignment) override; virtual bool visit(UnaryOperation const& _unaryOperation) override; virtual bool visit(BinaryOperation const& _binaryOperation) override; @@ -87,11 +94,6 @@ private: void appendShiftOperatorCode(Token::Value _operator); /// @} - /// Appends an implicit or explicit type conversion. For now this comprises only erasing - /// higher-order bits (@see appendHighBitCleanup) when widening integer. - /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be - /// necessary. - void appendTypeConversion(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false); //// Appends code that cleans higher-order bits for integer types. void appendHighBitsCleanup(IntegerType const& _typeOnStack); @@ -111,75 +113,17 @@ private: /// expected to be on the stack and is updated by this call. void appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression); - /// Appends code for a State Variable accessor function - void appendStateVariableAccessor(VariableDeclaration const& _varDecl); - - /// Appends code for a State Variable initialization - void appendStateVariableInitialization(VariableDeclaration const& _varDecl); - - /** - * Helper class to store and retrieve lvalues to and from various locations. - * All types except STACK store a reference in a slot on the stack, STACK just - * stores the base stack offset of the variable in @a m_baseStackOffset. - */ - class LValue - { - public: - enum class LValueType { None, Stack, Memory, Storage }; - - explicit LValue(CompilerContext& _compilerContext): m_context(&_compilerContext) { reset(); } - LValue(CompilerContext& _compilerContext, LValueType _type, - std::shared_ptr<Type const> const& _dataType, unsigned _baseStackOffset = 0); - - /// Set type according to the declaration and retrieve the reference. - /// @a _location is the current location - void fromDeclaration(Declaration const& _declaration, SourceLocation const& _location); - - void reset() { m_type = LValueType::None; m_dataType.reset(); m_baseStackOffset = 0; m_size = 0; } - - bool isValid() const { return m_type != LValueType::None; } - bool isInOnStack() const { return m_type == LValueType::Stack; } - bool isInMemory() const { return m_type == LValueType::Memory; } - bool isInStorage() const { return m_type == LValueType::Storage; } - - /// @returns true if this lvalue reference type occupies a slot on the stack. - bool storesReferenceOnStack() const { return m_type == LValueType::Storage || m_type == LValueType::Memory; } - - /// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true, - /// also removes the reference from the stack (note that is does not reset the type to @a NONE). - /// @a _location source location of the current expression, used for error reporting. - void retrieveValue(SourceLocation const& _location, bool _remove = false) const; - /// Moves a value from the stack to the lvalue. Removes the value if @a _move is true. - /// @a _location is the source location of the expression that caused this operation. - /// Stack pre: value [lvalue_ref] - /// Stack post if !_move: value_of(lvalue_ref) - void storeValue(Type const& _sourceType, SourceLocation const& _location = SourceLocation(), bool _move = false) const; - /// Stores zero in the lvalue. - /// @a _location is the source location of the requested operation - void setToZero(SourceLocation const& _location = SourceLocation()) const; - /// Convenience function to convert the stored reference to a value and reset type to NONE if - /// the reference was not requested by @a _expression. - void retrieveValueIfLValueNotRequested(Expression const& _expression); - - private: - /// Convenience function to retrieve Value from Storage. Specific version of @ref retrieveValue - void retrieveValueFromStorage(bool _remove = false) const; - /// Copies from a byte array to a byte array in storage, both references on the stack. - void copyByteArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; - - CompilerContext* m_context; - LValueType m_type = LValueType::None; - std::shared_ptr<Type const> m_dataType; - /// If m_type is STACK, this is base stack offset (@see - /// CompilerContext::getBaseStackOffsetOfVariable) of a local variable. - unsigned m_baseStackOffset = 0; - /// Size of the value of this lvalue on the stack or the storage. - unsigned m_size = 0; - }; + /// Sets the current LValue to a new one (of the appropriate type) from the given declaration. + /// Also retrieves the value if it was not requested by @a _expression. + void setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression); + /// Sets the current LValue to a StorageItem holding the type of @a _expression. The reference is assumed + /// to be on the stack. + /// Also retrieves the value if it was not requested by @a _expression. + void setLValueToStorageItem(Expression const& _expression); bool m_optimize; CompilerContext& m_context; - LValue m_currentLValue; + std::unique_ptr<LValue> m_currentLValue; }; diff --git a/LValue.cpp b/LValue.cpp new file mode 100644 index 00000000..8b9bd53b --- /dev/null +++ b/LValue.cpp @@ -0,0 +1,220 @@ +/* + 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 2015 + * LValues for use in the expresison compiler. + */ + +#include <libsolidity/LValue.h> +#include <libevmcore/Instruction.h> +#include <libsolidity/Types.h> +#include <libsolidity/AST.h> +#include <libsolidity/CompilerUtils.h> + +using namespace std; +using namespace dev; +using namespace solidity; + + +StackVariable::StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration): + LValue(_compilerContext, _declaration.getType()), + m_baseStackOffset(m_context.getBaseStackOffsetOfVariable(_declaration)), + m_size(m_dataType->getSizeOnStack()) +{ +} + +void StackVariable::retrieveValue(SourceLocation const& _location, bool _remove) const +{ + unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset); + if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory + BOOST_THROW_EXCEPTION(CompilerError() + << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); + for (unsigned i = 0; i < m_size; ++i) + m_context << eth::dupInstruction(stackPos + 1); +} + +void StackVariable::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const +{ + unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1; + if (stackDiff > 16) + BOOST_THROW_EXCEPTION(CompilerError() + << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); + else if (stackDiff > 0) + for (unsigned i = 0; i < m_size; ++i) + m_context << eth::swapInstruction(stackDiff) << eth::Instruction::POP; + if (!_move) + retrieveValue(_location); +} + +void StackVariable::setToZero(SourceLocation const& _location) const +{ + unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset); + if (stackDiff > 16) + BOOST_THROW_EXCEPTION(CompilerError() + << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); + solAssert(stackDiff >= m_size - 1, ""); + for (unsigned i = 0; i < m_size; ++i) + m_context << u256(0) << eth::swapInstruction(stackDiff + 1 - i) + << eth::Instruction::POP; +} + + +StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration): + StorageItem(_compilerContext, _declaration.getType()) +{ + m_context << m_context.getStorageLocationOfVariable(_declaration); +} + +StorageItem::StorageItem(CompilerContext& _compilerContext, TypePointer const& _type): + LValue(_compilerContext, _type) +{ + if (m_dataType->isValueType()) + { + solAssert(m_dataType->getStorageSize() == m_dataType->getSizeOnStack(), ""); + solAssert(m_dataType->getStorageSize() <= numeric_limits<unsigned>::max(), + "The storage size of " + m_dataType->toString() + " should fit in an unsigned"); + m_size = unsigned(m_dataType->getStorageSize()); + } + else + m_size = 0; // unused +} + +void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const +{ + if (!m_dataType->isValueType()) + return; // no distinction between value and reference for non-value types + if (!_remove) + m_context << eth::Instruction::DUP1; + if (m_size == 1) + m_context << eth::Instruction::SLOAD; + else + for (unsigned i = 0; i < m_size; ++i) + { + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; + if (i + 1 < m_size) + m_context << u256(1) << eth::Instruction::ADD; + else + m_context << eth::Instruction::POP; + } +} + +void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const +{ + // stack layout: value value ... value target_ref + if (m_dataType->isValueType()) + { + if (!_move) // copy values + { + if (m_size + 1 > 16) + BOOST_THROW_EXCEPTION(CompilerError() + << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); + for (unsigned i = 0; i < m_size; ++i) + m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1; + } + if (m_size > 1) // store high index value first + m_context << u256(m_size - 1) << eth::Instruction::ADD; + for (unsigned i = 0; i < m_size; ++i) + { + if (i + 1 >= m_size) + m_context << eth::Instruction::SSTORE; + else + // stack here: value value ... value value (target_ref+offset) + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 + << eth::Instruction::SSTORE + << u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB; + } + } + else + { + solAssert(_sourceType.getCategory() == m_dataType->getCategory(), + "Wrong type conversation for assignment."); + if (m_dataType->getCategory() == Type::Category::Array) + { + CompilerUtils(m_context).copyByteArrayToStorage( + dynamic_cast<ArrayType const&>(*m_dataType), + dynamic_cast<ArrayType const&>(_sourceType)); + if (_move) + m_context << eth::Instruction::POP; + } + else if (m_dataType->getCategory() == Type::Category::Struct) + { + // stack layout: source_ref target_ref + auto const& structType = dynamic_cast<StructType const&>(*m_dataType); + solAssert(structType == _sourceType, "Struct assignment with conversion."); + for (auto const& member: structType.getMembers()) + { + // assign each member that is not a mapping + TypePointer const& memberType = member.second; + if (memberType->getCategory() == Type::Category::Mapping) + continue; + m_context << structType.getStorageOffsetOfMember(member.first) + << eth::Instruction::DUP3 << eth::Instruction::DUP2 << eth::Instruction::ADD; + // stack: source_ref target_ref member_offset source_member_ref + StorageItem(m_context, memberType).retrieveValue(_location, true); + // stack: source_ref target_ref member_offset source_value... + m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) + << eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD; + // stack: source_ref target_ref member_offset source_value... target_member_ref + StorageItem(m_context, memberType).storeValue(*memberType, _location, true); + m_context << eth::Instruction::POP; + } + if (_move) + m_context << eth::Instruction::POP; + else + m_context << eth::Instruction::SWAP1; + m_context << eth::Instruction::POP; + } + else + BOOST_THROW_EXCEPTION(InternalCompilerError() + << errinfo_sourceLocation(_location) << errinfo_comment("Invalid non-value type for assignment.")); + } +} + + +void StorageItem::setToZero(SourceLocation const& _location) const +{ + if (m_dataType->getCategory() == Type::Category::Array) + CompilerUtils(m_context).clearByteArray(dynamic_cast<ArrayType const&>(*m_dataType)); + else if (m_dataType->getCategory() == Type::Category::Struct) + { + // stack layout: ref + auto const& structType = dynamic_cast<StructType const&>(*m_dataType); + for (auto const& member: structType.getMembers()) + { + // zero each member that is not a mapping + TypePointer const& memberType = member.second; + if (memberType->getCategory() == Type::Category::Mapping) + continue; + m_context << structType.getStorageOffsetOfMember(member.first) + << eth::Instruction::DUP2 << eth::Instruction::ADD; + StorageItem(m_context, memberType).setToZero(); + } + m_context << eth::Instruction::POP; + } + else + { + if (m_size == 0) + m_context << eth::Instruction::POP; + for (unsigned i = 0; i < m_size; ++i) + if (i + 1 >= m_size) + m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + else + m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE + << u256(1) << eth::Instruction::ADD; + } +} diff --git a/LValue.h b/LValue.h new file mode 100644 index 00000000..35ddc451 --- /dev/null +++ b/LValue.h @@ -0,0 +1,112 @@ +/* + 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 2015 + * LValues for use in the expresison compiler. + */ + +#pragma once + +#include <memory> +#include <libevmcore/SourceLocation.h> + +namespace dev +{ +namespace solidity +{ + +class Declaration; +class Type; +class CompilerContext; + +/** + * Abstract class used to retrieve, delete and store data in lvalues/variables. + */ +class LValue +{ +protected: + LValue(CompilerContext& _compilerContext, std::shared_ptr<Type const> const& _dataType): + m_context(_compilerContext), m_dataType(_dataType) {} + +public: + /// @returns true if this lvalue reference type occupies a slot on the stack. + virtual bool storesReferenceOnStack() const = 0; + /// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true, + /// also removes the reference from the stack. + /// @a _location source location of the current expression, used for error reporting. + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const = 0; + /// Moves a value from the stack to the lvalue. Removes the value if @a _move is true. + /// @a _location is the source location of the expression that caused this operation. + /// Stack pre: value [lvalue_ref] + /// Stack post if !_move: value_of(lvalue_ref) + virtual void storeValue(Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), bool _move = false) const = 0; + /// Stores zero in the lvalue. + /// @a _location is the source location of the requested operation + virtual void setToZero(SourceLocation const& _location = SourceLocation()) const = 0; + +protected: + CompilerContext& m_context; + std::shared_ptr<Type const> m_dataType; +}; + +/** + * Local variable that is completely stored on the stack. + */ +class StackVariable: public LValue +{ +public: + explicit StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration); + + virtual bool storesReferenceOnStack() const { return false; } + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + virtual void storeValue(Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), bool _move = false) const override; + virtual void setToZero(SourceLocation const& _location = SourceLocation()) const override; + +private: + /// Base stack offset (@see CompilerContext::getBaseStackOffsetOfVariable) of the local variable. + unsigned m_baseStackOffset; + /// Number of stack elements occupied by the value (not the reference). + unsigned m_size; +}; + +/** + * Reference to some item in storage. The (starting) position of the item is stored on the stack. + */ +class StorageItem: public LValue +{ +public: + /// Constructs the LValue and pushes the location of @a _declaration onto the stack. + explicit StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration); + /// Constructs the LValue and assumes that the storage reference is already on the stack. + explicit StorageItem(CompilerContext& _compilerContext, std::shared_ptr<Type const> const& _type); + virtual bool storesReferenceOnStack() const { return true; } + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + virtual void storeValue(Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), bool _move = false) const override; + virtual void setToZero(SourceLocation const& _location = SourceLocation()) const override; + +private: + /// Number of stack elements occupied by the value (not the reference). + /// Only used for value types. + unsigned m_size; +}; + +} +} |