From 8d8e5f54f9d86efc93c12d06cd5a1e0efb39ea93 Mon Sep 17 00:00:00 2001 From: Diego Novillo Date: Wed, 29 Jul 2026 17:53:26 -0400 Subject: [PATCH] [SPIR-V] Preserve members after merged bitfields in flat conversions A same-type flat conversion must decompose to one scalar per SPIR-V field so that reconstruction consumes the same sequence. --- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 28 +++++++++++++------ tools/clang/lib/SPIRV/SpirvEmitter.h | 6 ++-- .../type.constant-buffer.fn-var.bitfield.hlsl | 23 +++++++++++++++ 3 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/type.constant-buffer.fn-var.bitfield.hlsl diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index d940b460ed..09047e1cf9 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -3951,8 +3951,16 @@ SpirvEmitter::processFlatConversion(const QualType type, initInstr->setAstResultType(astContext.UnsignedLongLongTy); } - // Decompose `initInstr`. - std::vector flatValues = decomposeToScalars(initInstr); + QualType sourceType = initInstr->getAstResultType(); + if (hlsl::IsHLSLResourceType(sourceType)) + sourceType = hlsl::GetHLSLResourceResultType(sourceType); + + // Converting the same AST type between layouts preserves its physical field + // sequence. Shape-changing flat conversions operate on AST fields. + const bool includeMergedBitfields = + !astContext.hasSameUnqualifiedType(type, sourceType); + std::vector flatValues = + decomposeToScalars(initInstr, includeMergedBitfields); if (flatValues.size() == 1) { return splatScalarToGenerate(type, flatValues[0], SpirvLayoutRule::Void); @@ -16783,7 +16791,8 @@ SpirvEmitter::doUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *expr) { } std::vector -SpirvEmitter::decomposeToScalars(SpirvInstruction *inst) { +SpirvEmitter::decomposeToScalars(SpirvInstruction *inst, + bool includeMergedBitfields) { QualType elementType; uint32_t elementCount = 0; uint32_t numOfRows = 0; @@ -16828,7 +16837,8 @@ SpirvEmitter::decomposeToScalars(SpirvInstruction *inst) { auto *element = spvBuilder.createCompositeExtract( elementType, inst, {i}, inst->getSourceLocation()); element->setLayoutRule(inst->getLayoutRule()); - auto decomposedElement = decomposeToScalars(element); + auto decomposedElement = + decomposeToScalars(element, includeMergedBitfields); // See how we can improve the performance by avoiding this copy. result.insert(result.end(), decomposedElement.begin(), @@ -16848,20 +16858,22 @@ SpirvEmitter::decomposeToScalars(SpirvInstruction *inst) { forEachSpirvField( recordType, dyn_cast(type), - [this, inst, &result](size_t spirvFieldIndex, const QualType &fieldType, - const StructType::FieldInfo &fieldInfo) { + [this, inst, &result, includeMergedBitfields]( + size_t spirvFieldIndex, const QualType &fieldType, + const StructType::FieldInfo &fieldInfo) { auto *field = spvBuilder.createCompositeExtract( fieldType, inst, {fieldInfo.fieldIndex}, inst->getSourceLocation()); field->setLayoutRule(inst->getLayoutRule()); - auto decomposedField = decomposeToScalars(field); + auto decomposedField = + decomposeToScalars(field, includeMergedBitfields); // See how we can improve the performance by avoiding this copy. result.insert(result.end(), decomposedField.begin(), decomposedField.end()); return true; }, - true); + includeMergedBitfields); return result; } diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index 10cc31023c..0510d88a72 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -1404,8 +1404,10 @@ class SpirvEmitter : public ASTConsumer { /// Returns a vector of SpirvInstruction that is the decompostion of `inst` /// into scalars. This is recursive. For example, a struct of a 4 element - /// vector will return 4 scalars. - std::vector decomposeToScalars(SpirvInstruction *inst); + /// vector will return 4 scalars. If `includeMergedBitfields` is false, + /// fields that share the same SPIR-V storage field produce one scalar. + std::vector + decomposeToScalars(SpirvInstruction *inst, bool includeMergedBitfields); /// Returns a spirv instruction with the value of the given type and layout /// rule that is obtained by assigning each scalar in `type` to corresponding diff --git a/tools/clang/test/CodeGenSPIRV/type.constant-buffer.fn-var.bitfield.hlsl b/tools/clang/test/CodeGenSPIRV/type.constant-buffer.fn-var.bitfield.hlsl new file mode 100644 index 0000000000..a8609034fd --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/type.constant-buffer.fn-var.bitfield.hlsl @@ -0,0 +1,23 @@ +// RUN: %dxc -T ps_6_0 -E main -HV 2021 -fcgl %s -spirv | FileCheck %s + +struct MyData { + uint a : 16; + uint b : 16; + uint c; +}; + +ConstantBuffer input; + +uint main() : SV_Target { + // CHECK: [[SOURCE:%[0-9]+]] = OpLoad {{%[^ ]+}} %input + // CHECK-NEXT: [[BITFIELDS:%[0-9]+]] = OpCompositeExtract %uint [[SOURCE]] 0 + // CHECK-NEXT: [[C:%[0-9]+]] = OpCompositeExtract %uint [[SOURCE]] 1 + // CHECK-NEXT: [[VALUE:%[0-9]+]] = OpCompositeConstruct {{%[^ ]+}} [[BITFIELDS]] [[C]] + // CHECK-NEXT: OpStore %local [[VALUE]] + MyData local = input; + + // CHECK: [[C_PTR:%[0-9]+]] = OpAccessChain %_ptr_Function_uint %local %int_1 + // CHECK: [[C_VALUE:%[0-9]+]] = OpLoad %uint [[C_PTR]] + // CHECK: OpReturnValue [[C_VALUE]] + return local.c; +}