[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