Repository navigation
[rocjitsu] Match FRACT and LDEXP rounding and MODE handling - #13690
yashjha123 wants to merge 10 commits into
Conversation
…w encodings On gfx1201, V_FRACT flushes a subnormal source under MODE input control, rounds x - floor(x) under MODE.FP_ROUND, caps the result below 1.0 and flushes a subnormal result under MODE output control. Integers and -0 give +0 in every rounding mode, and +-infinity gives the negative quiet NaN. The emulator computed v - floor(v) in host floats, so fract(-tiny) was 1.0, nothing was flushed and directed rounding was ignored. V_LDEXP saturates F16 overflow under FP16_OVFL. With output denormals disabled it flushes a result that stays below the smallest normal at full precision, even when its subnormal encoding would round up to it: ldexp(0x3f7fffff, -126) gives 0 under MODE 0xd0. The F16 SIMD path returned infinity under FP16_OVFL, and every format judged tininess on the subnormal encoding. shared/fract.h and shared/ldexp.h implement both operations on raw encodings for scalar lanes and SIMD vectors, with ABS/NEG and OMOD/CLAMP from floating_operation.h. float_fract.py and float_ldexp.py emit the calls from the existing FRACT and LDEXP semantic nodes. VOP1 FRACT keeps SIMD through the raw functor; VOP3 FRACT runs scalar. VOP2 F16 LDEXP and VOP3 LDEXP in every format run SIMD. The F32/F64 amdgpu::ldexp helper, its per-lane SIMD glue, fp_mode::ldexp_f16 and LDEXP's expression-level div_apply_omod are removed. The rules apply on every target that shares the generated code; only gfx1201 is measured. The gfx1201 replay matches all 26 FRACT variants and the 4 LDEXP_F16 SIMD variants (1634 -> 1664 of 1944 with SIMD, 1638 -> 1664 forced scalar), and gfx1100 gains the 3 FRACT clamp variants (1118 -> 1121); nothing is lost. LDEXP tininess is not covered by the audit captures; a targeted gfx1201 probe of all three formats matches on both paths, where develop differs on 6,144 F16, 1,920 F32 and 640 F64 results.
…RACT/LDEXP after rounding With OMOD in effect, gfx1201 flushes an LDEXP result that is tiny at full precision even when output denormals are kept, and OMOD then gives +0: ldexp(0x3bff, -14) with mul:2 at MODE 0xf0 is 0, where the emulator rounded to 0x0400 and doubled it to 0x0800. ldexp::Policy::make takes the effective OMOD, and the scalar body and VOP3 SIMD probes pass it. SDWA F16 FRACT and LDEXP applied OMOD in the body, then the SDWA store finalized the result as if the producer had scaled before narrowing, which turned -0 into +0: gfx1030 gives 0x8000 for SDWA ldexp(0x8400, 0) with div:2. The bodies now return the rounded half without OMOD. The new sdwa::OutputPolicy::ROUNDED, selected by SDWA_ROUNDED_F16_OPS, makes the store scale it with output_modifier::scale, as it already scales F32 results. On gfx1030, SDWA matches VOP3 bit for bit for both instructions under every OMOD and 52 MODE values. The FRACT decoded tests choose MODE explicitly per lane and add input-only and output-only denormal flushes for each format, plus +-infinity and negative NaN under CLAMP. LDEXP adds OMOD boundary lanes for F16/F32/F64, and RDNA2 SDWA lanes come from the gfx1030 probe. The format and MODE-field lookups go through input_policy.mode_field, which rejects unknown types. VOP3 FRACT F64 leaves the SIMD correctness list, since it runs scalar. The extended gfx1201 LDEXP probe (every FP_ROUND/FP_DENORM byte with FP16_OVFL clear and set, without OMOD and with mul:2 and div:2) matches on both paths; develop differs on 27,648 results. The audit-corpus replays are byte-identical: they never reach the OMOD boundary.
SDWA FRACT_F16 and LDEXP_F16 round before OMOD, as gfx1030 captures show. The other SDWA F16 producers still scale before narrowing. A gfx1030 capture contradicts that order: MUL_F16 0x3e00 * 0x7bff with div:2 gives 0x7c00 through SDWA and VOP3, where the emulator's SDWA path returns 0x79ff. output_modifier.h, the SDWA helpers, the generator and the pinned SDWA tests now say they keep the emulator's current order, not a validated one. Migrating those producers needs SDWA captures of each. A generator test pins SDWA FRACT/LDEXP to OutputPolicy::ROUNDED, with no OMOD in the body.
…and evidence the documentation
…r OMOD gating and FRACT SIMD paths
✅ All Checks Passed — Ready for Review
📖 Need help? See the Policy FAQ for details on every check and how to fix failures. |
|
🎉 All checks passed! This PR is ready for review. |
There was a problem hiding this comment.
🟡 Changes recommended
FRACT evaluates an out-of-range normalization shift for negative zero, introducing undefined behavior.
1 open issue
What changed in this PR
Updates rocjitsu’s FRACT and LDEXP execution to better match GPU rounding, denormal, and modifier behavior, advancing #12429’s bit-exact floating-point work.
Changes:
- Shares raw-bit implementations between scalar and eligible SIMD paths.
- Applies SDWA F16 scaling after rounding with MODE gating.
- Updates generators, executors, and hardware-backed regression coverage.
| File | Description |
|---|---|
| emulation/rocjitsu/tests/valu_fp_mode_test.cpp | Adds captured rounding and MODE regressions. |
| emulation/rocjitsu/tests/simd_correctness/vop3_ldexp_simd_correctness_test.cpp | Updates implementation comments. |
| emulation/rocjitsu/tests/simd_correctness/vop3_fp64_simd_correctness_test.cpp | Removes scalar-only FRACT case. |
| emulation/rocjitsu/tests/simd_correctness/vop3_fp16_unary_simd_correctness_test.cpp | Removes scalar-only FRACT case. |
| emulation/rocjitsu/tests/simd_correctness/vop1_f64_simd_correctness_test.cpp | Requires exact FRACT bit comparisons. |
| emulation/rocjitsu/tests/instruction_execution_harness_test.cpp | Documents existing SDWA arithmetic mismatches. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/simd_glue.h | Adds raw LDEXP SIMD dispatch. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/output_modifier.h | Documents modifier coverage and limitations. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/ldexp.h | Adds shared raw-bit LDEXP implementation. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/fract.h | Adds shared raw-bit FRACT implementation. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/fp_mode.h | Removes replaced LDEXP helpers. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/fp_format.h | Adds format constants and bit-width helper. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/floating_operation.h | Clarifies integer-source modifier behavior. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/dpp_sdwa_ops.h | Adds rounded F16 output scaling. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/shared/execute_shared.h | Migrates shared instruction executors. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/rdna4/vop3_exec.cpp | Migrates FRACT and LDEXP execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/rdna4/vop2_exec.cpp | Migrates F16 LDEXP execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/rdna4/vop1_exec.cpp | Migrates F16 FRACT execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/rdna3/vop3_exec.cpp | Migrates FRACT and LDEXP execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/rdna3/vop2_exec.cpp | Migrates F16 LDEXP execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/rdna3/vop1_exec.cpp | Migrates F16 FRACT execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/rdna3_5/vop3_exec.cpp | Migrates FRACT and LDEXP execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/rdna3_5/vop2_exec.cpp | Migrates F16 LDEXP execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/rdna3_5/vop1_exec.cpp | Migrates F16 FRACT execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/cdna5/vop3_exec_alu.cpp | Migrates FRACT and LDEXP execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/cdna5/vop2_exec.cpp | Migrates F16 LDEXP execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/cdna5/vop1_exec.cpp | Migrates F16 FRACT execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/cdna4/vop3_exec.cpp | Migrates FRACT and LDEXP execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/cdna3/vop3_exec.cpp | Migrates FRACT and LDEXP execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/cdna2/vop3_exec.cpp | Migrates FRACT and LDEXP execution. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/cdna1/vop3_exec.cpp | Migrates FRACT and LDEXP execution. |
| emulation/rocjitsu/lib/python/amdisa/tests/test_vector_special_codegen.py | Checks raw-operation SIMD generation. |
| emulation/rocjitsu/lib/python/amdisa/tests/test_sema_lower.py | Checks raw LDEXP lowering. |
| emulation/rocjitsu/lib/python/amdisa/tests/test_sema_derive.py | Checks shared operations and modifiers. |
| emulation/rocjitsu/lib/python/amdisa/tests/test_generator_profile_gates.py | Checks rounded SDWA generation. |
| emulation/rocjitsu/lib/python/amdisa/tests/test_floating_policy.py | Removes obsolete FRACT policy expectation. |
| emulation/rocjitsu/lib/python/amdisa/codegen/execute/simd_codegen.py | Routes SIMD through shared operations. |
| emulation/rocjitsu/lib/python/amdisa/codegen/execute/sema_lower.py | Lowers FRACT/LDEXP to raw helpers. |
| emulation/rocjitsu/lib/python/amdisa/codegen/execute/input_policy.py | Shares format and MODE lookup helpers. |
| emulation/rocjitsu/lib/python/amdisa/codegen/execute/floating_policy.py | Marks rounded SDWA F16 operations. |
| emulation/rocjitsu/lib/python/amdisa/codegen/execute/float_ldexp.py | Adds LDEXP code-generation helpers. |
| emulation/rocjitsu/lib/python/amdisa/codegen/execute/float_fract.py | Adds FRACT code-generation helpers. |
| emulation/rocjitsu/lib/python/amdisa/codegen/_generator.py | Selects rounded SDWA output policy. |
Review effort: Balanced
kuhar
left a comment
There was a problem hiding this comment.
Left an inline reply and 2 new comments
|
|
||
| /// @brief LDEXP with its MODE and effective-OMOD policy. | ||
| /// @details Independent of ABS/NEG and CLAMP, and applies no OMOD scaling; the | ||
| /// policy's omod flag only decides whether a tiny result flushes. |
There was a problem hiding this comment.
It would help to name flush_tiny in this explanation and in float_ldexp.py:48. The policy stores flush_tiny, computed from effective OMOD or disabled output denormals; it has no omod member. Describing that combined flag would make these short operation summaries match the policy definition, while retaining the distinction that the operation applies no OMOD scaling.
There was a problem hiding this comment.
We should also correct the new Policy::make description at ldexp.h:62: OMOD receives a signed zero, then converts it to +0. evaluate restores the source sign at line 193 after round_significand flushes the magnitude, so a negative tiny source reaches OMOD as -0.
The operation summaries at ldexp.h:201 and float_ldexp.py:48 still call flush_tiny an OMOD flag. Naming flush_tiny and its two triggers—effective OMOD or disabled output denormals—would make all three descriptions agree with the policy and identify which stage changes the zero's sign.
| case 2: | ||
| return rest != zero && negative; | ||
| default: | ||
| return rest != rest; |
There was a problem hiding this comment.
Nit: could we add a short comment explaining that rest != rest constructs a false value of the scalar or SIMD mask type? That makes this deliberate self-comparison easier to understand in the newly shared rounding helper.
kuhar
left a comment
There was a problem hiding this comment.
Left an inline reply and a new comment
| omod = f'{output_policy}.omod != 0' | ||
| elif destination and dtype == 'f16': | ||
| omod = f'{_SDWA_F16_OMOD} != 0' | ||
| if ctx.instruction_name not in SDWA_ROUNDED_F16_OPS: |
There was a problem hiding this comment.
I think we should remove this duplicate check and let _require_sdwa_rounded_store own the rule for both FRACT and LDEXP. The only caller passing destination=True already invokes that helper with the same fields at lines 1171–1172, so an unlisted F16 destination is rejected before this branch can raise. Lowering V_UNLISTED_F16 produces the shared helper's error; this error appears only with that helper disabled. Keeping one check avoids maintaining two versions of the same invariant.

Fix FRACT and LDEXP differences found while comparing rocjitsu with GPU captures.
Technical Details
Both instructions now share their core implementation between scalar and SIMD execution and reuse the common modifier handling. The generators and affected executors are updated across targets.
VOP1 FRACTretains SIMD support;VOP3 FRACTuses scalar execution.LDEXPsupports SIMD where eligible.What's covered: The changes follow gfx1201 captures, with additional gfx1100 checks and targeted gfx1030 F16 probes.
What's not covered: Other targets inherit the shared behavior without hardware verification. Older-target signaling-NaN and destination-half differences remain follow-ups, as do the documented SDWA ADD/MUL ordering mismatches.
Issue Tracking
Related to #12429.
Test Plan
Regression tests cover rounding, denormals, overflow, modifiers, register-half selection and inactive lanes, with SIMD enabled and forced scalar. Eligible cases check that the expected SIMD path executes.
Targeted boundary probes supplement the broader hardware replays: ordinary inputs can miss cases where rounding hides a tiny result. The SDWA regressions also detect deliberately broken MODE gating.
Test Result