Skip to content

Commit

Permalink
[WebAssembly] Implement ref.is_null MC layer support and codegen
Browse files Browse the repository at this point in the history
Custom type-checking (in WebAssemblyAsmTypeCheck.cpp) is used to
workaround the fact that separate variants of the instruction are
defined for externref and funcref.

Based on an initial patch by Paulo Matos <pmatos@igalia.com>.

Differential Revision: https://reviews.llvm.org/D123484
  • Loading branch information
asb committed May 13, 2022
1 parent 791e0d1 commit cb778e9
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 7 deletions.
4 changes: 4 additions & 0 deletions llvm/include/llvm/IR/IntrinsicsWebAssembly.td
Expand Up @@ -31,6 +31,10 @@ def int_wasm_memory_grow : Intrinsic<[llvm_anyint_ty],
//===----------------------------------------------------------------------===//
def int_wasm_ref_null_extern : Intrinsic<[llvm_externref_ty], [], [IntrNoMem]>;
def int_wasm_ref_null_func : Intrinsic<[llvm_funcref_ty], [], [IntrNoMem]>;
def int_wasm_ref_is_null_extern : Intrinsic<[llvm_i32_ty], [llvm_externref_ty],
[IntrNoMem], "llvm.wasm.ref.is_null.extern">;
def int_wasm_ref_is_null_func : Intrinsic<[llvm_i32_ty], [llvm_funcref_ty],
[IntrNoMem], "llvm.wasm.ref.is_null.func">;

//===----------------------------------------------------------------------===//
// Table intrinsics
Expand Down
17 changes: 17 additions & 0 deletions llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp
Expand Up @@ -102,6 +102,19 @@ bool WebAssemblyAsmTypeCheck::popType(SMLoc ErrorLoc,
return false;
}

bool WebAssemblyAsmTypeCheck::popRefType(SMLoc ErrorLoc) {
if (Stack.empty()) {
return typeError(ErrorLoc, StringRef("empty stack while popping reftype"));
}
auto PVT = Stack.pop_back_val();
if (!WebAssembly::isRefType(PVT)) {
return typeError(ErrorLoc, StringRef("popped ") +
WebAssembly::typeToString(PVT) +
", expected reftype");
}
return false;
}

bool WebAssemblyAsmTypeCheck::getLocal(SMLoc ErrorLoc, const MCInst &Inst,
wasm::ValType &Type) {
auto Local = static_cast<size_t>(Inst.getOperand(0).getImm());
Expand Down Expand Up @@ -308,6 +321,10 @@ bool WebAssemblyAsmTypeCheck::typeCheck(SMLoc ErrorLoc, const MCInst &Inst,
Stack.insert(Stack.end(), Sig->Params.begin(), Sig->Params.end());
} else if (Name == "unreachable") {
Unreachable = true;
} else if (Name == "ref.is_null") {
if (popRefType(ErrorLoc))
return true;
Stack.push_back(wasm::ValType::I32);
} else {
// The current instruction is a stack instruction which doesn't have
// explicit operands that indicate push/pop types, so we get those from
Expand Down
Expand Up @@ -39,6 +39,7 @@ class WebAssemblyAsmTypeCheck final {
void dumpTypeStack(Twine Msg);
bool typeError(SMLoc ErrorLoc, const Twine &Msg);
bool popType(SMLoc ErrorLoc, Optional<wasm::ValType> EVT);
bool popRefType(SMLoc ErrorLoc);
bool getLocal(SMLoc ErrorLoc, const MCInst &Inst, wasm::ValType &Type);
bool checkEnd(SMLoc ErrorLoc, bool PopVals = false);
bool checkSig(SMLoc ErrorLoc, const wasm::WasmSignature &Sig);
Expand Down
4 changes: 4 additions & 0 deletions llvm/lib/Target/WebAssembly/Utils/WebAssemblyTypeUtilities.h
Expand Up @@ -80,6 +80,10 @@ inline bool isRefType(const Type *Ty) {
return isFuncrefType(Ty) || isExternrefType(Ty);
}

inline bool isRefType(wasm::ValType Type) {
return Type == wasm::ValType::EXTERNREF || Type == wasm::ValType::FUNCREF;
}

// Convert StringRef to ValType / HealType / BlockType

Optional<wasm::ValType> parseType(StringRef Type);
Expand Down
6 changes: 6 additions & 0 deletions llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td
Expand Up @@ -27,6 +27,12 @@ multiclass REF_I<WebAssemblyRegClass rc, ValueType vt, string ht> {
vt#".select\t$dst, $lhs, $rhs, $cond",
vt#".select", 0x1b>,
Requires<[HasReferenceTypes]>;
defm REF_IS_NULL_#rc
: I<(outs I32:$dst), (ins rc:$ref), (outs), (ins),
[(set I32:$dst, (!cast<Intrinsic>("int_wasm_ref_is_null_" # ht) rc:$ref))],
"ref.is_null\t$ref",
"ref.is_null", 0xd1>,
Requires<[HasReferenceTypes]>;
}

defm "" : REF_I<FUNCREF, funcref, "func">;
Expand Down
53 changes: 46 additions & 7 deletions llvm/test/CodeGen/WebAssembly/ref-null.ll
@@ -1,26 +1,65 @@
; RUN: llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s | FileCheck %s
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc --mtriple=wasm32-unknown-unknown -mattr=+reference-types < %s | FileCheck %s

%extern = type opaque
%externref = type %extern addrspace(10)* ;; addrspace 10 is nonintegral
%funcref = type i8 addrspace(20)* ;; addrspace 20 is nonintegral

declare %externref @llvm.wasm.ref.null.extern() nounwind
declare %funcref @llvm.wasm.ref.null.func() nounwind
declare i32 @llvm.wasm.ref.is_null.extern(%externref) nounwind
declare i32 @llvm.wasm.ref.is_null.func(%funcref) nounwind

define %externref @get_null_extern() {
; CHECK-LABEL: get_null_extern:
; CHECK-NEXT: .functype get_null_extern () -> (externref)
; CHECK-NEXT: ref.null_extern
; CHECK-NEXT: end_function
; CHECK: .functype get_null_extern () -> (externref)
; CHECK-NEXT: # %bb.0:
; CHECK-NEXT: ref.null_extern
; CHECK-NEXT: # fallthrough-return
%null = call %externref @llvm.wasm.ref.null.extern()
ret %externref %null
}

define %funcref @get_null_func() {
; CHECK-LABEL: get_null_func:
; CHECK-NEXT: .functype get_null_func () -> (funcref)
; CHECK-NEXT: ref.null_func
; CHECK-NEXT: end_function
; CHECK: .functype get_null_func () -> (funcref)
; CHECK-NEXT: # %bb.0:
; CHECK-NEXT: ref.null_func
; CHECK-NEXT: # fallthrough-return
%null = call %funcref @llvm.wasm.ref.null.func()
ret %funcref %null
}

define i32 @ref_is_null_extern(%externref %eref) {
; CHECK-LABEL: ref_is_null_extern:
; CHECK: .functype ref_is_null_extern (externref) -> (i32)
; CHECK-NEXT: # %bb.0:
; CHECK-NEXT: ref.null_extern
; CHECK-NEXT: ref.is_null
; CHECK-NEXT: local.get 0
; CHECK-NEXT: ref.is_null
; CHECK-NEXT: i32.add
; CHECK-NEXT: # fallthrough-return
%null = call %externref @llvm.wasm.ref.null.extern()
%is_null = call i32 @llvm.wasm.ref.is_null.extern(%externref %null)
%arg_is_null = call i32 @llvm.wasm.ref.is_null.extern(%externref %eref)
%res = add i32 %is_null, %arg_is_null
ret i32 %res
}

define i32 @ref_is_null_func(%funcref %fref) {
; CHECK-LABEL: ref_is_null_func:
; CHECK: .functype ref_is_null_func (funcref) -> (i32)
; CHECK-NEXT: # %bb.0:
; CHECK-NEXT: ref.null_func
; CHECK-NEXT: ref.is_null
; CHECK-NEXT: local.get 0
; CHECK-NEXT: ref.is_null
; CHECK-NEXT: i32.add
; CHECK-NEXT: # fallthrough-return
%null = call %funcref @llvm.wasm.ref.null.func()
%is_null = call i32 @llvm.wasm.ref.is_null.func(%funcref %null)
%arg_is_null = call i32 @llvm.wasm.ref.is_null.func(%funcref %fref)
%res = add i32 %is_null, %arg_is_null
ret i32 %res
}
10 changes: 10 additions & 0 deletions llvm/test/MC/WebAssembly/reference-types.s
@@ -1,6 +1,16 @@
# RUN: llvm-mc -show-encoding -triple=wasm32-unknown-unknown -mattr=+reference-types < %s | FileCheck %s
# RUN: llvm-mc -show-encoding -triple=wasm64-unknown-unknown -mattr=+reference-types < %s | FileCheck %s

# CHECK-LABEL:ref_is_null:
# CHECK: ref.is_null # encoding: [0xd1]
ref_is_null:
.functype ref_is_null () -> (i32, i32)
ref.null_extern
ref.is_null
ref.null_func
ref.is_null
end_function

# CHECK-LABEL: ref_null_test:
# CHECK: ref.null_func # encoding: [0xd0,0x70]
# CHECK: ref.null_extern # encoding: [0xd0,0x6f]
Expand Down
20 changes: 20 additions & 0 deletions llvm/test/MC/WebAssembly/type-checker-errors.s
Expand Up @@ -468,6 +468,26 @@ catch_superfluous_value_at_end:
# CHECK: :[[@LINE+1]]:3: error: 1 superfluous return values
end_function

ref_is_null_empty_stack_while_popping:
.functype ref_is_null_empty_stack_while_popping () -> ()
# CHECK: [[@LINE+1]]:3: error: empty stack while popping reftype
ref.is_null
end_function

ref_is_null_type_mismatch:
.functype ref_is_null_type_mismatch () -> ()
i32.const 1
# CHECK: [[@LINE+1]]:3: error: popped i32, expected reftype
ref.is_null
end_function

ref_is_null_pushes_i32:
.functype ref_is_null_pushes_i32 () -> (i64)
ref.null_func
ref.is_null
# CHECK: :[[@LINE+1]]:3: error: popped i32, expected i64
end_function

# For the other instructions, the type checker checks vs the operands in the
# instruction definition. Perform some simple checks for these rather than
# exhaustively testing all instructions.
Expand Down

0 comments on commit cb778e9

Please sign in to comment.