From a8a897cb4999cebd5be1676aa02aa99a7befd684 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 27 Jul 2021 12:00:22 +0200 Subject: [PATCH] Fixed point type conversions. --- libsolidity/codegen/CompilerUtils.cpp | 118 ++++++++++++++++------ libsolidity/codegen/LValue.cpp | 16 +-- libsolidity/codegen/YulUtilFunctions.cpp | 121 +++++++++++++++++------ 3 files changed, 189 insertions(+), 66 deletions(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 9ae1741dd..0d63725ed 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -776,15 +776,11 @@ void CompilerUtils::convertType( solAssert(!contrType->isSuper(), "Cannot convert magic variable \"super\""); bool enumOverflowCheckPending = (targetTypeCategory == Type::Category::Enum || stackTypeCategory == Type::Category::Enum); - bool chopSignBitsPending = _chopSignBits && targetTypeCategory == Type::Category::Integer; - if (chopSignBitsPending) - { - IntegerType const& targetIntegerType = dynamic_cast(_targetType); - chopSignBitsPending = targetIntegerType.isSigned(); - } - - if (targetTypeCategory == Type::Category::FixedPoint) - solUnimplemented("Not yet implemented - FixedPointType."); + bool chopSignBitsPending = false; + if (_chopSignBits && targetTypeCategory == Type::Category::Integer) + chopSignBitsPending = dynamic_cast(_targetType).isSigned(); + else if (_chopSignBits && targetTypeCategory == Type::Category::FixedPoint) + chopSignBitsPending = dynamic_cast(_targetType).isSigned(); switch (stackTypeCategory) { @@ -800,6 +796,14 @@ void CompilerUtils::convertType( if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8) convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded); } + else if (targetTypeCategory == Type::Category::FixedPoint) + { + // Conversion from bytes to fixed point type. No need to clean the high bit + // only to shift right because of opposite alignment. + FixedPointType const& targetFixedPointType = dynamic_cast(_targetType); + solAssert(targetFixedPointType.numBits() == typeOnStack.numBytes() * 8, ""); + rightShiftNumberOnStack(256 - typeOnStack.numBytes() * 8); + } else if (targetTypeCategory == Type::Category::Address) { solAssert(typeOnStack.numBytes() * 8 == 160, ""); @@ -835,10 +839,9 @@ void CompilerUtils::convertType( enumOverflowCheckPending = false; } break; - case Type::Category::FixedPoint: - solUnimplemented("Not yet implemented - FixedPointType."); case Type::Category::Address: case Type::Category::Integer: + case Type::Category::FixedPoint: case Type::Category::Contract: case Type::Category::RationalNumber: if (targetTypeCategory == Type::Category::FixedBytes) @@ -846,6 +849,7 @@ void CompilerUtils::convertType( solAssert( stackTypeCategory == Type::Category::Address || stackTypeCategory == Type::Category::Integer || + stackTypeCategory == Type::Category::FixedPoint || stackTypeCategory == Type::Category::RationalNumber, "Invalid conversion to FixedBytesType requested." ); @@ -859,11 +863,14 @@ void CompilerUtils::convertType( } else if (stackTypeCategory == Type::Category::Address) solAssert(targetBytesType.numBytes() * 8 == 160, ""); + else if (stackTypeCategory == Type::Category::FixedPoint) + solAssert(targetBytesType.numBytes() * 8 == dynamic_cast(_typeOnStack).numBits(), ""); leftShiftNumberOnStack(256 - targetBytesType.numBytes() * 8); } else if (targetTypeCategory == Type::Category::Enum) { solAssert(stackTypeCategory != Type::Category::Address, "Invalid conversion to EnumType requested."); + solAssert(stackTypeCategory != Type::Category::FixedPoint, "Invalid conversion to EnumType requested."); solAssert(_typeOnStack.mobileType(), ""); // just clean convertType(_typeOnStack, *_typeOnStack.mobileType(), true); @@ -881,12 +888,50 @@ void CompilerUtils::convertType( stackTypeCategory == Type::Category::FixedPoint, "Invalid conversion to FixedMxNType requested." ); - //shift all integer bits onto the left side of the fixed type + FixedPointType const& targetFixedPointType = dynamic_cast(_targetType); - if (auto typeOnStack = dynamic_cast(&_typeOnStack)) - if (targetFixedPointType.numBits() > typeOnStack->numBits()) - cleanHigherOrderBits(*typeOnStack); - solUnimplemented("Not yet implemented - FixedPointType."); + int digitDifference = static_cast(targetFixedPointType.fractionalDigits()); + bool isSigned = false; + if (auto const* typeOnStack = dynamic_cast(&_typeOnStack)) + { + cleanHigherOrderBits(*typeOnStack); + isSigned = typeOnStack->isSigned(); + } + else if (auto const* typeOnStack = dynamic_cast(&_typeOnStack)) + { + isSigned = typeOnStack->isSigned(); + digitDifference -= static_cast(typeOnStack->fractionalDigits()); + if (typeOnStack->isSigned() != targetFixedPointType.isSigned()) + solAssert(digitDifference >= 0, ""); + if (digitDifference < 0 || typeOnStack->numBits() < targetFixedPointType.numBits()) + cleanHigherOrderBits(*typeOnStack->asIntegerType()); + } + else if (auto const* typeOnStack = dynamic_cast(&_typeOnStack)) + { + if (typeOnStack->isFractional()) + digitDifference -= static_cast(typeOnStack->fixedPointType()->fractionalDigits()); + isSigned = typeOnStack->isNegative(); + } + else + solAssert(false, ""); + + if (digitDifference > 0) + m_context << pow(u256(10), static_cast(digitDifference)) << Instruction::MUL; + else if (digitDifference < 0) + m_context << + pow(u256(10), static_cast(-digitDifference)) << + (isSigned ? Instruction::SDIV : Instruction::DIV); + + if (_cleanupNeeded) + cleanHigherOrderBits(*targetFixedPointType.asIntegerType()); + if (chopSignBitsPending) + { + if (targetFixedPointType.numBits() < 256) + m_context + << ((u256(1) << targetFixedPointType.numBits()) - 1) + << Instruction::AND; + chopSignBitsPending = false; + } } else { @@ -897,35 +942,50 @@ void CompilerUtils::convertType( "" ); IntegerType addressType(160); - IntegerType const& targetType = targetTypeCategory == Type::Category::Integer - ? dynamic_cast(_targetType) : addressType; + IntegerType const& targetType = + targetTypeCategory == Type::Category::Integer ? + dynamic_cast(_targetType) : + addressType; if (stackTypeCategory == Type::Category::RationalNumber) { RationalNumberType const& constType = dynamic_cast(_typeOnStack); // We know that the stack is clean, we only have to clean for a narrowing conversion // where cleanup is forced. - solUnimplementedAssert(!constType.isFractional(), "Not yet implemented - FixedPointType."); + solUnimplementedAssert(!constType.isFractional(), "Not yet implemented - rational to integer conversion."); if (targetType.numBits() < constType.integerType()->numBits() && _cleanupNeeded) cleanHigherOrderBits(targetType); } + else if (stackTypeCategory == Type::Category::FixedPoint) + { + FixedPointType const& typeOnStack = dynamic_cast(_typeOnStack); + solAssert(typeOnStack.isSigned() == targetType.isSigned(), ""); + cleanHigherOrderBits(*typeOnStack.asIntegerType()); + m_context << + pow(u256(10), typeOnStack.fractionalDigits()) << + (typeOnStack.isSigned() ? Instruction::SDIV : Instruction::DIV); + if (_cleanupNeeded) + cleanHigherOrderBits(targetType); + } else { - IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer - ? dynamic_cast(_typeOnStack) : addressType; + IntegerType const& typeOnStack = + stackTypeCategory == Type::Category::Integer ? + dynamic_cast(_typeOnStack) : + addressType; // Widening: clean up according to source type width // Non-widening and force: clean up according to target type bits if (targetType.numBits() > typeOnStack.numBits()) cleanHigherOrderBits(typeOnStack); else if (_cleanupNeeded) cleanHigherOrderBits(targetType); - if (chopSignBitsPending) - { - if (targetType.numBits() < 256) - m_context - << ((u256(1) << targetType.numBits()) - 1) - << Instruction::AND; - chopSignBitsPending = false; - } + } + if (chopSignBitsPending) + { + if (targetType.numBits() < 256) + m_context + << ((u256(1) << targetType.numBits()) - 1) + << Instruction::AND; + chopSignBitsPending = false; } } break; diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index e7e069352..8b34953e0 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -222,18 +222,18 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const m_context << Instruction::SWAP1 << Instruction::SLOAD << Instruction::SWAP1 << u256(0x100) << Instruction::EXP << Instruction::SWAP1 << Instruction::DIV; - if (m_dataType->category() == Type::Category::FixedPoint) - // implementation should be very similar to the integer case. - solUnimplemented("Not yet implemented - FixedPointType."); if (m_dataType->category() == Type::Category::FixedBytes) { CompilerUtils(m_context).leftShiftNumberOnStack(256 - 8 * m_dataType->storageBytes()); cleaned = true; } - else if ( - m_dataType->category() == Type::Category::Integer && - dynamic_cast(*m_dataType).isSigned() - ) + else if (( + m_dataType->category() == Type::Category::Integer && + dynamic_cast(*m_dataType).isSigned() + ) || ( + m_dataType->category() == Type::Category::FixedPoint && + dynamic_cast(*m_dataType).isSigned() + )) { m_context << u256(m_dataType->storageBytes() - 1) << Instruction::SIGNEXTEND; cleaned = true; @@ -332,7 +332,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc // remove the higher order bits utils.convertType(_sourceType, *m_dataType, true, true); } - m_context << Instruction::MUL << Instruction::OR; + m_context << Instruction::MUL << Instruction::OR; // stack: value storage_ref updated_value m_context << Instruction::SWAP1 << Instruction::SSTORE; if (_move) diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index bd86dc9c2..7a7e80051 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -282,7 +282,7 @@ string YulUtilFunctions::leftAlignFunction(Type const& _type) templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)"); break; case Type::Category::FixedPoint: - solUnimplemented("Fixed point types not implemented."); + templ("body", "aligned := " + leftAlignFunction(*dynamic_cast(_type).asIntegerType()) + "(value)"); break; case Type::Category::Array: case Type::Category::Struct: @@ -435,7 +435,7 @@ string YulUtilFunctions::shiftRightSignedFunctionDynamic() string YulUtilFunctions::typedShiftLeftFunction(Type const& _type, Type const& _amountType) { - solUnimplementedAssert(_type.category() != Type::Category::FixedPoint, "Not yet implemented - FixedPointType."); + solAssert(_type.category() != Type::Category::FixedPoint, "No operators for fixed point types."); solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, ""); solAssert(_amountType.category() == Type::Category::Integer, ""); solAssert(!dynamic_cast(_amountType).isSigned(), ""); @@ -458,7 +458,7 @@ string YulUtilFunctions::typedShiftLeftFunction(Type const& _type, Type const& _ string YulUtilFunctions::typedShiftRightFunction(Type const& _type, Type const& _amountType) { - solUnimplementedAssert(_type.category() != Type::Category::FixedPoint, "Not yet implemented - FixedPointType."); + solAssert(_type.category() != Type::Category::FixedPoint, "No operators for fixed point types."); solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, ""); solAssert(_amountType.category() == Type::Category::Integer, ""); solAssert(!dynamic_cast(_amountType).isSigned(), ""); @@ -3266,13 +3266,17 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) case Type::Category::Integer: case Type::Category::RationalNumber: case Type::Category::Contract: + case Type::Category::FixedPoint: { if (RationalNumberType const* rational = dynamic_cast(&_from)) - solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType."); + if (rational->isFractional()) + solAssert(toCategory == Type::Category::FixedPoint, ""); if (toCategory == Type::Category::FixedBytes) { solAssert( - fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber, + fromCategory == Type::Category::Integer || + fromCategory == Type::Category::FixedPoint || + fromCategory == Type::Category::RationalNumber, "Invalid conversion to FixedBytesType requested." ); FixedBytesType const& toBytesType = dynamic_cast(_to); @@ -3284,6 +3288,7 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) } else if (toCategory == Type::Category::Enum) { + solAssert(fromCategory != Type::Category::FixedPoint, ""); solAssert(_from.mobileType(), ""); body = Whiskers("converted := ((value))") @@ -3293,7 +3298,42 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) .render(); } else if (toCategory == Type::Category::FixedPoint) - solUnimplemented("Not yet implemented - FixedPointType."); + { + FixedPointType const& toFixedType = dynamic_cast(_to); + int digitDifference = static_cast(toFixedType.fractionalDigits()); + bool isSigned = false; + if (auto const* fromIntegerType = dynamic_cast(&_from)) + { + solAssert( + fromIntegerType->maxValue() <= toFixedType.maxIntegerValue() && + fromIntegerType->minValue() >= toFixedType.minIntegerValue(), + "" + ); + isSigned = fromIntegerType->isSigned(); + } + else if (auto const* fromFixedType = dynamic_cast(&_from)) + { + isSigned = fromFixedType->isSigned(); + digitDifference -= static_cast(fromFixedType->fractionalDigits()); + if (fromFixedType->isSigned() != toFixedType.isSigned()) + solAssert(digitDifference >= 0, ""); + } + else if (auto const* fromRationalType = dynamic_cast(&_from)) + { + if (fromRationalType->isFractional()) + digitDifference -= static_cast(fromRationalType->fixedPointType()->fractionalDigits()); + isSigned = fromRationalType->isNegative(); + } + else + solAssert(false, ""); + // TODO maybe target cleanup can be avoided in some cases. + body = Whiskers{"converted := (((value), ))"} + ("cleanupSource", cleanupFunction(_from)) + ("operator", digitDifference >= 0 ? "mul" : isSigned ? "sdiv" : "div") + ("factor", "1" + string(static_cast(abs(digitDifference)), '0')) + ("cleanupTarget", cleanupFunction(toFixedType)) + .render(); + } else if (toCategory == Type::Category::Address) body = Whiskers("converted := (value)") @@ -3310,23 +3350,37 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) toCategory == Type::Category::Integer ? dynamic_cast(_to) : addressType; - - // Clean according to the "to" type, except if this is - // a widening conversion. - IntegerType const* cleanupType = &to; - if (fromCategory != Type::Category::RationalNumber) + if (fromCategory == Type::Category::FixedPoint) { - IntegerType const& from = - fromCategory == Type::Category::Integer ? - dynamic_cast(_from) : - addressType; - if (to.numBits() > from.numBits()) - cleanupType = &from; + auto const& fixedFrom = dynamic_cast(_from); + solAssert(fixedFrom.isSigned() == to.isSigned(), "" ); + body = + Whiskers("converted := (
((value), ))") + ("cleanFixed", cleanupFunction(_from)) + ("div", to.isSigned() ? "sdiv" : "div") + ("digits", "1"s + string(fixedFrom.fractionalDigits(), '0')) + ("cleanInt", cleanupFunction(to)) + .render(); + } + else + { + // Clean according to the "to" type, except if this is + // a widening conversion. + IntegerType const* cleanupType = &to; + if (fromCategory != Type::Category::RationalNumber) + { + IntegerType const& from = + fromCategory == Type::Category::Integer ? + dynamic_cast(_from) : + addressType; + if (to.numBits() > from.numBits()) + cleanupType = &from; + } + body = + Whiskers("converted := (value)") + ("cleanInt", cleanupFunction(*cleanupType)) + .render(); } - body = - Whiskers("converted := (value)") - ("cleanInt", cleanupFunction(*cleanupType)) - .render(); } break; } @@ -3339,9 +3393,6 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) .render(); break; } - case Type::Category::FixedPoint: - solUnimplemented("Fixed point types not implemented."); - break; case Type::Category::Struct: { solAssert(toCategory == Type::Category::Struct, ""); @@ -3390,11 +3441,23 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) ("shift", shiftRightFunction(256 - from.numBytes() * 8)) ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) .render(); + else if (toCategory == Type::Category::FixedPoint) + { + auto const& toFixedPoint = dynamic_cast(_to); + IntegerType integerType( + toFixedPoint.numBits(), + toFixedPoint.isSigned() ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned + ); + body = + Whiskers("converted := (value)") + ("convert", conversionFunction(_from, integerType)) + .render(); + } else if (toCategory == Type::Category::Address) body = Whiskers("converted := (value)") - ("convert", conversionFunction(_from, IntegerType(160))) - .render(); + ("convert", conversionFunction(_from, IntegerType(160))) + .render(); else { // clear for conversion to longer bytes @@ -3734,15 +3797,15 @@ string YulUtilFunctions::cleanupFunction(Type const& _type) templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")"); break; } + case Type::Category::FixedPoint: + templ("body", "cleaned := " + cleanupFunction(*dynamic_cast(_type).asIntegerType()) + "(value)"); + break; case Type::Category::RationalNumber: templ("body", "cleaned := value"); break; case Type::Category::Bool: templ("body", "cleaned := iszero(iszero(value))"); break; - case Type::Category::FixedPoint: - solUnimplemented("Fixed point types not implemented."); - break; case Type::Category::Function: switch (dynamic_cast(_type).kind()) {