[llvm] [CodeGenPrepare] Reuse free wider extensions without truncating (PR #228750)

via llvm-commits llvm-commits at lists.llvm.org
Sat Oct 3 10:42:54 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Dávid Bolvanský (davidbolvansky)

<details>
<summary>Changes</summary>

Reuse an existing sign/zero extension for a wider extension of the same kind when the target considers it free. This avoids a truncate/re-extend pair that hides an extending-load promotion opportunity.

Motivated by zstd's `ZSTD_decompressSequences`: byte-sized FSE bit counts feed both 32-bit calculations and 64-bit `BIT_mask` indices. With #<!-- -->228736 applied, this improves zstd decompression by **10.1% on a source corpus and 12.6% on JSON** on Apple M1 Pro, versus #<!-- -->228736 alone, in a decoder-only compiler comparison.

Assisted-by: OpenAI Codex


---
Full diff: https://github.com/llvm/llvm-project/pull/228750.diff


2 Files Affected:

- (modified) llvm/lib/CodeGen/CodeGenPrepare.cpp (+16) 
- (added) llvm/test/Transforms/CodeGenPrepare/reuse-extension.ll (+228) 


``````````diff
diff --git a/llvm/lib/CodeGen/CodeGenPrepare.cpp b/llvm/lib/CodeGen/CodeGenPrepare.cpp
index 0b96c77ff33ea..fbb4d3b4e0261 100644
--- a/llvm/lib/CodeGen/CodeGenPrepare.cpp
+++ b/llvm/lib/CodeGen/CodeGenPrepare.cpp
@@ -7391,6 +7391,22 @@ bool CodeGenPrepare::optimizeExtUses(Instruction *I) {
       continue;
 
     // Both src and def are live in this block. Rewrite the use.
+    // Reuse an extension directly when a wider extension of the same kind is
+    // free for its users.
+    // Going through a truncation instead can hide an extending-load
+    // opportunity.
+    if (I->getOpcode() == User->getOpcode() &&
+        User->getType()->getScalarSizeInBits() >
+            I->getType()->getScalarSizeInBits()) {
+      U = I;
+      if (TLI->isExtFree(User)) {
+        ++NumExtUses;
+        MadeChange = true;
+        continue;
+      }
+      U = Src;
+    }
+
     Instruction *&InsertedTrunc = InsertedTruncs[UserBB];
 
     if (!InsertedTrunc) {
diff --git a/llvm/test/Transforms/CodeGenPrepare/reuse-extension.ll b/llvm/test/Transforms/CodeGenPrepare/reuse-extension.ll
new file mode 100644
index 0000000000000..8484ea8b42f7a
--- /dev/null
+++ b/llvm/test/Transforms/CodeGenPrepare/reuse-extension.ll
@@ -0,0 +1,228 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -S -passes='require<profile-summary>,function(codegenprepare)' -mtriple=aarch64 < %s | FileCheck %s --check-prefixes=CHECK,AARCH64
+; RUN: opt -S -passes='require<profile-summary>,function(codegenprepare)' -mtriple=x86_64 < %s | FileCheck %s --check-prefixes=CHECK,X86
+; REQUIRES: aarch64-registered-target, x86-registered-target
+
+; Reusing a free wider extension must not introduce a truncation that
+; prevents CodeGenPrepare from moving the extension to the original load.
+declare void @consume(i32)
+declare void @consume64(i64)
+
+define i64 @zext_i8(ptr %p, ptr %table, i1 %cold) {
+;
+; CHECK-LABEL: @zext_i8(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[V:%.*]] = load i8, ptr [[P:%.*]], align 1
+; CHECK-NEXT:    [[PROMOTED:%.*]] = zext i8 [[V]] to i64
+; CHECK-NEXT:    [[V32:%.*]] = zext i8 [[V]] to i32
+; CHECK-NEXT:    br i1 [[COLD:%.*]], label [[CALL:%.*]], label [[JOIN:%.*]]
+; CHECK:       call:
+; CHECK-NEXT:    call void @consume(i32 [[V32]])
+; CHECK-NEXT:    br label [[JOIN]]
+; CHECK:       join:
+; CHECK-NEXT:    [[PTR:%.*]] = getelementptr i32, ptr [[TABLE:%.*]], i64 [[PROMOTED]]
+; CHECK-NEXT:    [[MASK:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT:    [[SUM:%.*]] = add i32 [[MASK]], [[V32]]
+; CHECK-NEXT:    [[RESULT:%.*]] = zext i32 [[SUM]] to i64
+; CHECK-NEXT:    ret i64 [[RESULT]]
+;
+entry:
+  %v = load i8, ptr %p
+  %v32 = zext i8 %v to i32
+  br i1 %cold, label %call, label %join
+call:
+  call void @consume(i32 %v32)
+  br label %join
+join:
+  %v64 = zext i8 %v to i64
+  %ptr = getelementptr i32, ptr %table, i64 %v64
+  %mask = load i32, ptr %ptr
+  %sum = add i32 %mask, %v32
+  %result = zext i32 %sum to i64
+  ret i64 %result
+}
+
+define i64 @zext_i16(ptr %p, ptr %table, i1 %cold) {
+;
+; CHECK-LABEL: @zext_i16(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[V:%.*]] = load i16, ptr [[P:%.*]], align 2
+; CHECK-NEXT:    [[PROMOTED:%.*]] = zext i16 [[V]] to i64
+; CHECK-NEXT:    [[V32:%.*]] = zext i16 [[V]] to i32
+; CHECK-NEXT:    br i1 [[COLD:%.*]], label [[CALL:%.*]], label [[JOIN:%.*]]
+; CHECK:       call:
+; CHECK-NEXT:    call void @consume(i32 [[V32]])
+; CHECK-NEXT:    br label [[JOIN]]
+; CHECK:       join:
+; CHECK-NEXT:    [[PTR:%.*]] = getelementptr i32, ptr [[TABLE:%.*]], i64 [[PROMOTED]]
+; CHECK-NEXT:    [[MASK:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT:    [[SUM:%.*]] = add i32 [[MASK]], [[V32]]
+; CHECK-NEXT:    [[RESULT:%.*]] = zext i32 [[SUM]] to i64
+; CHECK-NEXT:    ret i64 [[RESULT]]
+;
+entry:
+  %v = load i16, ptr %p
+  %v32 = zext i16 %v to i32
+  br i1 %cold, label %call, label %join
+call:
+  call void @consume(i32 %v32)
+  br label %join
+join:
+  %v64 = zext i16 %v to i64
+  %ptr = getelementptr i32, ptr %table, i64 %v64
+  %mask = load i32, ptr %ptr
+  %sum = add i32 %mask, %v32
+  %result = zext i32 %sum to i64
+  ret i64 %result
+}
+
+define i64 @sext_then_zext(ptr %p, ptr %table, i1 %cold) {
+;
+; CHECK-LABEL: @sext_then_zext(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[V:%.*]] = load i8, ptr [[P:%.*]], align 1
+; CHECK-NEXT:    [[V32:%.*]] = sext i8 [[V]] to i32
+; CHECK-NEXT:    br i1 [[COLD:%.*]], label [[CALL:%.*]], label [[JOIN:%.*]]
+; CHECK:       call:
+; CHECK-NEXT:    call void @consume(i32 [[V32]])
+; CHECK-NEXT:    br label [[JOIN]]
+; CHECK:       join:
+; CHECK-NEXT:    [[TMP0:%.*]] = trunc i32 [[V32]] to i8
+; CHECK-NEXT:    [[V64:%.*]] = zext i8 [[TMP0]] to i64
+; CHECK-NEXT:    [[PTR:%.*]] = getelementptr i32, ptr [[TABLE:%.*]], i64 [[V64]]
+; CHECK-NEXT:    [[MASK:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT:    [[SUM:%.*]] = add i32 [[MASK]], [[V32]]
+; CHECK-NEXT:    [[RESULT:%.*]] = zext i32 [[SUM]] to i64
+; CHECK-NEXT:    ret i64 [[RESULT]]
+;
+entry:
+  %v = load i8, ptr %p
+  %v32 = sext i8 %v to i32
+  br i1 %cold, label %call, label %join
+call:
+  call void @consume(i32 %v32)
+  br label %join
+join:
+  %v64 = zext i8 %v to i64
+  %ptr = getelementptr i32, ptr %table, i64 %v64
+  %mask = load i32, ptr %ptr
+  %sum = add i32 %mask, %v32
+  %result = zext i32 %sum to i64
+  ret i64 %result
+}
+
+define i64 @zext_then_sext(ptr %p, ptr %table, i1 %cold) {
+;
+; CHECK-LABEL: @zext_then_sext(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[V:%.*]] = load i8, ptr [[P:%.*]], align 1
+; CHECK-NEXT:    [[V32:%.*]] = zext i8 [[V]] to i32
+; CHECK-NEXT:    br i1 [[COLD:%.*]], label [[CALL:%.*]], label [[JOIN:%.*]]
+; CHECK:       call:
+; CHECK-NEXT:    call void @consume(i32 [[V32]])
+; CHECK-NEXT:    br label [[JOIN]]
+; CHECK:       join:
+; CHECK-NEXT:    [[TMP0:%.*]] = trunc i32 [[V32]] to i8
+; CHECK-NEXT:    [[V64:%.*]] = sext i8 [[TMP0]] to i64
+; CHECK-NEXT:    [[PTR:%.*]] = getelementptr i32, ptr [[TABLE:%.*]], i64 [[V64]]
+; CHECK-NEXT:    [[MASK:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT:    [[SUM:%.*]] = add i32 [[MASK]], [[V32]]
+; CHECK-NEXT:    [[RESULT:%.*]] = zext i32 [[SUM]] to i64
+; CHECK-NEXT:    ret i64 [[RESULT]]
+;
+entry:
+  %v = load i8, ptr %p
+  %v32 = zext i8 %v to i32
+  br i1 %cold, label %call, label %join
+call:
+  call void @consume(i32 %v32)
+  br label %join
+join:
+  %v64 = sext i8 %v to i64
+  %ptr = getelementptr i32, ptr %table, i64 %v64
+  %mask = load i32, ptr %ptr
+  %sum = add i32 %mask, %v32
+  %result = zext i32 %sum to i64
+  ret i64 %result
+}
+
+
+define i64 @sext_address(ptr %p, ptr %table, i1 %cold) {
+; AARCH64-LABEL: @sext_address(
+; AARCH64-NEXT:  entry:
+; AARCH64-NEXT:    [[V:%.*]] = load i8, ptr [[P:%.*]], align 1
+; AARCH64-NEXT:    [[V64:%.*]] = sext i8 [[V]] to i64
+; AARCH64-NEXT:    [[V32:%.*]] = sext i8 [[V]] to i32
+; AARCH64-NEXT:    br i1 [[COLD:%.*]], label [[CALL:%.*]], label [[JOIN:%.*]]
+; AARCH64:       call:
+; AARCH64-NEXT:    call void @consume(i32 [[V32]])
+; AARCH64-NEXT:    br label [[JOIN]]
+; AARCH64:       join:
+; AARCH64-NEXT:    [[PTR:%.*]] = getelementptr i32, ptr [[TABLE:%.*]], i64 [[V64]]
+; AARCH64-NEXT:    [[MASK:%.*]] = load i32, ptr [[PTR]], align 4
+; AARCH64-NEXT:    [[SUM:%.*]] = add i32 [[MASK]], [[V32]]
+; AARCH64-NEXT:    [[RESULT:%.*]] = zext i32 [[SUM]] to i64
+; AARCH64-NEXT:    ret i64 [[RESULT]]
+;
+; X86-LABEL: @sext_address(
+; X86-NEXT:  entry:
+; X86-NEXT:    [[V:%.*]] = load i8, ptr [[P:%.*]], align 1
+; X86-NEXT:    [[V32:%.*]] = sext i8 [[V]] to i32
+; X86-NEXT:    br i1 [[COLD:%.*]], label [[CALL:%.*]], label [[JOIN:%.*]]
+; X86:       call:
+; X86-NEXT:    call void @consume(i32 [[V32]])
+; X86-NEXT:    br label [[JOIN]]
+; X86:       join:
+; X86-NEXT:    [[TMP0:%.*]] = trunc i32 [[V32]] to i8
+; X86-NEXT:    [[V64:%.*]] = sext i8 [[TMP0]] to i64
+; X86-NEXT:    [[PTR:%.*]] = getelementptr i32, ptr [[TABLE:%.*]], i64 [[V64]]
+; X86-NEXT:    [[MASK:%.*]] = load i32, ptr [[PTR]], align 4
+; X86-NEXT:    [[SUM:%.*]] = add i32 [[MASK]], [[V32]]
+; X86-NEXT:    [[RESULT:%.*]] = zext i32 [[SUM]] to i64
+; X86-NEXT:    ret i64 [[RESULT]]
+;
+entry:
+  %v = load i8, ptr %p
+  %v32 = sext i8 %v to i32
+  br i1 %cold, label %call, label %join
+call:
+  call void @consume(i32 %v32)
+  br label %join
+join:
+  %v64 = sext i8 %v to i64
+  %ptr = getelementptr i32, ptr %table, i64 %v64
+  %mask = load i32, ptr %ptr
+  %sum = add i32 %mask, %v32
+  %result = zext i32 %sum to i64
+  ret i64 %result
+}
+
+define i64 @sext_standalone(ptr %p, ptr %table, i1 %cold) {
+; CHECK-LABEL: @sext_standalone(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[V:%.*]] = load i8, ptr [[P:%.*]], align 1
+; CHECK-NEXT:    [[V32:%.*]] = sext i8 [[V]] to i32
+; CHECK-NEXT:    br i1 [[COLD:%.*]], label [[CALL:%.*]], label [[JOIN:%.*]]
+; CHECK:       call:
+; CHECK-NEXT:    call void @consume(i32 [[V32]])
+; CHECK-NEXT:    br label [[JOIN]]
+; CHECK:       join:
+; CHECK-NEXT:    [[TMP0:%.*]] = trunc i32 [[V32]] to i8
+; CHECK-NEXT:    [[V64:%.*]] = sext i8 [[TMP0]] to i64
+; CHECK-NEXT:    call void @consume64(i64 [[V64]])
+; CHECK-NEXT:    [[RESULT:%.*]] = zext i32 [[V32]] to i64
+; CHECK-NEXT:    ret i64 [[RESULT]]
+;
+entry:
+  %v = load i8, ptr %p
+  %v32 = sext i8 %v to i32
+  br i1 %cold, label %call, label %join
+call:
+  call void @consume(i32 %v32)
+  br label %join
+join:
+  %v64 = sext i8 %v to i64
+  call void @consume64(i64 %v64)
+  %result = zext i32 %v32 to i64
+  ret i64 %result
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/228750


More information about the llvm-commits mailing list