[SPIR-V] Fix struct size computation with bitfields (#6471)

The struct size & alignment computations were wrong when
bitfields were in use. This was due to a mismatch between size
computations at the AST level, and at the SPIR-V level.

This commit adds logic to correctly compute bitfields offsets on
an AST struct.

**note: there are 2 commits in this PR, one that moves code around with
no change, and one that adds the logic. To help you review.**

Fixes #5648

---------

Signed-off-by: Nathan Gauër <brioche@google.com>
This commit is contained in:
Nathan Gauër 2024-03-29 11:21:37 +01:00 коммит произвёл GitHub
Родитель 7e40ead3c7
Коммит bb9ec0fb56
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
4 изменённых файлов: 303 добавлений и 84 удалений

Просмотреть файл

@ -62,6 +62,123 @@ void AlignmentSizeCalculator::alignUsingHLSLRelaxedLayout(
}
}
std::pair<uint32_t, uint32_t> AlignmentSizeCalculator::getAlignmentAndSize(
QualType type, const RecordType *structType, SpirvLayoutRule rule,
llvm::Optional<bool> isRowMajor, uint32_t *stride) const {
bool hasBaseStructs = type->getAsCXXRecordDecl() &&
type->getAsCXXRecordDecl()->getNumBases() > 0;
// Special case for handling empty structs, whose size is 0 and has no
// requirement over alignment (thus 1).
if (structType->getDecl()->field_empty() && !hasBaseStructs)
return {1, 0};
uint32_t maxAlignment = 0;
uint32_t structSize = 0;
// If this struct is derived from some other structs, place an implicit
// field at the very beginning for the base struct.
if (const auto *cxxDecl = dyn_cast<CXXRecordDecl>(structType->getDecl())) {
for (const auto &base : cxxDecl->bases()) {
uint32_t memberAlignment = 0, memberSize = 0;
std::tie(memberAlignment, memberSize) =
getAlignmentAndSize(base.getType(), rule, isRowMajor, stride);
if (rule == SpirvLayoutRule::RelaxedGLSLStd140 ||
rule == SpirvLayoutRule::RelaxedGLSLStd430 ||
rule == SpirvLayoutRule::FxcCTBuffer) {
alignUsingHLSLRelaxedLayout(base.getType(), memberSize, memberAlignment,
&structSize);
} else {
structSize = roundToPow2(structSize, memberAlignment);
}
// The base alignment of the structure is N, where N is the largest
// base alignment value of any of its members...
maxAlignment = std::max(maxAlignment, memberAlignment);
structSize += memberSize;
}
}
const FieldDecl *lastField = nullptr;
uint32_t nextAvailableBitOffset = 0;
for (const FieldDecl *field : structType->getDecl()->fields()) {
uint32_t memberAlignment = 0, memberSize = 0;
std::tie(memberAlignment, memberSize) =
getAlignmentAndSize(field->getType(), rule, isRowMajor, stride);
uint32_t memberBitOffset = 0;
do {
if (!lastField)
break;
if (!field->isBitField() || !lastField->isBitField())
break;
// Not the same type as the previous bitfield. Ignoring.
if (lastField->getType() != field->getType())
break;
// Not enough room left to fit this bitfield. Starting a new slot.
if (nextAvailableBitOffset + field->getBitWidthValue(astContext) >
memberSize * 8)
break;
memberBitOffset = nextAvailableBitOffset;
memberSize = 0;
} while (0);
if (memberSize != 0) {
if (rule == SpirvLayoutRule::RelaxedGLSLStd140 ||
rule == SpirvLayoutRule::RelaxedGLSLStd430 ||
rule == SpirvLayoutRule::FxcCTBuffer) {
alignUsingHLSLRelaxedLayout(field->getType(), memberSize,
memberAlignment, &structSize);
} else {
structSize = roundToPow2(structSize, memberAlignment);
}
}
// Reset the current offset to the one specified in the source code
// if exists. It's debatable whether we should do sanity check here.
// If the developers want manually control the layout, we leave
// everything to them.
if (const auto *offsetAttr = field->getAttr<VKOffsetAttr>()) {
structSize = offsetAttr->getOffset();
}
// The base alignment of the structure is N, where N is the largest
// base alignment value of any of its members...
maxAlignment = std::max(maxAlignment, memberAlignment);
structSize += memberSize;
lastField = field;
nextAvailableBitOffset =
field->isBitField()
? memberBitOffset + field->getBitWidthValue(astContext)
: 0;
}
if (rule == SpirvLayoutRule::Scalar) {
// A structure has a scalar alignment equal to the largest scalar
// alignment of any of its members in VK_EXT_scalar_block_layout.
return {maxAlignment, structSize};
}
if (rule == SpirvLayoutRule::GLSLStd140 ||
rule == SpirvLayoutRule::RelaxedGLSLStd140 ||
rule == SpirvLayoutRule::FxcCTBuffer) {
// ... and rounded up to the base alignment of a vec4.
maxAlignment = roundToPow2(maxAlignment, kStd140Vec4Alignment);
}
if (rule != SpirvLayoutRule::FxcCTBuffer) {
// The base offset of the member following the sub-structure is rounded
// up to the next multiple of the base alignment of the structure.
structSize = roundToPow2(structSize, maxAlignment);
}
return {maxAlignment, structSize};
}
std::pair<uint32_t, uint32_t> AlignmentSizeCalculator::getAlignmentAndSize(
QualType type, SpirvLayoutRule rule, llvm::Optional<bool> isRowMajor,
uint32_t *stride) const {
@ -144,8 +261,7 @@ std::pair<uint32_t, uint32_t> AlignmentSizeCalculator::getAlignmentAndSize(
const auto desugaredType = desugarType(type, &isRowMajor);
if (desugaredType != type) {
auto result = getAlignmentAndSize(desugaredType, rule, isRowMajor, stride);
return result;
return getAlignmentAndSize(desugaredType, rule, isRowMajor, stride);
}
if (isEnumType(type))
@ -268,88 +384,7 @@ std::pair<uint32_t, uint32_t> AlignmentSizeCalculator::getAlignmentAndSize(
// Rule 9
if (const auto *structType = type->getAs<RecordType>()) {
bool hasBaseStructs = type->getAsCXXRecordDecl() &&
type->getAsCXXRecordDecl()->getNumBases() > 0;
// Special case for handling empty structs, whose size is 0 and has no
// requirement over alignment (thus 1).
if (structType->getDecl()->field_empty() && !hasBaseStructs)
return {1, 0};
uint32_t maxAlignment = 0;
uint32_t structSize = 0;
// If this struct is derived from some other structs, place an implicit
// field at the very beginning for the base struct.
if (const auto *cxxDecl = dyn_cast<CXXRecordDecl>(structType->getDecl())) {
for (const auto &base : cxxDecl->bases()) {
uint32_t memberAlignment = 0, memberSize = 0;
std::tie(memberAlignment, memberSize) =
getAlignmentAndSize(base.getType(), rule, isRowMajor, stride);
if (rule == SpirvLayoutRule::RelaxedGLSLStd140 ||
rule == SpirvLayoutRule::RelaxedGLSLStd430 ||
rule == SpirvLayoutRule::FxcCTBuffer) {
alignUsingHLSLRelaxedLayout(base.getType(), memberSize,
memberAlignment, &structSize);
} else {
structSize = roundToPow2(structSize, memberAlignment);
}
// The base alignment of the structure is N, where N is the largest
// base alignment value of any of its members...
maxAlignment = std::max(maxAlignment, memberAlignment);
structSize += memberSize;
}
}
for (const auto *field : structType->getDecl()->fields()) {
uint32_t memberAlignment = 0, memberSize = 0;
std::tie(memberAlignment, memberSize) =
getAlignmentAndSize(field->getType(), rule, isRowMajor, stride);
if (rule == SpirvLayoutRule::RelaxedGLSLStd140 ||
rule == SpirvLayoutRule::RelaxedGLSLStd430 ||
rule == SpirvLayoutRule::FxcCTBuffer) {
alignUsingHLSLRelaxedLayout(field->getType(), memberSize,
memberAlignment, &structSize);
} else {
structSize = roundToPow2(structSize, memberAlignment);
}
// Reset the current offset to the one specified in the source code
// if exists. It's debatable whether we should do sanity check here.
// If the developers want manually control the layout, we leave
// everything to them.
if (const auto *offsetAttr = field->getAttr<VKOffsetAttr>()) {
structSize = offsetAttr->getOffset();
}
// The base alignment of the structure is N, where N is the largest
// base alignment value of any of its members...
maxAlignment = std::max(maxAlignment, memberAlignment);
structSize += memberSize;
}
if (rule == SpirvLayoutRule::Scalar) {
// A structure has a scalar alignment equal to the largest scalar
// alignment of any of its members in VK_EXT_scalar_block_layout.
return {maxAlignment, structSize};
}
if (rule == SpirvLayoutRule::GLSLStd140 ||
rule == SpirvLayoutRule::RelaxedGLSLStd140 ||
rule == SpirvLayoutRule::FxcCTBuffer) {
// ... and rounded up to the base alignment of a vec4.
maxAlignment = roundToPow2(maxAlignment, kStd140Vec4Alignment);
}
if (rule != SpirvLayoutRule::FxcCTBuffer) {
// The base offset of the member following the sub-structure is rounded
// up to the next multiple of the base alignment of the structure.
structSize = roundToPow2(structSize, maxAlignment);
}
return {maxAlignment, structSize};
return getAlignmentAndSize(type, structType, rule, isRowMajor, stride);
}
// Rule 4, 6, 8, and 10

Просмотреть файл

@ -67,6 +67,13 @@ private:
return astContext.getDiagnostics().Report(srcLoc, diagId);
}
// Returns the alignment and size in bytes for the given struct
// according to the given LayoutRule.
std::pair<uint32_t, uint32_t>
getAlignmentAndSize(QualType type, const RecordType *structType,
SpirvLayoutRule rule, llvm::Optional<bool> isRowMajor,
uint32_t *stride) const;
private:
ASTContext &astContext; /// AST context
const SpirvCodeGenOptions &spvOptions; /// SPIR-V options

Просмотреть файл

@ -0,0 +1,23 @@
// RUN: %dxc -T cs_6_0 -E main -fcgl -spirv %s | FileCheck %s
// CHECK-DAG: %PTE = OpTypeStruct %uint
// CHECK-DAG: %_runtimearr_PTE = OpTypeRuntimeArray %PTE
// CHECK-DAG: OpDecorate %_runtimearr_PTE ArrayStride 4
// CHECK-DAG: [[buffer_type:%[a-zA-Z0-9_]+]] = OpTypeStruct %_runtimearr_PTE
// CHECK-DAG: [[buffer_ptr:%[a-zA-Z0-9_]+]] = OpTypePointer Uniform [[buffer_type]]
struct PTE {
uint valid : 1;
uint dirty : 1;
};
RWStructuredBuffer<PTE> bad;
// CHECK: %bad = OpVariable %_ptr_Uniform_type_RWStructuredBuffer_PTE Uniform
[numthreads(1, 1, 1)]
void main() {
// CHECK: [[ptr:%[0-9]+]] = OpAccessChain %_ptr_Uniform_uint %bad %int_0 %uint_1 %int_0
// CHECK: [[tmp:%[0-9]+]] = OpLoad %uint [[ptr]]
// CHECK: [[bit:%[0-9]+]] = OpBitFieldInsert %uint [[tmp]] %uint_1 %uint_1 %uint_1
// CHECK: OpStore [[ptr]] [[bit]]
bad[1].dirty = 1;
}

Просмотреть файл

@ -0,0 +1,154 @@
// RUN: %dxc -T cs_6_0 -E main -fcgl -spirv %s | FileCheck %s
struct S1 {
uint f1 : 1;
uint f2 : 1;
};
RWStructuredBuffer<S1> b1;
// CHECK-DAG: OpDecorate %_runtimearr_S1 ArrayStride 4
// CHECK-DAG: %_runtimearr_S1 = OpTypeRuntimeArray %S1
// CHECK-DAG: %S1 = OpTypeStruct %uint
// CHECK-DAG: OpMemberDecorate %S1 0 Offset 0
// CHECK-NOT-DAG: OpMemberDecorate %S1 1
struct S2 {
uint f1 : 31;
uint f2 : 1;
};
RWStructuredBuffer<S2> b2;
// CHECK-DAG: OpDecorate %_runtimearr_S2 ArrayStride 4
// CHECK-DAG: %_runtimearr_S2 = OpTypeRuntimeArray %S2
// CHECK-DAG: %S2 = OpTypeStruct %uint
// CHECK-DAG: OpMemberDecorate %S2 0 Offset 0
// CHECK-NOT-DAG: OpMemberDecorate %S2 1
struct S3 {
uint f1 : 1;
uint f2 : 31;
};
RWStructuredBuffer<S3> b3;
// CHECK-DAG: OpDecorate %_runtimearr_S3 ArrayStride 4
// CHECK-DAG: %_runtimearr_S3 = OpTypeRuntimeArray %S3
// CHECK-DAG: %S3 = OpTypeStruct %uint
// CHECK-DAG: OpMemberDecorate %S3 0 Offset 0
// CHECK-NOT-DAG: OpMemberDecorate %S3 1
struct S4 {
uint f1 : 1;
uint f2 : 32;
};
RWStructuredBuffer<S4> b4;
// CHECK-DAG: OpDecorate %_runtimearr_S4 ArrayStride 8
// CHECK-DAG: %_runtimearr_S4 = OpTypeRuntimeArray %S4
// CHECK-DAG: %S4 = OpTypeStruct %uint %uint
// CHECK-DAG: OpMemberDecorate %S4 0 Offset 0
// CHECK-DAG: OpMemberDecorate %S4 1 Offset 4
// CHECK-NOT-DAG: OpMemberDecorate %S4 2
struct S5 {
uint f1 : 1;
int f2 : 1;
uint f3 : 1;
};
RWStructuredBuffer<S5> b5;
// CHECK-DAG: OpDecorate %_runtimearr_S5 ArrayStride 12
// CHECK-DAG: %_runtimearr_S5 = OpTypeRuntimeArray %S5
// CHECK-DAG: %S5 = OpTypeStruct %uint %int %uint
// CHECK-DAG: OpMemberDecorate %S5 0 Offset 0
// CHECK-DAG: OpMemberDecorate %S5 1 Offset 4
// CHECK-DAG: OpMemberDecorate %S5 2 Offset 8
// CHECK-NOT-DAG: OpMemberDecorate %S5 3
struct S6 {
uint f1 : 1;
int f2 : 1;
uint f3 : 16;
uint f4 : 16;
};
RWStructuredBuffer<S6> b6;
// CHECK-DAG: OpDecorate %_runtimearr_S6 ArrayStride 12
// CHECK-DAG: %_runtimearr_S6 = OpTypeRuntimeArray %S6
// CHECK-DAG: %S6 = OpTypeStruct %uint %int %uint
// CHECK-DAG: OpMemberDecorate %S6 0 Offset 0
// CHECK-DAG: OpMemberDecorate %S6 1 Offset 4
// CHECK-DAG: OpMemberDecorate %S6 2 Offset 8
// CHECK-NOT-DAG: OpMemberDecorate %S6 3
struct S7 {
uint f1 : 1;
float f2;
uint f3 : 1;
};
RWStructuredBuffer<S7> b7;
// CHECK-DAG: OpDecorate %_runtimearr_S7 ArrayStride 12
// CHECK-DAG: %_runtimearr_S7 = OpTypeRuntimeArray %S7
// CHECK-DAG: %S7 = OpTypeStruct %uint %float %uint
// CHECK-DAG: OpMemberDecorate %S7 0 Offset 0
// CHECK-DAG: OpMemberDecorate %S7 1 Offset 4
// CHECK-DAG: OpMemberDecorate %S7 2 Offset 8
// CHECK-NOT-DAG: OpMemberDecorate %S7 3
struct S8 {
uint f1 : 1;
S1 f2;
};
RWStructuredBuffer<S8> b8;
// CHECK-DAG: OpDecorate %_runtimearr_S8 ArrayStride 8
// CHECK-DAG: %_runtimearr_S8 = OpTypeRuntimeArray %S8
// CHECK-DAG: %S8 = OpTypeStruct %uint %S1
// CHECK-DAG: OpMemberDecorate %S8 0 Offset 0
// CHECK-DAG: OpMemberDecorate %S8 1 Offset 4
// CHECK-NOT-DAG: OpMemberDecorate %S8 2
struct S9 {
uint f1 : 1;
uint f2 : 1;
S1 f3[10];
uint f4 : 1;
uint f5 : 1;
};
RWStructuredBuffer<S9> b9;
// CHECK-DAG: OpDecorate %_runtimearr_S9 ArrayStride 48
// CHECK-DAG: %_runtimearr_S9 = OpTypeRuntimeArray %S9
// CHECK-DAG: %S9 = OpTypeStruct %uint %_arr_S1_uint_10 %uint
// CHECK-DAG: OpMemberDecorate %S9 0 Offset 0
// CHECK-DAG: OpMemberDecorate %S9 1 Offset 4
// CHECK-DAG: OpMemberDecorate %S9 2 Offset 44
// CHECK-NOT-DAG: OpMemberDecorate %S9 3
struct S10 : S1 {
uint f1 : 1;
uint f2 : 1;
};
RWStructuredBuffer<S10> b10;
// CHECK-DAG: OpDecorate %_runtimearr_S10 ArrayStride 8
// CHECK-DAG: %_runtimearr_S10 = OpTypeRuntimeArray %S10
// CHECK-DAG: %S10 = OpTypeStruct %S1 %uint
// CHECK-DAG: OpMemberDecorate %S10 0 Offset 0
// CHECK-DAG: OpMemberDecorate %S10 1 Offset 4
// CHECK-NOT-DAG: OpMemberDecorate %S10 2
struct S11 : S10 {
uint f1 : 1;
};
RWStructuredBuffer<S11> b11;
// CHECK-DAG: OpDecorate %_runtimearr_S11 ArrayStride 12
// CHECK-DAG: %_runtimearr_S11 = OpTypeRuntimeArray %S11
// CHECK-DAG: %S11 = OpTypeStruct %S10 %uint
// CHECK-DAG: OpMemberDecorate %S11 0 Offset 0
// CHECK-DAG: OpMemberDecorate %S11 1 Offset 8
// CHECK-NOT-DAG: OpMemberDecorate %S11 2
struct S12 : S8 {
uint f1 : 1;
};
RWStructuredBuffer<S12> b12;
// CHECK-DAG: OpDecorate %_runtimearr_S12 ArrayStride 12
// CHECK-DAG: %_runtimearr_S12 = OpTypeRuntimeArray %S12
// CHECK-DAG: %S12 = OpTypeStruct %S8 %uint
// CHECK-DAG: OpMemberDecorate %S12 0 Offset 0
// CHECK-DAG: OpMemberDecorate %S12 1 Offset 8
// CHECK-NOT-DAG: OpMemberDecorate %S12 2
[numthreads(1, 1, 1)]
void main() { }