[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:
Родитель
7e40ead3c7
Коммит
bb9ec0fb56
|
@ -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() { }
|
Загрузка…
Ссылка в новой задаче