# FPR-to-GPR and GPR-to-FPR Introduction: High-performance CPU/GPU software needs to often convert between integers and floating-point, therefore fast conversion/data-movement instructions are needed. Also given that initialisation of floats tends to take up considerable space (even to just load 0.0) the inclusion of float immediate is up for consideration (BF16 as immediates) Libre-SOC will be compliant with the **Scalar Floating-Point Subset** (SFFS) i.e. is not implementing VMX/VSX, and with its focus on modern 3D GPU hybrid workloads represents an important new potential use-case for OpenPOWER. With VMX/VSX not available in the SFFS Compliancy Level, the existing non-VSX conversion/data-movement instructions require load/store instructions (slow and expensive) to transfer data between the FPRs and the GPRs. Also, because SimpleV needs efficient scalar instructions in order to generate efficient vector instructions, adding new instructions for data-transfer/conversion between FPRs and GPRs seems necessary. In addition, the vast majority of GPR <-> FPR data-transfers are as part of a FP <-> Integer conversion sequence, therefore reducing the number of instructions required to the minimum seems necessary. Therefore, we are proposing adding: * FPR load-immediate using `BF16` as the constant * FPR <-> GPR data-transfer instructions that just copy bits without conversion * FPR <-> GPR combined data-transfer/conversion instructions that do Integer <-> FP conversions If we're adding new Integer <-> FP conversion instructions, we may as well take this opportunity to modernise the instructions and make them well suited for common/important conversion sequences: * standard Integer -> FP conversion - rounding mode read from FPSCR * standard OpenPower FP -> Integer conversion -- saturation with NaN converted to minimum valid integer - Matches x86's conversion semantics - Has instructions for both: * rounding mode read from FPSCR * rounding mode is always truncate * Rust FP -> Integer conversion -- saturation with NaN converted to 0 Semantics required by all of: * Rust's FP -> Integer conversion using the [`as` operator](https://doc.rust-lang.org/reference/expressions/operator-expr.html#semantics) * Java's [FP -> Integer conversion](https://docs.oracle.com/javase/specs/jls/se16/html/jls-5.html#jls-5.1.3) * LLVM's [`llvm.fptosi.sat`](https://llvm.org/docs/LangRef.html#llvm-fptosi-sat-intrinsic) and [`llvm.fptoui.sat`](https://llvm.org/docs/LangRef.html#llvm-fptoui-sat-intrinsic) intrinsics * SPIR-V's OpenCL dialect's [`OpConvertFToU`](https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpConvertFToU) and [`OpConvertFToS`](https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpConvertFToS) instructions when decorated with [the `SaturatedConversion` decorator](https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#_a_id_decoration_a_decoration). * JavaScript FP -> Integer conversion -- modular with Inf/NaN converted to 0 Semantics required by JavaScript TODO: review and investigate other language semantics # Links * * * * # Proposed New Scalar Instructions All of the following instructions use the standard OpenPower conversion to/from 64-bit float format when reading/writing a 32-bit float from/to a FPR. This can be overridden by SimpleV, which sets the following operation "reinterpretation" rules: * any operation whose assembler mnemonic does not end in "s" (being defined in v3.0B as a "double" operation) is instead an operation at the overridden elwidth for the relevant operand. * any operation nominally defined as a "single" FP operation is redefined to be **half the elwidth** rather than "half of 64 bit". Examples: * `sv.fmvtg/sw=32 RT.v, FRA.v` is defined as treating FRA as a vector of *FP32* source operands each *32* bits wide which are to be placed into *64* bit integer destination elements. * `sv.fmvfgs/dw=32 FRT.v, RA.v` is defined as taking the bottom 32 bits of each RA integer source, then performing a **32 bit** FP32 to **FP16** conversion and storing the result in the **32 bits** of an FRT destination element. "Single" is therefore redefined in SVP64 to be "half elwidth" rather than Double width hardcoded to 64 and Single width hardcoded to 32. This allows a full range of conversions between FP64, FP32, FP16 and BF16. ## FPR to GPR moves * `fmvtg RT, FRA` * `fmvtg. RT, FRA` move a 64-bit float from a FPR to a GPR, just copying bits directly. Rc=1 tests RT and sets CR0 * `fmvtgs RT, FRA` * `fmvtgs. RT, FRA` move a 32-bit float from a FPR to a GPR, just copying bits. Converts the 64-bit float in `FRA` to a 32-bit float, then writes the 32-bit float to `RT`. Rc=1 tests RT and sets CR0 ## GPR to FPR moves `fmvfg FRT, RA` move a 64-bit float from a GPR to a FPR, just copying bits. `fmvfgs FRT, RA` move a 32-bit float from a GPR to a FPR, just copying bits. Converts the 32-bit float in `RA` to a 64-bit float, then writes the 64-bit float to `FRT`. TODO: Rc=1 variants? ### Float load immediate (kinda a variant of `fmvfg`) `fmvis FRT, FI` Reinterprets `FI << 16` as a 32-bit float, which is then converted to a 64-bit float and written to `FRT`. This is equivalent to reinterpreting `FI` as a `BF16` and converting to 64-bit float. Example: ``` # clearing a FPR fmvis f4, 0 # writes +0.0 to f4 # loading handy constants fmvis f4, 0x8000 # writes -0.0 to f4 fmvis f4, 0x3F80 # writes +1.0 to f4 fmvis f4, 0xBF80 # writes -1.0 to f4 fmvis f4, 0xBFC0 # writes -1.5 to f4 fmvis f4, 0x7FC0 # writes +qNaN to f4 fmvis f4, 0x7F80 # writes +Infinity to f4 fmvis f4, 0xFF80 # writes -Infinity to f4 fmvis f4, 0x3FFF # writes +1.9921875 to f4 # clearing 128 FPRs with 2 SVP64 instructions # by issuing 32 vec4 (subvector length 4) ops setvli VL=MVL=32 sv.fmvis/vec4 f0, 0 # writes +0.0 to f0-f127 ``` Important: If the float load immediate instruction(s) are left out, change all [GPR to FPR conversion instructions](#GPR-to-FPR-conversions) to instead write `+0.0` if `RA` is register `0`, at least allowing clearing FPRs. | 0-5 | 6-10 | 11-25 | 26-30 | 31 | |--------|------|-------|-------|-----| | Major | FRT | FI | XO | FI0 | The above fits reasonably well with Minor 19 and follows the pattern shown by `addpcis`, which uses an entire column of Minor 19 XO. 15 bits of FI fit into bits 11 to 25, the top bit FI0 (MSB0 numbered 0) makes 16. bf16 = FI0 || FI fp32 = bf16 || [0]*16 FRT = Single_to_Double(fp32) ## FPR to GPR conversions
X-Form: | 0-5 | 6-10 | 11-15 | 16-25 | 26-30 | 31 | |--------|------|--------|-------|-------|----| | Major | RT | //Mode | FRA | XO | Rc | | Major | FRT | //Mode | RA | XO | Rc | Mode values: | Mode | `rounding_mode` | Semantics | |------|-----------------|----------------------------------| | 000 | from `FPSCR` | [OpenPower semantics] | | 001 | Truncate | [OpenPower semantics] | | 010 | from `FPSCR` | [Rust semantics] | | 011 | Truncate | [Rust semantics] | | 100 | from `FPSCR` | [JavaScript semantics] | | 101 | Truncate | [JavaScript semantics] | | rest | -- | illegal instruction trap for now | [OpenPower semantics]: #fp-to-int-openpower-conversion-semantics [Rust semantics]: #fp-to-int-rust-conversion-semantics [JavaScript semantics]: #fp-to-int-javascript-conversion-semantics `fcvttgw RT, FRA, Mode` Convert from 64-bit float to 32-bit signed integer, writing the result to the GPR `RT`. Converts using [mode `Mode`] `fcvttguw RT, FRA, Mode` Convert from 64-bit float to 32-bit unsigned integer, writing the result to the GPR `RT`. Converts using [mode `Mode`] `fcvttgd RT, FRA, Mode` Convert from 64-bit float to 64-bit signed integer, writing the result to the GPR `RT`. Converts using [mode `Mode`] `fcvttgud RT, FRA, Mode` Convert from 64-bit float to 64-bit unsigned integer, writing the result to the GPR `RT`. Converts using [mode `Mode`] `fcvtstgw RT, FRA, Mode` Convert from 32-bit float to 32-bit signed integer, writing the result to the GPR `RT`. Converts using [mode `Mode`] `fcvtstguw RT, FRA, Mode` Convert from 32-bit float to 32-bit unsigned integer, writing the result to the GPR `RT`. Converts using [mode `Mode`] `fcvtstgd RT, FRA, Mode` Convert from 32-bit float to 64-bit signed integer, writing the result to the GPR `RT`. Converts using [mode `Mode`] `fcvtstgud RT, FRA, Mode` Convert from 32-bit float to 64-bit unsigned integer, writing the result to the GPR `RT`. Converts using [mode `Mode`] [mode `Mode`]: #fpr-to-gpr-conversion-mode ## GPR to FPR conversions All of the following GPR to FPR conversions use the rounding mode from `FPSCR`. `fcvtfgw FRT, RA` Convert from 32-bit signed integer in the GPR `RA` to 64-bit float in `FRT`. `fcvtfgws FRT, RA` Convert from 32-bit signed integer in the GPR `RA` to 32-bit float in `FRT`. `fcvtfguw FRT, RA` Convert from 32-bit unsigned integer in the GPR `RA` to 64-bit float in `FRT`. `fcvtfguws FRT, RA` Convert from 32-bit unsigned integer in the GPR `RA` to 32-bit float in `FRT`. `fcvtfgd FRT, RA` Convert from 64-bit signed integer in the GPR `RA` to 64-bit float in `FRT`. `fcvtfgds FRT, RA` Convert from 64-bit signed integer in the GPR `RA` to 32-bit float in `FRT`. `fcvtfgud FRT, RA` Convert from 64-bit unsigned integer in the GPR `RA` to 64-bit float in `FRT`. `fcvtfguds FRT, RA` Convert from 64-bit unsigned integer in the GPR `RA` to 32-bit float in `FRT`. # FP to Integer Conversion Pseudo-code Key for pseudo-code: | term | result type | definition | |---------------------------|-------------|----------------------------------------------------------------------------------------------------| | `fp` | -- | `f32` or `f64` (or other types from SimpleV) | | `int` | -- | `u32`/`u64`/`i32`/`i64` (or other types from SimpleV) | | `uint` | -- | the unsigned integer of the same bit-width as `int` | | `int::BITS` | `int` | the bit-width of `int` | | `int::MIN_VALUE` | `int` | the minimum value `int` can store (`0` if unsigned, `-2^(int::BITS-1)` if signed) | | `int::MAX_VALUE` | `int` | the maximum value `int` can store (`2^int::BITS - 1` if unsigned, `2^(int::BITS-1) - 1` if signed) | | `int::VALUE_COUNT` | Integer | the number of different values `int` can store (`2^int::BITS`). too big to fit in `int`. | | `rint(fp, rounding_mode)` | `fp` | rounds the floating-point value `fp` to an integer according to rounding mode `rounding_mode` |
OpenPower conversion semantics (section A.2 page 999 (page 1023) of OpenPower ISA v3.1): ``` def fp_to_int_open_power(v: fp) -> int: if v is NaN: return int::MIN_VALUE if v >= int::MAX_VALUE: return int::MAX_VALUE if v <= int::MIN_VALUE: return int::MIN_VALUE return (int)rint(v, rounding_mode) ```
Rust [conversion semantics](https://doc.rust-lang.org/reference/expressions/operator-expr.html#semantics) (with adjustment to add non-truncate rounding modes): ``` def fp_to_int_rust(v: fp) -> int: if v is NaN: return 0 if v >= int::MAX_VALUE: return int::MAX_VALUE if v <= int::MIN_VALUE: return int::MIN_VALUE return (int)rint(v, rounding_mode) ```
JavaScript [conversion semantics](https://262.ecma-international.org/11.0/#sec-toint32) (with adjustment to add non-truncate rounding modes): ``` def fp_to_int_java_script(v: fp) -> int: if v is NaN or infinite: return 0 v = rint(v, rounding_mode) v = v mod int::VALUE_COUNT # 2^32 for i32, 2^64 for i64, result is non-negative bits = (uint)v return (int)bits ``` # Equivalent OpenPower ISA v3.0 Assembly Language for FP -> Integer Conversion Modes ## Rust https://rust.godbolt.org/z/jervW7ofb ### 64-bit float -> 64-bit signed integer ``` .LCPI0_0: .long 0xdf000000 .LCPI0_1: .quad 0x43dfffffffffffff example::fcvttgd_rust: .Lfunc_gep0: addis 2, 12, .TOC.-.Lfunc_gep0@ha addi 2, 2, .TOC.-.Lfunc_gep0@l addis 3, 2, .LCPI0_0@toc@ha fctidz 2, 1 fcmpu 5, 1, 1 li 4, 1 li 5, -1 lfs 0, .LCPI0_0@toc@l(3) addis 3, 2, .LCPI0_1@toc@ha rldic 4, 4, 63, 0 fcmpu 0, 1, 0 lfd 0, .LCPI0_1@toc@l(3) stfd 2, -8(1) ld 3, -8(1) fcmpu 1, 1, 0 cror 24, 0, 3 isel 3, 4, 3, 24 rldic 4, 5, 0, 1 isel 3, 4, 3, 5 isel 3, 0, 3, 23 blr .long 0 .quad 0 ``` ### 64-bit float -> 64-bit unsigned integer ``` .LCPI1_0: .long 0x00000000 .LCPI1_1: .quad 0x43efffffffffffff example::fcvttgud_rust: .Lfunc_gep1: addis 2, 12, .TOC.-.Lfunc_gep1@ha addi 2, 2, .TOC.-.Lfunc_gep1@l addis 3, 2, .LCPI1_0@toc@ha fctiduz 2, 1 li 4, -1 lfs 0, .LCPI1_0@toc@l(3) addis 3, 2, .LCPI1_1@toc@ha fcmpu 0, 1, 0 lfd 0, .LCPI1_1@toc@l(3) stfd 2, -8(1) ld 3, -8(1) fcmpu 1, 1, 0 cror 20, 0, 3 isel 3, 0, 3, 20 isel 3, 4, 3, 5 blr .long 0 .quad 0 ``` ### 64-bit float -> 32-bit signed integer ``` .LCPI2_0: .long 0xcf000000 .LCPI2_1: .quad 0x41dfffffffc00000 example::fcvttgw_rust: .Lfunc_gep2: addis 2, 12, .TOC.-.Lfunc_gep2@ha addi 2, 2, .TOC.-.Lfunc_gep2@l addis 3, 2, .LCPI2_0@toc@ha fctiwz 2, 1 lis 4, -32768 lis 5, 32767 lfs 0, .LCPI2_0@toc@l(3) addis 3, 2, .LCPI2_1@toc@ha fcmpu 0, 1, 0 lfd 0, .LCPI2_1@toc@l(3) addi 3, 1, -4 stfiwx 2, 0, 3 fcmpu 5, 1, 1 lwz 3, -4(1) fcmpu 1, 1, 0 cror 24, 0, 3 isel 3, 4, 3, 24 ori 4, 5, 65535 isel 3, 4, 3, 5 isel 3, 0, 3, 23 blr .long 0 .quad 0 ``` ### 64-bit float -> 32-bit unsigned integer ``` .LCPI3_0: .long 0x00000000 .LCPI3_1: .quad 0x41efffffffe00000 example::fcvttguw_rust: .Lfunc_gep3: addis 2, 12, .TOC.-.Lfunc_gep3@ha addi 2, 2, .TOC.-.Lfunc_gep3@l addis 3, 2, .LCPI3_0@toc@ha fctiwuz 2, 1 li 4, -1 lfs 0, .LCPI3_0@toc@l(3) addis 3, 2, .LCPI3_1@toc@ha fcmpu 0, 1, 0 lfd 0, .LCPI3_1@toc@l(3) addi 3, 1, -4 stfiwx 2, 0, 3 lwz 3, -4(1) fcmpu 1, 1, 0 cror 20, 0, 3 isel 3, 0, 3, 20 isel 3, 4, 3, 5 blr .long 0 .quad 0 ``` ## JavaScript https://gcc.godbolt.org/z/nG8Yh4dfW ### 64-bit float -> 32-bit signed integer ``` toInt32(double): stfd 1,-16(1) li 3,0 ori 2,2,0 ld 9,-16(1) rldicl 8,9,12,53 addi 10,8,-1023 cmplwi 7,10,83 bgtlr 7 cmpwi 7,10,52 bgt 7,.L7 cmpwi 7,10,31 subfic 3,10,52 srad 3,9,3 extsw 3,3 bgt 7,.L4 li 8,1 slw 10,8,10 addi 8,10,-1 and 3,8,3 add 10,10,3 extsw 3,10 .L4: cmpdi 7,9,0 bgelr 7 .L8: neg 3,3 extsw 3,3 blr .L7: cmpdi 7,9,0 addi 3,8,-1075 sld 3,9,3 extsw 3,3 bgelr 7 b .L8 .long 0 .byte 0,9,0,0,0,0,0,0 ```