From d1aa21793652ed0cc024ae3cc4d5f440c646dc88 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 | 124 ++++++++++++++---- libsolidity/codegen/ExpressionCompiler.cpp | 2 + libsolidity/codegen/LValue.cpp | 16 +-- libsolidity/codegen/YulUtilFunctions.cpp | 112 +++++++++++++--- libsolidity/codegen/YulUtilFunctions.h | 4 + .../codegen/ir/IRGeneratorForStatements.cpp | 2 + 6 files changed, 205 insertions(+), 55 deletions(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 9ae1741dd..15fdd1ff6 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,51 @@ 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->closestFixedPointType()->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)) << + Instruction::SWAP1 << + (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 +943,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; @@ -1553,8 +1614,13 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda cleanupNeeded = false; } else if (IntegerType const* intType = dynamic_cast(&_type)) + { if (!intType->isSigned()) cleanupNeeded = false; + } + else if (auto const* fixedPointType = dynamic_cast(&_type)) + if (!fixedPointType->isSigned()) + cleanupNeeded = false; } if (_fromCalldata) convertType(_type, _type, cleanupNeeded, false, true); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 4c86aa980..0a4fcc75a 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -2229,6 +2229,8 @@ void ExpressionCompiler::appendCompareOperatorCode(Token _operator, Type const& bool isSigned = false; if (auto type = dynamic_cast(&_type)) isSigned = type->isSigned(); + else if (auto type = dynamic_cast(&_type)) + isSigned = type->isSigned(); switch (_operator) { 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 f61a76d32..c85d4a495 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -292,7 +292,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: @@ -445,7 +445,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(), ""); @@ -468,7 +468,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(), ""); @@ -492,6 +492,22 @@ string YulUtilFunctions::typedShiftRightFunction(Type const& _type, Type const& }); } +string YulUtilFunctions::fixedPointShiftFunction(int _digits, bool _signed) +{ + string digitsStr = _digits < 0 ? ("n_" + to_string(-_digits)) : to_string(_digits); + string const functionName = "fixed_point_shift_" + digitsStr + (_signed ? "_signed" : "_unsigned"); + return m_functionCollector.createFunction(functionName, [&](vector& _args, vector& _ret) { + _args = {"value"}; + _ret = {"result"}; + return + Whiskers("result := (value, )") + ("op", _digits >= 0 ? "mul" : _signed ? "sdiv" : "div") + ("factor", ("1" + string(static_cast(abs(_digits)), '0'))) + .render(); + }); +} + + string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes) { solAssert(_numBytes <= 32, ""); @@ -3276,11 +3292,18 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) break; case Type::Category::Integer: case Type::Category::RationalNumber: + case Type::Category::FixedPoint: { - solAssert(_from.mobileType(), ""); if (RationalNumberType const* rational = dynamic_cast(&_from)) + { if (rational->isFractional()) - solAssert(toCategory == Type::Category::FixedPoint, ""); + solAssert( + toCategory == Type::Category::FixedPoint && + rational->closestFixedPointType(), + ""); + else + solAssert(rational->mobileType(), ""); + } if (toCategory == Type::Category::Address || toCategory == Type::Category::Contract) body = @@ -3296,12 +3319,56 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) if (auto const* toFixedBytes = dynamic_cast(&_to)) convert = shiftLeftFunction(256 - toFixedBytes->numBytes() * 8); - else if (dynamic_cast(&_to)) - solUnimplementedAssert(false, ""); - else if (dynamic_cast(&_to)) + else if (auto const* toFixedPoint = dynamic_cast(&_to)) { - solUnimplementedAssert(fromCategory != Type::Category::FixedPoint, ""); - convert = identityFunction(); + int digitDifference = static_cast(toFixedPoint->fractionalDigits()); + bool isSigned = false; + if (auto const* fromIntegerType = dynamic_cast(&_from)) + { + solAssert( + fromIntegerType->maxValue() <= toFixedPoint->maxIntegerValue() && + fromIntegerType->minValue() >= toFixedPoint->minIntegerValue(), + "" + ); + isSigned = fromIntegerType->isSigned(); + } + else if (auto const* fromFixedType = dynamic_cast(&_from)) + { + isSigned = fromFixedType->isSigned(); + digitDifference -= static_cast(fromFixedType->fractionalDigits()); + if (fromFixedType->isSigned() != toFixedPoint->isSigned()) + solAssert(digitDifference >= 0, ""); + } + else if (auto const* fromRationalType = dynamic_cast(&_from)) + { + if (fromRationalType->isFractional()) + digitDifference -= static_cast( + fromRationalType->closestFixedPointType()->fractionalDigits() + ); + isSigned = fromRationalType->isNegative(); + solAssert( + fromRationalType->value() >= toFixedPoint->minValue() && + fromRationalType->value() <= toFixedPoint->maxValue(), + "" + ); + } + else + solAssert(false, ""); + convert = fixedPointShiftFunction(digitDifference, isSigned); + } + else if (auto const* to = dynamic_cast(&_to)) + { + if (fromCategory == Type::Category::FixedPoint) + { + auto const& fixedFrom = dynamic_cast(_from); + solAssert(fixedFrom.isSigned() == to->isSigned(), "" ); + convert = fixedPointShiftFunction( + static_cast(fixedFrom.fractionalDigits()), + to->isSigned() + ); + } + else + convert = identityFunction(); } else if (toCategory == Type::Category::Enum) { @@ -3325,9 +3392,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, ""); @@ -3376,11 +3440,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 @@ -3720,15 +3796,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()) { diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index a03ee75c2..b82b1e634 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -109,6 +109,10 @@ public: std::string typedShiftLeftFunction(Type const& _type, Type const& _amountType); std::string typedShiftRightFunction(Type const& _type, Type const& _amountType); + /// @returns the name of a function that shifts a decimal fixed point number by + /// @a _decimal decimals (positive multiplies, negative divides). + std::string fixedPointShiftFunction(int _digits, bool _signed); + /// @returns the name of a function which replaces the /// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant /// byte) by the _numBytes least significant bytes of `toInsert`. diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 96b8c991d..800bb0bb8 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -797,6 +797,8 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp) bool isSigned = false; if (auto type = dynamic_cast(commonType)) isSigned = type->isSigned(); + if (auto type = dynamic_cast(commonType)) + isSigned = type->isSigned(); string args = expressionAsType(_binOp.leftExpression(), *commonType, true) +