Skip to content

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts - #129361

Merged
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare
Jun 17, 2026
Merged

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts#129361
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

For xarch, mirror the existing TYP_UBYTE branch when the const fits in INT8. Lets (sbyte)x < -64 lower to cmp cl, 0xC0; setl instead of movsx rax, cl; cmp eax, -64; setl. Skip the transform for op2 == 0 with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is sign-extended to full register width.

Fixes#10337.

For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand
is sign-extended to full register width.
Fixesdotnet#10337.
CopilotAI review requested due to automatic review settings June 13, 2026 02:07
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 13, 2026
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL

CopilotAI 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.

Pull request overview

This PR extends CoreCLR xarch JIT lowering so Lowering::OptimizeConstCompare can remove TYP_BYTE casts when comparing against constants that fit in INT8, enabling byte-sized compares (avoiding an extra sign-extension) while preserving correctness for the x < 0 / x >= 0 zero-compare codegen special-case. It also adds a JIT regression test covering the relevant signed-byte comparison patterns.

Changes:

  • Extend Lowering::OptimizeConstCompare (xarch) with a new TYP_BYTE cast-removal path gated on FitsIn<INT8>(const) and excluding const == 0 with LT/GE.
  • Add a new JIT regression test (Runtime_10337) that validates semantics for various signed-byte constant compare patterns (including memory and truncating sources).
  • Wire the new test into the merged regression test project (Regression_ro_2.csproj).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/lower.cppAdds an xarch-only TYP_BYTE cast-removal case in OptimizeConstCompare, with a correctness bailout for the LT/GE compare-to-zero sign-bit optimization.
src/tests/JIT/Regression/Regression_ro_2.csprojIncludes the new Runtime_10337 test source in the regression project.
src/tests/JIT/Regression/JitBlue/Runtime_10337/Runtime_10337.csNew regression test validating behavior for signed-byte constant compares (including truncation and memory-load shapes).

@tannergooding

Copy link
Copy Markdown
Member

I'm not actually sure this is better here, due to how Intel and AMD tend to treat operations on partial registers. This is particularly true if the small value comes from memory.

I think it would be good to benchmark this and/or consult our friends at Intel to determine what the recommended codegen here is.

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

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

All the diffs are basically variants of

image

@tannergooding

Copy link
Copy Markdown
Member

All the diffs are basically variants of

Right. The question is really whether or not using a small register as part of a test/compare is ok (I think it is) or if it has a penalty as compared to just doing the sign extension in the first place.

The rules have changed here quite a lot over time and while I think modern processors are fine for this case since it doesn't use the upper bits and isn't further modifying the register, there are many documented cases where using the 8/16-bit form can and will force a delay because the CPU functionally ends up having to insert the "merge with upper 24/16-bits" logic, which it elides if the instruction which writes the small register immediately zero/sign extends the value instead. -- This is notably a feature of APX as well, allowing the extension to be part of the original instruction and fully removing the merge semantics.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@Ruihan-Yin any concern over the perf impact of this optimization? My simple local tests don't find any unusual stalls.

@Ruihan-Yin

Ruihan-Yin commented Jun 16, 2026

Copy link
Copy Markdown
Member

The optimization looks valid from my perspective, CMP/TEST does not update data registers but just EFLAGS registers, so I don't expect they have partial write issue, flag register update should be the same no matter the data size. There should be no uarch level concern for this optimization.

But as Tanner pointed out, 8-bit operation is generally costly for its merging behavior (does not apply to TEST/CMP). And JIT avoids it AFAIK.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

/ba-g persistent timeouts

@AndyAyersMS
AndyAyersMS merged commit 3e4b29a into dotnet:mainJun 17, 2026
143 of 147 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is
sign-extended to full register width.
Fixes#10337.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suboptimal codegen when comparing sbyte and 16-bit values

4 participants

@AndyAyersMS@tannergooding@Ruihan-Yin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
JIT: extend OptimizeConstCompare to remove TYP_BYTE casts by AndyAyersMS · Pull Request #129361 · dotnet/runtime · GitHub
Skip to content

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts - #129361

Merged
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare
Jun 17, 2026
Merged

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts#129361
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

For xarch, mirror the existing TYP_UBYTE branch when the const fits in INT8. Lets (sbyte)x < -64 lower to cmp cl, 0xC0; setl instead of movsx rax, cl; cmp eax, -64; setl. Skip the transform for op2 == 0 with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is sign-extended to full register width.

Fixes#10337.

For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand
is sign-extended to full register width.
Fixesdotnet#10337.
CopilotAI review requested due to automatic review settings June 13, 2026 02:07
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 13, 2026
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL

CopilotAI 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.

Pull request overview

This PR extends CoreCLR xarch JIT lowering so Lowering::OptimizeConstCompare can remove TYP_BYTE casts when comparing against constants that fit in INT8, enabling byte-sized compares (avoiding an extra sign-extension) while preserving correctness for the x < 0 / x >= 0 zero-compare codegen special-case. It also adds a JIT regression test covering the relevant signed-byte comparison patterns.

Changes:

  • Extend Lowering::OptimizeConstCompare (xarch) with a new TYP_BYTE cast-removal path gated on FitsIn<INT8>(const) and excluding const == 0 with LT/GE.
  • Add a new JIT regression test (Runtime_10337) that validates semantics for various signed-byte constant compare patterns (including memory and truncating sources).
  • Wire the new test into the merged regression test project (Regression_ro_2.csproj).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/lower.cppAdds an xarch-only TYP_BYTE cast-removal case in OptimizeConstCompare, with a correctness bailout for the LT/GE compare-to-zero sign-bit optimization.
src/tests/JIT/Regression/Regression_ro_2.csprojIncludes the new Runtime_10337 test source in the regression project.
src/tests/JIT/Regression/JitBlue/Runtime_10337/Runtime_10337.csNew regression test validating behavior for signed-byte constant compares (including truncation and memory-load shapes).

@tannergooding

Copy link
Copy Markdown
Member

I'm not actually sure this is better here, due to how Intel and AMD tend to treat operations on partial registers. This is particularly true if the small value comes from memory.

I think it would be good to benchmark this and/or consult our friends at Intel to determine what the recommended codegen here is.

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

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

All the diffs are basically variants of

image

@tannergooding

Copy link
Copy Markdown
Member

All the diffs are basically variants of

Right. The question is really whether or not using a small register as part of a test/compare is ok (I think it is) or if it has a penalty as compared to just doing the sign extension in the first place.

The rules have changed here quite a lot over time and while I think modern processors are fine for this case since it doesn't use the upper bits and isn't further modifying the register, there are many documented cases where using the 8/16-bit form can and will force a delay because the CPU functionally ends up having to insert the "merge with upper 24/16-bits" logic, which it elides if the instruction which writes the small register immediately zero/sign extends the value instead. -- This is notably a feature of APX as well, allowing the extension to be part of the original instruction and fully removing the merge semantics.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@Ruihan-Yin any concern over the perf impact of this optimization? My simple local tests don't find any unusual stalls.

@Ruihan-Yin

Ruihan-Yin commented Jun 16, 2026

Copy link
Copy Markdown
Member

The optimization looks valid from my perspective, CMP/TEST does not update data registers but just EFLAGS registers, so I don't expect they have partial write issue, flag register update should be the same no matter the data size. There should be no uarch level concern for this optimization.

But as Tanner pointed out, 8-bit operation is generally costly for its merging behavior (does not apply to TEST/CMP). And JIT avoids it AFAIK.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

/ba-g persistent timeouts

@AndyAyersMS
AndyAyersMS merged commit 3e4b29a into dotnet:mainJun 17, 2026
143 of 147 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is
sign-extended to full register width.
Fixes#10337.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suboptimal codegen when comparing sbyte and 16-bit values

4 participants

@AndyAyersMS@tannergooding@Ruihan-Yin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' JIT: extend OptimizeConstCompare to remove TYP_BYTE casts by AndyAyersMS · Pull Request #129361 · dotnet/runtime · GitHub
Skip to content

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts - #129361

Merged
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare
Jun 17, 2026
Merged

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts#129361
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

For xarch, mirror the existing TYP_UBYTE branch when the const fits in INT8. Lets (sbyte)x < -64 lower to cmp cl, 0xC0; setl instead of movsx rax, cl; cmp eax, -64; setl. Skip the transform for op2 == 0 with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is sign-extended to full register width.

Fixes#10337.

For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand
is sign-extended to full register width.
Fixesdotnet#10337.
CopilotAI review requested due to automatic review settings June 13, 2026 02:07
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 13, 2026
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL

CopilotAI 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.

Pull request overview

This PR extends CoreCLR xarch JIT lowering so Lowering::OptimizeConstCompare can remove TYP_BYTE casts when comparing against constants that fit in INT8, enabling byte-sized compares (avoiding an extra sign-extension) while preserving correctness for the x < 0 / x >= 0 zero-compare codegen special-case. It also adds a JIT regression test covering the relevant signed-byte comparison patterns.

Changes:

  • Extend Lowering::OptimizeConstCompare (xarch) with a new TYP_BYTE cast-removal path gated on FitsIn<INT8>(const) and excluding const == 0 with LT/GE.
  • Add a new JIT regression test (Runtime_10337) that validates semantics for various signed-byte constant compare patterns (including memory and truncating sources).
  • Wire the new test into the merged regression test project (Regression_ro_2.csproj).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/lower.cppAdds an xarch-only TYP_BYTE cast-removal case in OptimizeConstCompare, with a correctness bailout for the LT/GE compare-to-zero sign-bit optimization.
src/tests/JIT/Regression/Regression_ro_2.csprojIncludes the new Runtime_10337 test source in the regression project.
src/tests/JIT/Regression/JitBlue/Runtime_10337/Runtime_10337.csNew regression test validating behavior for signed-byte constant compares (including truncation and memory-load shapes).

@tannergooding

Copy link
Copy Markdown
Member

I'm not actually sure this is better here, due to how Intel and AMD tend to treat operations on partial registers. This is particularly true if the small value comes from memory.

I think it would be good to benchmark this and/or consult our friends at Intel to determine what the recommended codegen here is.

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

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

All the diffs are basically variants of

image

@tannergooding

Copy link
Copy Markdown
Member

All the diffs are basically variants of

Right. The question is really whether or not using a small register as part of a test/compare is ok (I think it is) or if it has a penalty as compared to just doing the sign extension in the first place.

The rules have changed here quite a lot over time and while I think modern processors are fine for this case since it doesn't use the upper bits and isn't further modifying the register, there are many documented cases where using the 8/16-bit form can and will force a delay because the CPU functionally ends up having to insert the "merge with upper 24/16-bits" logic, which it elides if the instruction which writes the small register immediately zero/sign extends the value instead. -- This is notably a feature of APX as well, allowing the extension to be part of the original instruction and fully removing the merge semantics.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@Ruihan-Yin any concern over the perf impact of this optimization? My simple local tests don't find any unusual stalls.

@Ruihan-Yin

Ruihan-Yin commented Jun 16, 2026

Copy link
Copy Markdown
Member

The optimization looks valid from my perspective, CMP/TEST does not update data registers but just EFLAGS registers, so I don't expect they have partial write issue, flag register update should be the same no matter the data size. There should be no uarch level concern for this optimization.

But as Tanner pointed out, 8-bit operation is generally costly for its merging behavior (does not apply to TEST/CMP). And JIT avoids it AFAIK.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

/ba-g persistent timeouts

@AndyAyersMS
AndyAyersMS merged commit 3e4b29a into dotnet:mainJun 17, 2026
143 of 147 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is
sign-extended to full register width.
Fixes#10337.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suboptimal codegen when comparing sbyte and 16-bit values

4 participants

@AndyAyersMS@tannergooding@Ruihan-Yin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' JIT: extend OptimizeConstCompare to remove TYP_BYTE casts by AndyAyersMS · Pull Request #129361 · dotnet/runtime · GitHub
Skip to content

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts - #129361

Merged
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare
Jun 17, 2026
Merged

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts#129361
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

For xarch, mirror the existing TYP_UBYTE branch when the const fits in INT8. Lets (sbyte)x < -64 lower to cmp cl, 0xC0; setl instead of movsx rax, cl; cmp eax, -64; setl. Skip the transform for op2 == 0 with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is sign-extended to full register width.

Fixes#10337.

For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand
is sign-extended to full register width.
Fixesdotnet#10337.
CopilotAI review requested due to automatic review settings June 13, 2026 02:07
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 13, 2026
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL

CopilotAI 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.

Pull request overview

This PR extends CoreCLR xarch JIT lowering so Lowering::OptimizeConstCompare can remove TYP_BYTE casts when comparing against constants that fit in INT8, enabling byte-sized compares (avoiding an extra sign-extension) while preserving correctness for the x < 0 / x >= 0 zero-compare codegen special-case. It also adds a JIT regression test covering the relevant signed-byte comparison patterns.

Changes:

  • Extend Lowering::OptimizeConstCompare (xarch) with a new TYP_BYTE cast-removal path gated on FitsIn<INT8>(const) and excluding const == 0 with LT/GE.
  • Add a new JIT regression test (Runtime_10337) that validates semantics for various signed-byte constant compare patterns (including memory and truncating sources).
  • Wire the new test into the merged regression test project (Regression_ro_2.csproj).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/lower.cppAdds an xarch-only TYP_BYTE cast-removal case in OptimizeConstCompare, with a correctness bailout for the LT/GE compare-to-zero sign-bit optimization.
src/tests/JIT/Regression/Regression_ro_2.csprojIncludes the new Runtime_10337 test source in the regression project.
src/tests/JIT/Regression/JitBlue/Runtime_10337/Runtime_10337.csNew regression test validating behavior for signed-byte constant compares (including truncation and memory-load shapes).

@tannergooding

Copy link
Copy Markdown
Member

I'm not actually sure this is better here, due to how Intel and AMD tend to treat operations on partial registers. This is particularly true if the small value comes from memory.

I think it would be good to benchmark this and/or consult our friends at Intel to determine what the recommended codegen here is.

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

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

All the diffs are basically variants of

image

@tannergooding

Copy link
Copy Markdown
Member

All the diffs are basically variants of

Right. The question is really whether or not using a small register as part of a test/compare is ok (I think it is) or if it has a penalty as compared to just doing the sign extension in the first place.

The rules have changed here quite a lot over time and while I think modern processors are fine for this case since it doesn't use the upper bits and isn't further modifying the register, there are many documented cases where using the 8/16-bit form can and will force a delay because the CPU functionally ends up having to insert the "merge with upper 24/16-bits" logic, which it elides if the instruction which writes the small register immediately zero/sign extends the value instead. -- This is notably a feature of APX as well, allowing the extension to be part of the original instruction and fully removing the merge semantics.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@Ruihan-Yin any concern over the perf impact of this optimization? My simple local tests don't find any unusual stalls.

@Ruihan-Yin

Ruihan-Yin commented Jun 16, 2026

Copy link
Copy Markdown
Member

The optimization looks valid from my perspective, CMP/TEST does not update data registers but just EFLAGS registers, so I don't expect they have partial write issue, flag register update should be the same no matter the data size. There should be no uarch level concern for this optimization.

But as Tanner pointed out, 8-bit operation is generally costly for its merging behavior (does not apply to TEST/CMP). And JIT avoids it AFAIK.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

/ba-g persistent timeouts

@AndyAyersMS
AndyAyersMS merged commit 3e4b29a into dotnet:mainJun 17, 2026
143 of 147 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is
sign-extended to full register width.
Fixes#10337.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suboptimal codegen when comparing sbyte and 16-bit values

4 participants

@AndyAyersMS@tannergooding@Ruihan-Yin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' JIT: extend OptimizeConstCompare to remove TYP_BYTE casts by AndyAyersMS · Pull Request #129361 · dotnet/runtime · GitHub
Skip to content

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts - #129361

Merged
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare
Jun 17, 2026
Merged

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts#129361
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

For xarch, mirror the existing TYP_UBYTE branch when the const fits in INT8. Lets (sbyte)x < -64 lower to cmp cl, 0xC0; setl instead of movsx rax, cl; cmp eax, -64; setl. Skip the transform for op2 == 0 with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is sign-extended to full register width.

Fixes#10337.

For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand
is sign-extended to full register width.
Fixesdotnet#10337.
CopilotAI review requested due to automatic review settings June 13, 2026 02:07
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 13, 2026
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL

CopilotAI 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.

Pull request overview

This PR extends CoreCLR xarch JIT lowering so Lowering::OptimizeConstCompare can remove TYP_BYTE casts when comparing against constants that fit in INT8, enabling byte-sized compares (avoiding an extra sign-extension) while preserving correctness for the x < 0 / x >= 0 zero-compare codegen special-case. It also adds a JIT regression test covering the relevant signed-byte comparison patterns.

Changes:

  • Extend Lowering::OptimizeConstCompare (xarch) with a new TYP_BYTE cast-removal path gated on FitsIn<INT8>(const) and excluding const == 0 with LT/GE.
  • Add a new JIT regression test (Runtime_10337) that validates semantics for various signed-byte constant compare patterns (including memory and truncating sources).
  • Wire the new test into the merged regression test project (Regression_ro_2.csproj).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/lower.cppAdds an xarch-only TYP_BYTE cast-removal case in OptimizeConstCompare, with a correctness bailout for the LT/GE compare-to-zero sign-bit optimization.
src/tests/JIT/Regression/Regression_ro_2.csprojIncludes the new Runtime_10337 test source in the regression project.
src/tests/JIT/Regression/JitBlue/Runtime_10337/Runtime_10337.csNew regression test validating behavior for signed-byte constant compares (including truncation and memory-load shapes).

@tannergooding

Copy link
Copy Markdown
Member

I'm not actually sure this is better here, due to how Intel and AMD tend to treat operations on partial registers. This is particularly true if the small value comes from memory.

I think it would be good to benchmark this and/or consult our friends at Intel to determine what the recommended codegen here is.

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

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

All the diffs are basically variants of

image

@tannergooding

Copy link
Copy Markdown
Member

All the diffs are basically variants of

Right. The question is really whether or not using a small register as part of a test/compare is ok (I think it is) or if it has a penalty as compared to just doing the sign extension in the first place.

The rules have changed here quite a lot over time and while I think modern processors are fine for this case since it doesn't use the upper bits and isn't further modifying the register, there are many documented cases where using the 8/16-bit form can and will force a delay because the CPU functionally ends up having to insert the "merge with upper 24/16-bits" logic, which it elides if the instruction which writes the small register immediately zero/sign extends the value instead. -- This is notably a feature of APX as well, allowing the extension to be part of the original instruction and fully removing the merge semantics.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@Ruihan-Yin any concern over the perf impact of this optimization? My simple local tests don't find any unusual stalls.

@Ruihan-Yin

Ruihan-Yin commented Jun 16, 2026

Copy link
Copy Markdown
Member

The optimization looks valid from my perspective, CMP/TEST does not update data registers but just EFLAGS registers, so I don't expect they have partial write issue, flag register update should be the same no matter the data size. There should be no uarch level concern for this optimization.

But as Tanner pointed out, 8-bit operation is generally costly for its merging behavior (does not apply to TEST/CMP). And JIT avoids it AFAIK.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

/ba-g persistent timeouts

@AndyAyersMS
AndyAyersMS merged commit 3e4b29a into dotnet:mainJun 17, 2026
143 of 147 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is
sign-extended to full register width.
Fixes#10337.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suboptimal codegen when comparing sbyte and 16-bit values

4 participants

@AndyAyersMS@tannergooding@Ruihan-Yin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' JIT: extend OptimizeConstCompare to remove TYP_BYTE casts by AndyAyersMS · Pull Request #129361 · dotnet/runtime · GitHub
Skip to content

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts - #129361

Merged
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare
Jun 17, 2026
Merged

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts#129361
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

For xarch, mirror the existing TYP_UBYTE branch when the const fits in INT8. Lets (sbyte)x < -64 lower to cmp cl, 0xC0; setl instead of movsx rax, cl; cmp eax, -64; setl. Skip the transform for op2 == 0 with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is sign-extended to full register width.

Fixes#10337.

For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand
is sign-extended to full register width.
Fixesdotnet#10337.
CopilotAI review requested due to automatic review settings June 13, 2026 02:07
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 13, 2026
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL

CopilotAI 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.

Pull request overview

This PR extends CoreCLR xarch JIT lowering so Lowering::OptimizeConstCompare can remove TYP_BYTE casts when comparing against constants that fit in INT8, enabling byte-sized compares (avoiding an extra sign-extension) while preserving correctness for the x < 0 / x >= 0 zero-compare codegen special-case. It also adds a JIT regression test covering the relevant signed-byte comparison patterns.

Changes:

  • Extend Lowering::OptimizeConstCompare (xarch) with a new TYP_BYTE cast-removal path gated on FitsIn<INT8>(const) and excluding const == 0 with LT/GE.
  • Add a new JIT regression test (Runtime_10337) that validates semantics for various signed-byte constant compare patterns (including memory and truncating sources).
  • Wire the new test into the merged regression test project (Regression_ro_2.csproj).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/lower.cppAdds an xarch-only TYP_BYTE cast-removal case in OptimizeConstCompare, with a correctness bailout for the LT/GE compare-to-zero sign-bit optimization.
src/tests/JIT/Regression/Regression_ro_2.csprojIncludes the new Runtime_10337 test source in the regression project.
src/tests/JIT/Regression/JitBlue/Runtime_10337/Runtime_10337.csNew regression test validating behavior for signed-byte constant compares (including truncation and memory-load shapes).

@tannergooding

Copy link
Copy Markdown
Member

I'm not actually sure this is better here, due to how Intel and AMD tend to treat operations on partial registers. This is particularly true if the small value comes from memory.

I think it would be good to benchmark this and/or consult our friends at Intel to determine what the recommended codegen here is.

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

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

All the diffs are basically variants of

image

@tannergooding

Copy link
Copy Markdown
Member

All the diffs are basically variants of

Right. The question is really whether or not using a small register as part of a test/compare is ok (I think it is) or if it has a penalty as compared to just doing the sign extension in the first place.

The rules have changed here quite a lot over time and while I think modern processors are fine for this case since it doesn't use the upper bits and isn't further modifying the register, there are many documented cases where using the 8/16-bit form can and will force a delay because the CPU functionally ends up having to insert the "merge with upper 24/16-bits" logic, which it elides if the instruction which writes the small register immediately zero/sign extends the value instead. -- This is notably a feature of APX as well, allowing the extension to be part of the original instruction and fully removing the merge semantics.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@Ruihan-Yin any concern over the perf impact of this optimization? My simple local tests don't find any unusual stalls.

@Ruihan-Yin

Ruihan-Yin commented Jun 16, 2026

Copy link
Copy Markdown
Member

The optimization looks valid from my perspective, CMP/TEST does not update data registers but just EFLAGS registers, so I don't expect they have partial write issue, flag register update should be the same no matter the data size. There should be no uarch level concern for this optimization.

But as Tanner pointed out, 8-bit operation is generally costly for its merging behavior (does not apply to TEST/CMP). And JIT avoids it AFAIK.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

/ba-g persistent timeouts

@AndyAyersMS
AndyAyersMS merged commit 3e4b29a into dotnet:mainJun 17, 2026
143 of 147 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is
sign-extended to full register width.
Fixes#10337.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suboptimal codegen when comparing sbyte and 16-bit values

4 participants

@AndyAyersMS@tannergooding@Ruihan-Yin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' JIT: extend OptimizeConstCompare to remove TYP_BYTE casts by AndyAyersMS · Pull Request #129361 · dotnet/runtime · GitHub
Skip to content

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts - #129361

Merged
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare
Jun 17, 2026
Merged

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts#129361
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

For xarch, mirror the existing TYP_UBYTE branch when the const fits in INT8. Lets (sbyte)x < -64 lower to cmp cl, 0xC0; setl instead of movsx rax, cl; cmp eax, -64; setl. Skip the transform for op2 == 0 with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is sign-extended to full register width.

Fixes#10337.

For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand
is sign-extended to full register width.
Fixesdotnet#10337.
CopilotAI review requested due to automatic review settings June 13, 2026 02:07
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 13, 2026
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL

CopilotAI 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.

Pull request overview

This PR extends CoreCLR xarch JIT lowering so Lowering::OptimizeConstCompare can remove TYP_BYTE casts when comparing against constants that fit in INT8, enabling byte-sized compares (avoiding an extra sign-extension) while preserving correctness for the x < 0 / x >= 0 zero-compare codegen special-case. It also adds a JIT regression test covering the relevant signed-byte comparison patterns.

Changes:

  • Extend Lowering::OptimizeConstCompare (xarch) with a new TYP_BYTE cast-removal path gated on FitsIn<INT8>(const) and excluding const == 0 with LT/GE.
  • Add a new JIT regression test (Runtime_10337) that validates semantics for various signed-byte constant compare patterns (including memory and truncating sources).
  • Wire the new test into the merged regression test project (Regression_ro_2.csproj).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/lower.cppAdds an xarch-only TYP_BYTE cast-removal case in OptimizeConstCompare, with a correctness bailout for the LT/GE compare-to-zero sign-bit optimization.
src/tests/JIT/Regression/Regression_ro_2.csprojIncludes the new Runtime_10337 test source in the regression project.
src/tests/JIT/Regression/JitBlue/Runtime_10337/Runtime_10337.csNew regression test validating behavior for signed-byte constant compares (including truncation and memory-load shapes).

@tannergooding

Copy link
Copy Markdown
Member

I'm not actually sure this is better here, due to how Intel and AMD tend to treat operations on partial registers. This is particularly true if the small value comes from memory.

I think it would be good to benchmark this and/or consult our friends at Intel to determine what the recommended codegen here is.

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

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

All the diffs are basically variants of

image

@tannergooding

Copy link
Copy Markdown
Member

All the diffs are basically variants of

Right. The question is really whether or not using a small register as part of a test/compare is ok (I think it is) or if it has a penalty as compared to just doing the sign extension in the first place.

The rules have changed here quite a lot over time and while I think modern processors are fine for this case since it doesn't use the upper bits and isn't further modifying the register, there are many documented cases where using the 8/16-bit form can and will force a delay because the CPU functionally ends up having to insert the "merge with upper 24/16-bits" logic, which it elides if the instruction which writes the small register immediately zero/sign extends the value instead. -- This is notably a feature of APX as well, allowing the extension to be part of the original instruction and fully removing the merge semantics.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@Ruihan-Yin any concern over the perf impact of this optimization? My simple local tests don't find any unusual stalls.

@Ruihan-Yin

Ruihan-Yin commented Jun 16, 2026

Copy link
Copy Markdown
Member

The optimization looks valid from my perspective, CMP/TEST does not update data registers but just EFLAGS registers, so I don't expect they have partial write issue, flag register update should be the same no matter the data size. There should be no uarch level concern for this optimization.

But as Tanner pointed out, 8-bit operation is generally costly for its merging behavior (does not apply to TEST/CMP). And JIT avoids it AFAIK.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

/ba-g persistent timeouts

@AndyAyersMS
AndyAyersMS merged commit 3e4b29a into dotnet:mainJun 17, 2026
143 of 147 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is
sign-extended to full register width.
Fixes#10337.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suboptimal codegen when comparing sbyte and 16-bit values

4 participants

@AndyAyersMS@tannergooding@Ruihan-Yin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); JIT: extend OptimizeConstCompare to remove TYP_BYTE casts by AndyAyersMS · Pull Request #129361 · dotnet/runtime · GitHub
Skip to content

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts - #129361

Merged
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare
Jun 17, 2026
Merged

JIT: extend OptimizeConstCompare to remove TYP_BYTE casts#129361
AndyAyersMS merged 1 commit into
dotnet:mainfrom
AndyAyersMS:fix-10337-typ-byte-compare

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

For xarch, mirror the existing TYP_UBYTE branch when the const fits in INT8. Lets (sbyte)x < -64 lower to cmp cl, 0xC0; setl instead of movsx rax, cl; cmp eax, -64; setl. Skip the transform for op2 == 0 with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is sign-extended to full register width.

Fixes#10337.

For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand
is sign-extended to full register width.
Fixesdotnet#10337.
CopilotAI review requested due to automatic review settings June 13, 2026 02:07
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 13, 2026
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@dotnet/jit-contrib PTAL

CopilotAI 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.

Pull request overview

This PR extends CoreCLR xarch JIT lowering so Lowering::OptimizeConstCompare can remove TYP_BYTE casts when comparing against constants that fit in INT8, enabling byte-sized compares (avoiding an extra sign-extension) while preserving correctness for the x < 0 / x >= 0 zero-compare codegen special-case. It also adds a JIT regression test covering the relevant signed-byte comparison patterns.

Changes:

  • Extend Lowering::OptimizeConstCompare (xarch) with a new TYP_BYTE cast-removal path gated on FitsIn<INT8>(const) and excluding const == 0 with LT/GE.
  • Add a new JIT regression test (Runtime_10337) that validates semantics for various signed-byte constant compare patterns (including memory and truncating sources).
  • Wire the new test into the merged regression test project (Regression_ro_2.csproj).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/lower.cppAdds an xarch-only TYP_BYTE cast-removal case in OptimizeConstCompare, with a correctness bailout for the LT/GE compare-to-zero sign-bit optimization.
src/tests/JIT/Regression/Regression_ro_2.csprojIncludes the new Runtime_10337 test source in the regression project.
src/tests/JIT/Regression/JitBlue/Runtime_10337/Runtime_10337.csNew regression test validating behavior for signed-byte constant compares (including truncation and memory-load shapes).

@tannergooding

Copy link
Copy Markdown
Member

I'm not actually sure this is better here, due to how Intel and AMD tend to treat operations on partial registers. This is particularly true if the small value comes from memory.

I think it would be good to benchmark this and/or consult our friends at Intel to determine what the recommended codegen here is.

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

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

The changes look correct to me, but it's probably goodness to dig a little deeper into whether or not this is actually an optimization or not.

All the diffs are basically variants of

image

@tannergooding

Copy link
Copy Markdown
Member

All the diffs are basically variants of

Right. The question is really whether or not using a small register as part of a test/compare is ok (I think it is) or if it has a penalty as compared to just doing the sign extension in the first place.

The rules have changed here quite a lot over time and while I think modern processors are fine for this case since it doesn't use the upper bits and isn't further modifying the register, there are many documented cases where using the 8/16-bit form can and will force a delay because the CPU functionally ends up having to insert the "merge with upper 24/16-bits" logic, which it elides if the instruction which writes the small register immediately zero/sign extends the value instead. -- This is notably a feature of APX as well, allowing the extension to be part of the original instruction and fully removing the merge semantics.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@Ruihan-Yin any concern over the perf impact of this optimization? My simple local tests don't find any unusual stalls.

@Ruihan-Yin

Ruihan-Yin commented Jun 16, 2026

Copy link
Copy Markdown
Member

The optimization looks valid from my perspective, CMP/TEST does not update data registers but just EFLAGS registers, so I don't expect they have partial write issue, flag register update should be the same no matter the data size. There should be no uarch level concern for this optimization.

But as Tanner pointed out, 8-bit operation is generally costly for its merging behavior (does not apply to TEST/CMP). And JIT avoids it AFAIK.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

/ba-g persistent timeouts

@AndyAyersMS
AndyAyersMS merged commit 3e4b29a into dotnet:mainJun 17, 2026
143 of 147 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
For xarch, mirror the existing TYP_UBYTE branch when the const fits in
INT8. Lets `(sbyte)x < -64` lower to `cmp cl, 0xC0; setl` instead of
`movsx rax, cl; cmp eax, -64; setl`. Skip the transform for op2 == 0
with GT_LT/GT_GE: codegen's sign-bit-shift trick assumes the operand is
sign-extended to full register width.
Fixes#10337.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suboptimal codegen when comparing sbyte and 16-bit values

4 participants

@AndyAyersMS@tannergooding@Ruihan-Yin