Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 85 additions & 3 deletions kmir/src/kmir/kdist/mir-semantics/rt/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -1421,7 +1421,41 @@ Boolean values can also be cast to Integers (encoding `true` as `1`).
[preserves-definedness] // ensures #numTypeOf is defined
```

Casts involving `Float` values are currently not implemented.
Casts involving `Float` values: `IntToFloat`, `FloatToInt`, and `FloatToFloat`.

```k
// IntToFloat: convert integer to float with the target float type's precision
rule <k> #cast(Integer(VAL, _WIDTH, _SIGNEDNESS), castKindIntToFloat, _, TY)
=> Float(
Int2Float(VAL,
#significandBits(#floatTypeOf(lookupTy(TY))),
#exponentBits(#floatTypeOf(lookupTy(TY)))),
#bitWidth(#floatTypeOf(lookupTy(TY)))
)
...
</k>
[preserves-definedness]

// FloatToInt: truncate float towards zero and convert to integer
rule <k> #cast(Float(VAL, _WIDTH), castKindFloatToInt, _, TY)
=> #intAsType(Float2Int(VAL), 128, #intTypeOf(lookupTy(TY)))
...
</k>
requires #isIntType(lookupTy(TY))
[preserves-definedness]

// FloatToFloat: round float to the target float type's precision
rule <k> #cast(Float(VAL, _WIDTH), castKindFloatToFloat, _, TY)
=> Float(
roundFloat(VAL,
#significandBits(#floatTypeOf(lookupTy(TY))),
#exponentBits(#floatTypeOf(lookupTy(TY)))),
#bitWidth(#floatTypeOf(lookupTy(TY)))
)
...
</k>
[preserves-definedness]
```

### Casts between pointer types

Expand Down Expand Up @@ -2015,6 +2049,20 @@ are correct.
rule onInt(binOpRem, X, Y) => X %Int Y requires Y =/=Int 0 [preserves-definedness]
// operation undefined otherwise

// performs the given operation on IEEE 754 floats
// Note: Rust's float % is truncating remainder: x - trunc(x/y) * y
// This differs from K's %Float which is IEEE 754 remainder (round to nearest).
syntax Float ::= onFloat( BinOp, Float, Float ) [function]
// -------------------------------------------------------
rule onFloat(binOpAdd, X, Y) => X +Float Y [preserves-definedness]
rule onFloat(binOpAddUnchecked, X, Y) => X +Float Y [preserves-definedness]
rule onFloat(binOpSub, X, Y) => X -Float Y [preserves-definedness]
rule onFloat(binOpSubUnchecked, X, Y) => X -Float Y [preserves-definedness]
rule onFloat(binOpMul, X, Y) => X *Float Y [preserves-definedness]
rule onFloat(binOpMulUnchecked, X, Y) => X *Float Y [preserves-definedness]
rule onFloat(binOpDiv, X, Y) => X /Float Y [preserves-definedness]
rule onFloat(binOpRem, X, Y) => X -Float (Y *Float truncFloat(X /Float Y)) [preserves-definedness]

// error cases for isArithmetic(BOP):
// * arguments must be Numbers

Expand Down Expand Up @@ -2083,6 +2131,18 @@ are correct.
// infinite precision result must equal truncated result
andBool truncate(onInt(BOP, ARG1, ARG2), WIDTH, Unsigned) ==Int onInt(BOP, ARG1, ARG2)
[preserves-definedness]

// Float arithmetic: Rust never emits CheckedBinaryOp for floats (only BinaryOp),
// so the checked flag is always false here. See rustc_const_eval/src/interpret/operator.rs:
// binary_float_op returns a plain value, not a (value, overflow) pair.
rule #applyBinOp(
BOP,
Float(ARG1, WIDTH),
Float(ARG2, WIDTH),
false)
=> Float(onFloat(BOP, ARG1, ARG2), WIDTH)
requires isArithmetic(BOP)
[preserves-definedness]
```

#### Comparison operations
Expand Down Expand Up @@ -2119,6 +2179,14 @@ The argument types must be the same for all comparison operations, however this
rule cmpOpBool(binOpGe, X, Y) => cmpOpBool(binOpLe, Y, X)
rule cmpOpBool(binOpGt, X, Y) => cmpOpBool(binOpLt, Y, X)

syntax Bool ::= cmpOpFloat ( BinOp, Float, Float ) [function]
rule cmpOpFloat(binOpEq, X, Y) => X ==Float Y
rule cmpOpFloat(binOpLt, X, Y) => X <Float Y
rule cmpOpFloat(binOpLe, X, Y) => X <=Float Y
rule cmpOpFloat(binOpNe, X, Y) => X =/=Float Y
rule cmpOpFloat(binOpGe, X, Y) => X >=Float Y
rule cmpOpFloat(binOpGt, X, Y) => X >Float Y

// error cases for isComparison and binOpCmp:
// * arguments must be numbers or Bool

Expand Down Expand Up @@ -2146,9 +2214,19 @@ The argument types must be the same for all comparison operations, however this
BoolVal(cmpOpBool(OP, VAL1, VAL2))
requires isComparison(OP)
[priority(60), preserves-definedness] // OP known to be a comparison

rule #applyBinOp(OP, Float(VAL1, WIDTH), Float(VAL2, WIDTH), _)
=>
BoolVal(cmpOpFloat(OP, VAL1, VAL2))
requires isComparison(OP)
[preserves-definedness] // OP known to be a comparison
```

The `binOpCmp` operation returns `-1`, `0`, or `+1` (the behaviour of Rust's `std::cmp::Ordering as i8`), indicating `LE`, `EQ`, or `GT`.
Types that are equivlance relations can implement [Eq](https://doc.rust-lang.org/std/cmp/trait.Eq.html),
and then they may implement [Ord](https://doc.rust-lang.org/std/cmp/trait.Ord.html) for a total ordering.
For types that implement `Ord` the `cmp` method must be implemented which can compare any two elements respective to their total ordering.
Here we provide the `binOpCmp` for `Bool` and `Int` operation which returns `-1`, `0`, or `+1` (the behaviour of Rust's `std::cmp::Ordering as i8`),
indicating `LE`, `EQ`, or `GT`.

```k
syntax Int ::= cmpInt ( Int , Int ) [function , total]
Expand Down Expand Up @@ -2183,7 +2261,11 @@ The semantics of the operation in this case is to wrap around (with the given bi
...
</k>

// TODO add rule for Floats once they are supported.
rule <k> #applyUnOp(unOpNeg, Float(VAL, WIDTH))
=>
Float(--Float VAL, WIDTH)
...
</k>
```

The `unOpNot` operation works on boolean and integral values, with the usual semantics for booleans and a bitwise semantics for integral values (overflows cannot occur).
Expand Down
7 changes: 5 additions & 2 deletions kmir/src/kmir/kdist/mir-semantics/rt/decoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,13 @@ and arrays (where layout is trivial).
requires #isIntType(TYPEINFO) andBool lengthBytes(BYTES) ==Int #elemSize(TYPEINFO)
[preserves-definedness]

// Float: handled in separate module for numeric operations
rule #decodeValue(BYTES, TYPEINFO) => #decodeFloat(BYTES, #floatTypeOf(TYPEINFO))
requires #isFloatType(TYPEINFO) andBool lengthBytes(BYTES) ==Int #elemSize(TYPEINFO)
[preserves-definedness]

// TODO Char type
// rule #decodeConstant(constantKindAllocated(allocation(BYTES, _, _, _)), typeInfoPrimitiveType(primTypeChar)) => typedValue(Str(...), TY, mutabilityNot)

// TODO Float decoding: not supported natively in K
```


Expand Down
203 changes: 203 additions & 0 deletions kmir/src/kmir/kdist/mir-semantics/rt/numbers.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The code in this file implements functionality for `Integer` and `Float` values
```k
requires "./value.md"
requires "../ty.md"
requires "rat.md"

module RT-NUMBERS
imports TYPES
Expand All @@ -13,6 +14,8 @@ module RT-NUMBERS
imports BOOL
imports BYTES
imports INT
imports FLOAT
imports RAT
```

## Helpers and Constants for Integer Operations
Expand All @@ -38,6 +41,15 @@ module RT-NUMBERS
rule #isIntType(typeInfoPrimitiveType(primTypeInt(_))) => true
rule #isIntType(typeInfoPrimitiveType(primTypeUint(_))) => true
rule #isIntType(_) => false [owise]

syntax Bool ::= #isFloatType ( TypeInfo ) [function, total]
// --------------------------------------------------------
rule #isFloatType(typeInfoPrimitiveType(primTypeFloat(_))) => true
rule #isFloatType(_) => false [owise]

syntax FloatTy ::= #floatTypeOf ( TypeInfo ) [function]
// ----------------------------------------------------
rule #floatTypeOf(typeInfoPrimitiveType(primTypeFloat(FLOATTY))) => FLOATTY
```

Constants used for overflow-checking and truncation are defined here as macros.
Expand Down Expand Up @@ -110,6 +122,197 @@ This truncation function is instrumental in the implementation of Integer arithm
[preserves-definedness]
```

## Helpers and Constants for Float Operations

Rust supports four fixed-width IEEE 754 float types: `f16`, `f32`, `f64`, and `f128`.
The helpers below extract format parameters for each type. First, an overview of the format.

### IEEE 754 Binary Format

An IEEE 754 binary floating-point word has three fields stored left-to-right:

```
MSB LSB
+---------+----------------+----------------------+
| sign | exponent | fraction |
| (1 bit) | (EB bits) | (SB - 1 bits) |
+---------+----------------+----------------------+
total bits = 1 + EB + (SB - 1)
```

The **significand** (also called **precision**) is the total number of significant bits
in the represented value, including an implicit leading 1 that is not stored in the
fraction field. For a normal number, the mathematical value is:

value = (-1)^sign * 2^(exponent - bias) * 1.fraction

The "1." prefix is the implicit bit, so the significand has `SB` bits of precision
even though only `SB - 1` fraction bits are stored. For example, f64 stores 52 fraction
bits but has 53 bits of significand precision.

K's built-in `FLOAT` module uses this convention: `Int2Float(x, precision, exponentBits)`
takes `precision = SB` (total significand bits including the implicit 1) and `exponentBits = EB`.
See [IEEE 754 on Wikipedia](https://en.wikipedia.org/wiki/IEEE_754) for full details.

The exponent is stored as an unsigned integer in
[excess-M encoding](https://en.wikipedia.org/wiki/Offset_binary) with `bias = 2^(EB-1) - 1`,
so that the actual exponent is `stored - bias`. For f64, bias = 1023: a stored value of 1023
means exponent 0, 1024 means +1, and 1 means -1022. Stored values 0 and `2^EB - 1` are
reserved for zero/subnormals and infinity/NaN respectively.

| Type | Total bits | Sign | Exponent (EB) | Fraction (SB-1) | Significand (SB) | Bias |
|------|------------|------|---------------|-----------------|------------------|------------|
| f16 | 16 | 1 | 5 | 10 | 11 | 15 |
| f32 | 32 | 1 | 8 | 23 | 24 | 127 |
| f64 | 64 | 1 | 11 | 52 | 53 | 1023 |
| f128 | 128 | 1 | 15 | 112 | 113 | 16383 |

```k
syntax Int ::= #significandBits ( FloatTy ) [function, total]
// ----------------------------------------------------------
rule #significandBits(floatTyF16) => 11
rule #significandBits(floatTyF32) => 24
rule #significandBits(floatTyF64) => 53
rule #significandBits(floatTyF128) => 113

syntax Int ::= #exponentBits ( FloatTy ) [function, total]
// -------------------------------------------------------
rule #exponentBits(floatTyF16) => 5
rule #exponentBits(floatTyF32) => 8
rule #exponentBits(floatTyF64) => 11
rule #exponentBits(floatTyF128) => 15

syntax Int ::= #bias ( FloatTy ) [function, total]
// -----------------------------------------------
rule #bias(FLOATTY) => (1 <<Int (#exponentBits(FLOATTY) -Int 1)) -Int 1
```

### IEEE 754 Special Values

When the exponent field is all 1s (`2^EB - 1`), the value is either infinity
(fraction = 0) or NaN (fraction != 0). K's `FLOAT-SYNTAX` module in
[domains.md](https://github.com/runtimeverification/k/blob/master/k-distribution/include/kframework/builtin/domains.md#ieee-754-floating-point-numbers)
defines float literals with a `p<SB>x<EB>` suffix to specify precision and exponent
bits. See IEEE 754 Binary Format above for values of SB and EB.

For example, `Infinityp53x11` is f64 positive infinity, `NaNp24x8` is f32 NaN.

```k
syntax Float ::= #posInfFloat ( FloatTy ) [function, total]
// --------------------------------------------------------
rule #posInfFloat(floatTyF16) => Infinityp11x5
rule #posInfFloat(floatTyF32) => Infinityp24x8
rule #posInfFloat(floatTyF64) => Infinityp53x11
rule #posInfFloat(floatTyF128) => Infinityp113x15

syntax Float ::= #nanFloat ( FloatTy ) [function, total]
// -----------------------------------------------------
rule #nanFloat(floatTyF16) => NaNp11x5
rule #nanFloat(floatTyF32) => NaNp24x8
rule #nanFloat(floatTyF64) => NaNp53x11
rule #nanFloat(floatTyF128) => NaNp113x15
```

## Decoding Float values from `Bytes` for `OperandConstant`

The `#decodeFloat` function reconstructs a `Float` value from its IEEE 754 byte representation.
The bytes are first converted to a raw integer, then the sign, biased exponent, and stored significand
are extracted. The value is reconstructed using K's `Int2Float` and float arithmetic, with a
high-precision intermediate to avoid overflow when reconstructing subnormals and small normal values.

```k
syntax Value ::= #decodeFloat ( Bytes, FloatTy ) [function]
// --------------------------------------------------------
rule #decodeFloat(BYTES, FLOATTY) => #decodeFloatRaw(Bytes2Int(BYTES, LE, Unsigned), FLOATTY)
requires lengthBytes(BYTES) ==Int #bitWidth(FLOATTY) /Int 8
[preserves-definedness]

syntax Value ::= #decodeFloatRaw ( Int, FloatTy ) [function, total]
// ----------------------------------------------------------------
rule #decodeFloatRaw(RAW, FLOATTY)
=> #decodeFloatParts(
RAW >>Int (#significandBits(FLOATTY) +Int #exponentBits(FLOATTY) -Int 1),
(RAW >>Int (#significandBits(FLOATTY) -Int 1)) &Int ((1 <<Int #exponentBits(FLOATTY)) -Int 1),
RAW &Int ((1 <<Int (#significandBits(FLOATTY) -Int 1)) -Int 1),
FLOATTY
)

syntax Value ::= #decodeFloatParts ( sign: Int, biasedExp: Int, storedSig: Int, FloatTy ) [function, total]
// --------------------------------------------------------------------------------------------------------

// Zero (positive or negative)
rule #decodeFloatParts(SIGN, 0, 0, FLOATTY)
=> Float(#applyFloatSign(Int2Float(0, #significandBits(FLOATTY), #exponentBits(FLOATTY)), SIGN), #bitWidth(FLOATTY))

// Subnormal: no implicit leading 1, exponent is 1 - bias
rule #decodeFloatParts(SIGN, 0, SIG, FLOATTY)
=> Float(
#applyFloatSign(
#reconstructFloat(SIG, 2 -Int #bias(FLOATTY) -Int #significandBits(FLOATTY), FLOATTY),
SIGN
),
#bitWidth(FLOATTY)
)
requires SIG =/=Int 0

// Normal: implicit leading 1 in significand
rule #decodeFloatParts(SIGN, EXP, SIG, FLOATTY)
=> Float(
#applyFloatSign(
#reconstructFloat(
SIG |Int (1 <<Int (#significandBits(FLOATTY) -Int 1)),
EXP -Int #bias(FLOATTY) -Int #significandBits(FLOATTY) +Int 1,
FLOATTY
),
SIGN
),
#bitWidth(FLOATTY)
)
requires EXP >Int 0 andBool EXP <Int ((1 <<Int #exponentBits(FLOATTY)) -Int 1)

// Infinity
rule #decodeFloatParts(SIGN, EXP, 0, FLOATTY)
=> Float(#applyFloatSign(#posInfFloat(FLOATTY), SIGN), #bitWidth(FLOATTY))
requires EXP ==Int ((1 <<Int #exponentBits(FLOATTY)) -Int 1)

// NaN
rule #decodeFloatParts(_SIGN, EXP, SIG, FLOATTY)
=> Float(#nanFloat(FLOATTY), #bitWidth(FLOATTY))
requires EXP ==Int ((1 <<Int #exponentBits(FLOATTY)) -Int 1) andBool SIG =/=Int 0

// Owise case should not be reachable, returning a string value that should get stuck in the semantics
rule #decodeFloatParts(_, _, _, _) => StringVal( "ERRORFailedToDecodeFloat" ) [owise]

```

Reconstruct a float from its integer significand and adjusted exponent.
For positive exponents, shift the significand left and convert directly.
For negative exponents, use `Rat2Float` to convert the exact rational
`SIG / 2^|AEXP|` to the target float precision.

```k
syntax Float ::= #reconstructFloat ( sig: Int, adjExp: Int, FloatTy ) [function]
// -------------------------------------------------------------------------------
rule #reconstructFloat(SIG, AEXP, FLOATTY)
=> Int2Float(SIG <<Int AEXP, #significandBits(FLOATTY), #exponentBits(FLOATTY))
requires AEXP >=Int 0
[preserves-definedness]

rule #reconstructFloat(SIG, AEXP, FLOATTY)
=> Rat2Float(SIG /Rat (1 <<Int (0 -Int AEXP)),
#significandBits(FLOATTY),
#exponentBits(FLOATTY))
requires AEXP <Int 0
[preserves-definedness]

// Apply the sign bit to a float value
syntax Float ::= #applyFloatSign ( Float, Int ) [function, total]
// ---------------------------------------------------------------
rule #applyFloatSign(F, 0) => F
rule #applyFloatSign(F, 1) => --Float F
rule #applyFloatSign(F, _) => F [owise]
```

## Type Casts Between Different Numeric Types


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
ListItem ( newLocal ( ty ( 1 ) , mutabilityMut ) )
ListItem ( typedValue ( Aggregate ( variantIdx ( 0 ) , ListItem ( Integer ( 1 , 32 , true ) )
ListItem ( BoolVal ( true ) )
ListItem ( thunk ( UnableToDecode ( b"33333sE@" , typeInfoPrimitiveType ( primTypeFloat ( floatTyF64 ) ) ) ) )
ListItem ( Float ( 0.42899999999999999e2 , 64 ) )
ListItem ( Aggregate ( variantIdx ( 0 ) , ListItem ( Integer ( 1 , 32 , true ) )
ListItem ( Integer ( 10 , 32 , true ) ) ) ) ) , ty ( 28 ) , mutabilityMut ) )
ListItem ( typedValue ( Moved , ty ( 27 ) , mutabilityMut ) )
Expand Down
Loading
Loading