diff --git a/include/dxc/HLSL/DxilUtil.h b/include/dxc/HLSL/DxilUtil.h index 2109ec4bd..77babddb8 100644 --- a/include/dxc/HLSL/DxilUtil.h +++ b/include/dxc/HLSL/DxilUtil.h @@ -33,6 +33,15 @@ namespace dxilutil { llvm::Type *GetArrayEltTy(llvm::Type *Ty); bool HasDynamicIndexing(llvm::Value *V); + // Find alloca insertion point, given instruction + llvm::Instruction *FindAllocaInsertionPt(llvm::Instruction* I); + llvm::Instruction *FindAllocaInsertionPt(llvm::Function* F); + llvm::Instruction *SkipAllocas(llvm::Instruction *I); + // Get first non-alloca insertion point, to avoid inserting non-allocas before alloca + llvm::Instruction *FirstNonAllocaInsertionPt(llvm::Instruction* I); + llvm::Instruction *FirstNonAllocaInsertionPt(llvm::BasicBlock* BB); + llvm::Instruction *FirstNonAllocaInsertionPt(llvm::Function* F); + bool IsStaticGlobal(llvm::GlobalVariable *GV); bool IsSharedMemoryGlobal(llvm::GlobalVariable *GV); bool RemoveUnusedFunctions(llvm::Module &M, llvm::Function *EntryFunc, diff --git a/include/dxc/Support/HLSLOptions.h b/include/dxc/Support/HLSLOptions.h index c3a64c301..bdf6b56ba 100644 --- a/include/dxc/Support/HLSLOptions.h +++ b/include/dxc/Support/HLSLOptions.h @@ -130,6 +130,7 @@ public: bool AvoidFlowControl = false; // OPT_Gfa bool PreferFlowControl = false; // OPT_Gfp bool EnableStrictMode = false; // OPT_Ges + bool EnableBackCompatMode = false; // OPT_Gec unsigned long HLSLVersion = 0; // OPT_hlsl_version (2015-2018) bool Enable16BitTypes = false; // OPT_enable_16bit_types bool OptDump = false; // OPT_ODump - dump optimizer commands diff --git a/include/dxc/Support/HLSLOptions.td b/include/dxc/Support/HLSLOptions.td index da4f10737..49caa2926 100644 --- a/include/dxc/Support/HLSLOptions.td +++ b/include/dxc/Support/HLSLOptions.td @@ -306,6 +306,7 @@ def Zsb : Flag<["-", "/"], "Zsb">, Flags<[CoreOption]>, Group, def Gfa : Flag<["-", "/"], "Gfa">, HelpText<"Avoid flow control constructs">, Flags<[CoreOption]>, Group; def Gfp : Flag<["-", "/"], "Gfp">, HelpText<"Prefer flow control constructs">, Flags<[CoreOption]>, Group; // /Gdp - disable effect performance mode - unsupported +def Gec : Flag<["-", "/"], "Gec">, HelpText<"Enable backward compatibility mode">, Flags<[CoreOption]>, Group; def Ges : Flag<["-", "/"], "Ges">, HelpText<"Enable strict mode">, Flags<[CoreOption]>, Group; def Gis : Flag<["-", "/"], "Gis">, HelpText<"Force IEEE strictness">, Flags<[CoreOption]>, Group; @@ -368,7 +369,7 @@ def getprivate : JoinedOrSeparate<["-", "/"], "getprivate">, Flags<[DriverOption def nologo : Flag<["-", "/"], "nologo">, Group, Flags<[DriverOption]>, HelpText<"Suppress copyright message">; -// Also removed: compress, decompress, /Gch (child effect), /Gec (back compat), /Gpp (partial precision) +// Also removed: compress, decompress, /Gch (child effect), /Gpp (partial precision) // /Op - no support for preshaders. def flegacy_macro_expansion : Flag<["-"], "flegacy-macro-expansion">, Group, Flags<[CoreOption, DriverOption]>, diff --git a/lib/DxcSupport/HLSLOptions.cpp b/lib/DxcSupport/HLSLOptions.cpp index 5c1bd8905..3d714386f 100644 --- a/lib/DxcSupport/HLSLOptions.cpp +++ b/lib/DxcSupport/HLSLOptions.cpp @@ -340,9 +340,14 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, } } + opts.EnableBackCompatMode = Args.hasFlag(OPT_Gec, OPT_INVALID, false); llvm::StringRef ver = Args.getLastArgValue(OPT_hlsl_version); - if (ver.empty()) { opts.HLSLVersion = 2018; } // Default to latest version - else { + if (ver.empty()) { + if (opts.EnableBackCompatMode) + opts.HLSLVersion = 2016; // Default to max supported version with /Gec flag + else + opts.HLSLVersion = 2018; // Default to latest version + } else { try { opts.HLSLVersion = std::stoul(std::string(ver)); if (opts.HLSLVersion < 2015 || opts.HLSLVersion > 2018) { @@ -365,6 +370,11 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, return 1; } + if (opts.EnableBackCompatMode && opts.HLSLVersion > 2016) { + errors << "/Gec is not supported with HLSLVersion " << opts.HLSLVersion; + return 1; + } + // AssemblyCodeHex not supported (Fx) // OutputLibrary not supported (Fl) opts.AssemblyCode = Args.getLastArgValue(OPT_Fc); diff --git a/lib/HLSL/DxilUtil.cpp b/lib/HLSL/DxilUtil.cpp index d9540ec29..58326d61d 100644 --- a/lib/HLSL/DxilUtil.cpp +++ b/lib/HLSL/DxilUtil.cpp @@ -148,5 +148,33 @@ std::unique_ptr LoadModuleFromBitcode(llvm::StringRef BC, return LoadModuleFromBitcode(pBitcodeBuf.get(), Ctx, DiagStr); } +llvm::Instruction *SkipAllocas(llvm::Instruction *I) { + // Step past any allocas: + while (I && isa(I)) + I = I->getNextNode(); + return I; +} +llvm::Instruction *FindAllocaInsertionPt(llvm::Instruction* I) { + Function *F = I->getParent()->getParent(); + if (F) + return F->getEntryBlock().getFirstInsertionPt(); + else // BB with no parent function + return I->getParent()->getFirstInsertionPt(); +} +llvm::Instruction *FindAllocaInsertionPt(llvm::Function* F) { + return F->getEntryBlock().getFirstInsertionPt(); +} +llvm::Instruction *FirstNonAllocaInsertionPt(llvm::Instruction* I) { + return SkipAllocas(FindAllocaInsertionPt(I)); +} +llvm::Instruction *FirstNonAllocaInsertionPt(llvm::BasicBlock* BB) { + return SkipAllocas( + BB->getFirstInsertionPt()); +} +llvm::Instruction *FirstNonAllocaInsertionPt(llvm::Function* F) { + return SkipAllocas( + F->getEntryBlock().getFirstInsertionPt()); +} + } } diff --git a/tools/clang/include/clang/Basic/LangOptions.h b/tools/clang/include/clang/Basic/LangOptions.h index 3335ed1e0..82ccf59e6 100644 --- a/tools/clang/include/clang/Basic/LangOptions.h +++ b/tools/clang/include/clang/Basic/LangOptions.h @@ -156,6 +156,7 @@ public: unsigned RootSigMinor; bool IsHLSLLibrary; bool UseMinPrecision; // use min precision, not native precision. + bool EnableBackCompatMode; // HLSL Change Ends bool SPIRV = false; // SPIRV Change diff --git a/tools/clang/lib/CodeGen/CGHLSLMS.cpp b/tools/clang/lib/CodeGen/CGHLSLMS.cpp index b4709ebf2..cb4470fef 100644 --- a/tools/clang/lib/CodeGen/CGHLSLMS.cpp +++ b/tools/clang/lib/CodeGen/CGHLSLMS.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include "dxc/HLSL/DxilRootSignature.h" #include "dxc/HLSL/DxilCBuffer.h" @@ -4246,6 +4247,92 @@ void CGMSHLSLRuntime::SetPatchConstantFunctionWithAttr( } +// Returns true a global value is being updated +static bool GlobalHasStoreUserRec(Value *V, std::set &visited) { + bool isWriteEnabled = false; + if (V && visited.find(V) == visited.end()) { + visited.insert(V); + for (User *U : V->users()) { + if (isa(U)) { + return true; + } else if (CallInst* CI = dyn_cast(U)) { + Function *F = CI->getCalledFunction(); + if (!F->isIntrinsic()) { + HLOpcodeGroup hlGroup = GetHLOpcodeGroup(F); + switch (hlGroup) { + case HLOpcodeGroup::NotHL: + return true; + case HLOpcodeGroup::HLMatLoadStore: + { + HLMatLoadStoreOpcode opCode = static_cast(hlsl::GetHLOpcode(CI)); + if (opCode == HLMatLoadStoreOpcode::ColMatStore || opCode == HLMatLoadStoreOpcode::RowMatStore) + return true; + break; + } + case HLOpcodeGroup::HLCast: + case HLOpcodeGroup::HLSubscript: + if (GlobalHasStoreUserRec(U, visited)) + return true; + break; + default: + break; + } + } + } else if (isa(U) || isa(U) || isa(U)) { + if (GlobalHasStoreUserRec(U, visited)) + return true; + } + } + } + return isWriteEnabled; +} + +// Returns true if any of the direct user of a global is a store inst +// otherwise recurse through the remaining users and check if any GEP +// exists and which in turn has a store inst as user. +static bool GlobalHasStoreUser(GlobalVariable *GV) { + std::set visited; + Value *V = cast(GV); + return GlobalHasStoreUserRec(V, visited); +} + +static GlobalVariable *CreateStaticGlobal(llvm::Module *M, GlobalVariable *GV) { + Constant *GC = M->getOrInsertGlobal(GV->getName().str() + ".static.copy", + GV->getType()->getPointerElementType()); + GlobalVariable *NGV = cast(GC); + if (GV->hasInitializer()) { + NGV->setInitializer(GV->getInitializer()); + } + // static global should have internal linkage + NGV->setLinkage(GlobalValue::InternalLinkage); + return NGV; +} + +static void CreateWriteEnabledStaticGlobals(llvm::Module *M, + llvm::Function *EF) { + std::vector worklist; + for (GlobalVariable &GV : M->globals()) { + if (!GV.isConstant() && GV.getLinkage() != GlobalValue::InternalLinkage) { + if (GlobalHasStoreUser(&GV)) + worklist.emplace_back(&GV); + // TODO: Ensure that constant globals aren't using initializer + GV.setConstant(true); + } + } + + IRBuilder<> Builder( + dxilutil::FirstNonAllocaInsertionPt(&EF->getEntryBlock())); + for (GlobalVariable *GV : worklist) { + GlobalVariable *NGV = CreateStaticGlobal(M, GV); + GV->replaceAllUsesWith(NGV); + + // insert memcpy in all entryblocks + uint64_t size = M->getDataLayout().getTypeAllocSize( + GV->getType()->getPointerElementType()); + Builder.CreateMemCpy(NGV, GV, size, 1); + } +} + void CGMSHLSLRuntime::FinishCodeGen() { // Library don't have entry. if (!m_bIsLib) { @@ -4258,6 +4345,11 @@ void CGMSHLSLRuntime::FinishCodeGen() { return; } + // In back-compat mode (with /Gec flag) create a static global for each const global + // to allow writing to it. + // TODO: Verfiy the behavior of static globals in hull shader + if(CGM.getLangOpts().EnableBackCompatMode && CGM.getLangOpts().HLSLVersion <= 2016) + CreateWriteEnabledStaticGlobals(m_pHLModule->GetModule(), m_pHLModule->GetEntryFunction()); if (m_pHLModule->GetShaderModel()->IsHS()) { SetPatchConstantFunction(Entry); } diff --git a/tools/clang/lib/Parse/ParseDecl.cpp b/tools/clang/lib/Parse/ParseDecl.cpp index cb7671a46..754d970e0 100644 --- a/tools/clang/lib/Parse/ParseDecl.cpp +++ b/tools/clang/lib/Parse/ParseDecl.cpp @@ -2184,7 +2184,8 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS, // global variable can be inside a global structure as a static member. // Check if the global is a static member and skip global const pass. - bool CheckGlobalConst = true; + // in backcompat mode, the check for global const is deferred to later stage in CGMSHLSLRuntime::FinishCodeGen() + bool CheckGlobalConst = getLangOpts().HLSL && getLangOpts().EnableBackCompatMode && getLangOpts().HLSLVersion <= 2016 ? false : true; if (NestedNameSpecifier *nameSpecifier = D.getCXXScopeSpec().getScopeRep()) { if (nameSpecifier->getKind() == NestedNameSpecifier::SpecifierKind::TypeSpec) { const Type *type = D.getCXXScopeSpec().getScopeRep()->getAsType(); diff --git a/tools/clang/test/CodeGenHLSL/quick-test/global-var-write-test01.hlsl b/tools/clang/test/CodeGenHLSL/quick-test/global-var-write-test01.hlsl new file mode 100644 index 000000000..9a1e031df --- /dev/null +++ b/tools/clang/test/CodeGenHLSL/quick-test/global-var-write-test01.hlsl @@ -0,0 +1,46 @@ +// RUN: %dxc -E main -T ps_6_0 /Gec -HV 2016 > %s | FileCheck %s + +// Writing to globals only supported with HV <= 2016 +// CHECK: define void @main +// CHECK: ret void + +float g_s; +float4 g_v = float4(1.0, -1.0, 0.0, 1.0); +// TODO: writing to a global matrix currently causes the compiler to crash. fix it. +// int2x2 g_m; +bool g_b = false; +int g_a[5]; +int g_a2d[3][2]; +float4 main(uint a + : A) : SV_Target { + // update global scalar + g_s = a; + + // update global vector + g_v = float4(a + 1, a + 2, a + 3, a + 4); + + /* + // update global matrix + for (uint i = 0; i < 2; i++) + for (uint j = 0; j < 2; j++) + g_m[i][j] = a + i + j; + */ + + // update global 2d array + for (uint i = 0; i < 3; i++) + for (uint j = 0; j < 2; j++) + g_a2d[i][j] = a + i + j; + + // update global array + for (uint i = 0; i < 5; i++) + g_a[i] = a + i; + + // update global boolean + g_b = true; + + return float4(g_s, g_s, g_s, g_s) + + g_v + + // float4(g_m[0][0], g_m[0][1], g_m[1][0], g_m[1][1]) + + float4(g_a2d[0][0], g_a2d[0][1], g_a2d[1][0], g_a2d[1][1]) + + float4(g_a[0], g_a[1], g_a[2], g_a[3]); +} \ No newline at end of file diff --git a/tools/clang/test/CodeGenHLSL/quick-test/global-var-write-test02.hlsl b/tools/clang/test/CodeGenHLSL/quick-test/global-var-write-test02.hlsl new file mode 100644 index 000000000..36ad52ea2 --- /dev/null +++ b/tools/clang/test/CodeGenHLSL/quick-test/global-var-write-test02.hlsl @@ -0,0 +1,14 @@ +// RUN: %dxc -E main -T ps_6_0 /Gec -HV 2016 2> %s | FileCheck %s + +// Modifying local const should fail even with HV <= 2016 +// CHECK: warning: /Gec flag is a deprecated functionality. +// CHECK: error: cannot assign to variable 'l_s' with const-qualified type 'const int' +// CHECK: note: variable 'l_s' declared const here + +float main(uint a + : A) : SV_Target { + // update global scalar + const int l_s = 1; + l_s *= 2; + return 1.33f * l_s; +} \ No newline at end of file diff --git a/tools/clang/test/CodeGenHLSL/quick-test/global-var-write-test03.hlsl b/tools/clang/test/CodeGenHLSL/quick-test/global-var-write-test03.hlsl new file mode 100644 index 000000000..89930e6e9 --- /dev/null +++ b/tools/clang/test/CodeGenHLSL/quick-test/global-var-write-test03.hlsl @@ -0,0 +1,21 @@ +// RUN: %dxc -T ps_6_0 -E PSMain /Gec -HV 2016 > %s | FileCheck %s + +// CHECK: {{.*cbvar.*}} = constant float 0.000000e+00, align 4 +// CHECK: define void @PSMain() +// CHECK: call %dx.types.CBufRet.f32 @dx.op.cbufferLoadLegacy.f32(i32 59, %dx.types.Handle %"$Globals_buffer", i32 0) +// CHECK: %{{[a-z0-9]+.*[a-z0-9]*}} = fadd fast float %{{[a-z0-9]+.*[a-z0-9]*}}, 5.000000e+00 +// CHECK: %{{[a-z0-9]+.*[a-z0-9]*}} = fmul fast float %{{[a-z0-9]+.*[a-z0-9]*}}, 3.000000e+00 +// CHECK: ret void + +float cbvar; + +void AddVal() { cbvar += 5.0; } +void MulVal() { cbvar *= 3.0; } +float GetVal() { return cbvar; } + +float PSMain() : SV_Target +{ + AddVal(); + MulVal(); + return GetVal(); +} \ No newline at end of file diff --git a/tools/clang/test/CodeGenHLSL/quick-test/global-var-write-test04.hlsl b/tools/clang/test/CodeGenHLSL/quick-test/global-var-write-test04.hlsl new file mode 100644 index 000000000..bddb0a455 --- /dev/null +++ b/tools/clang/test/CodeGenHLSL/quick-test/global-var-write-test04.hlsl @@ -0,0 +1,46 @@ +// RUN: %dxc -E main -fcgl -T ps_6_0 /Gec -HV 2016 > %s | FileCheck %s + +// Writing to globals only supported with HV <= 2016 + +// CHECK: {{.*g_s1.*}} = constant float 0.000000e+00, align 4 +// CHECK: {{.*g_s2.*}} = constant float 0.000000e+00, align 4 +// CHECK: {{.*g_v.*}} = constant <4 x float> zeroinitializer, align 4 +// CHECK: {{.*g_m1.*}} = constant %class.matrix.int.2.2 zeroinitializer, align 4 +// CHECK: {{.*g_m2.*}} = constant %class.matrix.int.2.2 zeroinitializer, align 4 +// CHECK: {{.*g_b.*}} = constant i32 0, align 1 +// CHECK: {{.*g_a.*}} = constant [5 x i32] zeroinitializer, align 4 +// CHECK: {{.*g_a2d.*}} = constant [3 x [2 x i32]] zeroinitializer, align 4 +// CHECK-NOT: {{(.*g_s1.*)(.*static.copy.*)}} = internal global float 0.000000e+00 +// CHECK: {{(.*g_s2.*)(.*static.copy.*)}} = internal global float 0.000000e+00 +// CHECK-NOT: {{(.*g_v.*)(.*static.copy.*)}} = internal global <4 x float> zeroinitializer +// CHECK-NOT: {{(.*g_m1.*)(.*static.copy.*)}} = internal global %class.matrix.int.2.2 zeroinitializer +// CHECK: {{(.*g_m2.*)(.*static.copy.*)}} = internal global %class.matrix.int.2.2 zeroinitializer +// CHECK-NOT: {{(.*g_b.*)(.*static.copy.*)}} = internal global i32 0 +// CHECK-NOT: {{(.*g_a.*)(.*static.copy.*)}} = internal global [5 x i32] zeroinitializer +// CHECK-NOT: {{(.*g_a2d.*)(.*static.copy.*)}} = internal global [3 x [2 x i32]] zeroinitializer +// CHECK: define <4 x float> @main +// CHECK: ret %dx.types.Handle undef + +float g_s1; +float g_s2; // write enabled +float4 g_v; +int2x2 g_m1; +int2x2 g_m2; // write enabled +bool g_b; +int g_a[5]; +int g_a2d[3][2]; +float4 main(uint a + : A) : SV_Target { + g_s2 = a * 2.0f; + + for (uint i = 0; i < 2; i++) + for (uint j = 0; j < 2; j++) + g_m2[i][j] = a + i + j; + + + return float4(g_s1, g_s1, g_s2, g_s2) + + g_v + + float4(g_m1[0][0], g_m1[0][1], g_m2[1][0], g_m2[1][1]) + + float4(g_a2d[0][0], g_a2d[0][1], g_a2d[1][0], g_a2d[1][1]) + + float4(g_a[0], g_a[1], g_a[2], g_a[3]); +} \ No newline at end of file diff --git a/tools/clang/tools/dxcompiler/dxcompilerobj.cpp b/tools/clang/tools/dxcompiler/dxcompilerobj.cpp index 211d05b76..8addf1983 100644 --- a/tools/clang/tools/dxcompiler/dxcompilerobj.cpp +++ b/tools/clang/tools/dxcompiler/dxcompilerobj.cpp @@ -813,6 +813,10 @@ public: compiler.createSourceManager(compiler.getFileManager()); compiler.setTarget( TargetInfo::CreateTargetInfo(compiler.getDiagnostics(), targetOptions)); + if (Opts.EnableBackCompatMode) { + auto const ID = compiler.getDiagnostics().getCustomDiagID(clang::DiagnosticsEngine::Warning, "/Gec flag is a deprecated functionality."); + compiler.getDiagnostics().Report(ID); + } compiler.getFrontendOpts().Inputs.push_back(FrontendInputFile(pMainFile, IK_HLSL)); // Setup debug information. @@ -864,6 +868,7 @@ public: compiler.getLangOpts().RootSigMajor = 1; compiler.getLangOpts().RootSigMinor = rootSigMinor; compiler.getLangOpts().HLSLVersion = (unsigned) Opts.HLSLVersion; + compiler.getLangOpts().EnableBackCompatMode = Opts.EnableBackCompatMode; compiler.getLangOpts().UseMinPrecision = !Opts.Enable16BitTypes;