Hood Chatham 15715f4089
[WebAssembly,llvm] Add llvm.wasm.ref.test.func intrinsic (#147486)
This adds an llvm intrinsic for WebAssembly to test the type of a
function. It is intended for adding a future clang builtin
` __builtin_wasm_test_function_pointer_signature` so we can test whether
calling a function pointer will fail with function signature mismatch.

Since the type of a function pointer is just `ptr` we can't figure out
the expected type from that.
The way I figured out to encode the type was by passing 0's of the
appropriate type to the intrinsic.
The first argument gives the expected type of the return type and the
later values give the expected
type of the arguments. So
```llvm
@llvm.wasm.ref.test.func(ptr %func, float 0.000000e+00, double 0.000000e+00, i32 0)
```
tests if `%func` is of type `(double, i32) -> (i32)`. It will lower to:
```wat
local.get $func
table.get $__indirect_function_table
ref.test (double, i32) -> (i32)
```
To indicate the function should be void, I somewhat arbitrarily picked
`token poison`, so the following tests for `(i32) -> ()`:
```llvm
@llvm.wasm.ref.test.func(ptr %func, token poison, i32 0)
```

To lower this intrinsic, we need some place to put the type information.
With `encodeFunctionSignature()` we encode the signature information
into an `APInt`. We decode it in `lowerEncodedFunctionSignature` in
`WebAssemblyMCInstLower.cpp`.
2025-07-22 14:07:34 -07:00

121 lines
4.6 KiB
LLVM

; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -mcpu=mvp -mattr=+reference-types | FileCheck --check-prefixes CHECK,CHK32 %s
; RUN: llc < %s --mtriple=wasm64-unknown-unknown -mcpu=mvp -mattr=+reference-types | FileCheck --check-prefixes CHECK,CHK64 %s
define void @test_fpsig_void_void(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-LABEL: test_fpsig_void_void:
; CHK32: .functype test_fpsig_void_void (i32) -> ()
; CHK64: .functype test_fpsig_void_void (i64) -> ()
; CHECK-NEXT: # %bb.0: # %entry
; CHECK-NEXT: local.get 0
; CHECK-NEXT: table.get __indirect_function_table
; CHECK-NEXT: ref.test () -> ()
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func)
tail call void @use(i32 noundef %res) #3
ret void
}
define void @test_fpsig_return_i32(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-LABEL: test_fpsig_return_i32:
; CHK32: .functype test_fpsig_return_i32 (i32) -> ()
; CHK64: .functype test_fpsig_return_i32 (i64) -> ()
; CHECK-NEXT: # %bb.0: # %entry
; CHECK-NEXT: local.get 0
; CHECK-NEXT: table.get __indirect_function_table
; CHECK-NEXT: ref.test () -> (i32)
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 0)
tail call void @use(i32 noundef %res) #3
ret void
}
define void @test_fpsig_return_i64(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-LABEL: test_fpsig_return_i64:
; CHK32: .functype test_fpsig_return_i64 (i32) -> ()
; CHK64: .functype test_fpsig_return_i64 (i64) -> ()
; CHECK-NEXT: # %bb.0: # %entry
; CHECK-NEXT: local.get 0
; CHECK-NEXT: table.get __indirect_function_table
; CHECK-NEXT: ref.test () -> (i64)
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i64 0)
tail call void @use(i32 noundef %res) #3
ret void
}
define void @test_fpsig_return_f32(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-LABEL: test_fpsig_return_f32:
; CHK32: .functype test_fpsig_return_f32 (i32) -> ()
; CHK64: .functype test_fpsig_return_f32 (i64) -> ()
; CHECK-NEXT: # %bb.0: # %entry
; CHECK-NEXT: local.get 0
; CHECK-NEXT: table.get __indirect_function_table
; CHECK-NEXT: ref.test () -> (f32)
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float 0.)
tail call void @use(i32 noundef %res) #3
ret void
}
define void @test_fpsig_return_f64(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-LABEL: test_fpsig_return_f64:
; CHK32: .functype test_fpsig_return_f64 (i32) -> ()
; CHK64: .functype test_fpsig_return_f64 (i64) -> ()
; CHECK-NEXT: # %bb.0: # %entry
; CHECK-NEXT: local.get 0
; CHECK-NEXT: table.get __indirect_function_table
; CHECK-NEXT: ref.test () -> (f64)
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double 0.)
tail call void @use(i32 noundef %res) #3
ret void
}
define void @test_fpsig_param_i32(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-LABEL: test_fpsig_param_i32:
; CHK32: .functype test_fpsig_param_i32 (i32) -> ()
; CHK64: .functype test_fpsig_param_i32 (i64) -> ()
; CHECK-NEXT: # %bb.0: # %entry
; CHECK-NEXT: local.get 0
; CHECK-NEXT: table.get __indirect_function_table
; CHECK-NEXT: ref.test (f64) -> ()
; 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.)
tail call void @use(i32 noundef %res) #3
ret void
}
define void @test_fpsig_multiple_params_and_returns(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-LABEL: test_fpsig_multiple_params_and_returns:
; CHK32: .functype test_fpsig_multiple_params_and_returns (i32) -> ()
; CHK64: .functype test_fpsig_multiple_params_and_returns (i64) -> ()
; CHECK-NEXT: # %bb.0: # %entry
; CHECK-NEXT: local.get 0
; CHECK-NEXT: table.get __indirect_function_table
; CHECK-NEXT: ref.test (i64, f32, i64) -> (i32, i64, f32, f64)
; 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)
tail call void @use(i32 noundef %res) #3
ret void
}
declare void @use(i32 noundef) local_unnamed_addr #1