Repository navigation
[rocjitsu] Fix VOP3 integer CLAMP semantics - #10904
Conversation
✅ 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.
Pull request overview
This PR fixes rocJITsu’s semantic lowering for VOP3 integer add/sub (including reverse subtract) when the instruction encoding sets CLAMP=1, ensuring results saturate (rather than wrap) before narrowing to 16/32-bit signed/unsigned destinations. This addresses the clamp-omission root cause behind incorrect PyTorch FP8 E4M3→FP32 casts on the gfx1250 (CDNA5) model.
Changes:
- Introduces shared typed helpers (
vop3_integer_add/vop3_integer_sub) that implement correct saturating semantics whenCLAMPis set, while preserving modulo wraparound whenCLAMPis clear. - Updates generated execute paths across shared + multiple arch profiles to route relevant VOP3 integer ops through the helpers, and disables the VOP3 integer SIMD fast-path when
CLAMPis set (fallback to scalar for correctness). - Adds targeted C++ execution regression tests (including forced-scalar mode) and Python generator/lowering tests to ensure
inst_.clampis consumed via the shared helpers.
Reviewed changes
Copilot reviewed 6 out of 15 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| emulation/rocjitsu/tests/cdna5_execution_test.cpp | Adds a gfx1250/CDNA5 execution regression covering the exact reported instruction encoding and boundary saturation cases (plus forced-scalar). |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/simd_glue.h | Adds vop3_integer_add/sub saturation helpers and gates VOP3 integer SIMD fast-path on CLAMP==0. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/shared/execute_shared.h | Updates generated shared execute bodies for relevant VOP3 integer ops to call the new saturation helpers. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/rdna4/vop3_exec.cpp | Regenerates RDNA4 VOP3 execution to use the saturation helpers for relevant integer ops. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/rdna3/vop3_exec.cpp | Regenerates RDNA3 VOP3 execution to use the saturation helpers for relevant integer ops. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/rdna3_5/vop3_exec.cpp | Regenerates RDNA3.5 VOP3 execution to use the saturation helpers for relevant integer ops. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/cdna5/vop3_exec_alu.cpp | Regenerates CDNA5 VOP3 ALU execution to use the saturation helpers for relevant integer ops. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/cdna4/vop3_exec.cpp | Regenerates CDNA4 VOP3 execution to use the saturation helpers for relevant integer ops. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/cdna3/vop3_exec.cpp | Regenerates CDNA3 VOP3 execution to use the saturation helpers for relevant integer ops. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/cdna2/vop3_exec.cpp | Regenerates CDNA2 VOP3 execution to use the saturation helpers for relevant integer ops. |
| emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/generated/cdna1/vop3_exec.cpp | Regenerates CDNA1 VOP3 execution to use the saturation helpers for relevant integer ops. |
| emulation/rocjitsu/lib/python/amdisa/tests/test_sema_derive.py | Adds generator/lowering assertions that VOP3 integer add/sub variants consume inst_.clamp via the shared helpers. |
| emulation/rocjitsu/lib/python/amdisa/codegen/execute/simd_codegen.py | Updates SIMD codegen commentary to reflect scalar fallback for saturating CLAMP behavior. |
| emulation/rocjitsu/lib/python/amdisa/codegen/execute/sema_lower.py | Adds lowering support to emit vop3_integer_add/sub(..., inst_.clamp) for targeted integer add/sub nodes. |
| emulation/rocjitsu/lib/python/amdisa/codegen/_generator.py | Marks applicable VOP3 integer vector_binop ops as saturation-capable when clamp is present, enabling the new lowering path. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
sjain-stanford
left a comment
There was a problem hiding this comment.
We should handle CLAMP for the packed integer add/sub path as part of this fix. gen_pk_binop still narrows each result directly (emulation/rocjitsu/lib/python/amdisa/codegen/execute/packed.py:180), and the SIMD path does not gate on inst_.clamp (emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/simd_glue.h:3623). As a result, V_PK_ADD/SUB_[IU]16 still wrap each half; for example, UINT16_MAX + 1 writes 0 instead of UINT16_MAX. Please saturate both selected halves independently, gate or implement SIMD, and test signed/unsigned bounds with non-default op_sel/op_sel_hi.
sjain-stanford
left a comment
There was a problem hiding this comment.
Just one comment
VOP3 integer add and subtract semantic lowering ignored the encoded CLAMP bit and narrowed wrapped results directly. On gfx1250 this made v_sub_nc_u32 v4, v4, 4 clamp turn an underflow into 0xfffffffd instead of zero, corrupting exponent normalization in FP8 conversion (#10760). Route signed and unsigned 16- and 32-bit VOP3 add/sub operations through shared helpers that widen, saturate to the destination type, and then narrow. Preserve modulo arithmetic when CLAMP is clear, and make the generic SIMD fast path fall back to the authoritative scalar body for clamped operations. Regenerate the affected CDNA and RDNA execution sources. Add generator coverage plus a gfx1250 execution regression using the exact reported instruction encoding. Exercise signed and unsigned overflow and underflow boundaries at both widths, unclamped wrapping, and normal versus forced-scalar execution. Verified with the full amdisa Python suite (1901 passed, 46 skipped), focused native execution/SIMD tests, a full build, and the original PyTorch FP8 reproducer. Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
Extend CLAMP saturation to packed I16/U16 add/sub and VOP3 carry/borrow forms while preserving full-width carry results. Gate clamped SIMD paths to scalar and add generator and execution regressions for selectors, reverse subtract, carry outputs, and signed/unsigned boundaries. Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
Extend the destination-width-aware CLAMP helpers to unsigned 64-bit add and subtract so gfx1250 normal and DPP execution cannot wrap. Regenerate the execute paths and cover both saturation boundaries. Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
1423774 to
4be5d7b
Compare
Route clamp-capable V_ADD3_U32 through full-width unsigned saturation and bypass the wrapping SIMD path when CLAMP is set. Add generator coverage and RDNA4 normal/DPP execution regressions for the overflow boundary. Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
Limit integer clamp lowering to assembler-legal opcodes and remove unsupported V_ADD3_U32 behavior. Use exact wide intermediates for multiply-accumulate operations and cover scalar and SIMD saturation boundaries. Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
Handle the assembler-legal V_SAD_HI_U8 clamp form with an exact shifted accumulator before saturation. Extend execution coverage across signed lower bounds and unclamped I16/I24 wraparound behavior. Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
Seed the destination status bit before in-range clamped add and subtract cases. Verify both the arithmetic result and clearing of stale carry or borrow state in scalar and SIMD-selected execution. Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
Saturate ADD_MIN/MAX before selecting the third operand and honor CLAMP for packed and full-width QSAD accumulation. Add generator and execution coverage for the remaining legal instruction families. Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
Exercise the legacy modulo-before-selection behavior alongside the new clamped boundary cases. This guards the shared ADD_MIN/MAX helper from saturating unconditionally. Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
There was a problem hiding this comment.
Here are some agent findings:
Major findings
- V_ADD_{MIN,MAX}_{I,U}32 incorrectly makes the instruction’s intrinsic saturating add depend on the encoded CLAMP bit.
At emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/isa/arch/amdgpu/shared/simd_glue.h:129, vop3_integer_add_minmax() calls: vop3_integer_add(lhs, rhs, clamp)
Therefore CLAMP=0 wraps the intermediate addition. For example, V_ADD_MAX_I32(INT_MAX, 1, INT_MIN) returns INT_MIN.
That conflicts with:
-
CDNA5 MR ISA descriptions at shared/machine-readable-isa/isa/amdgpu_isa_cdna5.xml:126948, :127277, :127606, and :127935, which explicitly say the addition is performed “with clamping enabled”.
-
LLVM’s VOP3Instructions.td:996-999
(https://github.com/llvm/llvm-project/blob/551d5172dd3902efbce5f4720b75bfc4e6441dc8/llvm/lib/Target/AMDGPU/VOP3Instructions.td#L996-L999), which selects these instructions from saddsat/uaddsat followed by min/max without setting the encoded clamp modifier.
The new tests at tests/cdna5_execution_test.cpp:268-277 codify the incorrect wrapping behavior for CLAMP=0.
Fix: always saturate the internal addition for these four opcodes; treat the encoded destination clamp separately if it has any additional defined effect. Update the unclamped boundary expectations.
Confidence: high.
- The legal CLAMP modifier is ignored for all four 64-bit integer MAD forms.
The policy at lib/python/amdisa/isa_profile.py:584-642 omits:
- V_MAD_NC_U64_U32
- V_MAD_NC_I64_I32
- V_MAD_CO_U64_U32
- V_MAD_CO_I64_I32
Consequently, gen_vector_mad_64_32() is called without clamp capability at codegen/execute/init.py:220-222, and the generated implementations never read inst_.clamp:
- generated/cdna5/vop3_exec_ternary.cpp:3967 and :4013
- generated/shared/execute_shared.h:13193 and :13215
The public AMD definition explicitly enables clamping for both CO and NC forms, including an explicit “Saturation support” note for NC
I also confirmed that LLVM accepts all four gfx1250 forms with clamp.
For example, unsigned 0xffffffff * 0xffffffff + UINT64_MAX currently wraps instead of saturating VDST to UINT64_MAX.
Fix: add all four instructions to the policy, use a 65-bit-or-wider exact intermediate, saturate VDST, and preserve CO carry/overflow from the unsaturated result. Cover signed/unsigned, NC/CO, and normal/DPP execution.
Confidence: high.
- V_PK_MAD_I16 and V_PK_MAD_U16 still wrap when CLAMP=1.
The scalar generated bodies at generated/shared/execute_shared.h:17452 and :17493 directly narrow the multiply-add to 16 bits without consulting inst_.clamp. The SIMD path at shared/simd_glue.h:3770 also does not reject clamped execution, so both paths can return the wrapped result.
The public AMD definition enables clamp for packed MAD
LLVM also accepts both gfx1250 forms with the modifier.
Examples:
- Unsigned 0xffff * 2 + 0 returns 0xfffe instead of 0xffff.
- Signed 32767 * 2 + 0 returns -2 instead of 32767.
The added packed regression at tests/cdna5_execution_test.cpp:374-401 covers only packed ADD/SUB.
Fix: use an exact per-half MAD intermediate and signed/unsigned saturation. Until the SIMD implementation supports that operation, reject CLAMP in try_execute_vop3p_pk_ternary_int_simd(). Add signed/unsigned, selector, normal/SIMD, forced-scalar, and unclamped-wrap tests.
Confidence: high.
Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
There was a problem hiding this comment.
Validated all three GitHub review findings against the CDNA5 ISA metadata and generated paths. Addressed in commits affb385 and later, with intrinsic ADD_MIN/MAX saturation, exact 64-bit MAD saturation while preserving CO from the unsaturated result, and per-half packed MAD saturation with clamped SIMD fallback. Added normal, DPP, selector, forced-scalar, SIMD, and unclamped-wrap regression coverage; focused Python and native tests pass.
atgutier
left a comment
There was a problem hiding this comment.
I think this issue will break gcc under current flags.
Compute the exact 32x32 product in 64 bits and detect signed or unsigned addition overflow explicitly. This preserves clamp and carry semantics while compiling cleanly with GCC -Wpedantic. Also brace the associated carry assertion to satisfy GCC's dangling-else warning under -Werror. Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
Exercise positive overflow and negative underflow in both CDNA5 NC and CO execute bodies. This protects each generated saturation branch from direction-specific drift. Signed-off-by: Sambhav Jain <sambhav@alumni.stanford.edu>
atgutier
left a comment
There was a problem hiding this comment.
Thanks for fixing the issues! LGTM now
TheRock Submodule Bump ActivityNewest first
|
Context and provenance
Fixes #10760. Addresses the same clamp omission reported for reverse subtraction in #10762.
Issue #10760 reported that a PyTorch
float8_e4m3fntofloat32cast produced severely compressed values under rocJITsu's gfx1250 model even though the FP8 bytes were correct. The first incorrect intermediate was:For the raw E4M3 byte
0xc8, representing-4, rocJITsu wrapped1 - 4to0xfffffffdinstead of saturating the unsigned result to zero. That value corrupted the later shift and exponent reconstruction, producing-0.0625instead of-4.0.Root cause
rocJITsu decoded the VOP3
clampfield, but integer semantic lowering narrowed ordinary wrapping arithmetic without consulting it. Several SIMD paths made the same assumption. Becauseclampis shared by the VOP3 encoding but is not legal for every opcode, deciding support from the encoding field or broad semantic class alone is also insufficient.Implementation
The fix is implemented in the ISA profile and semantic generator rather than special-casing PyTorch, FP8, or gfx1250:
V_ADD3_U32 clampdo not acquire invented semantics.clamp=1, and retain architectural modulo behavior whenclamp=0.V_ADD_MIN/MAX_{I,U}32always saturate their internal addition, as required by their instruction semantics, independently of the encoded CLAMP bit.VDST.V_SAD_U8/U16/U32,V_SAD_HI_U8, andV_MSAD_U8; shifted SAD accumulation is performed exactly before saturation.VDSTwhile deriving and updatingVCCfrom the original full-width arithmetic, including clearing stale result bits.Regression coverage
The gfx1250 execution regression decodes and executes the exact instruction words from #10760 in both normal and forced-scalar modes. Additional coverage exercises:
VCC/SDSTbits;V_SAD_HI_U8accumulation;Generator tests use real MR ISA definitions to verify positive instruction-policy routing, unsupported-opcode rejection, helper selection, and consumption of
inst_.clamp.Validation performed
Ran official amdisa regeneration and built the full
rocjitsu_teststarget across the generated architecture set.Passed all 107
Gfx1250ExecutionTesttests.Passed the focused amdisa generator and semantic suites: 1,012 passed, 1 skipped.
Passed focused CDNA4 and CDNA5 native regressions, including clamped and unclamped shared MAD64 scalar/SIMD behavior.
Re-ran the original PyTorch FP8 reproducer and restored the expected sequence:
Passed all configured pre-commit hooks and
git diff --check.Completed iterative multi-agent review with seven reviewers plus a curator; all curated findings were addressed and the final pass was clean.
Co-authored-by: GPT-5.6 Sol codex@openai.com
🤖 Generated with Codex