diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 06d6a0fd9..8c103466f 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -553,8 +553,6 @@ BoolResult IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const return (!isSigned() && (numBits() == fixedBytesType->numBytes() * 8)); else if (dynamic_cast(&_convertTo)) return true; - else if (auto fixedPointType = dynamic_cast(&_convertTo)) - return (isSigned() == fixedPointType->isSigned()) && (numBits() == fixedPointType->numBits()); return false; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 056878081..b4bee9b52 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -893,6 +893,7 @@ void CompilerUtils::convertType( unsigned fractionalDigitsOnStack = 0; if (auto const* typeOnStack = dynamic_cast(&_typeOnStack)) { + // TODO do we have to subtract the fractional digits? if (targetFixedPointType.numBits() > typeOnStack->numBits()) cleanHigherOrderBits(*typeOnStack); } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 338430723..f15bdd213 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -434,6 +434,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) case Token::Inc: // ++ (pre- or postfix) case Token::Dec: // -- (pre- or postfix) solAssert(!!m_currentLValue, "LValue not retrieved."); + // TODO solUnimplementedAssert( type.category() != Type::Category::FixedPoint, "Not yet implemented - FixedPointType." @@ -2265,7 +2266,30 @@ void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token _operator, Type void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type const& _type) { if (_type.category() == Type::Category::FixedPoint) - solUnimplemented("Not yet implemented - FixedPointType."); + { + bool checked = (m_context.arithmetic() == Arithmetic::Checked); + FixedPointType const& type = dynamic_cast(_type); + string functionName; + switch (_operator) + { + case Token::Add: + functionName = m_context.utilFunctions().fixedAddFunction(type, checked); + break; + case Token::Sub: + functionName = m_context.utilFunctions().fixedSubFunction(type, checked); + break; + case Token::Mul: + functionName = m_context.utilFunctions().fixedMulFunction(type, checked); + break; + case Token::Div: + functionName = m_context.utilFunctions().fixedDivFunction(type, checked); + break; + default: + solAssert(false, "Unknown arithmetic operator."); + } + m_context.callYulFunction(functionName, 2, 1); + return; + } IntegerType const& type = dynamic_cast(_type); if (m_context.arithmetic() == Arithmetic::Checked) diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 6c0ddb831..1f4c4948e 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -1139,6 +1139,108 @@ string YulUtilFunctions::wrappingIntExpFunction( }); } +string YulUtilFunctions::fixedAddFunction(FixedPointType const& _type, bool _checked) +{ + return + _checked ? + overflowCheckedIntAddFunction(*_type.asIntegerType()) : + wrappingIntAddFunction(*_type.asIntegerType()); +} + +string YulUtilFunctions::fixedSubFunction(FixedPointType const& _type, bool _checked) +{ + return + _checked ? + overflowCheckedIntSubFunction(*_type.asIntegerType()) : + wrappingIntSubFunction(*_type.asIntegerType()); +} + +string YulUtilFunctions::fixedMulFunction(FixedPointType const& _type, bool _checked) +{ + // TODO how does truncation behave for negative numbers? + solUnimplementedAssert(_type.numBits() <= 128, "Multiplication only implemented for up to 128 bits."); + + // TODO This is exactly the same as integer mul, but with maxValue being + // maxValue * mulitplier and re-scaling at the end + // This only works for up to 128 bits. + string functionName = "fixed_mul_" + _type.identifier() + (_checked ? "_checked" : ""); + return m_functionCollector.createFunction(functionName, [&]() { + u256 multiplier = pow(u256(10), _type.fractionalDigits()); + return + // Multiplication by zero could be treated separately and directly return zero. + Whiskers(R"( + function (x, y) -> product { + x := (x) + y := (y) + + // overflow, if x > 0, y > 0 and x > (maxValue / y) + if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(, y))) { () } + // underflow, if x > 0, y < 0 and y < (minValue / x) + if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(, x))) { () } + // underflow, if x < 0, y > 0 and x < (minValue / y) + if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(, y))) { () } + // overflow, if x < 0, y < 0 and x < (maxValue / y) + if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(, y))) { () } + + // overflow, if x != 0 and y > (maxValue / x) + if and(iszero(iszero(x)), gt(y, div(, x))) { () } + + product := mul(x, y) + product := sdivdiv(product, ) + } + )") + ("functionName", functionName) + ("signed", _type.isSigned()) + ("maxValue", formatNumber(u256(_type.asIntegerType()->maxValue()) * multiplier)) + ("minValue", formatNumber(u256(_type.asIntegerType()->minValue()) * multiplier)) + ("cleanupFunction", cleanupFunction(_type)) + ("panic", panicFunction(PanicCode::UnderOverflow)) + ("multiplier", formatNumber(multiplier)) + .render(); + }); +} + +string YulUtilFunctions::fixedDivFunction(FixedPointType const& _type, bool _checked) +{ + // TODO how does truncation behave for negative numbers? + solUnimplementedAssert(_type.numBits() <= 128, "Division only implemented for up to 128 bits."); + + // TODO this is similar to int div in the same way as fixed mul is similar to int mul. + // TODO There more overflow cases than just ` / -1` + string functionName = "fixed_div_" + _type.identifier() + (_checked ? "_checked" : ""); + return m_functionCollector.createFunction(functionName, [&]() { + u256 multiplier = pow(u256(10), _type.fractionalDigits()); + return + Whiskers(R"( + function (x, y) -> r { + x := (x) + y := (y) + if iszero(y) { () } + + + // overflow for minVal / -1 + if and( + eq(x, ), + eq(y, mul(sub(0, 1), )) + ) { () } + + + r := sdivdiv(mul(x, ), y) + } + )") + ("functionName", functionName) + ("signed", _type.isSigned()) + ("checked", _checked) + ("minValue", formatNumber(u256(_type.asIntegerType()->minValue()))) + ("cleanupFunction", cleanupFunction(_type)) + ("panicDivZero", panicFunction(PanicCode::DivisionByZero)) + ("panicOverflow", panicFunction(PanicCode::UnderOverflow)) + ("multiplier", formatNumber(multiplier)) + .render(); + }); + +} + string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) { string functionName = "array_length_" + _type.identifier(); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 6b7aa1545..45fce26bb 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -202,6 +202,18 @@ public: /// signature: (base, exponent) -> power std::string wrappingIntExpFunction(IntegerType const& _type, IntegerType const& _exponentType); + /// signature: (x, y) -> sum + std::string fixedAddFunction(FixedPointType const& _type, bool _checked); + + /// signature: (x, y) -> difference + std::string fixedSubFunction(FixedPointType const& _type, bool _checked); + + /// signature: (x, y) -> product + std::string fixedMulFunction(FixedPointType const& _type, bool _checked); + + /// signature: (x, y) -> quotient + std::string fixedDivFunction(FixedPointType const& _type, bool _checked); + /// @returns the name of a function that fetches the length of the given /// array /// signature: (array) -> length diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 86fcddc92..4ea2d83da 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(); + else if (auto type = dynamic_cast(commonType)) + isSigned = type->isSigned(); string args = expressionAsType(_binOp.leftExpression(), *commonType, true) + @@ -2726,32 +2728,52 @@ string IRGeneratorForStatements::binaryOperation( } else if (TokenTraits::isArithmeticOp(_operator)) { - solUnimplementedAssert( - _type.category() != Type::Category::FixedPoint, - "Not yet implemented - FixedPointType." - ); - IntegerType const* type = dynamic_cast(&_type); - solAssert(type, ""); bool checked = m_context.arithmetic() == Arithmetic::Checked; - switch (_operator) + if (_type.category() == Type::Category::Integer) { - case Token::Add: - fun = checked ? m_utils.overflowCheckedIntAddFunction(*type) : m_utils.wrappingIntAddFunction(*type); - break; - case Token::Sub: - fun = checked ? m_utils.overflowCheckedIntSubFunction(*type) : m_utils.wrappingIntSubFunction(*type); - break; - case Token::Mul: - fun = checked ? m_utils.overflowCheckedIntMulFunction(*type) : m_utils.wrappingIntMulFunction(*type); - break; - case Token::Div: - fun = checked ? m_utils.overflowCheckedIntDivFunction(*type) : m_utils.wrappingIntDivFunction(*type); - break; - case Token::Mod: - fun = m_utils.intModFunction(*type); - break; - default: - break; + IntegerType const& type = dynamic_cast(_type); + switch (_operator) + { + case Token::Add: + fun = checked ? m_utils.overflowCheckedIntAddFunction(type) : m_utils.wrappingIntAddFunction(type); + break; + case Token::Sub: + fun = checked ? m_utils.overflowCheckedIntSubFunction(type) : m_utils.wrappingIntSubFunction(type); + break; + case Token::Mul: + fun = checked ? m_utils.overflowCheckedIntMulFunction(type) : m_utils.wrappingIntMulFunction(type); + break; + case Token::Div: + fun = checked ? m_utils.overflowCheckedIntDivFunction(type) : m_utils.wrappingIntDivFunction(type); + break; + case Token::Mod: + fun = m_utils.intModFunction(type); + break; + default: + break; + } + } + else + { + solAssert(_type.category() == Type::Category::FixedPoint, ""); + FixedPointType const& type = dynamic_cast(_type); + switch (_operator) + { + case Token::Add: + fun = m_utils.fixedAddFunction(type, checked); + break; + case Token::Sub: + fun = m_utils.fixedSubFunction(type, checked); + break; + case Token::Mul: + fun = m_utils.fixedMulFunction(type, checked); + break; + case Token::Div: + fun = m_utils.fixedDivFunction(type, checked); + break; + default: + solAssert(false, "Unknown arithmetic operator."); + } } } diff --git a/test/libsolidity/semanticTests/fixedPoint/mul_by_constant.sol b/test/libsolidity/semanticTests/fixedPoint/mul_by_constant.sol new file mode 100644 index 000000000..3a8496384 --- /dev/null +++ b/test/libsolidity/semanticTests/fixedPoint/mul_by_constant.sol @@ -0,0 +1,9 @@ +contract c { + function mul(fixed a) public pure returns (fixed c) { + return a * 1.5; + } +} +// ==== +// compileViaYul: also +// ---- +// mul(fixed128x18): 100 -> 0x96 diff --git a/test/libsolidity/semanticTests/fixedPoint/multiplication.sol b/test/libsolidity/semanticTests/fixedPoint/multiplication.sol new file mode 100644 index 000000000..7df9ad175 --- /dev/null +++ b/test/libsolidity/semanticTests/fixedPoint/multiplication.sol @@ -0,0 +1,7 @@ +contract c { + function mul(fixed a, fixed b) public pure returns (fixed c) { + return a * b; + } +} +// ---- +// mul(fixed128x18,fixed128x18): 200, 10000000000000000000000 -> 2000000 diff --git a/test/libsolidity/semanticTests/fixedPoint/state_variables.sol b/test/libsolidity/semanticTests/fixedPoint/state_variables.sol new file mode 100644 index 000000000..dddca498e --- /dev/null +++ b/test/libsolidity/semanticTests/fixedPoint/state_variables.sol @@ -0,0 +1,11 @@ +contract C { + fixed x = 1.7; + fixed immutable y = -1.8; + fixed public z = - 9.3; + fixed constant w = -3.4; + function f() public view returns (fixed, fixed, fixed, fixed) { + return (x, y, this.z(), w); + } +} +// ---- +// f() ->