Parcourir la source

Add pass to preserve all outputs (#326)

This commit adds a new pass that will transform all the storeOutput
instructions in the program. For each element in the output
signature we create an alloca to hold writes to the output.
We rewrite each storeOutput to store to the alloca'd location
instead. Then at each function return point we store to
each element in the output signature by reading the latest
value from the alloca'd location.

The above transformation provides the following properties
for outputs after it is run.

    1. Remove all dynamic indexing on output writes
    2. Remove multiple writes to same output location
    3. Ensure all output locations in the signature are written
David Peixotto il y a 8 ans
Parent
commit
0ea6620342

+ 2 - 0
include/dxc/HLSL/DxilGenerationPass.h

@@ -46,6 +46,7 @@ ModulePass *createDxilEmitMetadataPass();
 FunctionPass *createDxilExpandTrigIntrinsicsPass();
 ModulePass *createDxilLoadMetadataPass();
 ModulePass *createDxilPrecisePropagatePass();
+FunctionPass *createDxilPreserveAllOutputsPass();
 FunctionPass *createDxilLegalizeResourceUsePass();
 ModulePass *createDxilLegalizeStaticResourceUsePass();
 ModulePass *createDxilLegalizeEvalOperationsPass();
@@ -61,6 +62,7 @@ void initializeDxilEmitMetadataPass(llvm::PassRegistry&);
 void initializeDxilExpandTrigIntrinsicsPass(llvm::PassRegistry&);
 void initializeDxilLoadMetadataPass(llvm::PassRegistry&);
 void initializeDxilPrecisePropagatePassPass(llvm::PassRegistry&);
+void initializeDxilPreserveAllOutputsPass(llvm::PassRegistry&);
 void initializeDxilLegalizeResourceUsePassPass(llvm::PassRegistry&);
 void initializeDxilLegalizeStaticResourceUsePassPass(llvm::PassRegistry&);
 void initializeDxilLegalizeEvalOperationsPass(llvm::PassRegistry&);

+ 1 - 0
lib/HLSL/CMakeLists.txt

@@ -17,6 +17,7 @@ add_llvm_library(LLVMHLSL
   DxilMetadataHelper.cpp
   DxilModule.cpp
   DxilOperations.cpp
+  DxilPreserveAllOutputs.cpp
   DxilResource.cpp
   DxilResourceBase.cpp
   DxilRootSignature.cpp

+ 1 - 0
lib/HLSL/DxcOptimizer.cpp

@@ -93,6 +93,7 @@ HRESULT SetupRegistryPassForHLSL() {
     initializeDxilLegalizeStaticResourceUsePassPass(Registry);
     initializeDxilLoadMetadataPass(Registry);
     initializeDxilPrecisePropagatePassPass(Registry);
+    initializeDxilPreserveAllOutputsPass(Registry);
     initializeDynamicIndexingVectorToArrayPass(Registry);
     initializeEarlyCSELegacyPassPass(Registry);
     initializeEliminateAvailableExternallyPass(Registry);

+ 309 - 0
lib/HLSL/DxilPreserveAllOutputs.cpp

@@ -0,0 +1,309 @@
+///////////////////////////////////////////////////////////////////////////////
+//                                                                           //
+// DxilPreserveAllOutputs.cpp                                                //
+// Copyright (C) Microsoft Corporation. All rights reserved.                 //
+// This file is distributed under the University of Illinois Open Source     //
+// License. See LICENSE.TXT for details.                                     //
+//                                                                           //
+// Ensure we store to all elements in the output signature.                  //
+//                                                                           //
+///////////////////////////////////////////////////////////////////////////////
+
+#include "dxc/HLSL/DxilGenerationPass.h"
+#include "dxc/HLSL/DxilOperations.h"
+#include "dxc/HLSL/DxilSignatureElement.h"
+#include "dxc/HLSL/DxilModule.h"
+#include "dxc/Support/Global.h"
+#include "dxc/HLSL/DxilInstructions.h"
+
+#include "llvm/IR/Module.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/Pass.h"
+#include "llvm/IR/IRBuilder.h"
+#include <llvm/ADT/DenseSet.h>
+
+using namespace llvm;
+using namespace hlsl;
+
+namespace {
+class OutputWrite {
+public:
+  explicit OutputWrite(CallInst *call)
+    : m_Call(call)
+  {
+    assert(DxilInst_StoreOutput(call) || DxilInst_StorePatchConstant(call));
+  }
+
+  unsigned GetSignatureID() const {
+    Value *id = m_Call->getOperand(SignatureIndex);
+    return cast<ConstantInt>(id)->getLimitedValue();
+  }
+
+  DxilSignatureElement &GetSignatureElement(DxilModule &DM) const {
+    if (DxilInst_StorePatchConstant(m_Call))
+      return DM.GetPatchConstantSignature().GetElement(GetSignatureID());
+    else
+      return DM.GetOutputSignature().GetElement(GetSignatureID());
+  }
+
+  CallInst *GetStore() const {
+    return m_Call;
+  }
+
+  Value *GetValue() const {
+    return m_Call->getOperand(ValueIndex);
+  }
+
+  Value *GetRow() const {
+    return m_Call->getOperand(RowIndex);
+  }
+  
+  Value *GetColumn() const {
+    return m_Call->getOperand(ColumnIndex);
+  }
+
+  void DeleteStore() {
+    m_Call->eraseFromParent();
+    m_Call = nullptr;
+  }
+
+private:
+  CallInst *m_Call;
+  enum OperandIndex {
+    SignatureIndex = 1,
+    RowIndex = 2,
+    ColumnIndex = 3,
+    ValueIndex = 4,
+  };
+};
+
+class OutputElement {
+public:
+  explicit OutputElement(const DxilSignatureElement &outputElement)
+    : m_OutputElement(outputElement)
+    , m_Rows(outputElement.GetRows())
+    , m_Columns(outputElement.GetCols())
+  {
+  }
+
+  void CreateAlloca(IRBuilder<> &builder) {
+    LLVMContext &context = builder.getContext();
+    Type *elementType = m_OutputElement.GetCompType().GetLLVMType(context);
+    Type *allocaType = nullptr;
+    if (IsSingleElement())
+      allocaType = elementType;
+    else
+      allocaType = ArrayType::get(elementType, NumElements());
+    m_Alloca = builder.CreateAlloca(allocaType, nullptr, m_OutputElement.GetName());
+  }
+
+  void StoreTemp(IRBuilder<> &builder, Value *row, Value *col, Value *value) const {
+    Value *addr = GetTempAddr(builder, row, col);
+    builder.CreateStore(value, addr);
+  }
+
+  void StoreOutput(IRBuilder<> &builder, DxilModule &DM) const {
+    for (unsigned row = 0; row < m_Rows; ++row)
+      for (unsigned col = 0; col < m_Columns; ++col) {
+        StoreOutput(builder, DM, row, col);
+      }
+  }
+
+  unsigned NumElements() const {
+    return m_Rows * m_Columns;
+  }
+
+private:
+  const DxilSignatureElement &m_OutputElement;
+  unsigned m_Rows;
+  unsigned m_Columns;
+  AllocaInst *m_Alloca;
+
+  bool IsSingleElement() const {
+    return m_Rows == 1 && m_Columns == 1;
+  }
+
+  Value *GetAsI32(IRBuilder<> &builder, Value *col) const {
+    assert(col->getType()->isIntegerTy());
+    Type *i32Ty = builder.getInt32Ty();
+    if (col->getType() != i32Ty) {
+      if (col->getType()->getScalarSizeInBits() > i32Ty->getScalarSizeInBits())
+        col = builder.CreateTrunc(col, i32Ty);
+      else
+        col = builder.CreateZExt(col, i32Ty);
+    }
+
+    return col;
+  }
+
+  Value *GetTempAddr(IRBuilder<> &builder, Value *row, Value *col) const {
+    // Load directly from alloca for non-array output.
+    if (IsSingleElement())
+      return m_Alloca;
+    else
+      return CreateGEP(builder, row, col);
+  }
+
+  Value *CreateGEP(IRBuilder<> &builder, Value *row, Value *col) const {
+    assert(m_Alloca);
+    Constant *rowStride = ConstantInt::get(row->getType(), m_Columns);
+    Value *rowOffset = builder.CreateMul(row, rowStride);
+    Value *index     = builder.CreateAdd(rowOffset, GetAsI32(builder, col));
+    return builder.CreateInBoundsGEP(m_Alloca, {builder.getInt32(0), index});
+  }
+  
+  Value *LoadTemp(IRBuilder<> &builder, Value *row,  Value *col) const {
+    Value *addr = GetTempAddr(builder, row, col);
+    return builder.CreateLoad(addr);
+  }
+  
+  void StoreOutput(IRBuilder<> &builder, DxilModule &DM, unsigned row, unsigned col) const {
+    Value *opcodeV = builder.getInt32(static_cast<unsigned>(GetOutputOpCode()));
+    Value *sigID = builder.getInt32(m_OutputElement.GetID());
+    Value *rowV = builder.getInt32(row);
+    Value *colV = builder.getInt8(col);
+    Value *val = LoadTemp(builder, rowV, colV);
+    Value *args[] = { opcodeV, sigID, rowV, colV, val };
+    Function *Store = GetOutputFunction(DM);
+    builder.CreateCall(Store, args);
+  }
+
+  DXIL::OpCode GetOutputOpCode() const {
+    if (m_OutputElement.IsPatchConstant())
+      return DXIL::OpCode::StorePatchConstant;
+    else
+      return DXIL::OpCode::StoreOutput;
+  }
+
+  Function *GetOutputFunction(DxilModule &DM) const {
+    hlsl::OP *opInfo = DM.GetOP();
+    return opInfo->GetOpFunc(GetOutputOpCode(), m_OutputElement.GetCompType().GetLLVMBaseType(DM.GetCtx()));
+  }
+    
+};
+
+class DxilPreserveAllOutputs : public FunctionPass {
+private:
+
+public:
+  static char ID; // Pass identification, replacement for typeid
+  DxilPreserveAllOutputs() : FunctionPass(ID) {}
+
+  const char *getPassName() const override {
+    return "DXIL preserve all outputs";
+  }
+
+  bool runOnFunction(Function &F) override;
+
+private:
+  typedef std::vector<OutputWrite> OutputVec;
+  typedef std::unordered_map<unsigned, OutputElement>  OutputMap;
+  OutputVec collectOutputStores(Function &F);
+  OutputMap generateOutputMap(const OutputVec &calls, DxilModule &DM);
+  void createTempAllocas(OutputMap &map, IRBuilder<> &builder);
+  void insertTempOutputStores(const OutputVec &calls, const OutputMap &map, IRBuilder<> &builder);
+  void insertFinalOutputStores(Function &F, const OutputMap &outputMap, IRBuilder<> &builder, DxilModule &DM);
+  void removeOriginalOutputStores(OutputVec &outputStores);
+};
+
+bool DxilPreserveAllOutputs::runOnFunction(Function &F) {
+  DxilModule &DM = F.getParent()->GetOrCreateDxilModule();
+  
+  OutputVec outputStores = collectOutputStores(F);
+  if (outputStores.empty())
+    return false;
+
+  IRBuilder<> builder(F.getEntryBlock().getFirstInsertionPt());
+  OutputMap outputMap = generateOutputMap(outputStores, DM);
+  createTempAllocas(outputMap, builder);
+  insertTempOutputStores(outputStores, outputMap, builder);
+  insertFinalOutputStores(F,outputMap, builder, DM);
+  removeOriginalOutputStores(outputStores);
+
+  return false;
+}
+
+DxilPreserveAllOutputs::OutputVec DxilPreserveAllOutputs::collectOutputStores(Function &F) {
+  OutputVec calls;
+  for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I) {
+    Instruction *inst = &*I;
+    DxilInst_StoreOutput storeOutput(inst);
+    DxilInst_StorePatchConstant storePatch(inst);
+
+    if (storeOutput || storePatch)
+      calls.emplace_back(cast<CallInst>(inst));
+  }
+  return calls;
+}
+
+DxilPreserveAllOutputs::OutputMap DxilPreserveAllOutputs::generateOutputMap(const OutputVec &calls, DxilModule &DM) {
+  OutputMap map;
+  for (const OutputWrite &output : calls) {
+    unsigned sigID = output.GetSignatureID();
+    if (map.count(sigID))
+      continue;
+
+    map.insert(std::make_pair(sigID, OutputElement(output.GetSignatureElement(DM))));
+  }
+
+  return map;
+}
+
+void DxilPreserveAllOutputs::createTempAllocas(OutputMap &outputMap, IRBuilder<> &builder)
+{
+  for (auto &iter: outputMap) {
+    OutputElement &output = iter.second;
+    output.CreateAlloca(builder);
+  }
+}
+
+void DxilPreserveAllOutputs::insertTempOutputStores(const OutputVec &writes, const OutputMap &map, IRBuilder<>& builder)
+{
+  for (const OutputWrite& outputWrite : writes) {
+    OutputMap::const_iterator iter = map.find(outputWrite.GetSignatureID());
+    assert(iter != map.end());
+    const OutputElement &output = iter->second;
+
+    builder.SetInsertPoint(outputWrite.GetStore());
+    output.StoreTemp(builder, outputWrite.GetRow(), outputWrite.GetColumn(), outputWrite.GetValue());
+  }
+}
+
+void DxilPreserveAllOutputs::insertFinalOutputStores(Function &F, const OutputMap & outputMap, IRBuilder<>& builder, DxilModule & DM)
+{
+  // Find all return instructions.
+  SmallVector<ReturnInst *, 4> returns;
+  for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I) {
+    Instruction *inst = &*I;
+    if (ReturnInst *ret = dyn_cast<ReturnInst>(inst))
+      returns.push_back(ret);
+  }
+
+  // Write all outputs before each return. 
+  for (ReturnInst *ret : returns) {
+    for (const auto &iter : outputMap) {
+      const OutputElement &output = iter.second;
+      builder.SetInsertPoint(ret);
+      output.StoreOutput(builder, DM);
+    }
+  }
+}
+
+void DxilPreserveAllOutputs::removeOriginalOutputStores(OutputVec & outputStores)
+{
+  for (OutputWrite &write : outputStores) {
+    write.DeleteStore();
+  }
+}
+
+}
+
+char DxilPreserveAllOutputs::ID = 0;
+
+FunctionPass *llvm::createDxilPreserveAllOutputsPass() {
+  return new DxilPreserveAllOutputs();
+}
+
+INITIALIZE_PASS(DxilPreserveAllOutputs,
+                "hlsl-dxil-preserve-all-outputs",
+                "DXIL preserve all outputs", false, false)

+ 12 - 0
tools/clang/test/HLSL/preserve_all_outputs_1.hlsl

@@ -0,0 +1,12 @@
+// RUN: %dxc -Emain -Tvs_6_0 %s | %opt -S -hlsl-dxil-preserve-all-outputs | %FileCheck %s
+
+// CHECK: alloca [3 x float]
+// CHECK: store float 1.000000e+00
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 0,
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 1, i8 0,
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 2, i8 0,
+
+void main(out float a[3] : A, float4 pos : SV_POSITION)
+{
+    a[1] = 1.0;
+}

+ 16 - 0
tools/clang/test/HLSL/preserve_all_outputs_2.hlsl

@@ -0,0 +1,16 @@
+// RUN: %dxc -Emain -Tvs_6_0 %s | %opt -S -hlsl-dxil-preserve-all-outputs | %FileCheck %s
+
+// CHECK: alloca [3 x float]
+// CHECK: store float 1.000000e+00
+// CHECK: store float 2.000000e+00
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 0,
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 1, i8 0,
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 2, i8 0,
+
+void main(out float a[3] : A, float4 pos : SV_POSITION)
+{
+    if (pos.x)
+        a[1] = 1.0;
+    else
+        a[2] = 2.0;
+}

+ 14 - 0
tools/clang/test/HLSL/preserve_all_outputs_3.hlsl

@@ -0,0 +1,14 @@
+// RUN: %dxc -Emain -Tvs_6_0 %s | %opt -S -hlsl-dxil-preserve-all-outputs | %FileCheck %s
+
+// CHECK: alloca float
+// CHECK: store float 1.000000e+00
+// CHECK: store float 2.000000e+00
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 0,
+
+void main(out float a : A, float4 pos : SV_POSITION)
+{
+    if (pos.x)
+        a = 1.0;
+    else
+        a = 2.0;
+}

+ 20 - 0
tools/clang/test/HLSL/preserve_all_outputs_4.hlsl

@@ -0,0 +1,20 @@
+// RUN: %dxc -Emain -Tvs_6_0 %s | %opt -S -hlsl-dxil-preserve-all-outputs | %FileCheck %s
+
+// CHECK: alloca [8 x float]
+// CHECK-NOT: storeOutput.f32(i32 5, i32 0, i32 %
+// CHECK: storeOutput.f32(i32 5, i32 0, i32 0
+// CHECK: storeOutput.f32(i32 5, i32 0, i32 1
+// CHECK: storeOutput.f32(i32 5, i32 0, i32 2
+// CHECK: storeOutput.f32(i32 5, i32 0, i32 3
+// CHECK: storeOutput.f32(i32 5, i32 0, i32 4
+// CHECK: storeOutput.f32(i32 5, i32 0, i32 5
+// CHECK: storeOutput.f32(i32 5, i32 0, i32 6
+// CHECK: storeOutput.f32(i32 5, i32 0, i32 7
+
+int  count;
+float4 c[16];
+
+void main(out float o[8] : I, float4 pos: SV_POSITION) {
+    for (uint i=0;i<count;i++)
+        o[i] = c[i].x;
+}

+ 18 - 0
tools/clang/test/HLSL/preserve_all_outputs_5.hlsl

@@ -0,0 +1,18 @@
+// RUN: %dxc -Emain -Tvs_6_0 %s | %opt -S -hlsl-dxil-preserve-all-outputs | %FileCheck %s
+
+// CHECK: alloca [6 x float]
+// CHECK-NOT: storeOutput.f32(i32 5, i32 0, i32 %
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 0,
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 1,
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 1, i8 0,
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 1, i8 1,
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 2, i8 0,
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 2, i8 1,
+
+void main(out float2 a[3] : A, float4 pos : SV_POSITION)
+{
+    for (int i = 0; i < pos.x; ++i) {
+        a[i].x = pos.y + i;
+        a[i].y = pos.z + i;
+    }
+}

+ 19 - 0
tools/clang/test/HLSL/preserve_all_outputs_6.hlsl

@@ -0,0 +1,19 @@
+// RUN: %dxc -Emain -Tvs_6_0 %s | %opt -S -hlsl-dxil-preserve-all-outputs | %FileCheck %s
+
+// CHECK: alloca [2 x float]
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 0,
+// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 1, i8 0,
+// CHECK-NOT: call void @dx.op.storeOutput.f32
+
+void main(out float a[2] : A, float4 pos : SV_POSITION)
+{
+    [branch]
+    if (pos.x){
+        a[0] = pos.x;
+        a[1] = pos.y;
+        return;
+    }
+
+    a[0] = pos.w;
+    a[1] = pos.z;
+}

+ 95 - 0
tools/clang/test/HLSL/preserve_all_outputs_7.hlsl

@@ -0,0 +1,95 @@
+// RUN: %dxc -E main -T hs_6_0  %s | %opt -S -hlsl-dxil-preserve-all-outputs | %FileCheck %s
+
+// CHECK: alloca [3 x float]
+// CHECK: alloca float
+// CHECK: alloca [8 x float]
+
+// CHECK-NOT: storePatchConstant.f32(i32 106, i32 2, i32 %i
+
+// CHECK: storePatchConstant.f32(i32 106, i32 0, i32 0, i8 0
+// CHECK: storePatchConstant.f32(i32 106, i32 0, i32 1, i8 0
+// CHECK: storePatchConstant.f32(i32 106, i32 0, i32 2, i8 0
+
+// CHECK: storePatchConstant.f32(i32 106, i32 1, i32 0, i8 0
+
+// CHECK: storePatchConstant.f32(i32 106, i32 2, i32 0, i8 0
+// CHECK: storePatchConstant.f32(i32 106, i32 2, i32 1, i8 0
+// CHECK: storePatchConstant.f32(i32 106, i32 2, i32 2, i8 0
+// CHECK: storePatchConstant.f32(i32 106, i32 2, i32 3, i8 0
+// CHECK: storePatchConstant.f32(i32 106, i32 2, i32 4, i8 0
+// CHECK: storePatchConstant.f32(i32 106, i32 2, i32 5, i8 0
+// CHECK: storePatchConstant.f32(i32 106, i32 2, i32 6, i8 0
+// CHECK: storePatchConstant.f32(i32 106, i32 2, i32 7, i8 0
+
+
+//--------------------------------------------------------------------------------------
+// SimpleTessellation.hlsl
+//
+// Advanced Technology Group (ATG)
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//--------------------------------------------------------------------------------------
+
+struct PSSceneIn
+{
+    float4 pos  : SV_Position;
+    float2 tex  : TEXCOORD0;
+    float3 norm : NORMAL;
+
+uint   RTIndex      : SV_RenderTargetArrayIndex;
+};
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+// Simple forwarding Tessellation shaders
+
+struct HSPerVertexData
+{
+    // This is just the original vertex verbatim. In many real life cases this would be a
+    // control point instead
+    PSSceneIn v;
+};
+
+struct HSPerPatchData
+{
+    // We at least have to specify tess factors per patch
+    // As we're tesselating triangles, there will be 4 tess factors
+    // In real life case this might contain face normal, for example
+	float	edges[ 3 ]	: SV_TessFactor;
+	float	inside		: SV_InsideTessFactor;
+        float   custom[8]       : CCC;
+};
+
+int  count;
+float4 c[16];
+
+HSPerPatchData HSPerPatchFunc( const InputPatch< PSSceneIn, 3 > points )
+{
+    HSPerPatchData d;
+
+    d.edges[ 0 ] = 1;
+    d.edges[ 1 ] = 1;
+    d.edges[ 2 ] = 1;
+    d.inside = 1;
+    for (uint i=0;i<count;i++) {
+        d.custom[i] = c[i].x;
+    }    
+    return d;
+}
+
+// hull per-control point shader
+[domain("tri")]
+[partitioning("fractional_odd")]
+[outputtopology("triangle_cw")]
+[patchconstantfunc("HSPerPatchFunc")]
+[outputcontrolpoints(3)]
+HSPerVertexData main( const uint id : SV_OutputControlPointID,
+                               const InputPatch< PSSceneIn, 3 > points )
+{
+    HSPerVertexData v;
+
+    // Just forward the vertex
+    v.v = points[ id ];
+
+	return v;
+}
+

+ 11 - 0
tools/clang/unittests/HLSL/CompilerTest.cpp

@@ -568,6 +568,7 @@ public:
   TEST_METHOD(CodeGenPrecise4)
   TEST_METHOD(CodeGenPreciseOnCall)
   TEST_METHOD(CodeGenPreciseOnCallNot)
+  TEST_METHOD(CodeGenPreserveAllOutputs)
   TEST_METHOD(CodeGenRaceCond2)
   TEST_METHOD(CodeGenRaw_Buf1)
   TEST_METHOD(CodeGenRcp1)
@@ -3026,6 +3027,16 @@ TEST_F(CompilerTest, CodeGenPreciseOnCallNot) {
   CodeGenTestCheck(L"..\\CodeGenHLSL\\precise_call_not.hlsl");
 }
 
+TEST_F(CompilerTest, CodeGenPreserveAllOutputs) {
+  CodeGenTestCheck(L"preserve_all_outputs_1.hlsl");
+  CodeGenTestCheck(L"preserve_all_outputs_2.hlsl");
+  CodeGenTestCheck(L"preserve_all_outputs_3.hlsl");
+  CodeGenTestCheck(L"preserve_all_outputs_4.hlsl");
+  CodeGenTestCheck(L"preserve_all_outputs_5.hlsl");
+  CodeGenTestCheck(L"preserve_all_outputs_6.hlsl");
+  CodeGenTestCheck(L"preserve_all_outputs_7.hlsl");
+}
+
 TEST_F(CompilerTest, CodeGenRaceCond2) {
   CodeGenTest(L"..\\CodeGenHLSL\\RaceCond2.hlsl");
 }

+ 1 - 0
utils/hct/hctdb.py

@@ -1266,6 +1266,7 @@ class db_dxil(object):
         add_pass('hlsl-dxilload', 'DxilLoadMetadata', 'HLSL DXIL Metadata Load', [])
         add_pass('hlsl-dxil-expand-trig', 'DxilExpandTrigIntrinsics', 'DXIL expand trig intrinsics', [])
         add_pass('hlsl-hca', 'HoistConstantArray', 'HLSL constant array hoisting', [])
+        add_pass('hlsl-dxil-preserve-all-outputs', 'DxilPreserveAllOutputs', 'DXIL write to all outputs in signature', [])
         add_pass('ipsccp', 'IPSCCP', 'Interprocedural Sparse Conditional Constant Propagation', [])
         add_pass('globalopt', 'GlobalOpt', 'Global Variable Optimizer', [])
         add_pass('deadargelim', 'DAE', 'Dead Argument Elimination', [])