Skip to content

Commit

Permalink
Fixed point types.
Browse files Browse the repository at this point in the history
  • Loading branch information
chriseth committed Mar 1, 2021
1 parent eacf7c1 commit 75dbd58
Show file tree
Hide file tree
Showing 9 changed files with 291 additions and 39 deletions.
2 changes: 0 additions & 2 deletions libsolidity/ast/Types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
47 changes: 36 additions & 11 deletions libsolidity/codegen/CompilerUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -760,15 +760,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<IntegerType const&>(_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<IntegerType const&>(_targetType).isSigned();
else if (_chopSignBits && targetTypeCategory == Type::Category::FixedPoint)
chopSignBitsPending = dynamic_cast<FixedPointType const&>(_targetType).isSigned();

switch (stackTypeCategory)
{
Expand All @@ -784,6 +780,7 @@ void CompilerUtils::convertType(
if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8)
convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded);
}
// TODO FIxed?
else if (targetTypeCategory == Type::Category::Address)
{
solAssert(typeOnStack.numBytes() * 8 == 160, "");
Expand Down Expand Up @@ -820,6 +817,7 @@ void CompilerUtils::convertType(
}
break;
case Type::Category::FixedPoint:
// TODO
solUnimplemented("Not yet implemented - FixedPointType.");
case Type::Category::Address:
case Type::Category::Integer:
Expand Down Expand Up @@ -867,10 +865,37 @@ void CompilerUtils::convertType(
);
//shift all integer bits onto the left side of the fixed type
FixedPointType const& targetFixedPointType = dynamic_cast<FixedPointType const&>(_targetType);
if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack))
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);
solUnimplemented("Not yet implemented - FixedPointType.");
}
else if (auto const* typeOnStack = dynamic_cast<FixedPointType const*>(&_typeOnStack))
{
solUnimplementedAssert(typeOnStack->isSigned() == targetFixedPointType.isSigned(), "");
fractionalDigitsOnStack = typeOnStack->fractionalDigits();
// TODO cleanup?
}
else if (auto const* typeOnStack = dynamic_cast<RationalNumberType const*>(&_typeOnStack))
{
if (typeOnStack->isFractional())
fractionalDigitsOnStack = typeOnStack->fixedPointType()->fractionalDigits();
}
else
{
//TODO allow fixed bytes
solAssert(false, "");
}
if (fractionalDigitsOnStack < targetFixedPointType.fractionalDigits())
m_context <<
pow(u256(10), targetFixedPointType.fractionalDigits() - fractionalDigitsOnStack) <<
Instruction::MUL;
else if (fractionalDigitsOnStack > targetFixedPointType.fractionalDigits())
m_context <<
pow(u256(10), fractionalDigitsOnStack - targetFixedPointType.fractionalDigits()) <<
Instruction::DIV;
}
else
{
Expand Down
26 changes: 25 additions & 1 deletion libsolidity/codegen/ExpressionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,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."
Expand Down Expand Up @@ -2170,7 +2171,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)
Expand Down
150 changes: 145 additions & 5 deletions libsolidity/codegen/YulUtilFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,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();
Expand Down Expand Up @@ -3085,9 +3187,11 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
case Type::Category::Integer:
case Type::Category::RationalNumber:
case Type::Category::Contract:
// TODO add FixedPoint to the mix
{
if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&_from))
solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType.");
if (rational->isFractional())
solAssert(toCategory == Type::Category::FixedPoint, "");
if (toCategory == Type::Category::FixedBytes)
{
solAssert(
Expand All @@ -3112,7 +3216,43 @@ 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<FixedPointType const&>(_to);
int digitDifference = static_cast<int>(toFixedType.fractionalDigits());
bool isSigned = false;
if (auto const* fromIntegerType = dynamic_cast<IntegerType const*>(&_from))
{
// TODO assert that the scaled value still fits
isSigned = fromIntegerType->isSigned();
}
else if (auto const* fromFixedType = dynamic_cast<FixedPointType const*>(&_from))
{
// TODO clean source or target?
solUnimplementedAssert(fromFixedType->isSigned() == toFixedType.isSigned(), "");
isSigned = fromFixedType->isSigned();
digitDifference -= static_cast<int>(fromFixedType->fractionalDigits());
}
else if (auto const* fromRationalType = dynamic_cast<RationalNumberType const*>(&_from))
{
if (fromRationalType->isFractional())
digitDifference -= static_cast<int>(fromRationalType->fixedPointType()->fractionalDigits());
isSigned = fromRationalType->isNegative();
}
else
{
//TODO allow fixed bytes
solAssert(false, "");
}
// TODO cleanup?
if (digitDifference > 0)
body = "converted := mul(value, 1" + string(static_cast<unsigned>(digitDifference), '0') + ")";
else if (digitDifference < 0)
{
body = "converted := " + (isSigned ? string("s") : string()) + "div(value, 1" + string(static_cast<unsigned>(-digitDifference), '0') + ")";
}
else
body = "converted := value\n";
}
else if (toCategory == Type::Category::Address)
body =
Whiskers("converted := <convert>(value)")
Expand Down Expand Up @@ -3497,15 +3637,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<FixedPointType const&>(_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<FunctionType const&>(_type).kind())
{
Expand Down
12 changes: 12 additions & 0 deletions libsolidity/codegen/YulUtilFunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,18 @@ class YulUtilFunctions
/// 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
Expand Down
Loading

0 comments on commit 75dbd58

Please sign in to comment.