Explorar el Código

Reflection: optionally use new metadata+RDAT instead of instructions

- Makes it compatible with a module stripped of function bodies.
- Signature masks are improved, making it necessary to switch expected
  masks in DxilContainerTest.
Tex Riddell hace 6 años
padre
commit
bd929a7129
Se han modificado 2 ficheros con 168 adiciones y 103 borrados
  1. 113 93
      lib/HLSL/DxilContainerReflection.cpp
  2. 55 10
      tools/clang/unittests/HLSL/DxilContainerTest.cpp

+ 113 - 93
lib/HLSL/DxilContainerReflection.cpp

@@ -38,6 +38,8 @@
 #include "d3d12shader.h" // for compatibility
 #include "d3d11shader.h" // for compatibility
 
+#include "dxc/DxilContainer/DxilRuntimeReflection.h"
+
 const GUID IID_ID3D11ShaderReflection_43 = {
     0x0a233719,
     0x3960,
@@ -86,6 +88,7 @@ public:
   LLVMContext Context;
   std::unique_ptr<Module> m_pModule; // Must come after LLVMContext, otherwise unique_ptr will over-delete.
   DxilModule *m_pDxilModule = nullptr;
+  bool m_bUsageInMetadata = false;
   std::vector<std::unique_ptr<CShaderReflectionConstantBuffer>>    m_CBs;
   std::vector<D3D12_SHADER_INPUT_BIND_DESC>       m_Resources;
   std::vector<std::unique_ptr<CShaderReflectionType>> m_Types;
@@ -205,7 +208,6 @@ private:
   // Enable indexing into functions in deterministic order:
   std::vector<CFunctionReflection*> m_FunctionVector;
 
-  void AddResourceUseToFunctions(DxilResourceBase &resource, unsigned resIndex);
   void AddResourceDependencies();
   void SetCBufferUsage();
 
@@ -444,7 +446,8 @@ public:
 
   void Initialize(DxilModule &M,
                   DxilCBuffer &CB,
-                  std::vector<std::unique_ptr<CShaderReflectionType>>& allTypes);
+                  std::vector<std::unique_ptr<CShaderReflectionType>>& allTypes,
+                  bool bUsageInMetadata);
   void InitializeStructuredBuffer(DxilModule &M,
                                   DxilResource &R,
                                   std::vector<std::unique_ptr<CShaderReflectionType>>& allTypes);
@@ -1103,7 +1106,8 @@ HRESULT CShaderReflectionType::Initialize(
 void CShaderReflectionConstantBuffer::Initialize(
   DxilModule &M,
   DxilCBuffer &CB,
-  std::vector<std::unique_ptr<CShaderReflectionType>>& allTypes) {
+  std::vector<std::unique_ptr<CShaderReflectionType>>& allTypes,
+  bool bUsageInMetadata) {
   ZeroMemory(&m_Desc, sizeof(m_Desc));
   m_Desc.Name = CB.GetGlobalName().c_str();
   m_Desc.Size = CB.GetSize();
@@ -1129,12 +1133,16 @@ void CShaderReflectionConstantBuffer::Initialize(
     DXASSERT(m_Desc.Variables == 1, "otherwise, assumption is wrong");
   }
 
+  // If only one member, it's used if it's here.
+  bool bAllUsed = ST->getNumContainedTypes() < 2;
+  bAllUsed |= !bUsageInMetadata;  // Will update in SetCBufferUsage.
+
   for (unsigned i = 0; i < ST->getNumContainedTypes(); ++i) {
     DxilFieldAnnotation &fieldAnnotation = annotation->GetFieldAnnotation(i);
 
     D3D12_SHADER_VARIABLE_DESC VarDesc;
     ZeroMemory(&VarDesc, sizeof(VarDesc));
-    VarDesc.uFlags |= D3D_SVF_USED; // Will update in SetCBufferUsage.
+    VarDesc.uFlags = (bAllUsed || fieldAnnotation.IsCBVarUsed()) ? D3D_SVF_USED : 0;
     CShaderReflectionVariable Var;
     //Create reflection type.
     CShaderReflectionType *pVarType = new CShaderReflectionType();
@@ -1211,7 +1219,7 @@ void CShaderReflectionConstantBuffer::InitializeStructuredBuffer(
   VarDesc.Size = CalcResTypeSize(M, R);
   VarDesc.StartTexture = UINT_MAX;
   VarDesc.StartSampler = UINT_MAX;
-  VarDesc.uFlags |= D3D_SVF_USED; // TODO: not necessarily true
+  VarDesc.uFlags |= D3D_SVF_USED;
   CShaderReflectionVariable Var;
   CShaderReflectionType *pVarType = nullptr;
 
@@ -1476,26 +1484,37 @@ static unsigned GetCBOffset(Value *V) {
   }
 }
 
-void CollectInPhiChain(PHINode *cbUser, std::vector<unsigned> &cbufUsage,
-                  unsigned offset, std::unordered_set<Value *> &userSet) {
+static unsigned GetOffsetForCBExtractValue(ExtractValueInst *EV, bool bMinPrecision) {
+  DXASSERT(EV->getNumIndices() == 1, "otherwise, unexpected indices/type for extractvalue");
+  unsigned typeSize = 4;
+  unsigned bits = EV->getType()->getScalarSizeInBits();
+  if (bits == 64)
+    typeSize = 8;
+  else if (bits == 16 && bMinPrecision)
+    typeSize = 2;
+  return (EV->getIndices().front() * typeSize);
+}
+
+static void CollectInPhiChain(PHINode *cbUser, std::vector<unsigned> &cbufUsage,
+                              unsigned offset, std::unordered_set<Value *> &userSet,
+                              bool bMinPrecision) {
   if (userSet.count(cbUser) > 0)
     return;
 
   userSet.insert(cbUser);
   for (User *cbU : cbUser->users()) {
     if (ExtractValueInst *EV = dyn_cast<ExtractValueInst>(cbU)) {
-      for (unsigned idx : EV->getIndices()) {
-        cbufUsage.emplace_back(offset + idx * 4);
-      }
+      cbufUsage.emplace_back(offset + GetOffsetForCBExtractValue(EV, bMinPrecision));
     } else {
       PHINode *phi = cast<PHINode>(cbU);
-      CollectInPhiChain(phi, cbufUsage, offset, userSet);
+      CollectInPhiChain(phi, cbufUsage, offset, userSet, bMinPrecision);
     }
   }
 }
 
 static void CollectCBufUsage(Value *cbHandle,
-                             std::vector<unsigned> &cbufUsage) {
+                             std::vector<unsigned> &cbufUsage,
+                             bool bMinPrecision) {
   for (User *U : cbHandle->users()) {
     CallInst *CI = cast<CallInst>(U);
     ConstantInt *opcodeV =
@@ -1509,13 +1528,11 @@ static void CollectCBufUsage(Value *cbHandle,
       offset <<= 4;
       for (User *cbU : U->users()) {
         if (ExtractValueInst *EV = dyn_cast<ExtractValueInst>(cbU)) {
-          for (unsigned idx : EV->getIndices()) {
-            cbufUsage.emplace_back(offset + idx * 4);
-          }
+          cbufUsage.emplace_back(offset + GetOffsetForCBExtractValue(EV, bMinPrecision));
         } else {
           PHINode *phi = cast<PHINode>(cbU);
           std::unordered_set<Value *> userSet;
-          CollectInPhiChain(phi, cbufUsage, offset, userSet);
+          CollectInPhiChain(phi, cbufUsage, offset, userSet, bMinPrecision);
         }
       }
     } else if (opcode == DXIL::OpCode::CBufferLoad) {
@@ -1531,7 +1548,7 @@ static void CollectCBufUsage(Value *cbHandle,
 }
 
 static void SetCBufVarUsage(CShaderReflectionConstantBuffer &cb,
-                            std::vector<unsigned> usage) {
+                            std::vector<unsigned> &usage) {
   D3D12_SHADER_BUFFER_DESC Desc;
   if (FAILED(cb.GetDesc(&Desc)))
     return;
@@ -1590,7 +1607,7 @@ void DxilShaderReflection::SetCBufferUsage() {
     ConstantInt *immResClass = cast<ConstantInt>(resClass);
     if (immResClass->getLimitedValue() == (unsigned)DXIL::ResourceClass::CBuffer) {
       ConstantInt *cbID = cast<ConstantInt>(handle.get_rangeId());
-      CollectCBufUsage(U, cbufUsage[cbID->getLimitedValue()]);
+      CollectCBufUsage(U, cbufUsage[cbID->getLimitedValue()], m_pDxilModule->GetUseMinPrecision());
     }
   }
 
@@ -1605,7 +1622,7 @@ void DxilModuleReflection::CreateReflectionObjects() {
   // Create constant buffers, resources and signatures.
   for (auto && cb : m_pDxilModule->GetCBuffers()) {
     std::unique_ptr<CShaderReflectionConstantBuffer> rcb(new CShaderReflectionConstantBuffer());
-    rcb->Initialize(*m_pDxilModule, *(cb.get()), m_Types);
+    rcb->Initialize(*m_pDxilModule, *(cb.get()), m_Types, m_bUsageInMetadata);
     m_CBs.emplace_back(std::move(rcb));
   }
 
@@ -1771,7 +1788,14 @@ void DxilShaderReflection::CreateReflectionObjectsForSignature(
     // D3D11_43 does not have MinPrecison.
     if (m_PublicAPI != PublicAPI::D3D11_43)
       Desc.MinPrecision = CompTypeToMinPrecision(SigElem->GetCompType());
-    Desc.ReadWriteMask = Sig.IsInput() ? 0 : Desc.Mask; // Start with output-never-written/input-never-read.
+    if (m_bUsageInMetadata) {
+      unsigned UsageMask = SigElem->GetUsageMask();
+      if (SigElem->IsAllocated())
+        UsageMask <<= SigElem->GetStartCol();
+      Desc.ReadWriteMask = Sig.IsInput() ? UsageMask : NegMask(UsageMask);
+    } else {
+      Desc.ReadWriteMask = Sig.IsInput() ? 0 : Desc.Mask; // Start with output-never-written/input-never-read.
+    }
     Desc.Register = SigElem->GetStartRow();
     Desc.Stream = SigElem->GetOutputStream();
     Desc.SystemValueType = SemanticToSystemValueType(SigElem->GetSemantic(), m_pDxilModule->GetTessellatorDomain());
@@ -1842,6 +1866,11 @@ HRESULT DxilModuleReflection::LoadModule(IDxcBlob *pBlob,
     }
     std::swap(m_pModule, module.get());
     m_pDxilModule = &m_pModule->GetOrCreateDxilModule();
+
+    unsigned ValMajor, ValMinor;
+    m_pDxilModule->GetValidatorVersion(ValMajor, ValMinor);
+    m_bUsageInMetadata = hlsl::DXIL::CompareVersions(ValMajor, ValMinor, 1, 5) >= 0;
+
     CreateReflectionObjects();
     return S_OK;
   }
@@ -1854,13 +1883,15 @@ HRESULT DxilShaderReflection::Load(IDxcBlob *pBlob,
 
   try {
     // Set cbuf usage.
-    SetCBufferUsage();
+    if (!m_bUsageInMetadata)
+      SetCBufferUsage();
 
     // Populate input/output/patch constant signatures.
     CreateReflectionObjectsForSignature(m_pDxilModule->GetInputSignature(), m_InputSignature);
     CreateReflectionObjectsForSignature(m_pDxilModule->GetOutputSignature(), m_OutputSignature);
     CreateReflectionObjectsForSignature(m_pDxilModule->GetPatchConstOrPrimSignature(), m_PatchConstantSignature);
-    MarkUsedSignatureElements();
+    if (!m_bUsageInMetadata)
+      MarkUsedSignatureElements();
     return S_OK;
   }
   CATCH_CPP_RETURN_HRESULT();
@@ -2328,94 +2359,82 @@ HRESULT CFunctionReflection::GetResourceBindingDescByName(LPCSTR Name,
 
 // DxilLibraryReflection
 
-// From DxilContainerAssembler:
-static llvm::Function *FindUsingFunction(llvm::Value *User) {
-  if (llvm::Instruction *I = dyn_cast<llvm::Instruction>(User)) {
-    // Instruction should be inside a basic block, which is in a function
-    return cast<llvm::Function>(I->getParent()->getParent());
-  }
-  // User can be either instruction, constant, or operator. But User is an
-  // operator only if constant is a scalar value, not resource pointer.
-  llvm::Constant *CU = cast<llvm::Constant>(User);
-  if (!CU->user_empty())
-    return FindUsingFunction(*CU->user_begin());
-  else
-    return nullptr;
-}
-
-void DxilLibraryReflection::AddResourceUseToFunctions(DxilResourceBase &resource, unsigned resIndex) {
-  Constant *var = resource.GetGlobalSymbol();
-  if (var) {
-    for (auto user : var->users()) {
-      // Find the function.
-      if (llvm::Function *F = FindUsingFunction(user)) {
-        auto funcReflector = m_FunctionsByPtr[F];
-        funcReflector->AddResourceReference(resIndex);
-        if (resource.GetClass() == DXIL::ResourceClass::CBuffer) {
-          funcReflector->AddCBReference(resource.GetID());
-        }
-      }
+void DxilLibraryReflection::AddResourceDependencies() {
+  const DxilContainerHeader *pHeader =
+    IsDxilContainerLike(m_pContainer->GetBufferPointer(),
+                        m_pContainer->GetBufferSize());
+  IFTBOOL(pHeader, DXC_E_MALFORMED_CONTAINER);
+
+  const DxilPartHeader *pPart = nullptr;
+  for (uint32_t idx = 0; idx < pHeader->PartCount; ++idx) {
+    const DxilPartHeader *pPartTest = GetDxilContainerPart(pHeader, idx);
+    if (pPartTest->PartFourCC == DFCC_RuntimeData) {
+      pPart = pPartTest;
+      break;
     }
   }
-}
+  IFTBOOL(pPart, DXC_E_MISSING_PART);
 
-void DxilLibraryReflection::AddResourceDependencies() {
+  RDAT::DxilRuntimeData RDAT(GetDxilPartData(pPart), pPart->PartSize);
+  RDAT::FunctionTableReader *functionTable = RDAT.GetFunctionTableReader();
+  m_FunctionVector.clear();
+  m_FunctionVector.reserve(functionTable->GetNumFunctions());
   std::map<StringRef, CFunctionReflection*> orderedMap;
-  for (auto &F : m_pModule->functions()) {
-    if (F.isDeclaration())
-      continue;
-    auto &func = m_FunctionMap[F.getName()];
+
+  RDAT::ResourceTableReader *resourceTable = RDAT.GetResourceTableReader();
+  unsigned SamplersStart = resourceTable->GetNumCBuffers();
+  unsigned SRVsStart = SamplersStart + resourceTable->GetNumSamplers();
+  unsigned UAVsStart = SRVsStart + resourceTable->GetNumSRVs();
+  IFTBOOL(UAVsStart + resourceTable->GetNumUAVs() == m_Resources.size(), DXC_E_INCORRECT_DXIL_METADATA);
+
+  for (unsigned iFunc = 0; iFunc < functionTable->GetNumFunctions(); ++iFunc) {
+    RDAT::FunctionReader FR = functionTable->GetItem(iFunc);
+    auto &func = m_FunctionMap[FR.GetName()];
     DXASSERT(!func.get(), "otherwise duplicate named functions");
+    Function *F = m_pModule->getFunction(FR.GetName());
     func.reset(new CFunctionReflection());
-    func->Initialize(this, &F);
-    m_FunctionsByPtr[&F] = func.get();
-    orderedMap[F.getName()] = func.get();
+    func->Initialize(this, F);
+    m_FunctionsByPtr[F] = func.get();
+    orderedMap[FR.GetName()] = func.get();
+
+    for (unsigned iRes = 0; iRes < FR.GetNumResources(); ++iRes) {
+      RDAT::ResourceReader RR = FR.GetResource(iRes);
+      unsigned id = RR.GetID();
+      switch (RR.GetResourceClass()) {
+      case DXIL::ResourceClass::CBuffer:
+        func->AddResourceReference(id);
+        func->AddCBReference(id);
+        break;
+      case DXIL::ResourceClass::Sampler:
+        func->AddResourceReference(SamplersStart + id);
+        break;
+      case DXIL::ResourceClass::SRV:
+        func->AddResourceReference(SRVsStart + id);
+        break;
+      case DXIL::ResourceClass::UAV:
+        func->AddResourceReference(UAVsStart + id);
+        break;
+      default:
+        DXASSERT(false, "Unrecognized ResourceClass in RDAT");
+      }
+    }
   }
-  // Fill in function vector sorted by name
-  m_FunctionVector.clear();
-  m_FunctionVector.reserve(orderedMap.size());
+
   for (auto &it : orderedMap) {
     m_FunctionVector.push_back(it.second);
   }
-  UINT resIndex = 0;
-  for (auto &resource : m_Resources) {
-    switch ((UINT32)resource.Type) {
-    case D3D_SIT_CBUFFER:
-      AddResourceUseToFunctions(m_pDxilModule->GetCBuffer(resource.uID), resIndex);
-      break;
-    case D3D_SIT_TBUFFER:   // TODO: Handle when TBuffers are added to CB list
-    case D3D_SIT_TEXTURE:
-    case D3D_SIT_STRUCTURED:
-    case D3D_SIT_BYTEADDRESS:
-    case D3D_SIT_RTACCELERATIONSTRUCTURE:
-      AddResourceUseToFunctions(m_pDxilModule->GetSRV(resource.uID), resIndex);
-      break;
-    case D3D_SIT_UAV_RWTYPED:
-    case D3D_SIT_UAV_RWSTRUCTURED:
-    case D3D_SIT_UAV_RWBYTEADDRESS:
-    case D3D_SIT_UAV_APPEND_STRUCTURED:
-    case D3D_SIT_UAV_CONSUME_STRUCTURED:
-    case D3D_SIT_UAV_RWSTRUCTURED_WITH_COUNTER:
-      AddResourceUseToFunctions(m_pDxilModule->GetUAV(resource.uID), resIndex);
-      break;
-    case D3D_SIT_SAMPLER:
-      AddResourceUseToFunctions(m_pDxilModule->GetSampler(resource.uID), resIndex);
-      break;
-    }
-    resIndex++;
-  }
 }
 
-static void CollectCBufUsageForLib(Value *V, std::vector<unsigned> &cbufUsage) {
+static void CollectCBufUsageForLib(Value *V, std::vector<unsigned> &cbufUsage, bool bMinPrecision) {
   for (auto user : V->users()) {
     Value *V = user;
     if (auto *CI = dyn_cast<CallInst>(V)) {
       if (hlsl::OP::IsDxilOpFuncCallInst(CI, hlsl::OP::OpCode::CreateHandleForLib)) {
-        CollectCBufUsage(CI, cbufUsage);
+        CollectCBufUsage(CI, cbufUsage, bMinPrecision);
       }
     } else if (isa<GEPOperator>(V) ||
                isa<LoadInst>(V)) {
-      CollectCBufUsageForLib(user, cbufUsage);
+      CollectCBufUsageForLib(user, cbufUsage, bMinPrecision);
     }
   }
 }
@@ -2425,7 +2444,7 @@ void DxilLibraryReflection::SetCBufferUsage() {
 
   for (unsigned i=0;i<cbSize;i++) {
     std::vector<unsigned> cbufUsage;
-    CollectCBufUsageForLib(m_pDxilModule->GetCBuffer(i).GetGlobalSymbol(), cbufUsage);
+    CollectCBufUsageForLib(m_pDxilModule->GetCBuffer(i).GetGlobalSymbol(), cbufUsage, m_pDxilModule->GetUseMinPrecision());
     SetCBufVarUsage(*m_CBs[i], cbufUsage);
   }
 }
@@ -2439,7 +2458,8 @@ HRESULT DxilLibraryReflection::Load(IDxcBlob *pBlob,
 
   try {
     AddResourceDependencies();
-    SetCBufferUsage();
+    if (!m_bUsageInMetadata)
+      SetCBufferUsage();
     return S_OK;
   }
   CATCH_CPP_RETURN_HRESULT();

+ 55 - 10
tools/clang/unittests/HLSL/DxilContainerTest.cpp

@@ -160,8 +160,12 @@ public:
     VERIFY_ARE_EQUAL(pTestDesc->ComponentType, pBaseDesc->ComponentType);
     VERIFY_ARE_EQUAL(MaskCount(pTestDesc->Mask), MaskCount(pBaseDesc->Mask));
     VERIFY_ARE_EQUAL(pTestDesc->MinPrecision, pBaseDesc->MinPrecision);
-    if (!isInput)
-      VERIFY_ARE_EQUAL(pTestDesc->ReadWriteMask != 0, pBaseDesc->ReadWriteMask != 0); // VERIFY_ARE_EQUAL(pTestDesc->ReadWriteMask, pBaseDesc->ReadWriteMask);
+    if (!isInput) {
+      if (hlsl::DXIL::CompareVersions(m_ver.m_ValMajor, m_ver.m_ValMinor, 1, 5) < 0)
+        VERIFY_ARE_EQUAL(pTestDesc->ReadWriteMask != 0, pBaseDesc->ReadWriteMask != 0);
+      else
+        VERIFY_ARE_EQUAL(MaskCount(pTestDesc->ReadWriteMask), MaskCount(pBaseDesc->ReadWriteMask));
+    }
     // VERIFY_ARE_EQUAL(pTestDesc->Register, pBaseDesc->Register);
     //VERIFY_ARE_EQUAL(pTestDesc->SemanticIndex, pBaseDesc->SemanticIndex);
     VERIFY_ARE_EQUAL(pTestDesc->Stream, pBaseDesc->Stream);
@@ -608,8 +612,8 @@ TEST_F(DxilContainerTest, CompileWhenOKThenIncludesSignatures) {
 
   {
     std::string s = DisassembleProgram(program, L"VSMain", L"vs_6_0");
-    // NOTE: this will change when proper packing is done, and when 'always-writes' is accurately implemented.
-    const char expected[] =
+    // NOTE: this will change when proper packing is done, and when 'always-reads' is accurately implemented.
+    const char expected_1_4[] =
       ";\n"
       "; Input signature:\n"
       ";\n"
@@ -625,14 +629,35 @@ TEST_F(DxilContainerTest, CompileWhenOKThenIncludesSignatures) {
       "; -------------------- ----- ------ -------- -------- ------- ------\n"
       "; SV_Position              0   xyzw        0      POS   float   xyzw\n"  // could read SV_POSITION
       "; COLOR                    0   xyzw        1     NONE   float   xyzw\n"; // should read '1' in register
-    std::string start(s.c_str(), strlen(expected));
-    VERIFY_ARE_EQUAL_STR(expected, start.c_str());
+    const char expected[] =
+      ";\n"
+      "; Input signature:\n"
+      ";\n"
+      "; Name                 Index   Mask Register SysValue  Format   Used\n"
+      "; -------------------- ----- ------ -------- -------- ------- ------\n"
+      "; POSITION                 0   xyzw        0     NONE   float   xyzw\n" // should read 'xyzw' in Used
+      "; COLOR                    0   xyzw        1     NONE   float   xyzw\n" // should read '1' in register
+      ";\n"
+      ";\n"
+      "; Output signature:\n"
+      ";\n"
+      "; Name                 Index   Mask Register SysValue  Format   Used\n"
+      "; -------------------- ----- ------ -------- -------- ------- ------\n"
+      "; SV_Position              0   xyzw        0      POS   float   xyzw\n"  // could read SV_POSITION
+      "; COLOR                    0   xyzw        1     NONE   float   xyzw\n"; // should read '1' in register
+    if (hlsl::DXIL::CompareVersions(m_ver.m_ValMajor, m_ver.m_ValMinor, 1, 5) < 0) {
+      std::string start(s.c_str(), strlen(expected_1_4));
+      VERIFY_ARE_EQUAL_STR(expected_1_4, start.c_str());
+    } else {
+      std::string start(s.c_str(), strlen(expected));
+      VERIFY_ARE_EQUAL_STR(expected, start.c_str());
+    }
   }
 
   {
     std::string s = DisassembleProgram(program, L"PSMain", L"ps_6_0");
-    // NOTE: this will change when proper packing is done, and when 'always-writes' is accurately implemented.
-    const char expected[] =
+    // NOTE: this will change when proper packing is done, and when 'always-reads' is accurately implemented.
+    const char expected_1_4[] =
       ";\n"
       "; Input signature:\n"
       ";\n"
@@ -647,8 +672,28 @@ TEST_F(DxilContainerTest, CompileWhenOKThenIncludesSignatures) {
       "; Name                 Index   Mask Register SysValue  Format   Used\n"
       "; -------------------- ----- ------ -------- -------- ------- ------\n"
       "; SV_Target                0   xyzw        0   TARGET   float   xyzw\n";// could read SV_TARGET
-    std::string start(s.c_str(), strlen(expected));
-    VERIFY_ARE_EQUAL_STR(expected, start.c_str());
+    const char expected[] =
+      ";\n"
+      "; Input signature:\n"
+      ";\n"
+      "; Name                 Index   Mask Register SysValue  Format   Used\n"
+      "; -------------------- ----- ------ -------- -------- ------- ------\n"
+      "; SV_Position              0   xyzw        0      POS   float       \n" // could read SV_POSITION
+      "; COLOR                    0   xyzw        1     NONE   float   xyzw\n" // should read '1' in register, xyzw in Used
+      ";\n"
+      ";\n"
+      "; Output signature:\n"
+      ";\n"
+      "; Name                 Index   Mask Register SysValue  Format   Used\n"
+      "; -------------------- ----- ------ -------- -------- ------- ------\n"
+      "; SV_Target                0   xyzw        0   TARGET   float   xyzw\n";// could read SV_TARGET
+    if (hlsl::DXIL::CompareVersions(m_ver.m_ValMajor, m_ver.m_ValMinor, 1, 5) < 0) {
+      std::string start(s.c_str(), strlen(expected_1_4));
+      VERIFY_ARE_EQUAL_STR(expected_1_4, start.c_str());
+    } else {
+      std::string start(s.c_str(), strlen(expected));
+      VERIFY_ARE_EQUAL_STR(expected, start.c_str());
+    }
   }
 }