Preserve operand execution order during JIT morphing - #134464
tannergooding wants to merge 2 commits into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 5 pipeline(s). 11 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
A critical operand-ordering issue remains, and several modified paths lack focused regression coverage.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (2)
What changed in this PR
This PR preserves operand execution order during JIT morphing and adds regression coverage for ordering-related failures.
Changes:
- Updates morphing and rewrite paths to respect execution order.
- Adds sequencing and reverse-flag handling for affected transformations.
- Adds SIMD and scalar regression tests.
Review findings:
- Critical (1 vote):
morph.cpprewrites at lines 7979, 9712, 10335, and 10732 may retainGTF_REVERSE_OPSafter swapping operands, reversing side effects or exceptions. - Moderate (2 votes): Tests do not cover several other changed ordering paths.
| File | Summary |
|---|---|
src/tests/JIT/Regression_ro_2/Runtime_134334.cs |
Adds regression coverage for operand-order failures. |
src/coreclr/jit/morph.cpp |
Preserves execution order across morphing and optimization rewrites. |
|
I'd probably also extend debug diagnostics in fgdiagnose (e.g. to catch stale GTF_REVERSE on things that shouldn't have it), but not necessary for this PR. LGTM, but I wonder if @jakobbotsch or @dotnet/jit-contrib want to take a look |
Agreed and I'd prefer we do that for the |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| // A resolved callvirt needs its null check even when the target is inlined. | ||
| if (check_this) { | ||
| MONO_EMIT_NEW_CHECK_THIS (cfg, sp [0]->dreg); | ||
| check_this = FALSE; | ||
| } |
There was a problem hiding this comment.
@lewing, found this issue in Mono from the regression test. Essentially Mono would sometimes drop the nullcheck for a callvirt in some cases when it tried to convert it to a direct call instead.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
A critical rewrite may retain reverse-order metadata and reorder side effects.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Resolved since last review (2)
For some context: for a long time we never set Various other places in the JIT would not handle the reverse order correctly either, e.g. local morph and the way it associates extra information with operands nodes does not work with this flag either. So even with this PR I do not think we can just start setting If we were to try to support this everywhere then I think it would warrant a stress mode where the importer reorders operands and sets the flag. The code patterns of this PR are quite complex and with zero testing of those paths I am not very convinced about the correctness. Would it be possible to limit this PR to the nodes that can set the flag before morph today? I.e. the SIMD nodes? I agree with Egor it would be nice to expand |
Notably we do end up setting it in some cases, from prepping call argument costs in morph itself, and so remorph risks encountering it already. Likewise it can be hit from any post costing phase that recalls morph (like early prop or assertion prop do). Ideally none of these are impactful, but I'm not convinced they actually are correct as is.
I can limit it to the SIMD cases -or- I guess I could go and remove its usage from all the pre-import SIMD cases too? Really its whatever you think is the best long term here. I personally expect that it would be better to support
I am interested as to what you find complex about them in particular? Most of the changes are SIMD related already with a couple for the helper operands and fgMorphSmpOp where its explicitly using evaluation order. That is primarily just replacing direct op1/op2 usage with a firstOp/secondOp that is swapped based on whether isReverseOps is set; and corresponding regression tests were added for those scenarios. |
I think the primary thing depending on execution order is local assertion prop, which we only run during global morph. So that's probably why it's not been as problematic. FWIW, I would also like us to stop calling morph after morph. Recent work on
If we can remove it from pre-import SIMD cases that would IMO be best. We can avoid having to teach the frontend about the possibility of the flag and keep that as the invariant for the future. In my opinion a better design would be new The reverse versions could be HIR-only and rationalization would normalize them away. The main source of
The flag handling and how |
|
Thanks. Will look at removing the reverse_ops from the simd instead and get a separate PR up. |
`ConcatUpperUpper` and variable-index shuffle creation swap SIMD operands to match hardware encodings. Marking those imported trees with `GTF_REVERSE_OPS` does not preserve source evaluation order through morph, which can use a later operand's null check to eliminate a check needed by an earlier call. Use `gtPrepareOperandsForReordering` to sequence the original first operand via a temp and `GT_COMMA` only when reordering is observable. This also covers shuffles expanded during rationalization, before tree threading switches to LIR. Retain the existing SIMD instruction shapes and assert that HW intrinsics enter morph without a reverse flag. This is a focused follow-up to #134464. Validation: x64, x86, and ARM64 Debug JIT builds; JIT formatting; three targeted tests pass normally, with MinOpts, AVX2 disabled, hardware intrinsics disabled, and JitStress2. The full merged regression runner reports 423 passed, 0 failed, 7 skipped. Delayed shuffle codegen matches the reverse-flag implementation instruction-for-instruction in the measured 256- and 512-bit cases. Resolves #134334 > [!NOTE] > This PR description and implementation were prepared with GitHub Copilot assistance. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>


Morph must visit operands in execution order: assertions established by a later-executing operand cannot be used to optimize an earlier one. The HW intrinsic walk used physical operand order, allowing a null check to be removed before a side-effecting call. Ordinary binary-node traversal and several morph rewrites also failed to preserve reversed operand ordering.
This change:
Adds compact regressions for the original concatenation failure and the SIMD double-negation subtraction exception-order failure at 128/256/512 bits, plus basic scalar coverage. The scalar controls pass on the original JIT; they are not claimed as reproductions of the reversed-IR failures.
Validation
Independent of #133776; no changes from that PR are required.
Resolves #134334
Note
This PR description and implementation were prepared with GitHub Copilot assistance.