diff --git a/clang/docs/HLSL/EntryFunctions.rst b/clang/docs/HLSL/EntryFunctions.rst --- a/clang/docs/HLSL/EntryFunctions.rst +++ b/clang/docs/HLSL/EntryFunctions.rst @@ -37,10 +37,16 @@ The actual exported entry function which can be called by the GPU driver is a ``void(void)`` function that isn't name mangled. In code generation we generate -the unmangled entry function, instantiations of the parameters with their -semantic values populated, and a call to the user-defined function. After the -call instruction the return value (if any) is saved using a target-appropriate -intrinsic for storing outputs (for DirectX, the ``llvm.dx.store.output``). +the unmangled entry function to serve as the actual shader entry. The shader +entry function is annotated with the ``hlsl.shader`` function attribute +identifying the entry's pipeline stage. + +The body of the unmangled entry function contains first a call to execute global +constructors, then instantiations of the user-defined entry parameters with +their semantic values populated, and a call to the user-defined function. +After the call instruction the return value (if any) is saved using a +target-appropriate intrinsic for storing outputs (for DirectX, the +``llvm.dx.store.output``). Global destructors are not supported in HLSL. .. note:: 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 @@ -11646,6 +11646,8 @@ def err_hlsl_missing_semantic_annotation : Error< "semantic annotations must be present for all parameters of an entry " "function or patch constant function">; +def err_hlsl_init_priority_unsupported : Error< + "initializer priorities are not supported in HLSL">; def err_hlsl_pointers_unsupported : Error< "%select{pointers|references}0 are unsupported in HLSL">; diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -46,6 +46,7 @@ virtual ~CGHLSLRuntime() {} void annotateHLSLResource(const VarDecl *D, llvm::GlobalVariable *GV); + void generateGlobalCtorCalls(); void finishCodeGen(); diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -54,6 +54,8 @@ Triple T(M.getTargetTriple()); if (T.getArch() == Triple::ArchType::dxil) addDxilValVersion(TargetOpts.DxilValidatorVersion, M); + + generateGlobalCtorCalls(); } void CGHLSLRuntime::annotateHLSLResource(const VarDecl *D, GlobalVariable *GV) { @@ -143,3 +145,40 @@ // FIXME: Handle codegen for return type semantics. B.CreateRetVoid(); } + +void CGHLSLRuntime::generateGlobalCtorCalls() { + llvm::Module &M = CGM.getModule(); + const auto *GlobalCtors = M.getNamedGlobal("llvm.global_ctors"); + if (!GlobalCtors) + return; + const auto *CA = dyn_cast(GlobalCtors->getInitializer()); + if (!CA) + return; + // The global_ctor array elements are a struct [Priority, Fn *, COMDat]. + // HLSL neither supports priorities or COMDat values, so we will check those + // in an assert but not handle them. + + llvm::SmallVector CtorFns; + for (const auto &Ctor : CA->operands()) { + if (isa(Ctor)) + continue; + ConstantStruct *CS = cast(Ctor); + + assert(cast(CS->getOperand(0))->getValue() == 65535 && + "HLSL doesn't support setting priority for global ctors."); + assert(isa(CS->getOperand(2)) && + "HLSL doesn't support COMDat for global ctors."); + CtorFns.push_back(cast(CS->getOperand(1))); + } + + // Insert a call to the global constructor at the beginning of the entry block + // to externally exported functions. This is a bit of a hack, but HLSL allows + // global constructors, but doesn't support driver initialization of globals. + for (auto &F : M.functions()) { + if (!F.hasFnAttribute("hlsl.shader")) + continue; + IRBuilder<> B(&F.getEntryBlock(), F.getEntryBlock().begin()); + for (auto *Fn : CtorFns) + B.CreateCall(FunctionCallee(Fn)); + } +} diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2328,6 +2328,10 @@ static void handleConstructorAttr(Sema &S, Decl *D, const ParsedAttr &AL) { uint32_t priority = ConstructorAttr::DefaultPriority; + if (S.getLangOpts().HLSL && AL.getNumArgs()) { + S.Diag(AL.getLoc(), diag::err_hlsl_init_priority_unsupported); + return; + } if (AL.getNumArgs() && !checkUInt32Argument(S, AL, AL.getArgAsExpr(0), priority)) return; @@ -3729,6 +3733,11 @@ S.Diag(AL.getLoc(), diag::warn_attribute_ignored) << AL; return; } + + if (S.getLangOpts().HLSL) { + S.Diag(AL.getLoc(), diag::err_hlsl_init_priority_unsupported); + return; + } if (S.getCurFunctionOrMethodDecl()) { S.Diag(AL.getLoc(), diag::err_init_priority_object_attr); diff --git a/clang/test/CodeGenHLSL/GlobalConstructorFunction.hlsl b/clang/test/CodeGenHLSL/GlobalConstructorFunction.hlsl new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenHLSL/GlobalConstructorFunction.hlsl @@ -0,0 +1,22 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -S -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s + +int i; + +__attribute__((constructor)) void call_me_first(void) { + i = 12; +} + +__attribute__((constructor)) void then_call_me(void) { + i = 12; +} + +[numthreads(1,1,1)] +void main(unsigned GI : SV_GroupIndex) {} + +//CHECK: define void @main() +//CHECK-NEXT: entry: +//CHECK-NEXT: call void @"?call_me_first@@YAXXZ"() +//CHECK-NEXT: call void @"?then_call_me@@YAXXZ"() +//CHECK-NEXT: %0 = call i32 @llvm.dx.flattened.thread.id.in.group() +//CHECK-NEXT: call void @"?main@@YAXI@Z"(i32 %0) +//CHECK-NEXT: ret void diff --git a/clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl b/clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -S -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s + +RWBuffer Buffer; + +[shader("compute")] +[numthreads(1,1,1)] +void FirstEntry() {} + +// CHECK: define void @FirstEntry() +// CHECK-NEXT: entry: +// CHECK-NEXT: call void @_GLOBAL__sub_I_GlobalConstructorLib.hlsl() + +[shader("compute")] +[numthreads(1,1,1)] +void SecondEntry() {} + +// CHECK: define void @SecondEntry() +// CHECK-NEXT: entry: +// CHECK-NEXT: call void @_GLOBAL__sub_I_GlobalConstructorLib.hlsl() +// CHECK-NEXT: call void @"?SecondEntry@@YAXXZ"() diff --git a/clang/test/CodeGenHLSL/GlobalConstructors.hlsl b/clang/test/CodeGenHLSL/GlobalConstructors.hlsl new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenHLSL/GlobalConstructors.hlsl @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -x hlsl -S -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s + +RWBuffer Buffer; + +[numthreads(1,1,1)] +void main(unsigned GI : SV_GroupIndex) {} + +//CHECK: define void @main() +//CHECK-NEXT: entry: +//CHECK-NEXT: call void @_GLOBAL__sub_I_GlobalConstructors.hlsl() +//CHECK-NEXT: %0 = call i32 @llvm.dx.flattened.thread.id.in.group() +//CHECK-NEXT: call void @"?main@@YAXI@Z"(i32 %0) +//CHECK-NEXT: ret void diff --git a/clang/test/SemaHLSL/GlobalConstructors.hlsl b/clang/test/SemaHLSL/GlobalConstructors.hlsl new file mode 100644 --- /dev/null +++ b/clang/test/SemaHLSL/GlobalConstructors.hlsl @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -fsyntax-only %s -verify + +int i; + +struct Pup { + Pup() { + i++; + } +}; + +// expected-error@+1 {{initializer priorities are not supported in HLSL}} +Pup __attribute__((init_priority(1))) Fido; + +// expected-error@+1 {{initializer priorities are not supported in HLSL}} +__attribute__((constructor(1))) void call_me_first(void) { + i = 12; +} +