Quellcode durchsuchen

[spirv] Derive storage class properly from original variables (#538)

We now compute and store decl storage class in DeclResultIdMapper.
Querying is done using RecursiveASTVisitor to check DeclRefExpr,
FunctionDecl, ParmVarDecl, and FieldDecl for normal and remapped
Decls.

Fixed various hard coded storage class in SPIRVEmitter.
Lei Zhang vor 8 Jahren
Ursprung
Commit
aef18a9966

+ 3 - 1
tools/clang/include/clang/SPIRV/ModuleBuilder.h

@@ -210,7 +210,9 @@ public:
   ///
   /// The corresponding pointer type of the given type will be constructed in
   /// this method for the variable itself.
-  uint32_t addStageBuiltinVariable(uint32_t type, spv::BuiltIn);
+  uint32_t addStageBuiltinVariable(uint32_t type,
+                                   spv::StorageClass storageClass,
+                                   spv::BuiltIn);
 
   /// \brief Decorates the given target <result-id> with the given location.
   void decorateLocation(uint32_t targetId, uint32_t location);

+ 104 - 35
tools/clang/lib/SPIRV/DeclResultIdMapper.cpp

@@ -11,7 +11,9 @@
 
 #include "dxc/HLSL/DxilConstants.h"
 #include "dxc/HLSL/DxilTypeSystem.h"
+#include "clang/AST/Expr.h"
 #include "clang/AST/HlslTypes.h"
+#include "clang/AST/RecursiveASTVisitor.h"
 
 namespace clang {
 namespace spirv {
@@ -29,31 +31,42 @@ bool DeclResultIdMapper::createStageVarFromFnParam(
 
 void DeclResultIdMapper::registerDeclResultId(const NamedDecl *symbol,
                                               uint32_t resultId) {
-  normalDecls[symbol] = resultId;
+  auto sc = spv::StorageClass::Function;
+  // TODO: need to fix the storage class for other cases
+  if (const auto *varDecl = dyn_cast<VarDecl>(symbol)) {
+    if (!varDecl->isLocalVarDecl()) {
+      // Global variables are by default constant. But the default behavior
+      // can be changed via command line option.
+      sc = spv::StorageClass::Uniform;
+    }
+  }
+  normalDecls[symbol] = {resultId, sc};
 }
 
-bool DeclResultIdMapper::isStageVariable(uint32_t varId) const {
-  for (const auto &var : stageVars)
-    if (var.getSpirvId() == varId)
-      return true;
-  return false;
+const DeclResultIdMapper::DeclSpirvInfo *
+DeclResultIdMapper::getDeclSpirvInfo(const NamedDecl *decl) const {
+  auto it = remappedDecls.find(decl);
+  if (it != remappedDecls.end())
+    return &it->second;
+
+  it = normalDecls.find(decl);
+  if (it != normalDecls.end())
+    return &it->second;
+
+  return nullptr;
 }
 
 uint32_t DeclResultIdMapper::getDeclResultId(const NamedDecl *decl) const {
-  if (const uint32_t id = getNormalDeclResultId(decl))
-    return id;
-  if (const uint32_t id = getRemappedDeclResultId(decl))
-    return id;
+  if (const auto *info = getDeclSpirvInfo(decl))
+    return info->resultId;
 
   assert(false && "found unregistered decl");
   return 0;
 }
 
 uint32_t DeclResultIdMapper::getOrRegisterDeclResultId(const NamedDecl *decl) {
-  if (const uint32_t id = getNormalDeclResultId(decl))
-    return id;
-  if (const uint32_t id = getRemappedDeclResultId(decl))
-    return id;
+  if (const auto *info = getDeclSpirvInfo(decl))
+    return info->resultId;
 
   const uint32_t id = theBuilder.getSPIRVContext()->takeNextId();
   registerDeclResultId(decl, id);
@@ -65,16 +78,67 @@ uint32_t
 DeclResultIdMapper::getRemappedDeclResultId(const NamedDecl *decl) const {
   auto it = remappedDecls.find(decl);
   if (it != remappedDecls.end())
-    return it->second;
+    return it->second.resultId;
   return 0;
 }
 
-uint32_t
-DeclResultIdMapper::getNormalDeclResultId(const NamedDecl *decl) const {
-  auto it = normalDecls.find(decl);
-  if (it != normalDecls.end())
-    return it->second;
-  return 0;
+namespace {
+/// A class for resolving the storage class of a given Decl or Expr.
+class StorageClassResolver : public RecursiveASTVisitor<StorageClassResolver> {
+public:
+  explicit StorageClassResolver(const DeclResultIdMapper &mapper)
+      : declIdMapper(mapper), storageClass(spv::StorageClass::Max) {}
+
+  // For querying the storage class of a remapped decl
+
+  // Semantics may be attached to FunctionDecl, ParmVarDecl, and FieldDecl.
+  // We create stage variables for them and we may need to query the storage
+  // classes of these stage variables.
+  bool VisitFunctionDecl(FunctionDecl *decl) { return processDecl(decl); }
+  bool VisitFieldDecl(FieldDecl *decl) { return processDecl(decl); }
+  bool VisitParmVarDecl(ParmVarDecl *decl) { return processDecl(decl); }
+
+  // For querying the storage class of a normal decl
+
+  // Normal decls should be referred in expressions.
+  bool VisitDeclRefExpr(DeclRefExpr *expr) {
+    return processDecl(expr->getDecl());
+  }
+
+  bool processDecl(NamedDecl *decl) {
+    const auto *info = declIdMapper.getDeclSpirvInfo(decl);
+    assert(info);
+    if (storageClass == spv::StorageClass::Max) {
+      storageClass = info->storageClass;
+      return true;
+    }
+
+    // Two decls with different storage classes are referenced in this
+    // expression. We should not visit such expression using this class.
+    assert(storageClass == info->storageClass);
+    return false;
+  }
+
+  spv::StorageClass get() const { return storageClass; }
+
+private:
+  const DeclResultIdMapper &declIdMapper;
+  spv::StorageClass storageClass;
+};
+} // namespace
+
+spv::StorageClass
+DeclResultIdMapper::resolveStorageClass(const Expr *expr) const {
+  auto resolver = StorageClassResolver(*this);
+  resolver.TraverseStmt(const_cast<Expr *>(expr));
+  return resolver.get();
+}
+
+spv::StorageClass
+DeclResultIdMapper::resolveStorageClass(const Decl *decl) const {
+  auto resolver = StorageClassResolver(*this);
+  resolver.TraverseDecl(const_cast<Decl *>(decl));
+  return resolver.get();
 }
 
 std::vector<uint32_t> DeclResultIdMapper::collectStageVariables() const {
@@ -153,7 +217,7 @@ bool DeclResultIdMapper::createStageVariables(const DeclaratorDecl *decl,
     stageVar.setSpirvId(varId);
 
     stageVars.push_back(stageVar);
-    remappedDecls[decl] = varId;
+    remappedDecls[decl] = {varId, stageVar.getStorageClass()};
   } else {
     // If the decl itself doesn't have semantic, it should be a struct having
     // all its fields with semantics.
@@ -173,6 +237,8 @@ bool DeclResultIdMapper::createStageVariables(const DeclaratorDecl *decl,
 }
 
 uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar) {
+  using spv::BuiltIn;
+
   const auto semanticKind = stageVar->getSemantic()->GetKind();
   const auto sigPointKind = stageVar->getSigPoint()->GetKind();
   const uint32_t type = stageVar->getSpirvTypeId();
@@ -181,6 +247,12 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar) {
   // shader model is already checked, so it only covers valid SigPoints for
   // each semantic.
 
+  // TODO: case for patch constant
+  const auto sc = stageVar->getSigPoint()->IsInput()
+                      ? spv::StorageClass::Input
+                      : spv::StorageClass::Output;
+  stageVar->setStorageClass(sc);
+
   switch (semanticKind) {
   // According to DXIL spec, the Position SV can be used by all SigPoints
   // other than PCIn, HSIn, GSIn, PSOut, CSIn.
@@ -189,13 +261,13 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar) {
   case hlsl::Semantic::Kind::Position: {
     switch (sigPointKind) {
     case hlsl::SigPoint::Kind::VSIn:
-      return theBuilder.addStageIOVariable(type, spv::StorageClass::Input);
+      return theBuilder.addStageIOVariable(type, sc);
     case hlsl::SigPoint::Kind::VSOut:
       stageVar->setIsSpirvBuiltin();
-      return theBuilder.addStageBuiltinVariable(type, spv::BuiltIn::Position);
+      return theBuilder.addStageBuiltinVariable(type, sc, BuiltIn::Position);
     case hlsl::SigPoint::Kind::PSIn:
       stageVar->setIsSpirvBuiltin();
-      return theBuilder.addStageBuiltinVariable(type, spv::BuiltIn::FragCoord);
+      return theBuilder.addStageBuiltinVariable(type, sc, BuiltIn::FragCoord);
     default:
       emitError("semantic Position for SigPoint %0 unimplemented yet")
           << stageVar->getSigPoint()->GetName();
@@ -205,7 +277,7 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar) {
   // According to DXIL spec, the VertexID SV can only be used by VSIn.
   case hlsl::Semantic::Kind::VertexID:
     stageVar->setIsSpirvBuiltin();
-    return theBuilder.addStageBuiltinVariable(type, spv::BuiltIn::VertexIndex);
+    return theBuilder.addStageBuiltinVariable(type, sc, BuiltIn::VertexIndex);
   // According to DXIL spec, the InstanceID SV can  be used by VSIn, VSOut,
   // HSCPIn, HSCPOut, DSCPIn, DSOut, GSVIn, GSOut, PSIn.
   // According to Vulkan spec, the InstanceIndex can only be used by VSIn.
@@ -213,12 +285,12 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar) {
     switch (sigPointKind) {
     case hlsl::SigPoint::Kind::VSIn:
       stageVar->setIsSpirvBuiltin();
-      return theBuilder.addStageBuiltinVariable(type,
-                                                spv::BuiltIn::InstanceIndex);
+      return theBuilder.addStageBuiltinVariable(type, sc,
+                                                BuiltIn::InstanceIndex);
     case hlsl::SigPoint::Kind::VSOut:
-      return theBuilder.addStageIOVariable(type, spv::StorageClass::Output);
+      return theBuilder.addStageIOVariable(type, sc);
     case hlsl::SigPoint::Kind::PSIn:
-      return theBuilder.addStageIOVariable(type, spv::StorageClass::Input);
+      return theBuilder.addStageIOVariable(type, sc);
     default:
       emitError("semantic InstanceID for SigPoint %0 unimplemented yet")
           << stageVar->getSigPoint()->GetName();
@@ -228,7 +300,7 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar) {
   // According to DXIL spec, the Depth SV can only be used by PSOut.
   case hlsl::Semantic::Kind::Depth:
     stageVar->setIsSpirvBuiltin();
-    return theBuilder.addStageBuiltinVariable(type, spv::BuiltIn::FragDepth);
+    return theBuilder.addStageBuiltinVariable(type, sc, BuiltIn::FragDepth);
   // According to DXIL spec, the Target SV can only be used by PSOut.
   // There is no corresponding builtin decoration in SPIR-V. So generate normal
   // Vulkan stage input/output variables.
@@ -236,10 +308,7 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar) {
   // An arbitrary semantic is defined by users. Generate normal Vulkan stage
   // input/output variables.
   case hlsl::Semantic::Kind::Arbitrary: {
-    if (stageVar->getSigPoint()->IsInput())
-      return theBuilder.addStageIOVariable(type, spv::StorageClass::Input);
-    if (stageVar->getSigPoint()->IsOutput())
-      return theBuilder.addStageIOVariable(type, spv::StorageClass::Output);
+    return theBuilder.addStageIOVariable(type, sc);
     // TODO: patch constant function in hull shader
   }
   default:

+ 30 - 12
tools/clang/lib/SPIRV/DeclResultIdMapper.h

@@ -30,9 +30,8 @@ namespace spirv {
 /// (builtin/input/output) variable.
 class StageVar {
 public:
-  StageVar(const hlsl::SigPoint *sig, const hlsl::Semantic *sema, uint32_t type)
-      : sigPoint(sig), semantic(sema), typeId(type), valueId(0),
-        isBuiltin(false), location(llvm::None) {}
+  inline StageVar(const hlsl::SigPoint *sig, const hlsl::Semantic *sema,
+                  uint32_t type);
 
   const hlsl::SigPoint *getSigPoint() const { return sigPoint; }
   const hlsl::Semantic *getSemantic() const { return semantic; }
@@ -45,6 +44,9 @@ public:
   bool isSpirvBuitin() const { return isBuiltin; }
   void setIsSpirvBuiltin() { isBuiltin = true; }
 
+  spv::StorageClass getStorageClass() const { return storageClass; }
+  void setStorageClass(spv::StorageClass sc) { storageClass = sc; }
+
   bool hasLocation() const { return location.hasValue(); }
   void setLocation(uint32_t loc) { location = llvm::Optional<uint32_t>(loc); }
 
@@ -60,10 +62,17 @@ private:
   uint32_t valueId;
   /// Indicates whether this stage variable should be a SPIR-V builtin.
   bool isBuiltin;
+  /// SPIR-V storage class this stage variable belongs to.
+  spv::StorageClass storageClass;
   /// Location assignment if input/output variable.
   llvm::Optional<uint32_t> location;
 };
 
+StageVar::StageVar(const hlsl::SigPoint *sig, const hlsl::Semantic *sema,
+                   uint32_t type)
+    : sigPoint(sig), semantic(sema), typeId(type), valueId(0), isBuiltin(false),
+      storageClass(spv::StorageClass::Max), location(llvm::None) {}
+
 /// \brief The class containing mappings from Clang frontend Decls to their
 /// corresponding SPIR-V <result-id>s.
 ///
@@ -104,8 +113,16 @@ public:
   /// instruction. The given decl will be treated as normal decl.
   void registerDeclResultId(const NamedDecl *symbol, uint32_t resultId);
 
-  /// \brief Returns true if the given <result-id> is for a stage variable.
-  bool isStageVariable(uint32_t varId) const;
+public:
+  /// The struct containing SPIR-V information of a AST Decl.
+  struct DeclSpirvInfo {
+    uint32_t resultId;
+    spv::StorageClass storageClass;
+  };
+
+  /// \brief Returns the SPIR-V information for the given decl.
+  /// Returns nullptr if no such decl was previously registered.
+  const DeclSpirvInfo *getDeclSpirvInfo(const NamedDecl *decl) const;
 
   /// \brief Returns the <result-id> for the given decl.
   ///
@@ -121,9 +138,10 @@ public:
   /// if it is not a registered remapped decl.
   uint32_t getRemappedDeclResultId(const NamedDecl *decl) const;
 
-  /// \brief Returns the <result-id> for the given normal decl. Returns zero if
-  /// it is not a registered normal decl.
-  uint32_t getNormalDeclResultId(const NamedDecl *decl) const;
+  /// Returns the storage class for the given expression. The expression is
+  /// expected to be an lvalue. Otherwise this method may panic.
+  spv::StorageClass resolveStorageClass(const Expr *expr) const;
+  spv::StorageClass resolveStorageClass(const Decl *decl) const;
 
   /// \brief Returns all defined stage (builtin/input/ouput) variables in this
   /// mapper.
@@ -156,8 +174,8 @@ private:
   bool createStageVariables(const DeclaratorDecl *decl, bool forReturnValue);
 
   /// Creates the SPIR-V variable instruction for the given StageVar and returns
-  /// the <result-id>. Also sets whether the StageVar is a SPIR-V builtin
-  /// accordingly.
+  /// the <result-id>. Also sets whether the StageVar is a SPIR-V builtin and
+  /// its storage class accordingly.
   uint32_t createSpirvStageVar(StageVar *);
 
   /// \brief Returns the stage variable's semantic for the given Decl.
@@ -170,9 +188,9 @@ private:
   DiagnosticsEngine &diags;
 
   /// Mapping of all remapped decls to their <result-id>s.
-  llvm::DenseMap<const NamedDecl *, uint32_t> remappedDecls;
+  llvm::DenseMap<const NamedDecl *, DeclSpirvInfo> remappedDecls;
   /// Mapping of all normal decls to their <result-id>s.
-  llvm::DenseMap<const NamedDecl *, uint32_t> normalDecls;
+  llvm::DenseMap<const NamedDecl *, DeclSpirvInfo> normalDecls;
   /// Vector of all defined stage variables.
   llvm::SmallVector<StageVar, 8> stageVars;
 };

+ 1 - 11
tools/clang/lib/SPIRV/ModuleBuilder.cpp

@@ -313,18 +313,8 @@ uint32_t ModuleBuilder::addStageIOVariable(uint32_t type,
 }
 
 uint32_t ModuleBuilder::addStageBuiltinVariable(uint32_t type,
+                                                spv::StorageClass sc,
                                                 spv::BuiltIn builtin) {
-  spv::StorageClass sc = spv::StorageClass::Input;
-  switch (builtin) {
-  case spv::BuiltIn::Position:
-  case spv::BuiltIn::PointSize:
-  case spv::BuiltIn::FragDepth:
-    // TODO: add the rest output builtins
-    sc = spv::StorageClass::Output;
-    break;
-  default:
-    break;
-  }
   const uint32_t pointerType = getPointerType(type, sc);
   const uint32_t varId = theContext.takeNextId();
   instBuilder.opVariable(pointerType, varId, sc, llvm::None).x();

+ 13 - 19
tools/clang/lib/SPIRV/SPIRVEmitter.cpp

@@ -240,8 +240,8 @@ uint32_t SPIRVEmitter::doExpr(const Expr *expr) {
           theBuilder.getConstantInt32(fieldDecl->getFieldIndex());
       const uint32_t fieldType =
           typeTranslator.translateType(fieldDecl->getType());
-      const uint32_t ptrType =
-          theBuilder.getPointerType(fieldType, spv::StorageClass::Function);
+      const uint32_t ptrType = theBuilder.getPointerType(
+          fieldType, declIdMapper.resolveStorageClass(memberExpr->getBase()));
       return theBuilder.createAccessChain(ptrType, base, {index});
     } else {
       emitError("Decl '%0' in MemberExpr is not supported yet.")
@@ -754,21 +754,19 @@ void SPIRVEmitter::doReturnStmt(const ReturnStmt *stmt) {
     // for returning a struct variable. We need to ignore the cast to avoid
     // creating OpLoad instruction since we need the pointer to the variable
     // for creating access chain later.
-    const uint32_t retValue =
-        doExpr(stmt->getRetValue()->IgnoreParenLValueCasts());
+    const Expr *retValue = stmt->getRetValue()->IgnoreParenLValueCasts();
 
     // Then go through all fields.
     uint32_t fieldIndex = 0;
     for (const auto *field : structType->getDecl()->fields()) {
       // Load the value from the current field.
       const uint32_t valueType = typeTranslator.translateType(field->getType());
-      // TODO: We may need to change the storage class accordingly.
       const uint32_t ptrType = theBuilder.getPointerType(
           typeTranslator.translateType(field->getType()),
-          spv::StorageClass::Function);
+          declIdMapper.resolveStorageClass(retValue));
       const uint32_t indexId = theBuilder.getConstantInt32(fieldIndex++);
       const uint32_t valuePtr =
-          theBuilder.createAccessChain(ptrType, retValue, {indexId});
+          theBuilder.createAccessChain(ptrType, doExpr(retValue), {indexId});
       const uint32_t value = theBuilder.createLoad(valueType, valuePtr);
       // Store it to the corresponding stage variable.
       const uint32_t targetVar = declIdMapper.getDeclResultId(field);
@@ -1133,9 +1131,8 @@ uint32_t SPIRVEmitter::doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr) {
       }
 
       const uint32_t elemType = typeTranslator.translateType(expr->getType());
-      // TODO: select storage type based on the underlying variable
-      const uint32_t ptrType =
-          theBuilder.getPointerType(elemType, spv::StorageClass::Function);
+      const uint32_t ptrType = theBuilder.getPointerType(
+          elemType, declIdMapper.resolveStorageClass(baseExpr));
 
       return theBuilder.createAccessChain(ptrType, base, indices);
     }
@@ -1179,9 +1176,8 @@ SPIRVEmitter::doExtMatrixElementExpr(const ExtMatrixElementExpr *expr) {
       for (uint32_t i = 0; i < indices.size(); ++i)
         indices[i] = theBuilder.getConstantInt32(indices[i]);
 
-      // TODO: select storage type based on the underlying variable
-      const uint32_t ptrType =
-          theBuilder.getPointerType(elemType, spv::StorageClass::Function);
+      const uint32_t ptrType = theBuilder.getPointerType(
+          elemType, declIdMapper.resolveStorageClass(baseExpr));
       if (!indices.empty()) {
         // Load the element via access chain
         elem = theBuilder.createAccessChain(ptrType, base, indices);
@@ -1239,9 +1235,8 @@ SPIRVEmitter::doHLSLVectorElementExpr(const HLSLVectorElementExpr *expr) {
     // instead of the original base here since we can have something like
     // v.xyyz to turn a lvalue v into rvalue.
     if (expr->getBase()->isGLValue()) { // E.g., v.x;
-      // TODO: select the correct storage class
-      const uint32_t ptrType =
-          theBuilder.getPointerType(type, spv::StorageClass::Function);
+      const uint32_t ptrType = theBuilder.getPointerType(
+          type, declIdMapper.resolveStorageClass(baseExpr));
       const uint32_t index = theBuilder.getConstantInt32(accessor.Swz0);
       // We need a lvalue here. Do not try to load.
       return theBuilder.createAccessChain(ptrType, doExpr(baseExpr), {index});
@@ -1952,9 +1947,8 @@ uint32_t SPIRVEmitter::tryToAssignToMatrixElements(const Expr *lhs,
     if (accessor.Count > 1)
       rhsElem = theBuilder.createCompositeExtract(elemTypeId, rhs, {i});
 
-    // TODO: select storage type based on the underlying variable
-    const uint32_t ptrType =
-        theBuilder.getPointerType(elemTypeId, spv::StorageClass::Function);
+    const uint32_t ptrType = theBuilder.getPointerType(
+        elemTypeId, declIdMapper.resolveStorageClass(baseMat));
 
     // If the lhs is actually a matrix of size 1x1, we don't need the access
     // chain. base is already the dest pointer.

+ 19 - 0
tools/clang/test/CodeGenSPIRV/spirv.storage-class.hlsl

@@ -0,0 +1,19 @@
+// Run: %dxc -T vs_6_0 -E main
+
+struct VSOut {
+    float4 out1: C;
+    float4 out2: D;
+};
+
+// CHECK: [[input:%\d+]] = OpVariable %_ptr_Input_v4float Input
+
+VSOut main(float4 input: A, uint index: B) {
+    VSOut ret;
+
+// CHECK:      {{%\d+}} = OpAccessChain %_ptr_Input_float [[input]] {{%\d+}}
+// CHECK:      [[lhs:%\d+]] = OpAccessChain %_ptr_Function_v4float %ret %int_0
+// CHECK-NEXT: {{%\d+}} = OpAccessChain %_ptr_Function_float [[lhs]] {{%\d+}}
+    ret.out1[index] = input[index];
+
+    return ret;
+}

+ 12 - 3
tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp

@@ -250,16 +250,22 @@ TEST_F(FileTest, IntrinsicsAbs) { runFileTest("intrinsics.abs.hlsl"); }
 TEST_F(FileTest, IntrinsicsCeil) { runFileTest("intrinsics.ceil.hlsl"); }
 TEST_F(FileTest, IntrinsicsDegrees) { runFileTest("intrinsics.degrees.hlsl"); }
 TEST_F(FileTest, IntrinsicsRadians) { runFileTest("intrinsics.radians.hlsl"); }
-TEST_F(FileTest, IntrinsicsDeterminant) { runFileTest("intrinsics.determinant.hlsl"); }
+TEST_F(FileTest, IntrinsicsDeterminant) {
+  runFileTest("intrinsics.determinant.hlsl");
+}
 TEST_F(FileTest, IntrinsicsExp) { runFileTest("intrinsics.exp.hlsl"); }
 TEST_F(FileTest, IntrinsicsExp2) { runFileTest("intrinsics.exp2.hlsl"); }
 TEST_F(FileTest, IntrinsicsFloor) { runFileTest("intrinsics.floor.hlsl"); }
 TEST_F(FileTest, IntrinsicsLength) { runFileTest("intrinsics.length.hlsl"); }
 TEST_F(FileTest, IntrinsicsLog) { runFileTest("intrinsics.log.hlsl"); }
 TEST_F(FileTest, IntrinsicsLog2) { runFileTest("intrinsics.log2.hlsl"); }
-TEST_F(FileTest, IntrinsicsNormalize) { runFileTest("intrinsics.normalize.hlsl"); }
+TEST_F(FileTest, IntrinsicsNormalize) {
+  runFileTest("intrinsics.normalize.hlsl");
+}
 TEST_F(FileTest, IntrinsicsRsqrt) { runFileTest("intrinsics.rsqrt.hlsl"); }
-TEST_F(FileTest, IntrinsicsFloatSign) { runFileTest("intrinsics.floatsign.hlsl"); }
+TEST_F(FileTest, IntrinsicsFloatSign) {
+  runFileTest("intrinsics.floatsign.hlsl");
+}
 TEST_F(FileTest, IntrinsicsIntSign) { runFileTest("intrinsics.intsign.hlsl"); }
 TEST_F(FileTest, IntrinsicsSqrt) { runFileTest("intrinsics.sqrt.hlsl"); }
 TEST_F(FileTest, IntrinsicsTrunc) { runFileTest("intrinsics.trunc.hlsl"); }
@@ -275,4 +281,7 @@ TEST_F(FileTest, IntrinsicsAsin) { runFileTest("intrinsics.asin.hlsl"); }
 TEST_F(FileTest, IntrinsicsAcos) { runFileTest("intrinsics.acos.hlsl"); }
 TEST_F(FileTest, IntrinsicsAtan) { runFileTest("intrinsics.atan.hlsl"); }
 
+// SPIR-V specific
+TEST_F(FileTest, SpirvStorageClass) { runFileTest("spirv.storage-class.hlsl"); }
+
 } // namespace