mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-17 08:57:26 +00:00
Implement Default Struct Layout
Implements https://github.com/gpuweb/gpuweb/pull/1447 SPIR-V Reader is still TODO, but continues to function as the offset decoration is still supported. Bug: tint:626 Bug: tint:629 Change-Id: Id574eb3a5c6729559382812de37b23f0c68fd406 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/43640 Commit-Queue: Ben Clayton <bclayton@chromium.org> Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
committed by
Commit Bot service account
parent
717fbbf183
commit
d614dd5d12
137
src/resolver/is_storeable_test.cc
Normal file
137
src/resolver/is_storeable_test.cc
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/resolver/resolver_test_helper.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverIsStorableTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Void) {
|
||||
auto* void_ty = ty.void_();
|
||||
|
||||
EXPECT_FALSE(r()->IsStorable(void_ty));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Scalar) {
|
||||
auto* bool_ = ty.bool_();
|
||||
auto* i32 = ty.i32();
|
||||
auto* u32 = ty.u32();
|
||||
auto* f32 = ty.f32();
|
||||
|
||||
EXPECT_TRUE(r()->IsStorable(bool_));
|
||||
EXPECT_TRUE(r()->IsStorable(i32));
|
||||
EXPECT_TRUE(r()->IsStorable(u32));
|
||||
EXPECT_TRUE(r()->IsStorable(f32));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Vector) {
|
||||
auto* vec2_i32 = ty.vec2<int>();
|
||||
auto* vec3_i32 = ty.vec3<int>();
|
||||
auto* vec4_i32 = ty.vec4<int>();
|
||||
auto* vec2_u32 = ty.vec2<unsigned>();
|
||||
auto* vec3_u32 = ty.vec3<unsigned>();
|
||||
auto* vec4_u32 = ty.vec4<unsigned>();
|
||||
auto* vec2_f32 = ty.vec2<float>();
|
||||
auto* vec3_f32 = ty.vec3<float>();
|
||||
auto* vec4_f32 = ty.vec4<float>();
|
||||
|
||||
EXPECT_TRUE(r()->IsStorable(vec2_i32));
|
||||
EXPECT_TRUE(r()->IsStorable(vec3_i32));
|
||||
EXPECT_TRUE(r()->IsStorable(vec4_i32));
|
||||
EXPECT_TRUE(r()->IsStorable(vec2_u32));
|
||||
EXPECT_TRUE(r()->IsStorable(vec3_u32));
|
||||
EXPECT_TRUE(r()->IsStorable(vec4_u32));
|
||||
EXPECT_TRUE(r()->IsStorable(vec2_f32));
|
||||
EXPECT_TRUE(r()->IsStorable(vec3_f32));
|
||||
EXPECT_TRUE(r()->IsStorable(vec4_f32));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Matrix) {
|
||||
auto* mat2x2 = ty.mat2x2<float>();
|
||||
auto* mat2x3 = ty.mat2x3<float>();
|
||||
auto* mat2x4 = ty.mat2x4<float>();
|
||||
auto* mat3x2 = ty.mat3x2<float>();
|
||||
auto* mat3x3 = ty.mat3x3<float>();
|
||||
auto* mat3x4 = ty.mat3x4<float>();
|
||||
auto* mat4x2 = ty.mat4x2<float>();
|
||||
auto* mat4x3 = ty.mat4x3<float>();
|
||||
auto* mat4x4 = ty.mat4x4<float>();
|
||||
|
||||
EXPECT_TRUE(r()->IsStorable(mat2x2));
|
||||
EXPECT_TRUE(r()->IsStorable(mat2x3));
|
||||
EXPECT_TRUE(r()->IsStorable(mat2x4));
|
||||
EXPECT_TRUE(r()->IsStorable(mat3x2));
|
||||
EXPECT_TRUE(r()->IsStorable(mat3x3));
|
||||
EXPECT_TRUE(r()->IsStorable(mat3x4));
|
||||
EXPECT_TRUE(r()->IsStorable(mat4x2));
|
||||
EXPECT_TRUE(r()->IsStorable(mat4x3));
|
||||
EXPECT_TRUE(r()->IsStorable(mat4x4));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Pointer) {
|
||||
auto* ptr_ty = ty.pointer<int>(ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->IsStorable(ptr_ty));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, AliasVoid) {
|
||||
auto* alias = ty.alias("myalias", ty.void_());
|
||||
|
||||
EXPECT_FALSE(r()->IsStorable(alias));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, AliasI32) {
|
||||
auto* alias = ty.alias("myalias", ty.i32());
|
||||
|
||||
EXPECT_TRUE(r()->IsStorable(alias));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, ArraySizedOfStorable) {
|
||||
auto* arr = ty.array(ty.i32(), 5);
|
||||
|
||||
EXPECT_TRUE(r()->IsStorable(arr));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, ArrayUnsizedOfStorable) {
|
||||
auto* arr = ty.array<int>();
|
||||
|
||||
EXPECT_TRUE(r()->IsStorable(arr));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Struct_AllMembersStorable) {
|
||||
ast::StructMemberList members{Member("a", ty.i32()), Member("b", ty.f32())};
|
||||
auto* s = create<ast::Struct>(Source{}, members, ast::DecorationList{});
|
||||
auto* s_ty = ty.struct_("mystruct", s);
|
||||
|
||||
EXPECT_TRUE(r()->IsStorable(s_ty));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Struct_SomeMembersNonStorable) {
|
||||
auto* ptr_ty = ty.pointer<int>(ast::StorageClass::kPrivate);
|
||||
ast::StructMemberList members{Member("a", ty.i32()), Member("b", ptr_ty)};
|
||||
auto* s = create<ast::Struct>(Source{}, members, ast::DecorationList{});
|
||||
auto* s_ty = ty.struct_("mystruct", s);
|
||||
|
||||
EXPECT_FALSE(r()->IsStorable(s_ty));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
@@ -30,11 +30,14 @@
|
||||
#include "src/ast/switch_statement.h"
|
||||
#include "src/ast/unary_op_expression.h"
|
||||
#include "src/ast/variable_decl_statement.h"
|
||||
#include "src/semantic/array.h"
|
||||
#include "src/semantic/call.h"
|
||||
#include "src/semantic/function.h"
|
||||
#include "src/semantic/member_accessor_expression.h"
|
||||
#include "src/semantic/statement.h"
|
||||
#include "src/semantic/struct.h"
|
||||
#include "src/semantic/variable.h"
|
||||
#include "src/type/access_control_type.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
@@ -59,6 +62,20 @@ class ScopedAssignment {
|
||||
T old_value_;
|
||||
};
|
||||
|
||||
/// Rounds `value` to the next multiple of `alignment`
|
||||
/// Assumes `alignment` is positive.
|
||||
template <typename T>
|
||||
T RoundUp(T alignment, T value) {
|
||||
return ((value + alignment - 1) / alignment) * alignment;
|
||||
}
|
||||
|
||||
/// Returns true if `value` is a power-of-two.
|
||||
/// Assumes `alignment` is positive.
|
||||
template <typename T>
|
||||
bool IsPowerOfTwo(T value) {
|
||||
return (value & (value - 1)) == 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Resolver::Resolver(ProgramBuilder* builder)
|
||||
@@ -98,7 +115,47 @@ bool Resolver::Resolve() {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Resolver::IsStorable(type::Type* type) {
|
||||
if (type == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (type->is_scalar() || type->Is<type::Vector>() ||
|
||||
type->Is<type::Matrix>()) {
|
||||
return true;
|
||||
}
|
||||
if (type::Array* array_type = type->As<type::Array>()) {
|
||||
return IsStorable(array_type->type());
|
||||
}
|
||||
if (type::Struct* struct_type = type->As<type::Struct>()) {
|
||||
for (const auto* member : struct_type->impl()->members()) {
|
||||
if (!IsStorable(member->type())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (type::Alias* alias_type = type->As<type::Alias>()) {
|
||||
return IsStorable(alias_type->type());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Resolver::ResolveInternal() {
|
||||
for (auto* ty : builder_->Types()) {
|
||||
if (auto* str = ty->As<type::Struct>()) {
|
||||
if (!Structure(str)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (auto* arr = ty->As<type::Array>()) {
|
||||
if (!Array(arr)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* var : builder_->AST().GlobalVariables()) {
|
||||
variable_stack_.set_global(var->symbol(), CreateVariableInfo(var));
|
||||
|
||||
@@ -962,6 +1019,204 @@ void Resolver::CreateSemanticNodes() const {
|
||||
}
|
||||
}
|
||||
|
||||
bool Resolver::DefaultAlignAndSize(type::Type* ty,
|
||||
uint32_t& align,
|
||||
uint32_t& size) {
|
||||
static constexpr uint32_t vector_size[] = {
|
||||
/* padding */ 0,
|
||||
/* padding */ 0,
|
||||
/*vec2*/ 8,
|
||||
/*vec3*/ 12,
|
||||
/*vec4*/ 16,
|
||||
};
|
||||
static constexpr uint32_t vector_align[] = {
|
||||
/* padding */ 0,
|
||||
/* padding */ 0,
|
||||
/*vec2*/ 8,
|
||||
/*vec3*/ 16,
|
||||
/*vec4*/ 16,
|
||||
};
|
||||
|
||||
ty = ty->UnwrapAliasIfNeeded();
|
||||
if (ty->is_scalar()) {
|
||||
// Note: Also captures booleans, but these are not host-sharable.
|
||||
align = 4;
|
||||
size = 4;
|
||||
return true;
|
||||
} else if (auto* vec = ty->As<type::Vector>()) {
|
||||
if (vec->size() < 2 || vec->size() > 4) {
|
||||
TINT_UNREACHABLE(diagnostics_)
|
||||
<< "Invalid vector size: vec" << vec->size();
|
||||
return false;
|
||||
}
|
||||
align = vector_align[vec->size()];
|
||||
size = vector_size[vec->size()];
|
||||
return true;
|
||||
} else if (auto* mat = ty->As<type::Matrix>()) {
|
||||
if (mat->columns() < 2 || mat->columns() > 4 || mat->rows() < 2 ||
|
||||
mat->rows() > 4) {
|
||||
TINT_UNREACHABLE(diagnostics_)
|
||||
<< "Invalid matrix size: mat" << mat->columns() << "x" << mat->rows();
|
||||
return false;
|
||||
}
|
||||
align = vector_align[mat->rows()];
|
||||
size = vector_align[mat->rows()] * mat->columns();
|
||||
return true;
|
||||
} else if (auto* s = ty->As<type::Struct>()) {
|
||||
if (auto* sem = Structure(s)) {
|
||||
align = sem->Align();
|
||||
size = sem->Size();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if (auto* arr = ty->As<type::Array>()) {
|
||||
if (auto* sem = Array(arr)) {
|
||||
align = sem->Align();
|
||||
size = sem->Size();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
TINT_UNREACHABLE(diagnostics_) << "Invalid type " << ty->TypeInfo().name;
|
||||
return false;
|
||||
}
|
||||
|
||||
const semantic::Array* Resolver::Array(type::Array* arr) {
|
||||
if (auto* sem = builder_->Sem().Get(arr)) {
|
||||
// Semantic info already constructed for this array type
|
||||
return sem;
|
||||
}
|
||||
|
||||
// First check the element type is legal
|
||||
auto* el_ty = arr->type();
|
||||
if (!IsStorable(el_ty)) {
|
||||
builder_->Diagnostics().add_error(
|
||||
std::string(el_ty->FriendlyName(builder_->Symbols())) +
|
||||
" cannot be used as an element type of an array");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto create_semantic = [&](uint32_t stride) -> semantic::Array* {
|
||||
uint32_t el_align = 0;
|
||||
uint32_t el_size = 0;
|
||||
if (!DefaultAlignAndSize(arr->type(), el_align, el_size)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto align = el_align;
|
||||
// WebGPU requires runtime arrays have at least one element, but the AST
|
||||
// records an element count of 0 for it.
|
||||
auto size = std::max<uint32_t>(arr->size(), 1) * stride;
|
||||
auto* sem = builder_->create<semantic::Array>(arr, align, size, stride);
|
||||
builder_->Sem().Add(arr, sem);
|
||||
return sem;
|
||||
};
|
||||
|
||||
// Look for explicit stride via [[stride(n)]] decoration
|
||||
for (auto* deco : arr->decorations()) {
|
||||
if (auto* stride = deco->As<ast::StrideDecoration>()) {
|
||||
return create_semantic(stride->stride());
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate implicit stride
|
||||
uint32_t el_align = 0;
|
||||
uint32_t el_size = 0;
|
||||
if (!DefaultAlignAndSize(el_ty, el_align, el_size)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return create_semantic(RoundUp(el_align, el_size));
|
||||
}
|
||||
|
||||
const semantic::Struct* Resolver::Structure(type::Struct* str) {
|
||||
if (auto* sem = builder_->Sem().Get(str)) {
|
||||
// Semantic info already constructed for this structure type
|
||||
return sem;
|
||||
}
|
||||
|
||||
semantic::StructMemberList sem_members;
|
||||
sem_members.reserve(str->impl()->members().size());
|
||||
|
||||
// Calculate the effective size and alignment of each field, and the overall
|
||||
// size of the structure.
|
||||
// For size, use the size attribute if provided, otherwise use the default
|
||||
// size for the type.
|
||||
// For alignment, use the alignment attribute if provided, otherwise use the
|
||||
// default alignment for the member type.
|
||||
// Diagnostic errors are raised if a basic rule is violated.
|
||||
// Validation of storage-class rules requires analysing the actual variable
|
||||
// usage of the structure, and so is performed as part of the variable
|
||||
// validation.
|
||||
// TODO(crbug.com/tint/628): Actually implement storage-class validation.
|
||||
uint32_t struct_size = 0;
|
||||
uint32_t struct_align = 1;
|
||||
|
||||
for (auto* member : str->impl()->members()) {
|
||||
// First check the member type is legal
|
||||
if (!IsStorable(member->type())) {
|
||||
builder_->Diagnostics().add_error(
|
||||
std::string(member->type()->FriendlyName(builder_->Symbols())) +
|
||||
" cannot be used as the type of a structure member");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t offset = struct_size;
|
||||
uint32_t align = 0;
|
||||
uint32_t size = 0;
|
||||
if (!DefaultAlignAndSize(member->type(), align, size)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (auto* deco : member->decorations()) {
|
||||
if (auto* o = deco->As<ast::StructMemberOffsetDecoration>()) {
|
||||
// [DEPRECATED]
|
||||
if (o->offset() < struct_size) {
|
||||
diagnostics_.add_error("offsets must be in ascending order",
|
||||
o->source());
|
||||
return nullptr;
|
||||
}
|
||||
offset = o->offset();
|
||||
align = 1;
|
||||
} else if (auto* a = deco->As<ast::StructMemberAlignDecoration>()) {
|
||||
if (a->align() <= 0 || !IsPowerOfTwo(a->align())) {
|
||||
diagnostics_.add_error(
|
||||
"align value must be a positive, power-of-two integer",
|
||||
a->source());
|
||||
return nullptr;
|
||||
}
|
||||
align = a->align();
|
||||
} else if (auto* s = deco->As<ast::StructMemberSizeDecoration>()) {
|
||||
if (s->size() < size) {
|
||||
diagnostics_.add_error(
|
||||
"size must be at least as big as the type's size (" +
|
||||
std::to_string(size) + ")",
|
||||
s->source());
|
||||
return nullptr;
|
||||
}
|
||||
size = s->size();
|
||||
}
|
||||
}
|
||||
|
||||
offset = RoundUp(align, offset);
|
||||
|
||||
auto* sem_member =
|
||||
builder_->create<semantic::StructMember>(member, offset, size);
|
||||
builder_->Sem().Add(member, sem_member);
|
||||
sem_members.emplace_back(sem_member);
|
||||
|
||||
struct_size = offset + size;
|
||||
struct_align = std::max(struct_align, align);
|
||||
}
|
||||
|
||||
struct_size = RoundUp(struct_align, struct_size);
|
||||
|
||||
auto* sem = builder_->create<semantic::Struct>(str, std::move(sem_members),
|
||||
struct_align, struct_size);
|
||||
builder_->Sem().Add(str, sem);
|
||||
return sem;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool Resolver::BlockScope(BlockInfo::Type type, F&& callback) {
|
||||
BlockInfo block_info(type, current_block_);
|
||||
|
||||
@@ -42,8 +42,12 @@ class UnaryOpExpression;
|
||||
class Variable;
|
||||
} // namespace ast
|
||||
namespace semantic {
|
||||
class Array;
|
||||
class Statement;
|
||||
} // namespace semantic
|
||||
namespace type {
|
||||
class Struct;
|
||||
} // namespace type
|
||||
|
||||
namespace resolver {
|
||||
|
||||
@@ -63,6 +67,10 @@ class Resolver {
|
||||
/// @returns true if the resolver was successful
|
||||
bool Resolve();
|
||||
|
||||
/// @param type the given type
|
||||
/// @returns true if the given type is storable.
|
||||
static bool IsStorable(type::Type* type);
|
||||
|
||||
private:
|
||||
/// Structure holding semantic information about a variable.
|
||||
/// Used to build the semantic::Variable nodes at the end of resolving.
|
||||
@@ -141,41 +149,6 @@ class Resolver {
|
||||
/// @returns true on success, false on error
|
||||
bool ResolveInternal();
|
||||
|
||||
/// Resolves functions
|
||||
/// @param funcs the functions to check
|
||||
/// @returns true on success, false on error
|
||||
bool Functions(const ast::FunctionList& funcs);
|
||||
/// Resolves a function. Requires all dependency
|
||||
/// (callee) functions to have DetermineFunction() called on them first.
|
||||
/// @param func the function to check
|
||||
/// @returns true on success, false on error
|
||||
bool Function(ast::Function* func);
|
||||
/// Resolves a block statement
|
||||
/// @param stmt the block statement
|
||||
/// @returns true if determination was successful
|
||||
bool BlockStatement(const ast::BlockStatement* stmt);
|
||||
/// Resolves the list of statements
|
||||
/// @param stmts the statements to resolve
|
||||
/// @returns true on success, false on error
|
||||
bool Statements(const ast::StatementList& stmts);
|
||||
/// Resolves a statement
|
||||
/// @param stmt the statement to check
|
||||
/// @returns true on success, false on error
|
||||
bool Statement(ast::Statement* stmt);
|
||||
/// Resolves an expression list
|
||||
/// @param list the expression list to check
|
||||
/// @returns true on success, false on error
|
||||
bool Expressions(const ast::ExpressionList& list);
|
||||
/// Resolves an expression
|
||||
/// @param expr the expression to check
|
||||
/// @returns true on success, false on error
|
||||
bool Expression(ast::Expression* expr);
|
||||
/// Resolves the storage class for variables. This assumes that it is only
|
||||
/// called for things in function scope, not module scope.
|
||||
/// @param stmt the statement to check
|
||||
/// @returns false on error
|
||||
bool VariableStorageClass(ast::Statement* stmt);
|
||||
|
||||
/// Creates the nodes and adds them to the semantic::Info mappings of the
|
||||
/// ProgramBuilder.
|
||||
void CreateSemanticNodes() const;
|
||||
@@ -195,20 +168,43 @@ class Resolver {
|
||||
|
||||
void set_referenced_from_function_if_needed(VariableInfo* var, bool local);
|
||||
|
||||
bool ArrayAccessor(ast::ArrayAccessorExpression* expr);
|
||||
bool Binary(ast::BinaryExpression* expr);
|
||||
bool Bitcast(ast::BitcastExpression* expr);
|
||||
bool Call(ast::CallExpression* expr);
|
||||
bool CaseStatement(ast::CaseStatement* stmt);
|
||||
bool Constructor(ast::ConstructorExpression* expr);
|
||||
bool Identifier(ast::IdentifierExpression* expr);
|
||||
bool IfStatement(ast::IfStatement* stmt);
|
||||
bool IntrinsicCall(ast::CallExpression* call,
|
||||
semantic::IntrinsicType intrinsic_type);
|
||||
bool MemberAccessor(ast::MemberAccessorExpression* expr);
|
||||
bool UnaryOp(ast::UnaryOpExpression* expr);
|
||||
// AST and Type traversal methods
|
||||
// Each return true on success, false on failure.
|
||||
bool ArrayAccessor(ast::ArrayAccessorExpression*);
|
||||
bool Binary(ast::BinaryExpression*);
|
||||
bool Bitcast(ast::BitcastExpression*);
|
||||
bool BlockStatement(const ast::BlockStatement*);
|
||||
bool Call(ast::CallExpression*);
|
||||
bool CaseStatement(ast::CaseStatement*);
|
||||
bool Constructor(ast::ConstructorExpression*);
|
||||
bool Expression(ast::Expression*);
|
||||
bool Expressions(const ast::ExpressionList&);
|
||||
bool Function(ast::Function*);
|
||||
bool Functions(const ast::FunctionList&);
|
||||
bool Identifier(ast::IdentifierExpression*);
|
||||
bool IfStatement(ast::IfStatement*);
|
||||
bool IntrinsicCall(ast::CallExpression*, semantic::IntrinsicType);
|
||||
bool MemberAccessor(ast::MemberAccessorExpression*);
|
||||
bool Statement(ast::Statement*);
|
||||
bool Statements(const ast::StatementList&);
|
||||
bool UnaryOp(ast::UnaryOpExpression*);
|
||||
bool VariableDeclStatement(const ast::VariableDeclStatement*);
|
||||
bool VariableStorageClass(ast::Statement*);
|
||||
|
||||
bool VariableDeclStatement(const ast::VariableDeclStatement* stmt);
|
||||
/// @returns the semantic information for the array `arr`, building it if it
|
||||
/// hasn't been constructed already. If an error is raised, nullptr is
|
||||
/// returned.
|
||||
const semantic::Array* Array(type::Array*);
|
||||
|
||||
/// @returns the semantic information for the structure `str`, building it if
|
||||
/// it hasn't been constructed already. If an error is raised, nullptr is
|
||||
/// returned.
|
||||
const semantic::Struct* Structure(type::Struct* str);
|
||||
|
||||
/// @param align the output default alignment in bytes for the type `ty`
|
||||
/// @param size the output default size in bytes for the type `ty`
|
||||
/// @returns true on success, false on error
|
||||
bool DefaultAlignAndSize(type::Type* ty, uint32_t& align, uint32_t& size);
|
||||
|
||||
VariableInfo* CreateVariableInfo(ast::Variable*);
|
||||
|
||||
|
||||
333
src/resolver/struct_layout_test.cc
Normal file
333
src/resolver/struct_layout_test.cc
Normal file
@@ -0,0 +1,333 @@
|
||||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/resolver/resolver_test_helper.h"
|
||||
#include "src/semantic/struct.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverStructLayoutTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, Scalars) {
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.f32()),
|
||||
Member("b", ty.u32()),
|
||||
Member("c", ty.i32()),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 12u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 3u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 8u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 4u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, Alias) {
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.alias("a", ty.f32())),
|
||||
Member("b", ty.alias("b", ty.f32())),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 8u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 2u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 4u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayStaticSize) {
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.array<i32, 3>()),
|
||||
Member("b", ty.array<f32, 5>()),
|
||||
Member("c", ty.array<f32, 1>()),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 36u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 3u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 12u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 12u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 20u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 32u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 4u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayStaticSize) {
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.array<i32, 3>(/*stride*/ 8)),
|
||||
Member("b", ty.array<f32, 5>(/*stride*/ 16)),
|
||||
Member("c", ty.array<f32, 1>(/*stride*/ 32)),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 136u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 3u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 24u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 24u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 80u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 104u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 32u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayRuntimeSized) {
|
||||
auto* s = Structure("S", {
|
||||
Member("c", ty.array<f32>()),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 4u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 1u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayRuntimeSized) {
|
||||
auto* s = Structure("S", {
|
||||
Member("c", ty.array<f32>(/*stride*/ 32)),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 32u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 1u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 32u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfExplicitStrideArray) {
|
||||
auto* inner = ty.array<i32, 2>(/*stride*/ 16); // size: 32
|
||||
auto* outer = ty.array(inner, 12); // size: 12 * 32
|
||||
auto* s = Structure("S", {
|
||||
Member("c", outer),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 384u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 1u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 384u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfStructure) {
|
||||
auto* inner = Structure("Inner", {
|
||||
Member("a", ty.vec2<i32>()),
|
||||
Member("b", ty.vec3<i32>()),
|
||||
Member("c", ty.vec4<i32>()),
|
||||
}); // size: 48
|
||||
auto* outer = ty.array(inner, 12); // size: 12 * 48
|
||||
auto* s = Structure("S", {
|
||||
Member("c", outer),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 576u);
|
||||
EXPECT_EQ(sem->Align(), 16u);
|
||||
ASSERT_EQ(sem->Members().size(), 1u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 576u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, Vector) {
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.vec2<i32>()),
|
||||
Member("b", ty.vec3<i32>()),
|
||||
Member("c", ty.vec4<i32>()),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 48u);
|
||||
EXPECT_EQ(sem->Align(), 16u);
|
||||
ASSERT_EQ(sem->Members().size(), 3u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u); // vec2
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 8u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 16u); // vec3
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 12u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 32u); // vec4
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 16u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, Matrix) {
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.mat2x2<i32>()),
|
||||
Member("b", ty.mat2x3<i32>()),
|
||||
Member("c", ty.mat2x4<i32>()),
|
||||
Member("d", ty.mat3x2<i32>()),
|
||||
Member("e", ty.mat3x3<i32>()),
|
||||
Member("f", ty.mat3x4<i32>()),
|
||||
Member("g", ty.mat4x2<i32>()),
|
||||
Member("h", ty.mat4x3<i32>()),
|
||||
Member("i", ty.mat4x4<i32>()),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 368u);
|
||||
EXPECT_EQ(sem->Align(), 16u);
|
||||
ASSERT_EQ(sem->Members().size(), 9u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u); // mat2x2
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 16u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 16u); // mat2x3
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 32u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 48u); // mat2x4
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 32u);
|
||||
EXPECT_EQ(sem->Members()[3]->Offset(), 80u); // mat3x2
|
||||
EXPECT_EQ(sem->Members()[3]->Size(), 24u);
|
||||
EXPECT_EQ(sem->Members()[4]->Offset(), 112u); // mat3x3
|
||||
EXPECT_EQ(sem->Members()[4]->Size(), 48u);
|
||||
EXPECT_EQ(sem->Members()[5]->Offset(), 160u); // mat3x4
|
||||
EXPECT_EQ(sem->Members()[5]->Size(), 48u);
|
||||
EXPECT_EQ(sem->Members()[6]->Offset(), 208u); // mat4x2
|
||||
EXPECT_EQ(sem->Members()[6]->Size(), 32u);
|
||||
EXPECT_EQ(sem->Members()[7]->Offset(), 240u); // mat4x3
|
||||
EXPECT_EQ(sem->Members()[7]->Size(), 64u);
|
||||
EXPECT_EQ(sem->Members()[8]->Offset(), 304u); // mat4x4
|
||||
EXPECT_EQ(sem->Members()[8]->Size(), 64u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, NestedStruct) {
|
||||
auto* inner = Structure("Inner", {
|
||||
Member("a", ty.mat3x3<i32>()),
|
||||
});
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.i32()),
|
||||
Member("b", inner),
|
||||
Member("c", ty.i32()),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 80u);
|
||||
EXPECT_EQ(sem->Align(), 16u);
|
||||
ASSERT_EQ(sem->Members().size(), 3u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 16u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 48u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 64u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 4u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, SizeDecorations) {
|
||||
auto* inner = Structure("Inner", {
|
||||
Member("a", ty.f32(), {MemberSize(8)}),
|
||||
Member("b", ty.f32(), {MemberSize(16)}),
|
||||
Member("c", ty.f32(), {MemberSize(8)}),
|
||||
});
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.f32(), {MemberSize(4)}),
|
||||
Member("b", ty.u32(), {MemberSize(8)}),
|
||||
Member("c", inner),
|
||||
Member("d", ty.i32(), {MemberSize(32)}),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 76u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 8u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 12u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 32u);
|
||||
EXPECT_EQ(sem->Members()[3]->Offset(), 44u);
|
||||
EXPECT_EQ(sem->Members()[3]->Size(), 32u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, AlignDecorations) {
|
||||
auto* inner = Structure("Inner", {
|
||||
Member("a", ty.f32(), {MemberAlign(8)}),
|
||||
Member("b", ty.f32(), {MemberAlign(16)}),
|
||||
Member("c", ty.f32(), {MemberAlign(4)}),
|
||||
});
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.f32(), {MemberAlign(4)}),
|
||||
Member("b", ty.u32(), {MemberAlign(8)}),
|
||||
Member("c", inner),
|
||||
Member("d", ty.i32(), {MemberAlign(32)}),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(s);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 96u);
|
||||
EXPECT_EQ(sem->Align(), 32u);
|
||||
ASSERT_EQ(sem->Members().size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 8u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 16u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 32u);
|
||||
EXPECT_EQ(sem->Members()[3]->Offset(), 64u);
|
||||
EXPECT_EQ(sem->Members()[3]->Size(), 4u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
@@ -556,6 +556,38 @@ TEST_F(ResolverValidationTest, Stmt_BreakNotInLoopOrSwitch) {
|
||||
"12:34 error: break statement must be in a loop or switch case");
|
||||
}
|
||||
|
||||
TEST_F(ResolverValidationTest, NonPOTStructMemberAlignDecoration) {
|
||||
Structure("S", {
|
||||
Member("a", ty.f32(), {MemberAlign(Source{{12, 34}}, 3)}),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"12:34 error: align value must be a positive, power-of-two integer");
|
||||
}
|
||||
|
||||
TEST_F(ResolverValidationTest, ZeroStructMemberAlignDecoration) {
|
||||
Structure("S", {
|
||||
Member("a", ty.f32(), {MemberAlign(Source{{12, 34}}, 0)}),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"12:34 error: align value must be a positive, power-of-two integer");
|
||||
}
|
||||
|
||||
TEST_F(ResolverValidationTest, ZeroStructMemberSizeDecoration) {
|
||||
Structure("S", {
|
||||
Member("a", ty.f32(), {MemberSize(Source{{12, 34}}, 0)}),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: size must be at least as big as the type's size (4)");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
|
||||
Reference in New Issue
Block a user