[llvm] [GlobalISel] avoid G_TRUNC with floating-point G_MERGE_VALUES source (PR #206733)
Kacper Doga via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 2 05:49:48 PDT 2026
https://github.com/varev-dev updated https://github.com/llvm/llvm-project/pull/206733
>From aa702f937c998a2f9ed8ffdc13b102e107e65815 Mon Sep 17 00:00:00 2001
From: "Doga, Kacper" <kacper.doga at intel.com>
Date: Tue, 30 Jun 2026 15:13:22 +0200
Subject: [PATCH] [GlobalISel] avoid G_TRUNC with floating-point G_MERGE_VALUES
source
With extended LLT, scalar LLT carry an integer/float kind information. The `G_TRUNC(G_MERGE_VALUES)`
fold in `LegalizationArtifactCombiner::tryCombineTrunc` truncated, copied or rebuilt a merge directly
from the merge's source register.
**Problem**
When those sources are floating-point this produces artifacts with float source operand - e.g.
`i1 = G_TRUNC f32` or a rebuilt `iN = G_MERGE_VALUES f32, ...` which are bit level integer operations
and must not take a float operand.
**Fix**
Reinterpret a floating-point merge source to an integer of the same size via `G_BITCAST` before
truncating, copying or rebuilding the merge. So the emitted artifacts stay on integer operands.
Non-float sources and non-extended-LLT builds are unaffected.
---
.../GlobalISel/LegalizationArtifactCombiner.h | 25 +++++--
.../legalize-trunc-merge-float-src.mir | 72 +++++++++++++++++++
2 files changed, 90 insertions(+), 7 deletions(-)
create mode 100644 llvm/test/CodeGen/AArch64/GlobalISel/legalize-trunc-merge-float-src.mir
diff --git a/llvm/include/llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h b/llvm/include/llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h
index d8d7ccc0bd7a7..bd83b23eca38b 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h
@@ -292,29 +292,40 @@ class LegalizationArtifactCombiner {
if (!DstTy.isScalar() || !MergeSrcTy.isScalar())
return false;
+ // G_TRUNC/G_MERGE_VALUES operate on the raw bit pattern - if the merge
+ // feeds us float sources, reinterpret them to integer of the same size
+ // so we never emit a G_TRUNC or G_MERGE_VALUES with a floating-point
+ // source operand.
+ const LLT WorkTy =
+ MergeSrcTy.isFloat() ? LLT::integer(MergeSrcSize) : MergeSrcTy;
+ auto AsInt = [&](Register R) {
+ if (MergeSrcTy != WorkTy)
+ return Builder.buildBitcast(WorkTy, R).getReg(0);
+ return R;
+ };
+
if (DstSize < MergeSrcSize) {
// When the merge source is larger than the destination, we can just
// truncate the merge source directly
- if (isInstUnsupported({TargetOpcode::G_TRUNC, {DstTy, MergeSrcTy}}))
+ if (isInstUnsupported({TargetOpcode::G_TRUNC, {DstTy, WorkTy}}))
return false;
LLVM_DEBUG(dbgs() << "Combining G_TRUNC(G_MERGE_VALUES) to G_TRUNC: "
<< MI);
- Builder.buildTrunc(DstReg, MergeSrcReg);
+ Builder.buildTrunc(DstReg, AsInt(MergeSrcReg));
UpdatedDefs.push_back(DstReg);
} else if (DstSize == MergeSrcSize) {
// If the sizes match we can simply try to replace the register
LLVM_DEBUG(
dbgs() << "Replacing G_TRUNC(G_MERGE_VALUES) with merge input: "
<< MI);
- replaceRegOrBuildCopy(DstReg, MergeSrcReg, MRI, Builder, UpdatedDefs,
- Observer);
+ replaceRegOrBuildCopy(DstReg, AsInt(MergeSrcReg), MRI, Builder,
+ UpdatedDefs, Observer);
} else if (DstSize % MergeSrcSize == 0) {
// If the trunc size is a multiple of the merge source size we can use
// a smaller merge instead
- if (isInstUnsupported(
- {TargetOpcode::G_MERGE_VALUES, {DstTy, MergeSrcTy}}))
+ if (isInstUnsupported({TargetOpcode::G_MERGE_VALUES, {DstTy, WorkTy}}))
return false;
LLVM_DEBUG(
@@ -326,7 +337,7 @@ class LegalizationArtifactCombiner {
"trunc(merge) should require less inputs than merge");
SmallVector<Register, 8> SrcRegs(NumSrcs);
for (unsigned i = 0; i < NumSrcs; ++i)
- SrcRegs[i] = SrcMerge->getSourceReg(i);
+ SrcRegs[i] = AsInt(SrcMerge->getSourceReg(i));
Builder.buildMergeValues(DstReg, SrcRegs);
UpdatedDefs.push_back(DstReg);
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/legalize-trunc-merge-float-src.mir b/llvm/test/CodeGen/AArch64/GlobalISel/legalize-trunc-merge-float-src.mir
new file mode 100644
index 0000000000000..60d5c2e2b21fc
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/legalize-trunc-merge-float-src.mir
@@ -0,0 +1,72 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 6
+# RUN: llc -mtriple=aarch64 -run-pass=legalizer %s -o - | FileCheck %s
+
+# Regression test for LegalizationArtifactCombiner::tryCombineTrunc: a G_TRUNC
+# whose G_MERGE_VALUES source operands are floating-point must not fold to a
+# G_TRUNC (or rebuilt G_MERGE_VALUES) with a float source operand. The float
+# source is reinterpreted to an integer of the same size via G_BITCAST first.
+
+---
+# DstSize < MergeSrcSize - trunc folds trought the merge to the first source.
+name: trunc_merge_float_src_smaller
+legalized: true
+body: |
+ bb.0:
+ liveins: $s0, $s1
+ ; CHECK-LABEL: name: trunc_merge_float_src_smaller
+ ; CHECK: liveins: $s0, $s1
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:_(f32) = COPY $s0
+ ; CHECK-NEXT: [[BITCAST:%[0-9]+]]:_(i32) = G_BITCAST [[COPY]](f32)
+ ; CHECK-NEXT: [[TRUNC:%[0-9]+]]:_(i16) = G_TRUNC [[BITCAST]](i32)
+ ; CHECK-NEXT: $h0 = COPY [[TRUNC]](i16)
+ %0:_(f32) = COPY $s0
+ %1:_(f32) = COPY $s1
+ %2:_(i64) = G_MERGE_VALUES %0(f32), %1(f32)
+ %3:_(i16) = G_TRUNC %2(i64)
+ $h0 = COPY %3(i16)
+...
+---
+# DstSize == MergeSrcSize - trunc becomes the (bitcast of the) first source.
+name: trunc_merge_float_src_equal
+legalized: true
+body: |
+ bb.0:
+ liveins: $s0, $s1
+ ; CHECK-LABEL: name: trunc_merge_float_src_equal
+ ; CHECK: liveins: $s0, $s1
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:_(f32) = COPY $s0
+ ; CHECK-NEXT: [[BITCAST:%[0-9]+]]:_(i32) = G_BITCAST [[COPY]](f32)
+ ; CHECK-NEXT: $w0 = COPY [[BITCAST]](i32)
+ %0:_(f32) = COPY $s0
+ %1:_(f32) = COPY $s1
+ %2:_(i64) = G_MERGE_VALUES %0(f32), %1(f32)
+ %3:_(i32) = G_TRUNC %2(i64)
+ $w0 = COPY %3(i32)
+...
+---
+# DstSize % MergeSrcSize == 0 - rebuild a smaller merge, whose sources must be
+# the integer-reinterpreted halves, not the floats.
+name: trunc_merge_float_src_multiple
+legalized: true
+body: |
+ bb.0:
+ liveins: $h0, $h1, $h2, $h3
+ ; CHECK-LABEL: name: trunc_merge_float_src_multiple
+ ; CHECK: liveins: $h0, $h1, $h2, $h3
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:_(f16) = COPY $h0
+ ; CHECK-NEXT: [[COPY1:%[0-9]+]]:_(f16) = COPY $h1
+ ; CHECK-NEXT: [[BITCAST:%[0-9]+]]:_(i16) = G_BITCAST [[COPY]](f16)
+ ; CHECK-NEXT: [[BITCAST1:%[0-9]+]]:_(i16) = G_BITCAST [[COPY1]](f16)
+ ; CHECK-NEXT: [[MV:%[0-9]+]]:_(i32) = G_MERGE_VALUES [[BITCAST]](i16), [[BITCAST1]](i16)
+ ; CHECK-NEXT: $w0 = COPY [[MV]](i32)
+ %0:_(f16) = COPY $h0
+ %1:_(f16) = COPY $h1
+ %2:_(f16) = COPY $h2
+ %3:_(f16) = COPY $h3
+ %4:_(i64) = G_MERGE_VALUES %0(f16), %1(f16), %2(f16), %3(f16)
+ %5:_(i32) = G_TRUNC %4(i64)
+ $w0 = COPY %5(i32)
+...
More information about the llvm-commits
mailing list