Originally #71540 by Paolo Matos, I picked it up and finished it. Resolves https://github.com/llvm/llvm-project/issues/69894. Model WebAssembly externref and funcref as target("wasm.externref") / target("wasm.funcref") TargetExtTypes instead of pointers in non-integral address spaces 10 and 20. The entire WebAssemblyLowerRefTypesIntPtrConv can be removed. This breaks the GlobalISel handling for reference types, I just disabled GlobalISel handling for functions that use them. I added intrinsics for `wasm.ptr.to_funcref` and `wasm.funcref.to_ptr`. ptr.to_funcref does a table.get from the indirect function pointer table. As a special case, 0 is converted to the null funcref rather than doing table.get on 0. `wasm.funcref.to_ptr` is only handled when we call it immediately, otherwise it will fail to lower. We could dynamically put the funcref into the table to make it work but that would require a stack of spilled funcrefs and isn't worth the effort. In the process of looking into this, I noticed that clang used to allow casting from funcref to function pointer but if the cast result escaped it would hit `Cannot select: FUNCREF_TO_PTR` in the backend. I added a diagnostic that says "a funcref can only be converted to a pointer to be directly called" to make this a little cleaner. cc @QuantumSegfault --------- Co-authored-by: Paulo Matos <pmatos@igalia.com>
68 lines
2.2 KiB
LLVM
68 lines
2.2 KiB
LLVM
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
|
|
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -mattr=+reference-types | FileCheck %s
|
|
|
|
; The zero value of a WebAssembly reference type is the null reference, so a
|
|
; zeroinitializer (or null) reference constant must lower to ref.null rather
|
|
; than crash instruction selection.
|
|
|
|
%externref = type target("wasm.externref")
|
|
%funcref = type target("wasm.funcref")
|
|
|
|
declare void @take_externref(%externref)
|
|
declare i32 @llvm.wasm.ref.is_null.extern(%externref)
|
|
|
|
define %externref @ret_zero_externref() {
|
|
; CHECK-LABEL: ret_zero_externref:
|
|
; CHECK: .functype ret_zero_externref () -> (externref)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: ref.null_extern
|
|
; CHECK-NEXT: # fallthrough-return
|
|
ret %externref zeroinitializer
|
|
}
|
|
|
|
define %funcref @ret_zero_funcref() {
|
|
; CHECK-LABEL: ret_zero_funcref:
|
|
; CHECK: .functype ret_zero_funcref () -> (funcref)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: ref.null_func
|
|
; CHECK-NEXT: # fallthrough-return
|
|
ret %funcref zeroinitializer
|
|
}
|
|
|
|
define void @pass_zero_externref() {
|
|
; CHECK-LABEL: pass_zero_externref:
|
|
; CHECK: .functype pass_zero_externref () -> ()
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: ref.null_extern
|
|
; CHECK-NEXT: call take_externref
|
|
; CHECK-NEXT: # fallthrough-return
|
|
call void @take_externref(%externref zeroinitializer)
|
|
ret void
|
|
}
|
|
|
|
define %externref @select_zero_externref(i1 %c, %externref %x) {
|
|
; CHECK-LABEL: select_zero_externref:
|
|
; CHECK: .functype select_zero_externref (i32, externref) -> (externref)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get 1
|
|
; CHECK-NEXT: ref.null_extern
|
|
; CHECK-NEXT: local.get 0
|
|
; CHECK-NEXT: i32.const 1
|
|
; CHECK-NEXT: i32.and
|
|
; CHECK-NEXT: externref.select
|
|
; CHECK-NEXT: # fallthrough-return
|
|
%r = select i1 %c, %externref %x, %externref zeroinitializer
|
|
ret %externref %r
|
|
}
|
|
|
|
define i32 @is_null_zero_externref() {
|
|
; CHECK-LABEL: is_null_zero_externref:
|
|
; CHECK: .functype is_null_zero_externref () -> (i32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: ref.null_extern
|
|
; CHECK-NEXT: ref.is_null
|
|
; CHECK-NEXT: # fallthrough-return
|
|
%r = call i32 @llvm.wasm.ref.is_null.extern(%externref zeroinitializer)
|
|
ret i32 %r
|
|
}
|