[clang] [llvm] [clang][WebAssembly] Support reftypes & varargs in test_function_pointer_signature (PR #150921)
Hood Chatham via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 7 04:37:39 PDT 2025
https://github.com/hoodmane updated https://github.com/llvm/llvm-project/pull/150921
>From 6caa2f7b749a9c655864afbbff07e9e78dd9b1b0 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Mon, 28 Jul 2025 12:37:24 +0200
Subject: [PATCH 1/5] [clang,WebAssembly] Support reference types in
test_function_pointer_signature
---
.../CodeGen/TargetBuiltins/WebAssembly.cpp | 23 ++++----------
clang/lib/Sema/SemaWasm.cpp | 18 -----------
clang/test/CodeGen/builtins-wasm.c | 16 ++++++++--
clang/test/Sema/builtins-wasm.c | 4 ---
.../WebAssembly/WebAssemblyISelDAGToDAG.cpp | 9 ++++++
.../test/CodeGen/WebAssembly/ref-test-func.ll | 30 ++++++++++++++-----
6 files changed, 51 insertions(+), 49 deletions(-)
diff --git a/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp b/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp
index 33a8d8f8d1754..ca709e27a44f8 100644
--- a/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp
@@ -253,28 +253,15 @@ Value *CodeGenFunction::EmitWebAssemblyBuiltinExpr(unsigned BuiltinID,
Args.push_back(FuncRef);
// Add the type information
- auto addType = [this, &Args](llvm::Type *T) {
- if (T->isVoidTy()) {
- // Do nothing
- } else if (T->isFloatingPointTy()) {
- Args.push_back(ConstantFP::get(T, 0));
- } else if (T->isIntegerTy()) {
- Args.push_back(ConstantInt::get(T, 0));
- } else if (T->isPointerTy()) {
- Args.push_back(ConstantPointerNull::get(llvm::PointerType::get(
- getLLVMContext(), T->getPointerAddressSpace())));
- } else {
- // TODO: Handle reference types. For now, we reject them in Sema.
- llvm_unreachable("Unhandled type");
- }
- };
-
- addType(LLVMFuncTy->getReturnType());
+ llvm::Type *RetType = LLVMFuncTy->getReturnType();
+ if (!RetType->isVoidTy()) {
+ Args.push_back(PoisonValue::get(RetType));
+ }
// The token type indicates the boundary between return types and param
// types.
Args.push_back(PoisonValue::get(llvm::Type::getTokenTy(getLLVMContext())));
for (unsigned i = 0; i < NParams; i++) {
- addType(LLVMFuncTy->getParamType(i));
+ Args.push_back(PoisonValue::get(LLVMFuncTy->getParamType(i)));
}
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_ref_test_func);
return Builder.CreateCall(Callee, Args);
diff --git a/clang/lib/Sema/SemaWasm.cpp b/clang/lib/Sema/SemaWasm.cpp
index 8998492a71619..c42b8677470e1 100644
--- a/clang/lib/Sema/SemaWasm.cpp
+++ b/clang/lib/Sema/SemaWasm.cpp
@@ -250,24 +250,6 @@ bool SemaWasm::BuiltinWasmTestFunctionPointerSignature(CallExpr *TheCall) {
<< ArgType << FuncPtrArg->getSourceRange();
}
- // Check that the function pointer doesn't use reference types
- if (FuncTy->getReturnType().isWebAssemblyReferenceType()) {
- return Diag(
- FuncPtrArg->getBeginLoc(),
- diag::err_wasm_builtin_test_fp_sig_cannot_include_reference_type)
- << 0 << FuncTy->getReturnType() << FuncPtrArg->getSourceRange();
- }
- auto NParams = FuncTy->getNumParams();
- for (unsigned I = 0; I < NParams; I++) {
- if (FuncTy->getParamType(I).isWebAssemblyReferenceType()) {
- return Diag(
- FuncPtrArg->getBeginLoc(),
- diag::
- err_wasm_builtin_test_fp_sig_cannot_include_reference_type)
- << 1 << FuncPtrArg->getSourceRange();
- }
- }
-
// Set return type to int (the result of the test)
TheCall->setType(getASTContext().IntTy);
diff --git a/clang/test/CodeGen/builtins-wasm.c b/clang/test/CodeGen/builtins-wasm.c
index f201dfe704e7e..aeb03fd06268d 100644
--- a/clang/test/CodeGen/builtins-wasm.c
+++ b/clang/test/CodeGen/builtins-wasm.c
@@ -755,6 +755,8 @@ void *tp (void) {
typedef void (*Fvoid)(void);
typedef float (*Ffloats)(float, double, int);
typedef void (*Fpointers)(Fvoid, Ffloats, void*, int*, int***, char[5]);
+typedef __externref_t (*FExternRef)(__externref_t, __externref_t);
+typedef __funcref Fpointers (*FFuncRef)(__funcref Fvoid, __funcref Ffloats);
void use(int);
@@ -764,11 +766,21 @@ void test_function_pointer_signature_void(Fvoid func) {
}
void test_function_pointer_signature_floats(Ffloats func) {
- // WEBASSEMBLY: tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float 0.000000e+00, token poison, float 0.000000e+00, double 0.000000e+00, i32 0)
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float poison, token poison, float poison, double poison, i32 poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
void test_function_pointer_signature_pointers(Fpointers func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr null, ptr null, ptr null, ptr null, ptr null, ptr null)
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison, ptr poison, ptr poison, ptr poison, ptr poison, ptr poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+
+void test_function_pointer_externref(FExternRef func) {
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(10) poison, token poison, ptr addrspace(10) poison, ptr addrspace(10) poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+
+void test_function_pointer_funcref(FFuncRef func) {
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(20) poison, token poison, ptr addrspace(20) poison, ptr addrspace(20) poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
diff --git a/clang/test/Sema/builtins-wasm.c b/clang/test/Sema/builtins-wasm.c
index a3486b1aedb13..a93103e4804c8 100644
--- a/clang/test/Sema/builtins-wasm.c
+++ b/clang/test/Sema/builtins-wasm.c
@@ -57,8 +57,6 @@ void test_table_copy(int dst_idx, int src_idx, int nelem) {
typedef void (*F1)(void);
typedef int (*F2)(int);
-typedef int (*F3)(__externref_t);
-typedef __externref_t (*F4)(int);
void test_function_pointer_signature() {
// Test argument count validation
@@ -68,8 +66,6 @@ void test_function_pointer_signature() {
// // Test argument type validation - should require function pointer
(void)__builtin_wasm_test_function_pointer_signature((void*)0); // expected-error {{used type 'void *' where function pointer is required}}
(void)__builtin_wasm_test_function_pointer_signature((int)0); // expected-error {{used type 'int' where function pointer is required}}
- (void)__builtin_wasm_test_function_pointer_signature((F3)0); // expected-error {{not supported for function pointers with a reference type parameter}}
- (void)__builtin_wasm_test_function_pointer_signature((F4)0); // expected-error {{not supported for function pointers with a reference type return value}}
// // Test valid usage
int res = __builtin_wasm_test_function_pointer_signature((F1)0);
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
index b03b35028c69c..fc852d0a12e14 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
@@ -136,6 +136,15 @@ static APInt encodeFunctionSignature(SelectionDAG *DAG, SDLoc &DL,
if (VT == MVT::f64) {
return wasm::ValType::F64;
}
+ if (VT == MVT::externref) {
+ return wasm::ValType::EXTERNREF;
+ }
+ if (VT == MVT::funcref) {
+ return wasm::ValType::FUNCREF;
+ }
+ if (VT == MVT::exnref) {
+ return wasm::ValType::EXNREF;
+ }
LLVM_DEBUG(errs() << "Unhandled type for llvm.wasm.ref.test.func: " << VT
<< "\n");
llvm_unreachable("Unhandled type for llvm.wasm.ref.test.func");
diff --git a/llvm/test/CodeGen/WebAssembly/ref-test-func.ll b/llvm/test/CodeGen/WebAssembly/ref-test-func.ll
index ea2453faaed90..4fda253d39fe3 100644
--- a/llvm/test/CodeGen/WebAssembly/ref-test-func.ll
+++ b/llvm/test/CodeGen/WebAssembly/ref-test-func.ll
@@ -31,7 +31,7 @@ define void @test_fpsig_return_i32(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
- %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 0)
+ %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 poison)
tail call void @use(i32 noundef %res) #3
ret void
}
@@ -48,7 +48,7 @@ define void @test_fpsig_return_i64(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
- %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i64 0)
+ %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i64 poison)
tail call void @use(i32 noundef %res) #3
ret void
}
@@ -65,7 +65,7 @@ define void @test_fpsig_return_f32(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
- %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float 0.)
+ %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float poison)
tail call void @use(i32 noundef %res) #3
ret void
}
@@ -82,7 +82,7 @@ define void @test_fpsig_return_f64(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
- %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double 0.)
+ %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double poison)
tail call void @use(i32 noundef %res) #3
ret void
}
@@ -100,7 +100,7 @@ define void @test_fpsig_param_i32(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
- %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, double 0.)
+ %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, double poison)
tail call void @use(i32 noundef %res) #3
ret void
}
@@ -118,7 +118,7 @@ define void @test_fpsig_multiple_params_and_returns(ptr noundef %func) local_unn
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
- %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 0, i64 0, float 0., double 0., token poison, i64 0, float 0., i64 0)
+ %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 poison, i64 poison, float poison, double poison, token poison, i64 poison, float poison, i64 poison)
tail call void @use(i32 noundef %res) #3
ret void
}
@@ -137,10 +137,26 @@ define void @test_fpsig_ptrs(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
- %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr null, token poison, ptr null, ptr null)
+ %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr poison, token poison, ptr poison, ptr poison)
tail call void @use(i32 noundef %res) #3
ret void
}
+define void @test_reference_types(ptr noundef %func) local_unnamed_addr #0 {
+; CHECK-LABEL: test_reference_types:
+; CHK32: .functype test_reference_types (i32) -> ()
+; CHK64: .functype test_reference_types (i64) -> ()
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: local.get 0
+; CHK64-NEXT: i32.wrap_i64
+; CHECK-NEXT: table.get __indirect_function_table
+; CHECK-NEXT: ref.test (funcref, externref) -> (externref)
+; CHECK-NEXT: call use
+; CHECK-NEXT: # fallthrough-return
+entry:
+ %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(10) poison, token poison, ptr addrspace(20) poison, ptr addrspace(10) poison)
+ tail call void @use(i32 noundef %res) #3
+ ret void
+}
declare void @use(i32 noundef) local_unnamed_addr #1
>From e7b8d8ebeeec8357be93b1e297b57ebf13d9c419 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Mon, 28 Jul 2025 13:04:59 +0200
Subject: [PATCH 2/5] Fix handling of varargs function
---
clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp | 6 +++++-
clang/test/CodeGen/builtins-wasm.c | 6 ++++++
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp b/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp
index ca709e27a44f8..1a1889a4139d3 100644
--- a/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp
@@ -246,9 +246,10 @@ Value *CodeGenFunction::EmitWebAssemblyBuiltinExpr(unsigned BuiltinID,
llvm::FunctionType *LLVMFuncTy =
cast<llvm::FunctionType>(ConvertType(QualType(FuncTy, 0)));
+ bool VarArg = LLVMFuncTy->isVarArg();
unsigned NParams = LLVMFuncTy->getNumParams();
std::vector<Value *> Args;
- Args.reserve(NParams + 3);
+ Args.reserve(NParams + 3 + VarArg);
// The only real argument is the FuncRef
Args.push_back(FuncRef);
@@ -263,6 +264,9 @@ Value *CodeGenFunction::EmitWebAssemblyBuiltinExpr(unsigned BuiltinID,
for (unsigned i = 0; i < NParams; i++) {
Args.push_back(PoisonValue::get(LLVMFuncTy->getParamType(i)));
}
+ if (VarArg) {
+ Args.push_back(PoisonValue::get(Builder.getPtrTy()));
+ }
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_ref_test_func);
return Builder.CreateCall(Callee, Args);
}
diff --git a/clang/test/CodeGen/builtins-wasm.c b/clang/test/CodeGen/builtins-wasm.c
index aeb03fd06268d..61773db0dd2af 100644
--- a/clang/test/CodeGen/builtins-wasm.c
+++ b/clang/test/CodeGen/builtins-wasm.c
@@ -755,6 +755,7 @@ void *tp (void) {
typedef void (*Fvoid)(void);
typedef float (*Ffloats)(float, double, int);
typedef void (*Fpointers)(Fvoid, Ffloats, void*, int*, int***, char[5]);
+typedef void (*FVarArgs)(int, ...);
typedef __externref_t (*FExternRef)(__externref_t, __externref_t);
typedef __funcref Fpointers (*FFuncRef)(__funcref Fvoid, __funcref Ffloats);
@@ -775,6 +776,11 @@ void test_function_pointer_signature_pointers(Fpointers func) {
use(__builtin_wasm_test_function_pointer_signature(func));
}
+void test_function_pointer_signature_varargs(FVarArgs func) {
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, i32 poison, ptr poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+
void test_function_pointer_externref(FExternRef func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(10) poison, token poison, ptr addrspace(10) poison, ptr addrspace(10) poison)
use(__builtin_wasm_test_function_pointer_signature(func));
>From 6dafa4a31ca072ff194787671150fee2b2bb4d1a Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Tue, 29 Jul 2025 11:01:44 +0200
Subject: [PATCH 3/5] Add tests for struct and union abi
---
clang/test/CodeGen/builtins-wasm.c | 30 ++++++++++++++++++++++++------
1 file changed, 24 insertions(+), 6 deletions(-)
diff --git a/clang/test/CodeGen/builtins-wasm.c b/clang/test/CodeGen/builtins-wasm.c
index 61773db0dd2af..1b15c4a12a64f 100644
--- a/clang/test/CodeGen/builtins-wasm.c
+++ b/clang/test/CodeGen/builtins-wasm.c
@@ -752,41 +752,59 @@ void *tp (void) {
// WEBASSEMBLY: call {{.*}} @llvm.thread.pointer.p0()
}
-typedef void (*Fvoid)(void);
-typedef float (*Ffloats)(float, double, int);
-typedef void (*Fpointers)(Fvoid, Ffloats, void*, int*, int***, char[5]);
-typedef void (*FVarArgs)(int, ...);
-typedef __externref_t (*FExternRef)(__externref_t, __externref_t);
-typedef __funcref Fpointers (*FFuncRef)(__funcref Fvoid, __funcref Ffloats);
void use(int);
+typedef void (*Fvoid)(void);
void test_function_pointer_signature_void(Fvoid func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
+typedef float (*Ffloats)(float, double, int);
void test_function_pointer_signature_floats(Ffloats func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float poison, token poison, float poison, double poison, i32 poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
+typedef void (*Fpointers)(Fvoid, Ffloats, void*, int*, int***, char[5]);
void test_function_pointer_signature_pointers(Fpointers func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison, ptr poison, ptr poison, ptr poison, ptr poison, ptr poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
+typedef void (*FVarArgs)(int, ...);
void test_function_pointer_signature_varargs(FVarArgs func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, i32 poison, ptr poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
+typedef __externref_t (*FExternRef)(__externref_t, __externref_t);
void test_function_pointer_externref(FExternRef func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(10) poison, token poison, ptr addrspace(10) poison, ptr addrspace(10) poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
+typedef __funcref Fpointers (*FFuncRef)(__funcref Fvoid, __funcref Ffloats);
void test_function_pointer_funcref(FFuncRef func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(20) poison, token poison, ptr addrspace(20) poison, ptr addrspace(20) poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
+
+// Some tests that we get struct ABIs correct. There is no special code in
+// __builtin_wasm_test_function_pointer_signature for this, it gets handled by
+// the normal type lowering code.
+// Single element structs are unboxed, multi element structs are passed on
+// stack.
+typedef struct {double x;} (*Fstructs1)(struct {double x;}, struct {float x;}, struct {double x; float y;}, union {double x; float y;});
+void test_function_pointer_structs1(Fstructs1 func) {
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double poison, token poison, double poison, float poison, ptr poison, ptr poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+
+// Two element return struct ==> return ptr on stack
+typedef struct {double x; double y;} (*Fstructs2)(void);
+void test_function_pointer_structs2(Fstructs2 func) {
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
>From ceb363a8b346da56cc965086f1a9f9f8f8dc2969 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Tue, 29 Jul 2025 11:09:56 +0200
Subject: [PATCH 4/5] Add separate union test
---
clang/test/CodeGen/builtins-wasm.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/clang/test/CodeGen/builtins-wasm.c b/clang/test/CodeGen/builtins-wasm.c
index 1b15c4a12a64f..ecab8e9a0fd3c 100644
--- a/clang/test/CodeGen/builtins-wasm.c
+++ b/clang/test/CodeGen/builtins-wasm.c
@@ -796,9 +796,9 @@ void test_function_pointer_funcref(FFuncRef func) {
// the normal type lowering code.
// Single element structs are unboxed, multi element structs are passed on
// stack.
-typedef struct {double x;} (*Fstructs1)(struct {double x;}, struct {float x;}, struct {double x; float y;}, union {double x; float y;});
+typedef struct {double x;} (*Fstructs1)(struct {double x;}, struct {float x;}, struct {double x; float y;});
void test_function_pointer_structs1(Fstructs1 func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double poison, token poison, double poison, float poison, ptr poison, ptr poison)
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double poison, token poison, double poison, float poison, ptr poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
@@ -808,3 +808,10 @@ void test_function_pointer_structs2(Fstructs2 func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
+
+// Return union ==> return ptr on stack, one element union => unboxed
+typedef union {double x; float y;} (*FUnions)(union {double x; float y;}, union {double x;});
+void test_function_pointer_unions(FUnions func) {
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison, ptr poison, double poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
>From 60e2c93b2caf8aa7d58bfaf4ddc428a4f2ba9c8e Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Thu, 7 Aug 2025 11:57:54 +0200
Subject: [PATCH 5/5] Reject struct/union param/return type in Sema with
multivalue abi
---
.../clang/Basic/DiagnosticSemaKinds.td | 6 +-
clang/include/clang/Sema/SemaWasm.h | 3 +-
clang/lib/Sema/SemaWasm.cpp | 30 +++++++-
.../WebAssembly/builtins-test-fp-sig.c | 70 +++++++++++++++++++
clang/test/CodeGen/builtins-wasm.c | 64 -----------------
clang/test/Sema/builtins-wasm.c | 13 ++++
6 files changed, 115 insertions(+), 71 deletions(-)
create mode 100644 clang/test/CodeGen/WebAssembly/builtins-test-fp-sig.c
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index cf23594201143..116341f4b66d5 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13234,9 +13234,9 @@ def err_wasm_builtin_arg_must_match_table_element_type : Error <
"%ordinal0 argument must match the element type of the WebAssembly table in the %ordinal1 argument">;
def err_wasm_builtin_arg_must_be_integer_type : Error <
"%ordinal0 argument must be an integer">;
-def err_wasm_builtin_test_fp_sig_cannot_include_reference_type
- : Error<"not supported for "
- "function pointers with a reference type %select{return "
+def err_wasm_builtin_test_fp_sig_cannot_include_struct_or_union
+ : Error<"not supported with the multivalue ABI for "
+ "function pointers with a struct/union as %select{return "
"value|parameter}0">;
// OpenACC diagnostics.
diff --git a/clang/include/clang/Sema/SemaWasm.h b/clang/include/clang/Sema/SemaWasm.h
index 8c0639fd7e76f..f82590755d183 100644
--- a/clang/include/clang/Sema/SemaWasm.h
+++ b/clang/include/clang/Sema/SemaWasm.h
@@ -37,7 +37,8 @@ class SemaWasm : public SemaBase {
bool BuiltinWasmTableGrow(CallExpr *TheCall);
bool BuiltinWasmTableFill(CallExpr *TheCall);
bool BuiltinWasmTableCopy(CallExpr *TheCall);
- bool BuiltinWasmTestFunctionPointerSignature(CallExpr *TheCall);
+ bool BuiltinWasmTestFunctionPointerSignature(const TargetInfo &TI,
+ CallExpr *TheCall);
WebAssemblyImportNameAttr *
mergeImportNameAttr(Decl *D, const WebAssemblyImportNameAttr &AL);
diff --git a/clang/lib/Sema/SemaWasm.cpp b/clang/lib/Sema/SemaWasm.cpp
index c42b8677470e1..e7731136720e8 100644
--- a/clang/lib/Sema/SemaWasm.cpp
+++ b/clang/lib/Sema/SemaWasm.cpp
@@ -17,6 +17,7 @@
#include "clang/Basic/AddressSpaces.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/TargetBuiltins.h"
+#include "clang/Basic/TargetInfo.h"
#include "clang/Sema/Attr.h"
#include "clang/Sema/Sema.h"
@@ -227,7 +228,8 @@ bool SemaWasm::BuiltinWasmTableCopy(CallExpr *TheCall) {
return false;
}
-bool SemaWasm::BuiltinWasmTestFunctionPointerSignature(CallExpr *TheCall) {
+bool SemaWasm::BuiltinWasmTestFunctionPointerSignature(const TargetInfo &TI,
+ CallExpr *TheCall) {
if (SemaRef.checkArgCount(TheCall, 1))
return true;
@@ -250,9 +252,31 @@ bool SemaWasm::BuiltinWasmTestFunctionPointerSignature(CallExpr *TheCall) {
<< ArgType << FuncPtrArg->getSourceRange();
}
+ if (TI.getABI() == "experimental-mv") {
+ auto isStructOrUnion = [](QualType T) {
+ return T->isUnionType() || T->isStructureType();
+ };
+ if (isStructOrUnion(FuncTy->getReturnType())) {
+ return Diag(
+ FuncPtrArg->getBeginLoc(),
+ diag::
+ err_wasm_builtin_test_fp_sig_cannot_include_struct_or_union)
+ << 0 << FuncTy->getReturnType() << FuncPtrArg->getSourceRange();
+ }
+ auto NParams = FuncTy->getNumParams();
+ for (unsigned I = 0; I < NParams; I++) {
+ if (isStructOrUnion(FuncTy->getParamType(I))) {
+ return Diag(
+ FuncPtrArg->getBeginLoc(),
+ diag::
+ err_wasm_builtin_test_fp_sig_cannot_include_struct_or_union)
+ << 1 << FuncPtrArg->getSourceRange();
+ }
+ }
+ }
+
// Set return type to int (the result of the test)
TheCall->setType(getASTContext().IntTy);
-
return false;
}
@@ -279,7 +303,7 @@ bool SemaWasm::CheckWebAssemblyBuiltinFunctionCall(const TargetInfo &TI,
case WebAssembly::BI__builtin_wasm_table_copy:
return BuiltinWasmTableCopy(TheCall);
case WebAssembly::BI__builtin_wasm_test_function_pointer_signature:
- return BuiltinWasmTestFunctionPointerSignature(TheCall);
+ return BuiltinWasmTestFunctionPointerSignature(TI, TheCall);
}
return false;
diff --git a/clang/test/CodeGen/WebAssembly/builtins-test-fp-sig.c b/clang/test/CodeGen/WebAssembly/builtins-test-fp-sig.c
new file mode 100644
index 0000000000000..88447f7fa232d
--- /dev/null
+++ b/clang/test/CodeGen/WebAssembly/builtins-test-fp-sig.c
@@ -0,0 +1,70 @@
+// RUN: %clang_cc1 -triple wasm32-unknown-unknown -target-feature +gc -O3 -emit-llvm -DSINGLE_VALUE -o - %s | FileCheck %s -check-prefixes WEBASSEMBLY,WEBASSEMBLY-SV
+// RUN: %clang_cc1 -triple wasm64-unknown-unknown -target-feature +gc -O3 -emit-llvm -DSINGLE_VALUE -o - %s | FileCheck %s -check-prefixes WEBASSEMBLY,WEBASSEMBLY-SV
+// RUN: %clang_cc1 -triple wasm64-unknown-unknown -target-feature +gc -target-abi experimental-mv -O3 -emit-llvm -o - %s 2>&1 | FileCheck %s -check-prefixes WEBASSEMBLY
+// RUN: not %clang_cc1 -triple wasm64-unknown-unknown -O3 -emit-llvm -o - %s 2>&1 | FileCheck %s -check-prefixes MISSING-GC
+
+void use(int);
+
+typedef void (*Fvoid)(void);
+void test_function_pointer_signature_void(Fvoid func) {
+ // MISSING-GC: error: '__builtin_wasm_test_function_pointer_signature' needs target feature gc
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+
+typedef float (*Ffloats)(float, double, int);
+void test_function_pointer_signature_floats(Ffloats func) {
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float poison, token poison, float poison, double poison, i32 poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+
+typedef void (*Fpointers)(Fvoid, Ffloats, void*, int*, int***, char[5]);
+void test_function_pointer_signature_pointers(Fpointers func) {
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison, ptr poison, ptr poison, ptr poison, ptr poison, ptr poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+
+typedef void (*FVarArgs)(int, ...);
+void test_function_pointer_signature_varargs(FVarArgs func) {
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, i32 poison, ptr poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+
+typedef __externref_t (*FExternRef)(__externref_t, __externref_t);
+void test_function_pointer_externref(FExternRef func) {
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(10) poison, token poison, ptr addrspace(10) poison, ptr addrspace(10) poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+
+typedef __funcref Fpointers (*FFuncRef)(__funcref Fvoid, __funcref Ffloats);
+void test_function_pointer_funcref(FFuncRef func) {
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(20) poison, token poison, ptr addrspace(20) poison, ptr addrspace(20) poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+
+#ifdef SINGLE_VALUE
+// Some tests that we get struct ABIs correct. There is no special code in
+// __builtin_wasm_test_function_pointer_signature for this, it gets handled by
+// the normal type lowering code.
+// Single element structs are unboxed, multi element structs are passed on
+// stack.
+typedef struct {double x;} (*Fstructs1)(struct {double x;}, struct {float x;}, struct {double x; float y;});
+void test_function_pointer_structs1(Fstructs1 func) {
+ // WEBASSEMBLY-SV: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double poison, token poison, double poison, float poison, ptr poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+
+// Two element return struct ==> return ptr on stack
+typedef struct {double x; double y;} (*Fstructs2)(void);
+void test_function_pointer_structs2(Fstructs2 func) {
+ // WEBASSEMBLY-SV: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+
+// Return union ==> return ptr on stack, one element union => unboxed
+typedef union {double x; float y;} (*FUnions)(union {double x; float y;}, union {double x;});
+void test_function_pointer_unions(FUnions func) {
+ // WEBASSEMBLY-SV: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison, ptr poison, double poison)
+ use(__builtin_wasm_test_function_pointer_signature(func));
+}
+#endif
diff --git a/clang/test/CodeGen/builtins-wasm.c b/clang/test/CodeGen/builtins-wasm.c
index ecab8e9a0fd3c..375664b852636 100644
--- a/clang/test/CodeGen/builtins-wasm.c
+++ b/clang/test/CodeGen/builtins-wasm.c
@@ -751,67 +751,3 @@ void *tp (void) {
return __builtin_thread_pointer ();
// WEBASSEMBLY: call {{.*}} @llvm.thread.pointer.p0()
}
-
-
-void use(int);
-
-typedef void (*Fvoid)(void);
-void test_function_pointer_signature_void(Fvoid func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison)
- use(__builtin_wasm_test_function_pointer_signature(func));
-}
-
-typedef float (*Ffloats)(float, double, int);
-void test_function_pointer_signature_floats(Ffloats func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float poison, token poison, float poison, double poison, i32 poison)
- use(__builtin_wasm_test_function_pointer_signature(func));
-}
-
-typedef void (*Fpointers)(Fvoid, Ffloats, void*, int*, int***, char[5]);
-void test_function_pointer_signature_pointers(Fpointers func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison, ptr poison, ptr poison, ptr poison, ptr poison, ptr poison)
- use(__builtin_wasm_test_function_pointer_signature(func));
-}
-
-typedef void (*FVarArgs)(int, ...);
-void test_function_pointer_signature_varargs(FVarArgs func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, i32 poison, ptr poison)
- use(__builtin_wasm_test_function_pointer_signature(func));
-}
-
-typedef __externref_t (*FExternRef)(__externref_t, __externref_t);
-void test_function_pointer_externref(FExternRef func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(10) poison, token poison, ptr addrspace(10) poison, ptr addrspace(10) poison)
- use(__builtin_wasm_test_function_pointer_signature(func));
-}
-
-typedef __funcref Fpointers (*FFuncRef)(__funcref Fvoid, __funcref Ffloats);
-void test_function_pointer_funcref(FFuncRef func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(20) poison, token poison, ptr addrspace(20) poison, ptr addrspace(20) poison)
- use(__builtin_wasm_test_function_pointer_signature(func));
-}
-
-// Some tests that we get struct ABIs correct. There is no special code in
-// __builtin_wasm_test_function_pointer_signature for this, it gets handled by
-// the normal type lowering code.
-// Single element structs are unboxed, multi element structs are passed on
-// stack.
-typedef struct {double x;} (*Fstructs1)(struct {double x;}, struct {float x;}, struct {double x; float y;});
-void test_function_pointer_structs1(Fstructs1 func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double poison, token poison, double poison, float poison, ptr poison)
- use(__builtin_wasm_test_function_pointer_signature(func));
-}
-
-// Two element return struct ==> return ptr on stack
-typedef struct {double x; double y;} (*Fstructs2)(void);
-void test_function_pointer_structs2(Fstructs2 func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison)
- use(__builtin_wasm_test_function_pointer_signature(func));
-}
-
-// Return union ==> return ptr on stack, one element union => unboxed
-typedef union {double x; float y;} (*FUnions)(union {double x; float y;}, union {double x;});
-void test_function_pointer_unions(FUnions func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison, ptr poison, double poison)
- use(__builtin_wasm_test_function_pointer_signature(func));
-}
diff --git a/clang/test/Sema/builtins-wasm.c b/clang/test/Sema/builtins-wasm.c
index a93103e4804c8..9075e9eaa5230 100644
--- a/clang/test/Sema/builtins-wasm.c
+++ b/clang/test/Sema/builtins-wasm.c
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify -triple wasm32 -target-feature +reference-types %s
+// RUN: %clang_cc1 -fsyntax-only -verify -triple wasm32 -target-abi experimental-mv -DMULTIVALUE -target-feature +reference-types %s
#define EXPR_HAS_TYPE(expr, type) _Generic((expr), type : 1, default : 0)
@@ -57,6 +58,8 @@ void test_table_copy(int dst_idx, int src_idx, int nelem) {
typedef void (*F1)(void);
typedef int (*F2)(int);
+typedef void (*F3)(struct {int x; double y;});
+typedef struct {int x; double y;} (*F4)(void);
void test_function_pointer_signature() {
// Test argument count validation
@@ -73,4 +76,14 @@ void test_function_pointer_signature() {
// Test return type
_Static_assert(EXPR_HAS_TYPE(__builtin_wasm_test_function_pointer_signature((F1)0), int), "");
+
+#ifdef MULTIVALUE
+ // Test that struct arguments and returns are rejected with multivalue abi
+ (void)__builtin_wasm_test_function_pointer_signature((F3)0); // expected-error {{not supported with the multivalue ABI for function pointers with a struct/union as parameter}}
+ (void)__builtin_wasm_test_function_pointer_signature((F4)0); // expected-error {{not supported with the multivalue ABI for function pointers with a struct/union as return value}}
+#else
+ // with default abi they are fine
+ (void)__builtin_wasm_test_function_pointer_signature((F3)0);
+ (void)__builtin_wasm_test_function_pointer_signature((F4)0);
+#endif
}
More information about the llvm-commits
mailing list