Skip to content

[rocjitsu] Match FRACT and LDEXP rounding and MODE handling - #13690

Open
yashjha123 wants to merge 10 commits into
developfrom
users/yashjha123/rdna4-fract-ldexp-rounded
Open

yashjha123 wants to merge 10 commits into
developfrom
users/yashjha123/rdna4-fract-ldexp-rounded

Conversation

@yashjha123

Copy link
Copy Markdown
Contributor

Fix FRACT and LDEXP differences found while comparing rocjitsu with GPU captures.

  • FRACT: honor rounding and denormal settings, keep the fractional result below 1.0, and correct special-value handling.
  • LDEXP: honor F16 overflow saturation and correctly flush tiny results, including values that would otherwise round up to minimum-normal.
  • SDWA F16: apply FRACT/LDEXP output scaling after rounding and respect MODE settings that disable scaling.

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 FRACT retains SIMD support; VOP3 FRACT uses scalar execution. LDEXP supports 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

  • FP-mode suite: 33,856 passed.
  • Main suite: 7,130 passed;

…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.
@yashjha123
yashjha123 requested review from a team and atgutier as code owners October 17, 2026 21:52
Copilot AI balanced review requested due to automatic review settings October 17, 2026 21:52
@therock-pr-bot

therock-pr-bot Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

✅ All Checks Passed — Ready for Review

Check Status Details
📝 PR Description ✅ Pass —
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ✅ Pass —
🔎 pre-commit ✅ Pass —
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —
🤖 therock-pr-bot ✅ Pass —

🎉 All checks passed! This PR is ready for review.

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

🙋 Wish to Override Policy?

@therock-pr-bot

Copy link
Copy Markdown

🎉 All checks passed! This PR is ready for review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Comment thread emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/fract.h Outdated

@kuhar kuhar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few comments

Comment thread emulation/rocjitsu/lib/python/amdisa/codegen/execute/simd_codegen.py Outdated
Comment thread emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/ldexp.h Outdated
Comment thread emulation/rocjitsu/lib/python/amdisa/codegen/execute/sema_lower.py
Comment thread emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/ldexp.h Outdated
Comment thread emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/ldexp.h Outdated

@kuhar kuhar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left an inline reply and 2 new comments

Comment thread emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/ldexp.h Outdated

@kuhar kuhar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM


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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 kuhar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants