diff options
Diffstat (limited to 'libsolidity')
-rw-r--r-- | libsolidity/ast/Types.cpp | 32 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.cpp | 10 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.h | 18 | ||||
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.cpp | 122 | ||||
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.h | 6 |
5 files changed, 144 insertions, 44 deletions
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index d9660bc0..03ff8471 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -251,6 +251,19 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition return members; } +bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountType) +{ + // Disable >>> here. + if (_operator == Token::SHR) + return false; + else if (IntegerType const* otherInt = dynamic_cast<decltype(otherInt)>(&_shiftAmountType)) + return !otherInt->isAddress(); + else if (RationalNumberType const* otherRat = dynamic_cast<decltype(otherRat)>(&_shiftAmountType)) + return otherRat->integerType() && !otherRat->integerType()->isSigned(); + else + return false; +} + IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): m_bits(_bits), m_modifier(_modifier) { @@ -340,6 +353,17 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe _other->category() != category() ) return TypePointer(); + if (Token::isShiftOp(_operator)) + { + // Shifts are not symmetric with respect to the type + if (isAddress()) + return TypePointer(); + if (isValidShiftAndAmountType(_operator, *_other)) + return shared_from_this(); + else + return TypePointer(); + } + auto commonType = Type::commonType(shared_from_this(), _other); //might be a integer or fixed point if (!commonType) return TypePointer(); @@ -954,6 +978,14 @@ TypePointer FixedBytesType::unaryOperatorResult(Token::Value _operator) const TypePointer FixedBytesType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { + if (Token::isShiftOp(_operator)) + { + if (isValidShiftAndAmountType(_operator, *_other)) + return shared_from_this(); + else + return TypePointer(); + } + auto commonType = dynamic_pointer_cast<FixedBytesType const>(Type::commonType(shared_from_this(), _other)); if (!commonType) return TypePointer(); diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index da2e78e8..7d382aba 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -952,9 +952,9 @@ void CompilerUtils::storeStringData(bytesConstRef _data) } } -unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) +unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords) { - unsigned numBytes = _type.calldataEncodedSize(_padToWordBoundaries); + unsigned numBytes = _type.calldataEncodedSize(_padToWords); bool isExternalFunctionType = false; if (auto const* funType = dynamic_cast<FunctionType const*>(&_type)) if (funType->location() == FunctionType::Location::External) @@ -993,9 +993,9 @@ void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack) m_context << ((u256(1) << _typeOnStack.numBits()) - 1) << Instruction::AND; } -unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) +unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords) { - unsigned numBytes = _type.calldataEncodedSize(_padToWordBoundaries); + unsigned numBytes = _type.calldataEncodedSize(_padToWords); bool leftAligned = _type.category() == Type::Category::FixedBytes; if (numBytes == 0) m_context << Instruction::POP; @@ -1003,7 +1003,7 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBou { solAssert(numBytes <= 32, "Memory store of more than 32 bytes requested."); convertType(_type, _type, true); - if (numBytes != 32 && !leftAligned && !_padToWordBoundaries) + if (numBytes != 32 && !leftAligned && !_padToWords) // shift the value accordingly before storing m_context << (u256(1) << ((32 - numBytes) * 8)) << Instruction::MUL; } diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index ad53efea..b9ed6757 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -52,13 +52,13 @@ public: /// @param _offset offset in memory (or calldata) /// @param _type data type to load /// @param _fromCalldata if true, load from calldata, not from memory - /// @param _padToWordBoundaries if true, assume the data is padded to word (32 byte) boundaries + /// @param _padToWords if true, assume the data is padded to full words (32 bytes) /// @returns the number of bytes consumed in memory. unsigned loadFromMemory( unsigned _offset, Type const& _type = IntegerType(256), bool _fromCalldata = false, - bool _padToWordBoundaries = false + bool _padToWords = false ); /// Dynamic version of @see loadFromMemory, expects the memory offset on the stack. /// Stack pre: memory_offset @@ -66,7 +66,7 @@ public: void loadFromMemoryDynamic( Type const& _type, bool _fromCalldata = false, - bool _padToWordBoundaries = true, + bool _padToWords = true, bool _keepUpdatedMemoryOffset = true ); /// Stores a 256 bit integer from stack in memory. @@ -76,11 +76,11 @@ public: /// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack /// and also updates that. For reference types, only copies the data pointer. Fails for /// non-memory-references. - /// @param _padToWordBoundaries if true, adds zeros to pad to multiple of 32 bytes. Array elements + /// @param _padToWords if true, adds zeros to pad to multiple of 32 bytes. Array elements /// are always padded (except for byte arrays), regardless of this parameter. /// Stack pre: memory_offset value... /// Stack post: (memory_offset+length) - void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true); + void storeInMemoryDynamic(Type const& _type, bool _padToWords = true); /// Copies values (of types @a _givenTypes) given on the stack to a location in memory given /// at the stack top, encoding them according to the ABI as the given types @a _targetTypes. @@ -88,7 +88,7 @@ public: /// Stack pre: <v1> <v2> ... <vn> <memptr> /// Stack post: <memptr_updated> /// Does not touch the memory-free pointer. - /// @param _padToWordBoundaries if false, all values are concatenated without padding. + /// @param _padToWords if false, all values are concatenated without padding. /// @param _copyDynamicDataInPlace if true, dynamic types is stored (without length) /// together with fixed-length data. /// @param _encodeAsLibraryTypes if true, encodes for a library function, e.g. does not @@ -98,7 +98,7 @@ public: void encodeToMemory( TypePointers const& _givenTypes = {}, TypePointers const& _targetTypes = {}, - bool _padToWordBoundaries = true, + bool _padToWords = true, bool _copyDynamicDataInPlace = false, bool _encodeAsLibraryTypes = false ); @@ -193,9 +193,9 @@ private: void cleanHigherOrderBits(IntegerType const& _typeOnStack); /// Prepares the given type for storing in memory by shifting it if necessary. - unsigned prepareMemoryStore(Type const& _type, bool _padToWordBoundaries); + unsigned prepareMemoryStore(Type const& _type, bool _padToWords); /// Loads type from memory assuming memory offset is on stack top. - unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries); + unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords); CompilerContext& m_context; }; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 5748d818..a7fb8408 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -197,21 +197,39 @@ bool ExpressionCompiler::visit(Conditional const& _condition) bool ExpressionCompiler::visit(Assignment const& _assignment) { CompilerContext::LocationSetter locationSetter(m_context, _assignment); + Token::Value op = _assignment.assignmentOperator(); + Token::Value binOp = op == Token::Assign ? op : Token::AssignmentToBinaryOp(op); + Type const& leftType = *_assignment.leftHandSide().annotation().type; + if (leftType.category() == Type::Category::Tuple) + { + solAssert(*_assignment.annotation().type == TupleType(), ""); + solAssert(op == Token::Assign, ""); + } + else + solAssert(*_assignment.annotation().type == leftType, ""); + bool cleanupNeeded = false; + if (op != Token::Assign) + cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp); _assignment.rightHandSide().accept(*this); // Perform some conversion already. This will convert storage types to memory and literals // to their actual type, but will not convert e.g. memory to storage. - TypePointer type = _assignment.rightHandSide().annotation().type->closestTemporaryType( - _assignment.leftHandSide().annotation().type - ); - utils().convertType(*_assignment.rightHandSide().annotation().type, *type); + TypePointer rightIntermediateType; + if (op != Token::Assign && Token::isShiftOp(binOp)) + rightIntermediateType = _assignment.rightHandSide().annotation().type->mobileType(); + else + rightIntermediateType = _assignment.rightHandSide().annotation().type->closestTemporaryType( + _assignment.leftHandSide().annotation().type + ); + utils().convertType(*_assignment.rightHandSide().annotation().type, *rightIntermediateType, cleanupNeeded); _assignment.leftHandSide().accept(*this); solAssert(!!m_currentLValue, "LValue not retrieved."); - Token::Value op = _assignment.assignmentOperator(); - if (op != Token::Assign) // compound assignment + if (op == Token::Assign) + m_currentLValue->storeValue(*rightIntermediateType, _assignment.location()); + else // compound assignment { - solUnimplementedAssert(_assignment.annotation().type->isValueType(), "Compound operators not implemented for non-value types."); + solAssert(leftType.isValueType(), "Compound operators only available for value types."); unsigned lvalueSize = m_currentLValue->sizeOnStack(); unsigned itemSize = _assignment.annotation().type->sizeOnStack(); if (lvalueSize > 0) @@ -221,7 +239,15 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) // value lvalue_ref value lvalue_ref } m_currentLValue->retrieveValue(_assignment.location(), true); - appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.annotation().type); + utils().convertType(leftType, leftType, cleanupNeeded); + + if (Token::isShiftOp(binOp)) + appendShiftOperatorCode(binOp, leftType, *rightIntermediateType); + else + { + solAssert(leftType == *rightIntermediateType, ""); + appendOrdinaryBinaryOperatorCode(binOp, leftType); + } if (lvalueSize > 0) { solAssert(itemSize + lvalueSize <= 16, "Stack too deep, try removing local variables."); @@ -229,8 +255,8 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) for (unsigned i = 0; i < itemSize; ++i) m_context << swapInstruction(itemSize + lvalueSize) << Instruction::POP; } + m_currentLValue->storeValue(*_assignment.annotation().type, _assignment.location()); } - m_currentLValue->storeValue(*type, _assignment.location()); m_currentLValue.reset(); return false; } @@ -351,20 +377,19 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) Expression const& leftExpression = _binaryOperation.leftExpression(); Expression const& rightExpression = _binaryOperation.rightExpression(); solAssert(!!_binaryOperation.annotation().commonType, ""); - Type const& commonType = *_binaryOperation.annotation().commonType; + TypePointer const& commonType = _binaryOperation.annotation().commonType; Token::Value const c_op = _binaryOperation.getOperator(); if (c_op == Token::And || c_op == Token::Or) // special case: short-circuiting appendAndOrOperatorCode(_binaryOperation); - else if (commonType.category() == Type::Category::RationalNumber) - m_context << commonType.literalValue(nullptr); + else if (commonType->category() == Type::Category::RationalNumber) + m_context << commonType->literalValue(nullptr); else { - bool cleanupNeeded = false; - if (Token::isCompareOp(c_op)) - cleanupNeeded = true; - if (commonType.category() == Type::Category::Integer && (c_op == Token::Div || c_op == Token::Mod)) - cleanupNeeded = true; + bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op); + + TypePointer leftTargetType = commonType; + TypePointer rightTargetType = Token::isShiftOp(c_op) ? rightExpression.annotation().type->mobileType() : commonType; // for commutative operators, push the literal as late as possible to allow improved optimization auto isLiteral = [](Expression const& _e) @@ -375,21 +400,24 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) if (swap) { leftExpression.accept(*this); - utils().convertType(*leftExpression.annotation().type, commonType, cleanupNeeded); + utils().convertType(*leftExpression.annotation().type, *leftTargetType, cleanupNeeded); rightExpression.accept(*this); - utils().convertType(*rightExpression.annotation().type, commonType, cleanupNeeded); + utils().convertType(*rightExpression.annotation().type, *rightTargetType, cleanupNeeded); } else { rightExpression.accept(*this); - utils().convertType(*rightExpression.annotation().type, commonType, cleanupNeeded); + utils().convertType(*rightExpression.annotation().type, *rightTargetType, cleanupNeeded); leftExpression.accept(*this); - utils().convertType(*leftExpression.annotation().type, commonType, cleanupNeeded); + utils().convertType(*leftExpression.annotation().type, *leftTargetType, cleanupNeeded); } - if (Token::isCompareOp(c_op)) - appendCompareOperatorCode(c_op, commonType); + if (Token::isShiftOp(c_op)) + // shift only cares about the signedness of both sides + appendShiftOperatorCode(c_op, *leftTargetType, *rightTargetType); + else if (Token::isCompareOp(c_op)) + appendCompareOperatorCode(c_op, *commonType); else - appendOrdinaryBinaryOperatorCode(c_op, commonType); + appendOrdinaryBinaryOperatorCode(c_op, *commonType); } // do not visit the child nodes, we already did that explicitly @@ -1326,8 +1354,6 @@ void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator appendArithmeticOperatorCode(_operator, _type); else if (Token::isBitOp(_operator)) appendBitOperatorCode(_operator); - else if (Token::isShiftOp(_operator)) - appendShiftOperatorCode(_operator); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown binary operator.")); } @@ -1390,17 +1416,45 @@ void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) } } -void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) +void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type const& _valueType, Type const& _shiftAmountType) { - solUnimplemented("Shift operators not yet implemented."); + // stack: shift_amount value_to_shift + + bool c_valueSigned = false; + if (auto valueType = dynamic_cast<IntegerType const*>(&_valueType)) + c_valueSigned = valueType->isSigned(); + else + solAssert(dynamic_cast<FixedBytesType const*>(&_valueType), "Only integer and fixed bytes type supported for shifts."); + + // The amount can be a RationalNumberType too. + bool c_amountSigned = false; + if (auto amountType = dynamic_cast<RationalNumberType const*>(&_shiftAmountType)) + { + // This should be handled by the type checker. + solAssert(amountType->integerType(), ""); + solAssert(!amountType->integerType()->isSigned(), ""); + } + else if (auto amountType = dynamic_cast<IntegerType const*>(&_shiftAmountType)) + c_amountSigned = amountType->isSigned(); + else + solAssert(false, "Invalid shift amount type."); + + // shift by negative amount throws exception + if (c_amountSigned) + { + m_context << u256(0) << Instruction::DUP3 << Instruction::SLT; + m_context.appendConditionalJumpTo(m_context.errorTag()); + } + switch (_operator) { case Token::SHL: + m_context << Instruction::SWAP1 << u256(2) << Instruction::EXP << Instruction::MUL; break; case Token::SAR: + m_context << Instruction::SWAP1 << u256(2) << Instruction::EXP << Instruction::SWAP1 << (c_valueSigned ? Instruction::SDIV : Instruction::DIV); break; case Token::SHR: - break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown shift operator.")); } @@ -1688,6 +1742,16 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) setLValue<StorageItem>(_expression, *_expression.annotation().type); } +bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token::Value _op) +{ + if (Token::isCompareOp(_op) || Token::isShiftOp(_op)) + return true; + else if (_type == Type::Category::Integer && (_op == Token::Div || _op == Token::Mod)) + return true; + else + return false; +} + CompilerUtils ExpressionCompiler::utils() { return CompilerUtils(m_context); diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index f08bded9..d0a8ac15 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -91,7 +91,7 @@ private: void appendArithmeticOperatorCode(Token::Value _operator, Type const& _type); void appendBitOperatorCode(Token::Value _operator); - void appendShiftOperatorCode(Token::Value _operator); + void appendShiftOperatorCode(Token::Value _operator, Type const& _valueType, Type const& _shiftAmountType); /// @} /// Appends code to call a function of the given type with the given arguments. @@ -117,6 +117,10 @@ private: template <class _LValueType, class... _Arguments> void setLValue(Expression const& _expression, _Arguments const&... _arguments); + /// @returns true if the operator applied to the given type requires a cleanup prior to the + /// operation. + bool cleanupNeededForOp(Type::Category _type, Token::Value _op); + /// @returns the CompilerUtils object containing the current context. CompilerUtils utils(); |