[llvm] [WebAssembly][FastISel] Emit signed loads for sext of i8/i16/i32 (PR #182333)

via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 19 10:14:44 PST 2026


https://github.com/ParkHanbum updated https://github.com/llvm/llvm-project/pull/182333

>From d361cd92c2b74a65c0634a785a13cf97d1bdaea2 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Fri, 20 Feb 2026 02:54:39 +0900
Subject: [PATCH] [WebAssembly][FastISel] Emit signed loads for sext of
 i8/i16/i32

FastISel currently defaults to unsigned loads for i8/i16/i32 types,
leaving any sign-extension to be handled by a separate instruction.
This patch optimizes this by folding the SExtInst into the LoadInst,
directly emitting a signed load (e.g., i32.load8_s).

When a load has a single SExtInst use, selectLoad emits a signed
load and safely removes the redundantly emitted SExtInst, handling
the bottom-up instruction traversal of FastISel.

Fixed: #180783
---
 .../WebAssembly/WebAssemblyFastISel.cpp       | 53 ++++++++++++++++---
 llvm/test/CodeGen/WebAssembly/load-ext.ll     | 37 ++++---------
 .../CodeGen/WebAssembly/offset-fastisel.ll    |  3 +-
 3 files changed, 58 insertions(+), 35 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
index c869164d96945..43b98a2d0c7c5 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
@@ -1281,6 +1281,16 @@ bool WebAssemblyFastISel::selectLoad(const Instruction *I) {
     return false;
 
   // TODO: Fold a following sign-/zero-extend into the load instruction.
+  // Fold sext(load)
+  const Value *Ext = nullptr;
+  MVT::SimpleValueType ExtVT = MVT::Other;
+  bool FoldExt = false;
+  if (Subtarget->hasSignExt() && Load->hasOneUse()) {
+    if ((Ext = dyn_cast<SExtInst>(*Load->user_begin()))) {
+      ExtVT = getSimpleType(Ext->getType());
+      FoldExt = true;
+    }
+  }
 
   unsigned Opc;
   const TargetRegisterClass *RC;
@@ -1288,16 +1298,37 @@ bool WebAssemblyFastISel::selectLoad(const Instruction *I) {
   switch (getSimpleType(Load->getType())) {
   case MVT::i1:
   case MVT::i8:
-    Opc = A64 ? WebAssembly::LOAD8_U_I32_A64 : WebAssembly::LOAD8_U_I32_A32;
-    RC = &WebAssembly::I32RegClass;
+    if (FoldExt && ExtVT == MVT::i64) {
+      Opc = A64 ? WebAssembly::LOAD8_S_I64_A64 : WebAssembly::LOAD8_S_I64_A32;
+      RC = &WebAssembly::I64RegClass;
+    } else if (FoldExt && ExtVT == MVT::i32) {
+      Opc = A64 ? WebAssembly::LOAD8_S_I32_A64 : WebAssembly::LOAD8_S_I32_A32;
+      RC = &WebAssembly::I32RegClass;
+    } else {
+      Opc = A64 ? WebAssembly::LOAD8_U_I32_A64 : WebAssembly::LOAD8_U_I32_A32;
+      RC = &WebAssembly::I32RegClass;
+    }
     break;
   case MVT::i16:
-    Opc = A64 ? WebAssembly::LOAD16_U_I32_A64 : WebAssembly::LOAD16_U_I32_A32;
-    RC = &WebAssembly::I32RegClass;
+  if (FoldExt && ExtVT == MVT::i64) {
+      Opc = A64 ? WebAssembly::LOAD16_S_I64_A64 : WebAssembly::LOAD16_S_I64_A32;
+      RC = &WebAssembly::I64RegClass;
+    } else if (FoldExt && ExtVT == MVT::i32) {
+      Opc = A64 ? WebAssembly::LOAD16_S_I32_A64 : WebAssembly::LOAD16_S_I32_A32;
+      RC = &WebAssembly::I32RegClass;
+    } else {
+      Opc = A64 ? WebAssembly::LOAD16_U_I32_A64 : WebAssembly::LOAD16_U_I32_A32;
+      RC = &WebAssembly::I32RegClass;
+    }
     break;
   case MVT::i32:
-    Opc = A64 ? WebAssembly::LOAD_I32_A64 : WebAssembly::LOAD_I32_A32;
-    RC = &WebAssembly::I32RegClass;
+    if (FoldExt && ExtVT == MVT::i64) {
+      Opc = A64 ? WebAssembly::LOAD32_S_I64_A64 : WebAssembly::LOAD32_S_I64_A32;
+      RC = &WebAssembly::I64RegClass;
+    } else {
+      Opc = A64 ? WebAssembly::LOAD_I32_A64 : WebAssembly::LOAD_I32_A32;
+      RC = &WebAssembly::I32RegClass;
+    }
     break;
   case MVT::i64:
     Opc = A64 ? WebAssembly::LOAD_I64_A64 : WebAssembly::LOAD_I64_A32;
@@ -1323,6 +1354,16 @@ bool WebAssemblyFastISel::selectLoad(const Instruction *I) {
 
   addLoadStoreOperands(Addr, MIB, createMachineMemOperandFor(Load));
 
+  if (FoldExt) {
+    unsigned ExtReg = lookUpRegForValue(Ext);
+    if (ExtReg) {
+      if (MachineInstr *ExtMI = MRI.getUniqueVRegDef(ExtReg)) {
+        MRI.replaceRegWith(ExtReg, ResultReg);
+        ExtMI->eraseFromParent();
+      }
+    }
+    updateValueMap(Ext, ResultReg);
+  }
   updateValueMap(Load, ResultReg);
   return true;
 }
diff --git a/llvm/test/CodeGen/WebAssembly/load-ext.ll b/llvm/test/CodeGen/WebAssembly/load-ext.ll
index c3460f7916c30..f83678c112e09 100644
--- a/llvm/test/CodeGen/WebAssembly/load-ext.ll
+++ b/llvm/test/CodeGen/WebAssembly/load-ext.ll
@@ -6,11 +6,8 @@
 ; Test that extending loads are assembled properly.
 
 ; CHECK-LABEL: sext_i8_i32:
-; DAG: i32.load8_s $push0=, 0($0){{$}}
-; DAG-NEXT: return $pop0{{$}}
-; FAST:      i32.load8_u   $push[[L:[0-9]+]]=, 0($0)
-; FAST-NEXT: i32.extend8_s $push[[EXT:[0-9]+]]=, $pop[[L]]
-; FAST-NEXT: return        $pop[[EXT]]
+; CHECK: i32.load8_s $push0=, 0($0){{$}}
+; CHECK-NEXT: return $pop0{{$}}
 define i32 @sext_i8_i32(ptr %p) {
   %v = load i8, ptr %p
   %e = sext i8 %v to i32
@@ -31,11 +28,8 @@ define i32 @zext_i8_i32(ptr %p) {
 }
 
 ; CHECK-LABEL: sext_i16_i32:
-; DAG: i32.load16_s $push0=, 0($0){{$}}
-; DAG-NEXT: return $pop0{{$}}
-; FAST:      i32.load16_u   $push[[L:[0-9]+]]=, 0($0)
-; FAST-NEXT: i32.extend16_s $push[[EXT:[0-9]+]]=, $pop[[L]]
-; FAST-NEXT: return         $pop[[EXT]]
+; CHECK: i32.load16_s $push0=, 0($0){{$}}
+; CHECK-NEXT: return $pop0{{$}}
 define i32 @sext_i16_i32(ptr %p) {
   %v = load i16, ptr %p
   %e = sext i16 %v to i32
@@ -56,12 +50,8 @@ define i32 @zext_i16_i32(ptr %p) {
 }
 
 ; CHECK-LABEL: sext_i8_i64:
-; DAG: i64.load8_s $push0=, 0($0){{$}}
-; DAG-NEXT: return $pop0{{$}}
-; FAST:      i32.load8_u      $push[[L:[0-9]+]]=, 0($0)
-; FAST-NEXT: i64.extend_i32_u $push[[EXT32:[0-9]+]]=, $pop[[L]]
-; FAST-NEXT: i64.extend8_s    $push[[EXT8:[0-9]+]]=, $pop[[EXT32]]
-; FAST-NEXT: return           $pop[[EXT8]]
+; CHECK: i64.load8_s $push0=, 0($0){{$}}
+; CHECK-NEXT: return $pop0{{$}}
 define i64 @sext_i8_i64(ptr %p) {
   %v = load i8, ptr %p
   %e = sext i8 %v to i64
@@ -83,12 +73,8 @@ define i64 @zext_i8_i64(ptr %p) {
 }
 
 ; CHECK-LABEL: sext_i16_i64:
-; DAG: i64.load16_s $push0=, 0($0){{$}}
-; DAG-NEXT: return $pop0{{$}}
-; FAST:      i32.load16_u     $push[[L:[0-9]+]]=, 0($0)
-; FAST-NEXT: i64.extend_i32_u $push[[EXT32:[0-9]+]]=, $pop[[L]]
-; FAST-NEXT: i64.extend16_s   $push[[EXT16:[0-9]+]]=, $pop[[EXT32]]
-; FAST-NEXT: return           $pop[[EXT16]]
+; CHECK: i64.load16_s $push0=, 0($0){{$}}
+; CHECK-NEXT: return $pop0{{$}}
 define i64 @sext_i16_i64(ptr %p) {
   %v = load i16, ptr %p
   %e = sext i16 %v to i64
@@ -110,11 +96,8 @@ define i64 @zext_i16_i64(ptr %p) {
 }
 
 ; CHECK-LABEL: sext_i32_i64:
-; DAG: i64.load32_s $push0=, 0($0){{$}}
-; DAG-NEXT: return $pop0{{$}}
-; FAST:      i32.load         $push[[L:[0-9]+]]=, 0($0)
-; FAST-NEXT: i64.extend_i32_s $push[[EXT:[0-9]+]]=, $pop[[L]]
-; FAST-NEXT: return           $pop[[EXT]]
+; CHECK: i64.load32_s $push0=, 0($0){{$}}
+; CHECK-NEXT: return $pop0{{$}}
 define i64 @sext_i32_i64(ptr %p) {
   %v = load i32, ptr %p
   %e = sext i32 %v to i64
diff --git a/llvm/test/CodeGen/WebAssembly/offset-fastisel.ll b/llvm/test/CodeGen/WebAssembly/offset-fastisel.ll
index c004410f18122..cb68a2029fa9e 100644
--- a/llvm/test/CodeGen/WebAssembly/offset-fastisel.ll
+++ b/llvm/test/CodeGen/WebAssembly/offset-fastisel.ll
@@ -121,8 +121,7 @@ define i32 @load_i8_s_with_folded_offset(ptr %p) {
 ; DEFAULT-LABEL: load_i8_s_with_folded_offset:
 ; DEFAULT:         .functype load_i8_s_with_folded_offset (i32) -> (i32)
 ; DEFAULT-NEXT:  # %bb.0:
-; DEFAULT-NEXT:    i32.load8_u $push1=, 24($0)
-; DEFAULT-NEXT:    i32.extend8_s $push0=, $pop1
+; DEFAULT-NEXT:    i32.load8_s $push0=, 24($0)
 ; DEFAULT-NEXT:    # fallthrough-return
 ;
 ; MVP-LABEL: load_i8_s_with_folded_offset:



More information about the llvm-commits mailing list