diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -1495,6 +1495,9 @@ /// Return a WebAssembly externref type QualType getExternrefType() const; + /// Return a WebAssembly funcref type + QualType getFuncrefType() const; + /// Return the unique reference to a vector type of the specified /// element type and size. /// diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -1482,8 +1482,12 @@ return Quals.getObjCLifetime(); } - bool hasAddressSpace() const { return Quals.hasAddressSpace(); } - LangAS getAddressSpace() const { return Quals.getAddressSpace(); } + bool hasAddressSpace() const { + return Quals.hasAddressSpace(); + } + LangAS getAddressSpace() const { + return Quals.getAddressSpace(); + } const Type *getBaseType() const { return BaseType; } @@ -1971,6 +1975,8 @@ /// Check if this is a WebAssembly Reference Type. bool isWebAssemblyReferenceType() const; bool isWebAssemblyExternrefType() const; + bool isWebAssemblyFuncrefType() const; + /// Determines if this is a sizeless type supported by the /// 'arm_sve_vector_bits' type attribute, which can be applied to a single /// SVE vector or predicate, excluding tuple types such as svint32x4_t. @@ -6684,6 +6690,9 @@ /// Return the address space of this type. inline LangAS QualType::getAddressSpace() const { + if (getTypePtr()->isWebAssemblyFuncrefType()) { + return LangAS::wasm_funcref; + } return getQualifiers().getAddressSpace(); } @@ -6851,6 +6860,11 @@ return false; } +inline bool Type::isWebAssemblyFuncrefType() const { + return isFunctionPointerType() && + hasAttr(attr::WebAssemblyFuncref); +} + inline bool Type::isFunctionReferenceType() const { if (const auto *T = getAs()) return T->getPointeeType()->isFunctionType(); diff --git a/clang/include/clang/Basic/AddressSpaces.h b/clang/include/clang/Basic/AddressSpaces.h --- a/clang/include/clang/Basic/AddressSpaces.h +++ b/clang/include/clang/Basic/AddressSpaces.h @@ -58,6 +58,8 @@ wasm_var, wasm_externref, + wasm_funcref, + // This denotes the count of language-specific address spaces and also // the offset added to the target-specific address spaces, which are usually // specified by address space attributes __attribute__(address_space(n))). diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -4036,3 +4036,9 @@ let LangOpts = [COnly]; } def : MutualExclusions<[RandomizeLayout, NoRandomizeLayout]>; + +def WebAssemblyFuncref : TypeAttr, TargetSpecificAttr { + let Spellings = [Clang<"__funcref">]; + let Args = []; + let Documentation = [Undocumented]; +} diff --git a/clang/include/clang/Basic/BuiltinsWebAssembly.def b/clang/include/clang/Basic/BuiltinsWebAssembly.def --- a/clang/include/clang/Basic/BuiltinsWebAssembly.def +++ b/clang/include/clang/Basic/BuiltinsWebAssembly.def @@ -193,5 +193,9 @@ // Some builtins are polymorphic - see 't' as part of the third argument, // in which case the argument spec (second argument) is unused. TARGET_BUILTIN(__builtin_wasm_ref_null_extern, "e", "nc", "reference-types") +// A funcref represented as a function pointer with the funcref attribute +// attached to the type, therefore SemaChecking will check for the right +// return type. +TARGET_BUILTIN(__builtin_wasm_ref_null_func, "i", "nct", "reference-types") #undef BUILTIN #undef TARGET_BUILTIN diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7314,6 +7314,8 @@ "'__clang_arm_builtin_alias' attribute can only be applied to an ARM builtin">; def err_attribute_arm_mve_polymorphism : Error< "'__clang_arm_mve_strict_polymorphism' attribute can only be applied to an MVE/NEON vector type">; +def err_attribute_webassembly_funcref : Error< + "'__clang_webassembly_funcref' attribute can only be applied to function pointer types">; def warn_setter_getter_impl_required : Warning< "property %0 requires method %1 to be defined - " diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -13043,6 +13043,9 @@ ExprResult SemaBuiltinMatrixColumnMajorStore(CallExpr *TheCall, ExprResult CallResult); + // WebAssembly builtin handling + bool SemaBuiltinWasmRefNullFunc(CallExpr *TheCall); + public: enum FormatStringType { FST_Scanf, diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -951,6 +951,7 @@ 12, // ptr64 1, // wasm_var 10, // wasm_externref, + 20, // wasm_funcref }; return &FakeAddrSpaceMap; } else { @@ -3981,6 +3982,14 @@ return QualType(); } +/// getFuncrefType - Return a WebAssembly funcref type, which represents an +/// opaque reference to a function. +QualType ASTContext::getFuncrefType() const { + // FIXME: this cannot be written because there's no single funcref type + // anymore + return QualType(); +} + /// getScalableVectorType - Return the unique reference to a scalable vector /// type of the specified element type and size. VectorType must be a built-in /// type. diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -1737,6 +1737,8 @@ // AttributedType nodes for them. break; + case attr::WebAssemblyFuncref: OS << "__funcref"; break; + case attr::LifetimeBound: case attr::TypeNonNull: case attr::TypeNullable: diff --git a/clang/lib/Basic/Targets/DirectX.h b/clang/lib/Basic/Targets/DirectX.h --- a/clang/lib/Basic/Targets/DirectX.h +++ b/clang/lib/Basic/Targets/DirectX.h @@ -43,6 +43,7 @@ 0, // ptr64 1, // wasm_var 10, // wasm_externref + 20, // wasm_funcref }; class LLVM_LIBRARY_VISIBILITY DirectXTargetInfo : public TargetInfo { diff --git a/clang/lib/Basic/Targets/NVPTX.h b/clang/lib/Basic/Targets/NVPTX.h --- a/clang/lib/Basic/Targets/NVPTX.h +++ b/clang/lib/Basic/Targets/NVPTX.h @@ -45,6 +45,7 @@ 0, // ptr64 1, // wasm_var 10, // wasm_externref + 20, // wasm_funcref }; /// The DWARF address class. Taken from diff --git a/clang/lib/Basic/Targets/SPIR.h b/clang/lib/Basic/Targets/SPIR.h --- a/clang/lib/Basic/Targets/SPIR.h +++ b/clang/lib/Basic/Targets/SPIR.h @@ -45,6 +45,7 @@ 0, // ptr64 1, // wasm_var 10, // wasm_externref + 20, // wasm_funcref }; // Used by both the SPIR and SPIR-V targets. @@ -64,18 +65,19 @@ // cuda_constant pointer can be casted to default/"flat" pointer, but in // SPIR-V casts between constant and generic pointers are not allowed. For // this reason cuda_constant is mapped to SPIR-V CrossWorkgroup. - 1, // cuda_constant - 3, // cuda_shared - 1, // sycl_global - 5, // sycl_global_device - 6, // sycl_global_host - 3, // sycl_local - 0, // sycl_private - 0, // ptr32_sptr - 0, // ptr32_uptr - 0, // ptr64 - 1, // wasm_var - 10, // wasm_externref + 1, // cuda_constant + 3, // cuda_shared + 1, // sycl_global + 5, // sycl_global_device + 6, // sycl_global_host + 3, // sycl_local + 0, // sycl_private + 0, // ptr32_sptr + 0, // ptr32_uptr + 0, // ptr64 + 1, // wasm_var + 10,// wasm_externref + 20,// wasm_funcref }; // Base class for SPIR and SPIR-V target info. diff --git a/clang/lib/Basic/Targets/TCE.h b/clang/lib/Basic/Targets/TCE.h --- a/clang/lib/Basic/Targets/TCE.h +++ b/clang/lib/Basic/Targets/TCE.h @@ -52,6 +52,7 @@ 0, // ptr64 1, // wasm_var 10, // wasm_externref + 20, // wasm_funcref }; class LLVM_LIBRARY_VISIBILITY TCETargetInfo : public TargetInfo { diff --git a/clang/lib/Basic/Targets/X86.h b/clang/lib/Basic/Targets/X86.h --- a/clang/lib/Basic/Targets/X86.h +++ b/clang/lib/Basic/Targets/X86.h @@ -45,6 +45,7 @@ 272, // ptr64 1, // wasm_var 10, // wasm_externref, + 20, // wasm_funcref }; // X86 target abstract base class; x86-32 and x86-64 are very close, so diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -18414,6 +18414,10 @@ Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_ref_null_extern); return Builder.CreateCall(Callee); } + case WebAssembly::BI__builtin_wasm_ref_null_func: { + Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_ref_null_func); + return Builder.CreateCall(Callee); + } case WebAssembly::BI__builtin_wasm_swizzle_i8x16: { Value *Src = EmitScalarExpr(E->getArg(0)); Value *Indices = EmitScalarExpr(E->getArg(1)); diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -2036,6 +2036,17 @@ Value *Src = Visit(const_cast(E)); llvm::Type *SrcTy = Src->getType(); llvm::Type *DstTy = ConvertType(DestTy); + + // If the destination type is a WebAssembly Funcref then, we allow the + // types to have different address spaces and the one not in the + // funcref address space is converted at this point. + if (DestTy->isWebAssemblyFuncrefType()) { + llvm::PointerType *PtrTy = cast(DstTy); + // FIXME: hardcoded addrspace, should be + // llvm::WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_FUNCREF + DstTy = llvm::PointerType::getWithSamePointeeType(PtrTy, 20); + } + if (SrcTy->isPtrOrPtrVectorTy() && DstTy->isPtrOrPtrVectorTy() && SrcTy->getPointerAddressSpace() != DstTy->getPointerAddressSpace()) { llvm_unreachable("wrong cast for pointers in different address spaces" diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -5103,6 +5103,15 @@ const CGFunctionInfo &FI = getTypes().arrangeGlobalDeclaration(GD); llvm::FunctionType *Ty = getTypes().GetFunctionType(FI); + // If FI FunctionType is a WebAssembly funcref, + // then transform Ty so that it points to the funcref AS + if (D->getReturnType()->isWebAssemblyFuncrefType()) { + llvm::PointerType *PtrTy = dyn_cast(Ty->getReturnType()); + assert(PtrTy); + Ty = llvm::FunctionType::get(llvm::PointerType::getWithSamePointeeType(PtrTy, 20), + Ty->params(), Ty->isVarArg()); + } + // Get or create the prototype for the function. if (!GV || (GV->getValueType() != Ty)) GV = cast(GetAddrOfFunction(GD, Ty, /*ForVTable=*/false, diff --git a/clang/lib/CodeGen/CodeGenTypes.cpp b/clang/lib/CodeGen/CodeGenTypes.cpp --- a/clang/lib/CodeGen/CodeGenTypes.cpp +++ b/clang/lib/CodeGen/CodeGenTypes.cpp @@ -399,6 +399,7 @@ /// ConvertType - Convert the specified type to its LLVM form. llvm::Type *CodeGenTypes::ConvertType(QualType T) { + const bool isWebAssemblyFuncref = T.getTypePtr()->isWebAssemblyFuncrefType(); T = Context.getCanonicalType(T); const Type *Ty = T.getTypePtr(); @@ -417,6 +418,19 @@ } } + if (isWebAssemblyFuncref) { + // Copies what we do later for PointerType but takes Funcref attribute into + // consideration. We cannot this reach caching which doesn't take the + // funcref attribute into consideration. + const PointerType *PTy = cast(Ty); + QualType ETy = PTy->getPointeeType(); + llvm::Type *PointeeType = ConvertTypeForMem(ETy); + if (PointeeType->isVoidTy()) + PointeeType = llvm::Type::getInt8Ty(getLLVMContext()); + llvm::Type *FuncTy = llvm::PointerType::get(PointeeType, 20 /*FixMe hardcoded AS*/); + return FuncTy; + } + // RecordTypes are cached and processed specially. if (const RecordType *RT = dyn_cast(Ty)) return ConvertRecordDeclType(RT->getDecl()); diff --git a/clang/lib/CodeGen/TargetInfo.h b/clang/lib/CodeGen/TargetInfo.h --- a/clang/lib/CodeGen/TargetInfo.h +++ b/clang/lib/CodeGen/TargetInfo.h @@ -350,7 +350,14 @@ } /// Return the WebAssembly externref reference type. - virtual llvm::Type *getWasmExternrefReferenceType() const { return nullptr; } + virtual llvm::Type *getWasmExternrefReferenceType() const { + return nullptr; + } + /// Return the WebAssembly funcref reference type. + virtual llvm::Type *getWasmFuncrefReferenceType() const { + return nullptr; + } + /// Emit the device-side copy of the builtin surface type. virtual bool emitCUDADeviceBuiltinSurfaceDeviceCopy(CodeGenFunction &CGF, LValue Dst, diff --git a/clang/lib/CodeGen/TargetInfo.cpp b/clang/lib/CodeGen/TargetInfo.cpp --- a/clang/lib/CodeGen/TargetInfo.cpp +++ b/clang/lib/CodeGen/TargetInfo.cpp @@ -894,6 +894,10 @@ virtual llvm::Type *getWasmExternrefReferenceType() const override { return llvm::Type::getWasm_ExternrefTy(getABIInfo().getVMContext()); } + /// Return the WebAssembly funcref reference type. + virtual llvm::Type *getWasmFuncrefReferenceType() const override { + return llvm::Type::getWasm_FuncrefTy(getABIInfo().getVMContext()); + } }; /// Classify argument of given type \p Ty. diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -2640,7 +2640,14 @@ diag::err_hip_invalid_args_builtin_mangled_name); return ExprError(); } + break; } + + case WebAssembly::BI__builtin_wasm_ref_null_func: + if (SemaBuiltinWasmRefNullFunc(TheCall)) + return ExprError(); + break; + } // Since the target specific builtins for each arch overlap, only check those @@ -6480,6 +6487,20 @@ return false; } +bool +Sema::SemaBuiltinWasmRefNullFunc(CallExpr *TheCall) { + if (TheCall->getNumArgs() != 0) + return true; + + // Set return type to a function pointer with the funcref attribute attached + QualType FnType = Context.getFunctionType(Context.VoidTy, {}, {}); + QualType FnPtrType = Context.getPointerType(FnType); + + TheCall->setType(FnPtrType); + + return false; +} + /// We have a call to a function like __sync_fetch_and_add, which is an /// overloaded function based on the pointer type of its first argument. /// The main BuildCallExpr routines have already promoted the types of diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -14283,7 +14283,10 @@ // OpenCL allows function arguments declared to be an array of a type // to be qualified with an address space. !(getLangOpts().OpenCL && - (T->isArrayType() || T.getAddressSpace() == LangAS::opencl_private))) { + (T->isArrayType() || T.getAddressSpace() == LangAS::opencl_private)) && + // WebAssembly allows reference types as parameters. Funcref in particular + // lives in a different address space + !T->isWebAssemblyFuncrefType()) { Diag(NameLoc, diag::err_arg_with_address_space); New->setInvalidDecl(); } diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -8106,6 +8106,27 @@ CurType, CurType); } +static void HandleWebAssemblyFuncrefAttr(TypeProcessingState &State, + QualType &CurType, + ParsedAttr &Attr) { + if (!CurType->isFunctionPointerType()) { + State.getSema().Diag(Attr.getLoc(), + diag::err_attribute_webassembly_funcref); + Attr.setInvalid(); + return; + } + + // Add attribute + CurType = + State.getAttributedType(createSimpleAttr( + State.getSema().Context, Attr), + CurType, CurType); + + // Change address space to the funcref addrspace + Qualifiers Q = CurType.getQualifiers(); + Q.setAddressSpace(LangAS::wasm_funcref); +} + /// Handle OpenCL Access Qualifier Attribute. static void HandleOpenCLAccessAttr(QualType &CurType, const ParsedAttr &Attr, Sema &S) { @@ -8383,6 +8404,12 @@ attr.setUsedAsTypeAttr(); break; + case ParsedAttr::AT_WebAssemblyFuncref: { + HandleWebAssemblyFuncrefAttr(state, type, attr); + attr.setUsedAsTypeAttr(); + break; + } + MS_TYPE_ATTRS_CASELIST: if (!handleMSPointerTypeQualifierAttr(state, attr, type)) attr.setUsedAsTypeAttr(); diff --git a/clang/test/CodeGen/WebAssembly/wasm-funcref.c b/clang/test/CodeGen/WebAssembly/wasm-funcref.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/WebAssembly/wasm-funcref.c @@ -0,0 +1,38 @@ +// RUN: %clang_cc1 -triple wasm32 -target-feature +reference-types -o - -emit-llvm %s | FileCheck %s + +typedef void (*funcref_t)() __attribute__((__funcref)); + +funcref_t get_null() { +// CHECK: define i8 addrspace(20)* @get_null() #0 { +// CHECK-NEXT: entry: +// CHECK-NEXT: %0 = call i8 addrspace(20)* @llvm.wasm.ref.null.func() +// CHECK-NEXT: ret i8 addrspace(20)* %0 +// CHECK-NEXT: } + return __builtin_wasm_ref_null_func(); +} + +void helper(funcref_t); + +void handle(funcref_t fn) { +// CHECK: define void @handle(i8 addrspace(20)* %fn) #0 { +// CHECK-NEXT: entry: +// CHECK-NEXT: %fn.addr = alloca i8 addrspace(20)*, align 1 +// CHECK-NEXT: store i8 addrspace(20)* %fn, i8 addrspace(20)** %fn.addr, align 1 +// CHECK-NEXT: %0 = load i8 addrspace(20)*, i8 addrspace(20)** %fn.addr, align 1 +// CHECK-NEXT: call void @helper(i8 addrspace(20)* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + helper(fn); +} + +typedef int (*fn_t)(int); +typedef fn_t __attribute__((__funcref)) fn_funcref_t; + +// What happens when we move a function pointer into a funcref and then call it? +fn_funcref_t get_ref(fn_t fnptr) { + return fnptr; +} + +int call_fn(fn_funcref_t ref, int x) { + return ref(x); +} diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp @@ -404,11 +404,17 @@ } //===----------------------------------------------------------------------===// -// The following functions are called from lib/CodeGen/Passes.cpp to modify -// the CodeGen pass sequence. +// The following functions are called from lib/CodeGen/TargetPassConfig.cpp +// to modify the CodeGen pass sequence. //===----------------------------------------------------------------------===// void WebAssemblyPassConfig::addIRPasses() { + // Run mem2reg to remove alloca references - needed for reference types + // FIXME: this should only be added when the subtarget has reference types + // enabled but the subtarget is dependent on the function being compiled to + // which we don't have access atm. + addPass(createPromoteMemoryToRegisterPass()); + // Add signatures to prototype-less function declarations addPass(createWebAssemblyAddMissingPrototypes());