Fixed point types.

This commit is contained in:
chriseth 2021-02-25 18:11:02 +01:00
parent 70cbd5d8fb
commit f91e512f02
9 changed files with 213 additions and 27 deletions

View File

@ -553,8 +553,6 @@ BoolResult IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const
return (!isSigned() && (numBits() == fixedBytesType->numBytes() * 8));
else if (dynamic_cast<EnumType const*>(&_convertTo))
return true;
else if (auto fixedPointType = dynamic_cast<FixedPointType const*>(&_convertTo))
return (isSigned() == fixedPointType->isSigned()) && (numBits() == fixedPointType->numBits());
return false;
}

View File

@ -893,6 +893,7 @@ void CompilerUtils::convertType(
unsigned fractionalDigitsOnStack = 0;
if (auto const* typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack))
{
// TODO do we have to subtract the fractional digits?
if (targetFixedPointType.numBits() > typeOnStack->numBits())
cleanHigherOrderBits(*typeOnStack);
}

View File

@ -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<FixedPointType const&>(_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<IntegerType const&>(_type);
if (m_context.arithmetic() == Arithmetic::Checked)

View File

@ -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 <functionName>(x, y) -> product {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
<?signed>
// overflow, if x > 0, y > 0 and x > (maxValue / y)
if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(<maxValue>, y))) { <panic>() }
// underflow, if x > 0, y < 0 and y < (minValue / x)
if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(<minValue>, x))) { <panic>() }
// underflow, if x < 0, y > 0 and x < (minValue / y)
if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(<minValue>, y))) { <panic>() }
// overflow, if x < 0, y < 0 and x < (maxValue / y)
if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(<maxValue>, y))) { <panic>() }
<!signed>
// overflow, if x != 0 and y > (maxValue / x)
if and(iszero(iszero(x)), gt(y, div(<maxValue>, x))) { <panic>() }
</signed>
product := mul(x, y)
product := <?signed>sdiv<!signed>div</signed>(product, <multiplier>)
}
)")
("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 `<min> / -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 <functionName>(x, y) -> r {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
if iszero(y) { <panicDivZero>() }
<?signed>
<?checked>
// overflow for minVal / -1
if and(
eq(x, <minVal>),
eq(y, mul(sub(0, 1), <multiplier>))
) { <panicOverflow>() }
</checked>
</signed>
r := <?signed>sdiv<!signed>div</signed>(mul(x, <multiplier>), 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();

View File

@ -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

View File

@ -797,6 +797,8 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
bool isSigned = false;
if (auto type = dynamic_cast<IntegerType const*>(commonType))
isSigned = type->isSigned();
else if (auto type = dynamic_cast<FixedPointType const*>(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<IntegerType const*>(&_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<IntegerType const&>(_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<FixedPointType const&>(_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.");
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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() ->