[llvm-branch-commits] [llvm] [HLSLSemanticSignatures] Implement the prefix packing of elements (PR #218062)

Helena Kotas via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Aug 28 17:49:56 PDT 2026


================
@@ -433,4 +471,930 @@ TEST_F(HLSLSemanticSignaturePackingTest, IndexedRejectsSemanticIndexOverflow) {
                      /*ExpectedElementIndex=*/0);
 }
 
+//===----------------------------------------------------------------------===//
+// Basic prefix-stable packing tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableWhenAppended) {
+  // Appending an element to a signature never moves the elements declared
+  // before it; the appended element is only packed into the space they left.
+
+  // struct Prefix {
+  //   float3 A : A;
+  //   float2 B : B;
+  // };
+  TestConfig PrefixConfig(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: A.xyz | unused.w
+  // reg1: B.xy  | unused.zw
+  expectPacking(PackingMethod::PrefixStable, PrefixConfig, /*ExpectedRows=*/2,
+                {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+
+  // struct Extended {
+  //   float3 A : A;
+  //   float2 B : B;
+  //   float C  : C;
+  // };
+  TestConfig ExtendedConfig = PrefixConfig;
+  ExtendedConfig.Elements.push_back(
+      {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+       dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear});
+
+  // Expected layout:
+  // reg0: A.xyz | C.w
+  // reg1: B.xy  | unused.zw
+  //
+  // C is packed into the gap A left behind, and A and B keep the locations
+  // they were given in Prefix.
+  expectPacking(
+      PackingMethod::PrefixStable, ExtendedConfig, /*ExpectedRows=*/2,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/0, /*Col=*/3}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableFillsAllRows) {
+  // A signature may use all 32 rows.
+
+  // struct VSOut {
+  //   float4 A0  : A0;
+  //   ...
+  //   float4 A31 : A31;
+  // };
+  TestConfig Config(Triple::EnvironmentType::Vertex, IOType::Out,
+                    /*UseNative16BitTypes=*/false, {});
+  for (unsigned I = 0; I != MaxSignatureRows; ++I)
+    Config.Elements.push_back({dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1,
+                               /*Cols=*/MaxSignatureCols,
+                               dxil::ElementType::F32,
+                               dxbc::PSV::InterpolationMode::Linear});
+
+  SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+  ASSERT_THAT_ERROR(pack(PackingMethod::PrefixStable, Elements, Config),
+                    Succeeded());
+
+  for (unsigned I = 0; I != MaxSignatureRows; ++I) {
+    EXPECT_EQ(Elements[I].StartRow, I) << "element " << I;
+    EXPECT_EQ(Elements[I].StartCol, 0u) << "element " << I;
+  }
+}
+
+//===----------------------------------------------------------------------===//
+// Prefix-stable row compatibility tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableGeneralPacking) {
+  // Native 16-bit types are enabled.
+  // struct PSIn {
+  //   float16_t2 A : A;
+  //   float2 B     : B;
+  //   float16_t3 C : C;
+  //   float2 D     : D;
+  //   int E        : E;
+  //   float16_t2 F : F;
+  //   float16_t G  : G;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Pixel, IOType::In,
+      /*UseNative16BitTypes=*/true,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::I32, dxbc::PSV::InterpolationMode::Constant},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: A.xy | F.zw
+  // reg1: B.xy | D.zw
+  // reg2: C.xyz | G.w
+  // reg3: E.x | unused.yzw
+  expectPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/4,
+                {{/*Row=*/0, /*Col=*/0},
+                 {/*Row=*/1, /*Col=*/0},
+                 {/*Row=*/2, /*Col=*/0},
+                 {/*Row=*/1, /*Col=*/2},
+                 {/*Row=*/3, /*Col=*/0},
+                 {/*Row=*/0, /*Col=*/2},
+                 {/*Row=*/2, /*Col=*/3}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableNative16BitWidth) {
+  // struct VSOut {
+  //   float16_t2 A : A;
+  //   float2 B     : B;
+  //   float16_t2 C : C;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/true,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: A.xy | C.zw
+  // reg1: B.xy | unused.zw
+  expectPacking(
+      PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableInterpolationMode) {
+  // struct VSOut {
+  //   float2 A                : A;
+  //   nointerpolation float2 B : B;
+  //   float2 C                : C;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Constant},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: A.xy | C.zw
+  // reg1: B.xy | unused.zw
+  expectPacking(
+      PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableCompatible16BitTypes) {
+  // struct VSOut {
+  //   nointerpolation int16_t A    : A;
+  //   nointerpolation float16_t3 B : B;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/true,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::I16, dxbc::PSV::InterpolationMode::Constant},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Constant}});
+
+  // Expected layout:
+  // reg0: A.x | B.yzw
+  expectPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/1,
+                {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/1}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableNormalized16BitTypes) {
+  // A normalized 16-bit type has the same component width as any other 16-bit
+  // type, so it co-packs with them but not with a 32-bit type.
+
+  // struct VSOut {
+  //   nointerpolation snorm half A : A;
+  //   nointerpolation float16_t B  : B;
+  //   nointerpolation float C      : C;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/true,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::SNormF16, dxbc::PSV::InterpolationMode::Constant},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Constant},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Constant}});
+
+  // Expected layout:
+  // reg0: A.x | B.y | unused.zw
+  // reg1: C.x | unused.yzw
+  expectPacking(
+      PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/1}, {/*Row=*/1, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableMinPrecisionWidth) {
+  // Without native 16-bit types, 16-bit types are min-precision types which
+  // occupy a full 32-bit component, so they co-pack with 32-bit types. This is
+  // the same signature as PrefixStableNative16BitWidth, which packs
+  // differently.
+
+  // struct VSOut {
+  //   min16float2 A : A;
+  //   float2 B      : B;
+  //   min16float2 C : C;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: A.xy | B.zw
+  // reg1: C.xy | unused.zw
+  expectPacking(
+      PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}, {/*Row=*/1, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableUndefinedInterpMode) {
+  // An undefined interpolation mode does not constrain a register, but the
+  // first defined mode packed into it does.
+
+  // struct VSOut {
+  //   float2 A                : A; // undefined interpolation mode
+  //   float B                 : B; // linear
+  //   nointerpolation float C : C; // nointerpolation
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Constant}});
+
+  // Expected layout:
+  // reg0: A.xy | B.z | unused.w
+  // reg1: C.x  | unused.yzw
+  expectPacking(
+      PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}, {/*Row=*/1, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+       PrefixStableUndefinedInterpModeAfterDefined) {
+  // Once a register has a defined interpolation mode, an element with an
+  // undefined mode cannot be packed into it.
+
+  // struct VSOut {
+  //   float2 A : A; // linear
+  //   float2 B : B; // undefined interpolation mode
+  //   float2 C : C; // linear
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: A.xy | C.zw
+  // reg1: B.xy | unused.zw
+  expectPacking(
+      PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableDistinctInterpModes) {
+  // Every distinct interpolation mode requires its own register, including
+  // modes that only differ by their centroid or noperspective qualifier.
+
+  // struct VSOut {
+  //   float2 A               : A;
+  //   centroid float2 B      : B;
+  //   noperspective float2 C : C;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::LinearCentroid},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32,
+        dxbc::PSV::InterpolationMode::LinearNoperspective}});
+
+  // Expected layout:
+  // reg0: A.xy | unused.zw
+  // reg1: B.xy | unused.zw
+  // reg2: C.xy | unused.zw
+  expectPacking(
+      PackingMethod::PrefixStable, Config, /*ExpectedRows=*/3,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/2, /*Col=*/0}});
+}
+
+//===----------------------------------------------------------------------===//
+// Prefix-stable component ordering tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSystemValueOrdering) {
+  // System values may be co-packed to the right of arbitrary values, and a
+  // system generated value may be co-packed to the right of both.
+
+  // struct PSIn {
+  //   uint A             : A;
+  //   float Position      : SV_Position;
+  //   bool IsFrontFace   : SV_IsFrontFace;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Pixel, IOType::In,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Constant},
+       {dxbc::PSV::SemanticKind::Position, /*Rows=*/1,
+        /*Cols=*/1, dxil::ElementType::F32,
+        dxbc::PSV::InterpolationMode::Constant},
+       {dxbc::PSV::SemanticKind::IsFrontFace, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::I1, dxbc::PSV::InterpolationMode::Constant}});
+
+  // Expected layout:
+  // reg0: A.x | Position.y | IsFrontFace.z | unused.w
+  expectPacking(
+      PackingMethod::PrefixStable, Config, /*ExpectedRows=*/1,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/1}, {/*Row=*/0, /*Col=*/2}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableArbitraryNotRightOfSV) {
+  // Arbitrary values may never be placed to the right of a system value in the
+  // same register, so B cannot co-pack with Position even though there is
+  // space for it.
+
+  // struct VSOut {
+  //   float2 Position : SV_Position;
+  //   float2 A        : A;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Position, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: Position.xy | unused.zw
+  // reg1: A.xy        | unused.zw
+  expectPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
+                {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSGVIsRightmost) {
+  // Nothing may be placed to the right of a system generated value, so both A
+  // and Position are pushed into the next register.
+
+  // struct PSIn {
+  //   bool IsFrontFace : SV_IsFrontFace;
+  //   uint A           : A;
+  //   float Position    : SV_Position;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Pixel, IOType::In,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::IsFrontFace, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::I1, dxbc::PSV::InterpolationMode::Constant},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Constant},
+       {dxbc::PSV::SemanticKind::Position, /*Rows=*/1,
+        /*Cols=*/1, dxil::ElementType::F32,
+        dxbc::PSV::InterpolationMode::Constant}});
+
+  // Expected layout:
+  // reg0: IsFrontFace.x | unused.yzw
+  // reg1: A.x | Position.y | unused.zw
+  expectPacking(
+      PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/1, /*Col=*/1}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+       PrefixStableRejectsOverflowFromComponentOrdering) {
+  // Nothing may be placed to the right of a system generated value, so
+  // declaring one first leaves the rest of its register unusable by the
+  // arbitrary values that follow, and they no longer fit in 32 rows. Every
+  // element here shares an interpolation mode and a data width, so component
+  // ordering is the only reason the signature overflows.
+  //
+  // Note that the optimal algorithm packs the arbitrary values into reg0 to
+  // reg31 first and backfills IsFrontFace into reg0.w, so the very same
+  // signature does fit when it is packed optimally.
+
+  // struct PSIn {
+  //   nointerpolation bool IsFrontFace : SV_IsFrontFace;
+  //   nointerpolation int3 A0          : A0;
+  //   ...
+  //   nointerpolation int3 A31         : A31;
+  // };
+  TestConfig Config(Triple::EnvironmentType::Pixel, IOType::In,
+                    /*UseNative16BitTypes=*/false,
+                    {{dxbc::PSV::SemanticKind::IsFrontFace, /*Rows=*/1,
+                      /*Cols=*/1, dxil::ElementType::I1,
+                      dxbc::PSV::InterpolationMode::Constant}});
+  for (unsigned I = 0; I != MaxSignatureRows; ++I)
+    Config.Elements.push_back({dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1,
+                               /*Cols=*/3, dxil::ElementType::I32,
+                               dxbc::PSV::InterpolationMode::Constant});
+  // The last element is the one that no longer fits.
+  expectPackingError(PackingMethod::PrefixStable, Config,
+                     SignaturePackingError::SignatureOverflow,
+                     /*ExpectedElementIndex=*/MaxSignatureRows);
+}
+
+//===----------------------------------------------------------------------===//
+// Prefix-stable dynamic indexing tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableIndexedRanges) {
+  // An element with multiple rows occupies the same columns of a contiguous
+  // range of rows, and other elements may be co-packed into the columns those
+  // rows have left. A system value cannot be placed in a dynamically indexable
+  // row, so Position starts a new register.
+
+  // struct VSOut {
+  //   float2 A[2]    : A;
+  //   float B[2]     : B;
+  //   float C        : C;
+  //   float Position : SV_Position;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/2, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/2, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Position, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: A[0].xy | B[0].z | C.w
+  // reg1: A[1].xy | B[1].z | unused.w
+  // reg2: Position.x | unused.yzw
+  expectPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/3,
+                {{/*Row=*/0, /*Col=*/0},
+                 {/*Row=*/0, /*Col=*/2},
+                 {/*Row=*/0, /*Col=*/3},
+                 {/*Row=*/2, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableIndexedAfterSystemValue) {
+  // A multi-row element is dynamically indexable, so it may not share any of
+  // its rows with a system value, and it requires contiguous rows.
+
+  // struct VSOut {
+  //   float Position : SV_Position;
+  //   float3 A[2]    : A;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Position, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/2, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: Position.x | unused.yzw
+  // reg1: A[0].xyz   | unused.w
+  // reg2: A[1].xyz   | unused.w
+  expectPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/3,
+                {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+}
+
+//===----------------------------------------------------------------------===//
+// Prefix-stable tessellation factor tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableTessFactors) {
+  // Indexed tess factors are reserved in the last column of their rows so that
+  // arbitrary values can still be co-packed into the same rows.
+
+  // struct PatchConstants {
+  //   float TessFactor[2] : SV_TessFactor;
+  //   float3 Data[2]      : DATA;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Hull, IOType::PatchConstantOrPrimitive,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::TessFactor, /*Rows=*/2, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/2, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined}});
+
+  // Expected layout:
+  // reg0: Data[0].xyz | TessFactor[0].w
+  // reg1: Data[1].xyz | TessFactor[1].w
+  expectPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
+                {{/*Row=*/0, /*Col=*/3}, {/*Row=*/0, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSingleRowTessFactor) {
+  // A single row tess factor is not dynamically indexable and is packed like
+  // any other system value, rather than being reserved in the last column.
+
+  // struct PatchConstants {
+  //   float TessFactor : SV_TessFactor;
+  //   float3 Data      : DATA;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Hull, IOType::PatchConstantOrPrimitive,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::TessFactor, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined}});
+
+  // Expected layout:
+  // reg0: TessFactor.x | unused.yzw
+  // reg1: Data.xyz     | unused.w
+  expectPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
+                {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+       PrefixStableIndexedTessFactorAfterIndexedElement) {
+  // An indexed tess factor may only be placed in rows whose indexed range is
+  // contained by its own, so it cannot be packed into the rows of the wider
+  // indexed range of Data.
+
+  // struct PatchConstants {
+  //   float3 Data[3]      : DATA;
+  //   float TessFactor[2] : SV_TessFactor;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Hull, IOType::PatchConstantOrPrimitive,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/3, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined},
+       {dxbc::PSV::SemanticKind::TessFactor, /*Rows=*/2, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined}});
+
+  // Expected layout:
+  // reg0: Data[0].xyz | unused.w
+  // reg1: Data[1].xyz | unused.w
+  // reg2: Data[2].xyz | unused.w
+  // reg3: unused.xyz  | TessFactor[0].w
+  // reg4: unused.xyz  | TessFactor[1].w
+  expectPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/5,
+                {{/*Row=*/0, /*Col=*/0}, {/*Row=*/3, /*Col=*/3}});
+}
+
+//===----------------------------------------------------------------------===//
+// Prefix-stable clip/cull tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCull) {
+  // struct VSOut {
+  //   float3 First         : First;
+  //   float  Clip0         : SV_ClipDistance0;
+  //   float3 Cull1         : SV_CullDistance1;
+  //   float  Cull0         : SV_CullDistance0;
+  //   float2 Clip1         : SV_ClipDistance1;
+  //   float  WithFirst     : WithFirst;
+  //   float  AfterClipCull : AfterClipCull;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: First.xyz       | WithFirst.w
+  // reg1: Clip0.x         | Cull1.yzw
+  // reg2: Cull0.x         | Clip1.yz | unused.w
+  // reg3: AfterClipCull.x | unused.yzw
+  expectPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/4,
+                {{/*Row=*/0, /*Col=*/0},
+                 {/*Row=*/1, /*Col=*/0},
+                 {/*Row=*/1, /*Col=*/1},
+                 {/*Row=*/2, /*Col=*/0},
+                 {/*Row=*/2, /*Col=*/1},
+                 {/*Row=*/0, /*Col=*/3},
+                 {/*Row=*/3, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableIndexedClipCull) {
+  // struct VSOut {
+  //   float3 First         : First;
+  //   float  Clip0         : SV_ClipDistance0;
+  //   float2 Cull1[2]      : SV_CullDistance1;
+  //   float  Clip1         : SV_ClipDistance1;
+  //   float  WithFirst     : WithFirst;
+  //   float  AfterClipCull : AfterClipCull;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/2, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: First.xyz       | WithFirst.w
+  // reg1: Clip0.x         | Cull1[0].yz | Clip1.w
+  // reg2: unused.x        | Cull1[1].yz | unused.w
+  // reg3: AfterClipCull.x | unused.yzw
+  expectPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/4,
+                {{/*Row=*/0, /*Col=*/0},
+                 {/*Row=*/1, /*Col=*/0},
+                 {/*Row=*/1, /*Col=*/1},
+                 {/*Row=*/1, /*Col=*/3},
+                 {/*Row=*/0, /*Col=*/3},
+                 {/*Row=*/3, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableMultipleIndexedClipCull) {
+  // struct VSOut {
+  //   float3 First         : First;
+  //   float  Clip0         : SV_ClipDistance0;
+  //   float2 Cull1[2]      : SV_CullDistance1;
+  //   float  Clip1[2]      : SV_ClipDistance1;
+  //   float  WithFirst     : WithFirst;
+  //   float  AfterClipCull : AfterClipCull;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/2, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/2, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: First.xyz       | WithFirst.w
+  // reg1: Clip0.x         | Cull1[0].yz | Clip1[0].w
+  // reg2: unused.x        | Cull1[1].yz | Clip1[1].w
+  // reg3: AfterClipCull.x | unused.yzw
+  expectPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/4,
+                {{/*Row=*/0, /*Col=*/0},
+                 {/*Row=*/1, /*Col=*/0},
+                 {/*Row=*/1, /*Col=*/1},
+                 {/*Row=*/1, /*Col=*/3},
+                 {/*Row=*/0, /*Col=*/3},
+                 {/*Row=*/3, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCullFillsTwoRows) {
+  // Clip and cull distances may use a combined maximum of eight components
+  // spread over two registers.
+
+  // struct VSOut {
+  //   float4 Clip0 : SV_ClipDistance0;
+  //   float4 Cull0 : SV_CullDistance0;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/4,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/4,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: Clip0.xyzw
+  // reg1: Cull0.xyzw
+  expectPacking(PackingMethod::PrefixStable, Config,
+                /*ExpectedRows=*/MaxClipCullRows,
+                {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSeparatesClipCullRows) {
+  // Non-indexed clip and cull distance registers do not need to be adjacent.
+  // Elements declared between them may separate their reserved registers while
+  // the clip/cull elements continue to count toward the common two-register
+  // limit.
+
+  // struct VSOut {
+  //   float3 Clip0 : SV_ClipDistance0;
+  //   float4 A[30] : A;
+  //   float3 Cull0 : SV_CullDistance0;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/30, /*Cols=*/4,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0:     Clip0.xyz | unused.w
+  // reg1-30:  A[0-29].xyzw
+  // reg31:    Cull0.xyz | unused.w
+  expectPacking(PackingMethod::PrefixStable, Config,
+                /*ExpectedRows=*/MaxSignatureRows,
+                {{/*Row=*/0, /*Col=*/0},
+                 {/*Row=*/1, /*Col=*/0},
+                 {/*Row=*/31, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCullAsArbitrary) {
+  // Clip and cull distances are arbitrary values in a patch constant
+  // signature, so the two register limit does not apply to them.
+
+  // struct PatchConstants {
+  //   float3 Clip0 : SV_ClipDistance0;
+  //   float3 Clip1 : SV_ClipDistance1;
+  //   float3 Cull0 : SV_CullDistance0;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Hull, IOType::PatchConstantOrPrimitive,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined},
+       {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined},
+       {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined}});
+
+  // Expected layout:
+  // reg0: Clip0.xyz | unused.w
+  // reg1: Clip1.xyz | unused.w
+  // reg2: Cull0.xyz | unused.w
+  expectPacking(
+      PackingMethod::PrefixStable, Config, /*ExpectedRows=*/3,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/2, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCullWhenAppended) {
+  // Clip and cull distances reserve whole registers ahead of the elements that
+  // follow them, so appending elements does not move them either, not even
+  // when the appended elements are clip/cull values themselves.
+
+  // struct Prefix {
+  //   float3 First    : First;
+  //   float  Clip0    : SV_ClipDistance0;
+  //   float2 Cull1[2] : SV_CullDistance1;
+  // };
+  TestConfig PrefixConfig(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      /*UseNative16BitTypes=*/false,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/2, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: First.xyz | unused.w
+  // reg1: Clip0.x   | Cull1[0].yz | unused.w
+  // reg2: unused.x  | Cull1[1].yz | unused.w
+  expectPacking(
+      PackingMethod::PrefixStable, PrefixConfig, /*ExpectedRows=*/3,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/1, /*Col=*/1}});
+
+  TestConfig ExtendedConfig = PrefixConfig;
+  ExtendedConfig.Elements.push_back(
+      {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/1,
+       dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear});
+  ExtendedConfig.Elements.push_back(
+      {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+       dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear});
+  ExtendedConfig.Elements.push_back(
+      {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+       dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear});
+
+  // Expected layout:
+  // reg0: First.xyz       | WithFirst.w
+  // reg1: Clip0.x         | Cull1[0].yz | Clip1.w
+  // reg2: unused.x        | Cull1[1].yz | unused.w
+  // reg3: AfterClipCull.x | unused.yzw
+  expectPacking(PackingMethod::PrefixStable, ExtendedConfig, /*ExpectedRows=*/4,
+                {{/*Row=*/0, /*Col=*/0},
+                 {/*Row=*/1, /*Col=*/0},
+                 {/*Row=*/1, /*Col=*/1},
+                 {/*Row=*/1, /*Col=*/3},
+                 {/*Row=*/0, /*Col=*/3},
+                 {/*Row=*/3, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableRejectsClipCullOverflow) {
+  // Clip and cull distances may use at most eight components, shared between
+  // them, so nine components cannot be packed.
+
----------------
hekota wrote:

Could you please add an HLSL example here and in `PrefixStableRejectsUnpackableClipCull`?

https://github.com/llvm/llvm-project/pull/218062


More information about the llvm-branch-commits mailing list