[clang] [Objective-C][WebAssembly] Always emit nil-check for indirect calls (PR #215920)

Hendrik Hübner via cfe-commits cfe-commits at lists.llvm.org
Thu Aug 13 00:15:43 PDT 2026


https://github.com/HendrikHuebner updated https://github.com/llvm/llvm-project/pull/215920

>From 88a907d96e17f14a22d8101194aea786e30824fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hendrik=20H=C3=BCbner?= <hhuebner at MacBookPro.lan>
Date: Thu, 13 Aug 2026 01:19:46 +0200
Subject: [PATCH 1/2] [Objective-C][WebAssembly] Always emit nil-check for
 indirect calls

---
 clang/lib/CodeGen/CGObjCGNU.cpp              | 10 +++++
 clang/test/CodeGenObjC/gnustep2-wasm32-nil.m | 44 ++++++++++++++++++++
 2 files changed, 54 insertions(+)
 create mode 100644 clang/test/CodeGenObjC/gnustep2-wasm32-nil.m

diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index 32a1afe310629..e2ebca6bff1ce 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -2968,11 +2968,21 @@ CGObjCGNU::GenerateMessageSend(CodeGenFunction &CGF,
                                   Class, Receiver))
       return false;
 
+
     // If there's a consumed argument, we need a nil check.
     if (Method && Method->hasParamDestroyedInCallee()) {
       hasParamDestroyedInCallee = true;
     }
 
+    // WebAssembly indirect calls require an exact function type match.
+    // Therfore, we cannot use libobjc2's nil-IMP stubs for WebAssembly
+    // and must always emit a null check and optionally zero the result.
+    if (CGM.getTriple().isWasm() && !isDirect) {
+      requiresExplicitZeroResult =
+          !Return.isUnused() && !ResultType->isVoidType();
+      return true;
+    }
+
     // If the return value isn't flagged as unused, and the result
     // type isn't in our narrow set where we assume compatibility,
     // we need a nil check to ensure a nil value.
diff --git a/clang/test/CodeGenObjC/gnustep2-wasm32-nil.m b/clang/test/CodeGenObjC/gnustep2-wasm32-nil.m
new file mode 100644
index 0000000000000..093cdfaa8366b
--- /dev/null
+++ b/clang/test/CodeGenObjC/gnustep2-wasm32-nil.m
@@ -0,0 +1,44 @@
+// RUN: %clang_cc1 -triple wasm32-unknown-emscripten -emit-llvm -fobjc-runtime=gnustep-2.2 -o - %s | FileCheck %s
+
+
+typedef struct {
+  int x;
+  int y;
+  int z;
+} S;
+
+ at interface Object
+- (int)value;
+- (S)s;
+ at end
+
+// We expect generated nil-checks and zeroing of the result for WASM.
+
+int sendToPossiblyNil(Object *object) {
+  // CHECK-LABEL: define{{.*}} i32 @sendToPossiblyNil
+  // CHECK: icmp eq ptr %{{.*}}, null
+  // CHECK: br i1 %{{.*}}, label %[[CONTINUE:.*]], label %[[SEND:.*]]
+  // CHECK: [[SEND]]:
+  // CHECK: call ptr @objc_msg_lookup_sender
+  // CHECK: br label %[[CONTINUE]]
+  // CHECK: [[CONTINUE]]:
+  // CHECK: phi i32 [ %{{.*}}, %[[SEND]] ], [ 0, %{{.*}} ]
+  return [object value];
+}
+
+Triple sendStructToPossiblyNil(Object *object) {
+  // CHECK-LABEL: define{{.*}} void @sendStructToPossiblyNil
+  // CHECK: [[ISNIL:%.*]] = icmp eq ptr %{{.*}}, null
+  // CHECK: br i1 [[ISNIL]], label %[[NIL_CLEANUP:.*]], label %[[STRUCT_SEND:.*]]
+  // CHECK: [[STRUCT_SEND]]:
+  // CHECK: call ptr @objc_msg_lookup_sender
+  // CHECK: call void %{{.*}}(ptr{{.*}} sret(%struct.Triple){{.*}}
+  // CHECK: br label %[[STRUCT_CONTINUE:.*]]
+  // CHECK: [[NIL_CLEANUP]]:
+  // CHECK-NEXT: call void @llvm.memset.p0.i32(ptr align 4 %agg.result, i8 0, i32 12, i1 false)
+  // CHECK-NEXT: br label %[[STRUCT_CONTINUE]]
+  // CHECK: [[STRUCT_CONTINUE]]:
+  // CHECK: ret void
+  return [object s];
+}
+

>From 97fcbd083a18d7131d4444562f728d66ce0e377d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hendrik=20H=C3=BCbner?= <hhuebner at MacBookPro.lan>
Date: Thu, 13 Aug 2026 09:15:30 +0200
Subject: [PATCH 2/2] fix

---
 clang/lib/CodeGen/CGObjCGNU.cpp              | 1 -
 clang/test/CodeGenObjC/gnustep2-wasm32-nil.m | 5 ++---
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index e2ebca6bff1ce..ff79b0aacb96d 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -2968,7 +2968,6 @@ CGObjCGNU::GenerateMessageSend(CodeGenFunction &CGF,
                                   Class, Receiver))
       return false;
 
-
     // If there's a consumed argument, we need a nil check.
     if (Method && Method->hasParamDestroyedInCallee()) {
       hasParamDestroyedInCallee = true;
diff --git a/clang/test/CodeGenObjC/gnustep2-wasm32-nil.m b/clang/test/CodeGenObjC/gnustep2-wasm32-nil.m
index 093cdfaa8366b..ae66f3ffbf9a9 100644
--- a/clang/test/CodeGenObjC/gnustep2-wasm32-nil.m
+++ b/clang/test/CodeGenObjC/gnustep2-wasm32-nil.m
@@ -26,13 +26,13 @@ int sendToPossiblyNil(Object *object) {
   return [object value];
 }
 
-Triple sendStructToPossiblyNil(Object *object) {
+S sendStructToPossiblyNil(Object *object) {
   // CHECK-LABEL: define{{.*}} void @sendStructToPossiblyNil
   // CHECK: [[ISNIL:%.*]] = icmp eq ptr %{{.*}}, null
   // CHECK: br i1 [[ISNIL]], label %[[NIL_CLEANUP:.*]], label %[[STRUCT_SEND:.*]]
   // CHECK: [[STRUCT_SEND]]:
   // CHECK: call ptr @objc_msg_lookup_sender
-  // CHECK: call void %{{.*}}(ptr{{.*}} sret(%struct.Triple){{.*}}
+  // CHECK: call void %{{.*}}(ptr{{.*}} sret(%struct.S){{.*}}
   // CHECK: br label %[[STRUCT_CONTINUE:.*]]
   // CHECK: [[NIL_CLEANUP]]:
   // CHECK-NEXT: call void @llvm.memset.p0.i32(ptr align 4 %agg.result, i8 0, i32 12, i1 false)
@@ -41,4 +41,3 @@ Triple sendStructToPossiblyNil(Object *object) {
   // CHECK: ret void
   return [object s];
 }
-



More information about the cfe-commits mailing list