[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