Index: llvm/include/llvm/IR/IntrinsicsWebAssembly.td =================================================================== --- llvm/include/llvm/IR/IntrinsicsWebAssembly.td +++ llvm/include/llvm/IR/IntrinsicsWebAssembly.td @@ -31,6 +31,10 @@ //===----------------------------------------------------------------------===// 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 Index: llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.h =================================================================== --- llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.h +++ llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.h @@ -39,6 +39,7 @@ void dumpTypeStack(Twine Msg); bool typeError(SMLoc ErrorLoc, const Twine &Msg); bool popType(SMLoc ErrorLoc, Optional 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); Index: llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp =================================================================== --- llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp +++ llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp @@ -102,6 +102,19 @@ 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(Inst.getOperand(0).getImm()); @@ -308,6 +321,10 @@ 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 Index: llvm/lib/Target/WebAssembly/Utils/WebAssemblyTypeUtilities.h =================================================================== --- llvm/lib/Target/WebAssembly/Utils/WebAssemblyTypeUtilities.h +++ llvm/lib/Target/WebAssembly/Utils/WebAssemblyTypeUtilities.h @@ -80,6 +80,10 @@ 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 parseType(StringRef Type); Index: llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td =================================================================== --- llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td +++ llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td @@ -27,6 +27,12 @@ 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("int_wasm_ref_is_null_" # ht) rc:$ref))], + "ref.is_null\t$ref", + "ref.is_null", 0xd1>, + Requires<[HasReferenceTypes]>; } defm "" : REF_I; Index: llvm/test/CodeGen/WebAssembly/ref-null.ll =================================================================== --- llvm/test/CodeGen/WebAssembly/ref-null.ll +++ llvm/test/CodeGen/WebAssembly/ref-null.ll @@ -1,4 +1,5 @@ -; 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 @@ -6,21 +7,59 @@ 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 +} Index: llvm/test/MC/WebAssembly/reference-types.s =================================================================== --- llvm/test/MC/WebAssembly/reference-types.s +++ 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] Index: llvm/test/MC/WebAssembly/type-checker-errors.s =================================================================== --- llvm/test/MC/WebAssembly/type-checker-errors.s +++ llvm/test/MC/WebAssembly/type-checker-errors.s @@ -468,6 +468,26 @@ # 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.