[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