diff options
| -rw-r--r-- | docs/command-line-slangc-reference.md | 5 | ||||
| -rw-r--r-- | include/slang.h | 3 | ||||
| -rw-r--r-- | source/slang/slang-check-decl.cpp | 49 | ||||
| -rw-r--r-- | source/slang/slang-options.cpp | 8 | ||||
| -rw-r--r-- | tests/language-feature/bitfield/msvc-mixed.slang | 40 | ||||
| -rw-r--r-- | tests/language-feature/bitfield/msvc-repr-mixed.slang | 38 | ||||
| -rw-r--r-- | tests/language-feature/bitfield/msvc-repr.slang | 36 | ||||
| -rw-r--r-- | tests/language-feature/bitfield/msvc-simple.slang | 31 | ||||
| -rw-r--r-- | tests/language-feature/bitfield/msvc-sizeof.slang | 43 | ||||
| -rw-r--r-- | tests/language-feature/bitfield/msvc-zero-width.slang | 30 | ||||
| -rw-r--r-- | tests/language-feature/bitfield/repr-mixed.slang | 36 | ||||
| -rw-r--r-- | tests/language-feature/bitfield/repr.slang | 36 |
12 files changed, 348 insertions, 7 deletions
diff --git a/docs/command-line-slangc-reference.md b/docs/command-line-slangc-reference.md index 8cba2107f..650ab867a 100644 --- a/docs/command-line-slangc-reference.md +++ b/docs/command-line-slangc-reference.md @@ -361,6 +361,11 @@ Include additional type conformance during linking for dynamic dispatch. Emit reflection data in JSON format to a file. +<a id="msvc-style-bitfield-packing"></a> +### -msvc-style-bitfield-packing +Pack bitfields according to MSVC rules (msb first, new field when underlying type size changes) rather than gcc-style (lsb first) + + <a id="Target"></a> ## Target diff --git a/include/slang.h b/include/slang.h index 516af9603..65449a1ff 100644 --- a/include/slang.h +++ b/include/slang.h @@ -1044,6 +1044,9 @@ typedef uint32_t SlangSizeT; DenormalModeFp32, DenormalModeFp64, + // Bitfield options + UseMSVCStyleBitfieldPacking, // bool + CountOf, }; diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 02f404d88..98cdb9e61 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -14020,6 +14020,10 @@ void SemanticsDeclAttributesVisitor::visitStructDecl(StructDecl* structDecl) checkRayPayloadStructFields(structDecl); } + // Check if we should use MSVC-style bitfield packing + const bool useMSVCPacking = + getOptionSet().getBoolOption(CompilerOptionName::UseMSVCStyleBitfieldPacking); + int backingWidth = 0; [[maybe_unused]] int totalWidth = 0; struct BitFieldInfo @@ -14055,15 +14059,32 @@ void SemanticsDeclAttributesVisitor::visitStructDecl(StructDecl* structDecl) backingMember->parentDecl = structDecl; const auto backingMemberDeclRef = DeclRef<VarDecl>(backingMember->getDefaultDeclRef()); - int bottomOfMember = 0; - for (const auto m : groupInfo) + if (useMSVCPacking) { - SLANG_ASSERT(bottomOfMember <= backingWidth); + // MSVC packs from MSB to LSB + int currentBitPosition = backingWidth; + for (const auto& m : groupInfo) + { + currentBitPosition -= m.bitWidth; + SLANG_ASSERT(currentBitPosition >= 0); - m.bitFieldModifier->backingDeclRef = backingMemberDeclRef; - m.bitFieldModifier->offset = bottomOfMember; + m.bitFieldModifier->backingDeclRef = backingMemberDeclRef; + m.bitFieldModifier->offset = currentBitPosition; + } + } + else + { + // GCC/Clang pack from LSB to MSB + int bottomOfMember = 0; + for (const auto& m : groupInfo) + { + SLANG_ASSERT(bottomOfMember <= backingWidth); - bottomOfMember += m.bitWidth; + m.bitFieldModifier->backingDeclRef = backingMemberDeclRef; + m.bitFieldModifier->offset = bottomOfMember; + + bottomOfMember += m.bitWidth; + } } const auto backingMemberIndex = groupInfo[0].memberIndex; @@ -14080,6 +14101,9 @@ void SemanticsDeclAttributesVisitor::visitStructDecl(StructDecl* structDecl) totalWidth = 0; groupInfo.clear(); }; + + int previousFieldTypeWidth = 0; // Track the type width of the previous bitfield for MSVC mode + for (; memberIndex < structDecl->getDirectMemberDeclCount(); ++memberIndex) { const auto& m = structDecl->getDirectMemberDecl(memberIndex); @@ -14141,7 +14165,17 @@ void SemanticsDeclAttributesVisitor::visitStructDecl(StructDecl* structDecl) // If there's a 0 width type, dispatch the current group if (thisFieldWidth == 0) + { dispatchSomeBitPackedMembers(); + previousFieldTypeWidth = 0; + } + + // MSVC-specific behavior: start a new backing field if the type size changes + if (useMSVCPacking && groupInfo.getCount() > 0 && + thisFieldTypeWidth != previousFieldTypeWidth) + { + dispatchSomeBitPackedMembers(); + } // If this member wouldn't fit into the current group, dispatch // everything so far; @@ -14154,6 +14188,9 @@ void SemanticsDeclAttributesVisitor::visitStructDecl(StructDecl* structDecl) // Grow the total width totalWidth += int(thisFieldWidth); groupInfo.add({memberIndex, int(thisFieldWidth), t, bfm}); + + // Track the type width for MSVC mode + previousFieldTypeWidth = thisFieldTypeWidth; } // If the struct ended with a bitpacked member, then make sure we don't forget the last // group diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index 0ada33697..c3f2ad055 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -566,7 +566,12 @@ void initCommandOptions(CommandOptions& options) {OptionKind::EmitReflectionJSON, "-reflection-json", "-reflection-json <path>", - "Emit reflection data in JSON format to a file."}}; + "Emit reflection data in JSON format to a file."}, + {OptionKind::UseMSVCStyleBitfieldPacking, + "-msvc-style-bitfield-packing", + nullptr, + "Pack bitfields according to MSVC rules (msb first, new field when underlying type size " + "changes) rather than gcc-style (lsb first)"}}; _addOptions(makeConstArrayView(generalOpts), options); @@ -2264,6 +2269,7 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) case OptionKind::LoopInversion: case OptionKind::UnscopedEnum: case OptionKind::PreserveParameters: + case OptionKind::UseMSVCStyleBitfieldPacking: linkage->m_optionSet.set(optionKind, true); break; case OptionKind::MatrixLayoutRow: diff --git a/tests/language-feature/bitfield/msvc-mixed.slang b/tests/language-feature/bitfield/msvc-mixed.slang new file mode 100644 index 000000000..d21bdad37 --- /dev/null +++ b/tests/language-feature/bitfield/msvc-mixed.slang @@ -0,0 +1,40 @@ +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu -output-using-type -compile-arg -msvc-style-bitfield-packing + +// MSVC packing with mixed type sizes +// CHECK: 15 +// CHECK-NEXT: 255 +// CHECK-NEXT: 4095 +// CHECK-NEXT: 8 + +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer<uint> outputBuffer; + +struct MixedTypes { + uint8_t a : 4; // Uses uint8_t backing, bits 7-4 + uint8_t b : 4; // Same backing, bits 3-0 + uint16_t c : 8; // New uint16_t backing due to type change, bits 15-8 + uint16_t d : 8; // Same backing, bits 7-0 + uint32_t e : 12; // New uint32_t backing due to type change, bits 31-20 + uint32_t f : 12; // Same backing, bits 19-8 + uint32_t g : 8; // Same backing, bits 7-0 +}; + +[numthreads(1, 1, 1)] +void computeMain() +{ + MixedTypes m; + m.a = 15; // 0xF + m.b = 15; // 0xF + m.c = 255; // 0xFF + m.d = 255; // 0xFF + m.e = 4095; // 0xFFF + m.f = 4095; // 0xFFF + m.g = 255; // 0xFF + + outputBuffer[0] = m.a; + outputBuffer[1] = m.c; + outputBuffer[2] = m.e; + // Verify struct size includes all three backing fields + outputBuffer[3] = sizeof(MixedTypes); +} + diff --git a/tests/language-feature/bitfield/msvc-repr-mixed.slang b/tests/language-feature/bitfield/msvc-repr-mixed.slang new file mode 100644 index 000000000..47f03ad1d --- /dev/null +++ b/tests/language-feature/bitfield/msvc-repr-mixed.slang @@ -0,0 +1,38 @@ +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu -compile-arg -msvc-style-bitfield-packing + +// MSVC creates separate backing fields for different type sizes +// struct MixedSizes { uint8_t a:4; uint8_t b:4; uint16_t c:8; uint16_t d:8; } +// First backing (uint8_t): a=0xA (bits 4-7), b=0xB (bits 0-3) => 0xAB +// Second backing (uint16_t): c=0xCD (bits 8-15), d=0xEF (bits 0-7) => 0xCDEF +// Memory layout: [0xAB, padding, 0xCDEF] or [0xAB, padding, 0xEF, 0xCD] depending on endianness + +// CHECK: AB +// CHECK-NEXT: CDEF + +//TEST_INPUT:ubuffer(data=[0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer<uint> outputBuffer; + +struct MixedSizes { + uint8_t a : 4; // First backing field (uint8_t) + uint8_t b : 4; // Same backing field + uint16_t c : 8; // New backing field (uint16_t) due to type change + uint16_t d : 8; // Same backing field +}; + +[numthreads(1, 1, 1)] +void computeMain() +{ + MixedSizes m; + m.a = 0xA; + m.b = 0xB; + m.c = 0xCD; + m.d = 0xEF; + + // Read the two backing fields separately + uint8_t* p8 = (uint8_t*)&m; + uint16_t* p16 = (uint16_t*)((uint8_t*)&m + 2); // Skip uint8_t + padding + + outputBuffer[0] = uint(*p8); + outputBuffer[1] = uint(*p16); +} + diff --git a/tests/language-feature/bitfield/msvc-repr.slang b/tests/language-feature/bitfield/msvc-repr.slang new file mode 100644 index 000000000..4edd47556 --- /dev/null +++ b/tests/language-feature/bitfield/msvc-repr.slang @@ -0,0 +1,36 @@ +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu -compile-arg -msvc-style-bitfield-packing + +// MSVC style packs from MSB to LSB +// struct S { uint a:4; uint b:8; uint c:4; uint d:16; } +// Memory layout (32-bit): +// bits 28-31: a (0x5) +// bits 20-27: b (0xAB) +// bits 16-19: c (0xC) +// bits 0-15: d (0xDEF0) +// Expected: 0x5ABCDEF0 + +// CHECK: 5ABCDEF0 + +//TEST_INPUT:ubuffer(data=[0], stride=4):out,name=outputBuffer +RWStructuredBuffer<uint> outputBuffer; + +struct S { + uint a : 4; // bits 28-31 (MSB side) + uint b : 8; // bits 20-27 + uint c : 4; // bits 16-19 + uint d : 16; // bits 0-15 +}; + +[numthreads(1, 1, 1)] +void computeMain() +{ + S s; + s.a = 0x5; + s.b = 0xAB; + s.c = 0xC; + s.d = 0xDEF0; + + // Write the struct to memory and read it back as uint + outputBuffer[0] = *((uint*)&s); +} + diff --git a/tests/language-feature/bitfield/msvc-simple.slang b/tests/language-feature/bitfield/msvc-simple.slang new file mode 100644 index 000000000..4651626b8 --- /dev/null +++ b/tests/language-feature/bitfield/msvc-simple.slang @@ -0,0 +1,31 @@ +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu -output-using-type -compile-arg -msvc-style-bitfield-packing + +// With MSVC packing, bitfields are packed from MSB to LSB +// and new backing fields start when type sizes change +// CHECK: 123 +// CHECK-NEXT: 4567 +// CHECK-NEXT: 0 +// CHECK-NEXT: 0 + +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer<uint> outputBuffer; + +struct S { + int foo : 8; // Packed in bits 31-24 + uint bar : 24; // Packed in bits 23-0 +}; + +[numthreads(1, 1, 1)] +void computeMain() +{ + S s; + s.foo = 123; + s.bar = 4567; + outputBuffer[0] = s.foo; + outputBuffer[1] = s.bar; + s.foo = 0; + s.bar = 0; + outputBuffer[2] = s.foo; + outputBuffer[3] = s.bar; +} + diff --git a/tests/language-feature/bitfield/msvc-sizeof.slang b/tests/language-feature/bitfield/msvc-sizeof.slang new file mode 100644 index 000000000..9d4ccdcfc --- /dev/null +++ b/tests/language-feature/bitfield/msvc-sizeof.slang @@ -0,0 +1,43 @@ +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu -output-using-type -compile-arg -msvc-style-bitfield-packing + +// MSVC starts new backing fields when type sizes change +// CHECK: 4 +// CHECK-NEXT: 12 +// CHECK-NEXT: 8 +// CHECK-NEXT: 16 + +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer<uint> outputBuffer; + +struct S { + int foo : 8; + int bar : 24; +}; + +// MSVC will use separate backing fields for different sized types +struct T { + int64_t foo : 33; // Uses int64_t backing + int bar : 24; // Uses separate int backing due to type size change +}; + +// This still takes two ints to store all the bits +struct P { + int foo : 24; + int bar : 24; +}; + +// MSVC will use separate backing fields due to type size difference +struct Q { + int8_t foo : 1; // Uses int8_t backing + int64_t bar : 63; // Uses separate int64_t backing +}; + +[numthreads(1, 1, 1)] +void computeMain() +{ + outputBuffer[0] = sizeof(S); + outputBuffer[1] = sizeof(T); + outputBuffer[2] = sizeof(P); + outputBuffer[3] = sizeof(Q); +} + diff --git a/tests/language-feature/bitfield/msvc-zero-width.slang b/tests/language-feature/bitfield/msvc-zero-width.slang new file mode 100644 index 000000000..3363ef066 --- /dev/null +++ b/tests/language-feature/bitfield/msvc-zero-width.slang @@ -0,0 +1,30 @@ +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu -output-using-type -compile-arg -msvc-style-bitfield-packing + +// MSVC packing with zero-width bitfields +// CHECK: 123 +// CHECK-NEXT: 4567 +// CHECK-NEXT: 1 +// CHECK-NEXT: 0 + +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer<uint> outputBuffer; + +struct S { + int foo : 8; // First backing field + int breaker : 0; // Forces new backing field + int bar : 24; // Second backing field +}; + +[numthreads(1, 1, 1)] +void computeMain() +{ + S s; + s.foo = 123; + s.bar = 4567; + s.breaker = 9999; // Zero-width field, assignment has no effect + outputBuffer[0] = s.foo; + outputBuffer[1] = s.bar; + outputBuffer[2] = sizeof(S) == sizeof(int) * 2; // Two backing fields + outputBuffer[3] = s.breaker; // Always returns 0 +} + diff --git a/tests/language-feature/bitfield/repr-mixed.slang b/tests/language-feature/bitfield/repr-mixed.slang new file mode 100644 index 000000000..44a3f69d9 --- /dev/null +++ b/tests/language-feature/bitfield/repr-mixed.slang @@ -0,0 +1,36 @@ +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu + +// Default packing keeps all fields in one backing field when they fit +// struct MixedSizes { uint8_t a:4; uint8_t b:4; uint16_t c:8; uint16_t d:8; } +// All packed in single uint32_t backing: +// bits 0-3: a (0xA) +// bits 4-7: b (0xB) +// bits 8-15: c (0xCD) +// bits 16-23: d (0xEF) +// Expected: 0x00EFCDBA (top 8 bits unused) + +// CHECK: EFCDBA + +//TEST_INPUT:ubuffer(data=[0], stride=4):out,name=outputBuffer +RWStructuredBuffer<uint> outputBuffer; + +struct MixedSizes { + uint8_t a : 4; // bits 0-3 + uint8_t b : 4; // bits 4-7 + uint16_t c : 8; // bits 8-15 + uint16_t d : 8; // bits 16-23 +}; + +[numthreads(1, 1, 1)] +void computeMain() +{ + MixedSizes m; + m.a = 0xA; + m.b = 0xB; + m.c = 0xCD; + m.d = 0xEF; + + // With default packing, all fields are in one backing field + outputBuffer[0] = *((uint*)&m) & 0xFFFFFF; // Mask to 24 bits +} + diff --git a/tests/language-feature/bitfield/repr.slang b/tests/language-feature/bitfield/repr.slang new file mode 100644 index 000000000..a199b23ff --- /dev/null +++ b/tests/language-feature/bitfield/repr.slang @@ -0,0 +1,36 @@ +//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu + +// Default GCC/Clang style packs from LSB to MSB +// struct S { uint a:4; uint b:8; uint c:4; uint d:16; } +// Memory layout (32-bit): +// bits 0-3: a (0x5) +// bits 4-11: b (0xAB) +// bits 12-15: c (0xC) +// bits 16-31: d (0xDEF0) +// Expected: 0xDEF0CAB5 + +// CHECK: DEF0CAB5 + +//TEST_INPUT:ubuffer(data=[0], stride=4):out,name=outputBuffer +RWStructuredBuffer<uint> outputBuffer; + +struct S { + uint a : 4; // bits 0-3 + uint b : 8; // bits 4-11 + uint c : 4; // bits 12-15 + uint d : 16; // bits 16-31 +}; + +[numthreads(1, 1, 1)] +void computeMain() +{ + S s; + s.a = 0x5; + s.b = 0xAB; + s.c = 0xC; + s.d = 0xDEF0; + + // Write the struct to memory and read it back as uint + outputBuffer[0] = *((uint*)&s); +} + |
