Limit array element count to less then 65536 elements.

This CL adds element count limits to arrays. In FXC there is a maximum
of 65536 elements in an array. This limit is not yet in WGSL, but adding
this here allows us to fix the issue with large arrays and GLSL.

Bug: chromium:1367602
Change-Id: I7df9d3e4f6c3e5107420d5f8e576d1f33e453161
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/104240
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
dan sinclair 2022-10-03 17:25:42 +00:00 committed by Dawn LUCI CQ
parent 4d96762781
commit 66d3efb669
23 changed files with 369 additions and 34 deletions

View File

@ -89,6 +89,11 @@
#include "src/tint/utils/vector.h"
namespace tint::resolver {
namespace {
constexpr int64_t kMaxArrayElementCount = 65536;
} // namespace
Resolver::Resolver(ProgramBuilder* builder)
: builder_(builder),
@ -2704,7 +2709,8 @@ sem::Array* Resolver::Array(const Source& el_source,
size = const_count->value * stride;
if (size > std::numeric_limits<uint32_t>::max()) {
std::stringstream msg;
msg << "array size (0x" << std::hex << size << ") must not exceed 0xffffffff bytes";
msg << "array byte size (0x" << std::hex << size
<< ") must not exceed 0xffffffff bytes";
AddError(msg.str(), count_source);
return nullptr;
}
@ -3205,20 +3211,21 @@ sem::Statement* Resolver::IncrementDecrementStatement(
});
}
bool Resolver::ApplyAddressSpaceUsageToType(ast::AddressSpace sc,
bool Resolver::ApplyAddressSpaceUsageToType(ast::AddressSpace address_space,
sem::Type* ty,
const Source& usage) {
ty = const_cast<sem::Type*>(ty->UnwrapRef());
if (auto* str = ty->As<sem::Struct>()) {
if (str->AddressSpaceUsage().count(sc)) {
if (str->AddressSpaceUsage().count(address_space)) {
return true; // Already applied
}
str->AddUsage(sc);
str->AddUsage(address_space);
for (auto* member : str->Members()) {
if (!ApplyAddressSpaceUsageToType(sc, const_cast<sem::Type*>(member->Type()), usage)) {
if (!ApplyAddressSpaceUsageToType(address_space, const_cast<sem::Type*>(member->Type()),
usage)) {
std::stringstream err;
err << "while analysing structure member " << sem_.TypeNameOf(str) << "."
<< builder_->Symbols().NameFor(member->Declaration()->symbol);
@ -3230,21 +3237,29 @@ bool Resolver::ApplyAddressSpaceUsageToType(ast::AddressSpace sc,
}
if (auto* arr = ty->As<sem::Array>()) {
if (arr->IsRuntimeSized() && sc != ast::AddressSpace::kStorage) {
AddError(
"runtime-sized arrays can only be used in the <storage> address "
"space",
usage);
return false;
}
if (address_space != ast::AddressSpace::kStorage) {
if (arr->IsRuntimeSized()) {
AddError("runtime-sized arrays can only be used in the <storage> address space",
usage);
return false;
}
return ApplyAddressSpaceUsageToType(sc, const_cast<sem::Type*>(arr->ElemType()), usage);
auto count = arr->ConstantCount();
if (count.has_value() && count.value() >= kMaxArrayElementCount) {
AddError("array size (" + std::to_string(count.value()) + ") must be less than " +
std::to_string(kMaxArrayElementCount),
usage);
return false;
}
}
return ApplyAddressSpaceUsageToType(address_space, const_cast<sem::Type*>(arr->ElemType()),
usage);
}
if (ast::IsHostShareable(sc) && !validator_.IsHostShareable(ty)) {
if (ast::IsHostShareable(address_space) && !validator_.IsHostShareable(ty)) {
std::stringstream err;
err << "Type '" << sem_.TypeNameOf(ty) << "' cannot be used in address space '" << sc
<< "' as it is non-host-shareable";
err << "Type '" << sem_.TypeNameOf(ty) << "' cannot be used in address space '"
<< address_space << "' as it is non-host-shareable";
AddError(err.str(), usage);
return false;
}

View File

@ -308,22 +308,63 @@ TEST_F(ResolverTypeValidationTest, ArraySize_IVecConst) {
"'vec2<i32>'");
}
TEST_F(ResolverTypeValidationTest, ArraySize_UnderElementCountLimit) {
// var<private> a : array<f32, 65535>;
GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 65535_a)),
ast::AddressSpace::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_OverElementCountLimit) {
// var<private> a : array<f32, 65536>;
GlobalVar(Source{{1, 2}}, "a",
ty.array(Source{{12, 34}}, ty.f32(), Expr(Source{{12, 34}}, 65536_a)),
ast::AddressSpace::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(1:2 error: array size (65536) must be less than 65536
1:2 note: while instantiating 'var' a)");
}
TEST_F(ResolverTypeValidationTest, ArraySize_StorageBufferLargeArray) {
// var<storage> a : array<f32, 65536>;
GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 65536_a)), ast::AddressSpace::kStorage,
utils::Vector{Binding(0_u), Group(0_u)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_NestedStorageBufferLargeArray) {
// Struct S {
// a : array<f32, 65536>,
// }
// var<storage> a : S;
Structure("S", utils::Vector{Member(Source{{12, 34}}, "a",
ty.array(Source{{12, 20}}, ty.f32(), 65536_a))});
GlobalVar("a", ty.type_name(Source{{12, 30}}, "S"), ast::AddressSpace::kStorage,
utils::Vector{Binding(0_u), Group(0_u)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_TooBig_ImplicitStride) {
// var<private> a : array<f32, 0x40000000u>;
GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 0x40000000_u)),
// struct S {
// @offset(800000) a : f32
// }
// var<private> a : array<S, 65535>;
Structure("S", utils::Vector{Member(Source{{12, 34}}, "a", ty.f32(),
utils::Vector{MemberOffset(800000_a)})});
GlobalVar("a", ty.array(ty.type_name(Source{{12, 30}}, "S"), Expr(Source{{12, 34}}, 65535_a)),
ast::AddressSpace::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: array size (0x100000000) must not exceed 0xffffffff bytes");
"12:34 error: array byte size (0xc34f7cafc) must not exceed 0xffffffff bytes");
}
TEST_F(ResolverTypeValidationTest, ArraySize_TooBig_ExplicitStride) {
// var<private> a : @stride(8) array<f32, 0x20000000u>;
GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 0x20000000_u), 8),
// var<private> a : @stride(8000000) array<f32, 65535>;
GlobalVar("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 65535_a), 8000000),
ast::AddressSpace::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: array size (0x100000000) must not exceed 0xffffffff bytes");
"12:34 error: array byte size (0x7a1185ee00) must not exceed 0xffffffff bytes");
}
TEST_F(ResolverTypeValidationTest, ArraySize_Override_PrivateVar) {
@ -561,33 +602,38 @@ TEST_F(ResolverTypeValidationTest, Struct_Member_MatrixNoType) {
}
TEST_F(ResolverTypeValidationTest, Struct_TooBig) {
// Struct Bar {
// a: array<f32, 10000>;
// }
// struct Foo {
// a: array<f32, 0x20000000>;
// b: array<f32, 0x20000000>;
// };
// a: array<Bar, 65535>;
// b: array<Bar, 65535>;
// }
Structure(Source{{10, 34}}, "Bar", utils::Vector{Member("a", ty.array<f32, 10000>())});
Structure(Source{{12, 34}}, "Foo",
utils::Vector{
Member("a", ty.array<f32, 0x20000000>()),
Member("b", ty.array<f32, 0x20000000>()),
});
utils::Vector{Member("a", ty.array(ty.type_name(Source{{12, 30}}, "Bar"),
Expr(Source{{12, 34}}, 65535_a))),
Member("b", ty.array(ty.type_name(Source{{12, 30}}, "Bar"),
Expr(Source{{12, 34}}, 65535_a)))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: struct size (0x100000000) must not exceed 0xffffffff bytes");
"12:34 error: struct size (0x1387ec780) must not exceed 0xffffffff bytes");
}
TEST_F(ResolverTypeValidationTest, Struct_MemberOffset_TooBig) {
// struct Foo {
// a: array<f32, 0x3fffffff>;
// @size(2147483647) a: array<f32, 65535>;
// b: f32;
// c: f32;
// };
Structure("Foo", utils::Vector{
Member("a", ty.array<f32, 0x3fffffff>()),
Member("b", ty.f32()),
Member(Source{{12, 34}}, "c", ty.f32()),
Member("z", ty.f32(), utils::Vector{MemberSize(2147483647_a)}),
Member("y", ty.f32(), utils::Vector{MemberSize(2147483647_a)}),
Member(Source{{12, 34}}, "a", ty.array<f32, 65535>()),
Member("c", ty.f32()),
});
EXPECT_FALSE(r()->Resolve());

View File

@ -0,0 +1,4 @@
fn f() {
var v = array<bool, 65535>();
}

View File

@ -0,0 +1,6 @@
SKIP: FAILED
bug/chromium/1367602-1.wgsl:2:23 error: array size (65536) must be less than 65536
var v = array<bool, 65536>();
^^^^^

View File

@ -0,0 +1,6 @@
SKIP: FAILED
bug/chromium/1367602-1.wgsl:2:23 error: array size (65536) must be less than 65536
var v = array<bool, 65536>();
^^^^^

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,20 @@
#include <metal_stdlib>
using namespace metal;
template<typename T, size_t N>
struct tint_array {
const constant T& operator[](size_t i) const constant { return elements[i]; }
device T& operator[](size_t i) device { return elements[i]; }
const device T& operator[](size_t i) const device { return elements[i]; }
thread T& operator[](size_t i) thread { return elements[i]; }
const thread T& operator[](size_t i) const thread { return elements[i]; }
threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
T elements[N];
};
void f() {
tint_array<bool, 65535> v = tint_array<bool, 65535>{};
}

View File

@ -0,0 +1,31 @@
; SPIR-V
; Version: 1.3
; Generator: Google Tint Compiler; 0
; Bound: 14
; Schema: 0
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
OpExecutionMode %unused_entry_point LocalSize 1 1 1
OpName %unused_entry_point "unused_entry_point"
OpName %f "f"
OpName %v "v"
OpDecorate %_arr_bool_uint_65535 ArrayStride 4
%void = OpTypeVoid
%1 = OpTypeFunction %void
%bool = OpTypeBool
%uint = OpTypeInt 32 0
%uint_65535 = OpConstant %uint 65535
%_arr_bool_uint_65535 = OpTypeArray %bool %uint_65535
%11 = OpConstantNull %_arr_bool_uint_65535
%_ptr_Function__arr_bool_uint_65535 = OpTypePointer Function %_arr_bool_uint_65535
%unused_entry_point = OpFunction %void None %1
%4 = OpLabel
OpReturn
OpFunctionEnd
%f = OpFunction %void None %1
%6 = OpLabel
%v = OpVariable %_ptr_Function__arr_bool_uint_65535 Function %11
OpStore %v %11
OpReturn
OpFunctionEnd

View File

@ -0,0 +1,3 @@
fn f() {
var v = array<bool, 65535>();
}

View File

@ -0,0 +1,4 @@
fn f() {
var v : array<bool, 65535>;
}

View File

@ -0,0 +1,8 @@
[numthreads(1, 1, 1)]
void unused_entry_point() {
return;
}
void f() {
bool v[65535] = (bool[65535])0;
}

View File

@ -0,0 +1,8 @@
[numthreads(1, 1, 1)]
void unused_entry_point() {
return;
}
void f() {
bool v[65535] = (bool[65535])0;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,20 @@
#include <metal_stdlib>
using namespace metal;
template<typename T, size_t N>
struct tint_array {
const constant T& operator[](size_t i) const constant { return elements[i]; }
device T& operator[](size_t i) device { return elements[i]; }
const device T& operator[](size_t i) const device { return elements[i]; }
thread T& operator[](size_t i) thread { return elements[i]; }
const thread T& operator[](size_t i) const thread { return elements[i]; }
threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
T elements[N];
};
void f() {
tint_array<bool, 65535> v = {};
}

View File

@ -0,0 +1,30 @@
; SPIR-V
; Version: 1.3
; Generator: Google Tint Compiler; 0
; Bound: 14
; Schema: 0
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
OpExecutionMode %unused_entry_point LocalSize 1 1 1
OpName %unused_entry_point "unused_entry_point"
OpName %f "f"
OpName %v "v"
OpDecorate %_arr_bool_uint_65535 ArrayStride 4
%void = OpTypeVoid
%1 = OpTypeFunction %void
%bool = OpTypeBool
%uint = OpTypeInt 32 0
%uint_65535 = OpConstant %uint 65535
%_arr_bool_uint_65535 = OpTypeArray %bool %uint_65535
%_ptr_Function__arr_bool_uint_65535 = OpTypePointer Function %_arr_bool_uint_65535
%13 = OpConstantNull %_arr_bool_uint_65535
%unused_entry_point = OpFunction %void None %1
%4 = OpLabel
OpReturn
OpFunctionEnd
%f = OpFunction %void None %1
%6 = OpLabel
%v = OpVariable %_ptr_Function__arr_bool_uint_65535 Function %13
OpReturn
OpFunctionEnd

View File

@ -0,0 +1,3 @@
fn f() {
var v : array<bool, 65535>;
}

View File

@ -0,0 +1,8 @@
@group(0) @binding(0)
var<storage> v : array<i32, 1000000>;
struct A {
a : array<f32, 1000000>,
}
@group(0) @binding(1)
var<storage> b : A;

View File

@ -0,0 +1,8 @@
[numthreads(1, 1, 1)]
void unused_entry_point() {
return;
}
ByteAddressBuffer v : register(t0, space0);
ByteAddressBuffer b : register(t1, space0);

View File

@ -0,0 +1,8 @@
[numthreads(1, 1, 1)]
void unused_entry_point() {
return;
}
ByteAddressBuffer v : register(t0, space0);
ByteAddressBuffer b : register(t1, space0);

View File

@ -0,0 +1,14 @@
#version 310 es
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
void unused_entry_point() {
return;
}
layout(binding = 0, std430) buffer v_block_ssbo {
int inner[1000000];
} v;
layout(binding = 1, std430) buffer A_ssbo {
float a[1000000];
} b;

View File

@ -0,0 +1,20 @@
#include <metal_stdlib>
using namespace metal;
template<typename T, size_t N>
struct tint_array {
const constant T& operator[](size_t i) const constant { return elements[i]; }
device T& operator[](size_t i) device { return elements[i]; }
const device T& operator[](size_t i) const device { return elements[i]; }
thread T& operator[](size_t i) thread { return elements[i]; }
const thread T& operator[](size_t i) const thread { return elements[i]; }
threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
T elements[N];
};
struct A {
tint_array<float, 1000000> a;
};

View File

@ -0,0 +1,46 @@
; SPIR-V
; Version: 1.3
; Generator: Google Tint Compiler; 0
; Bound: 17
; Schema: 0
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
OpExecutionMode %unused_entry_point LocalSize 1 1 1
OpName %v_block "v_block"
OpMemberName %v_block 0 "inner"
OpName %v "v"
OpName %A "A"
OpMemberName %A 0 "a"
OpName %b "b"
OpName %unused_entry_point "unused_entry_point"
OpDecorate %v_block Block
OpMemberDecorate %v_block 0 Offset 0
OpDecorate %_arr_int_uint_1000000 ArrayStride 4
OpDecorate %v NonWritable
OpDecorate %v DescriptorSet 0
OpDecorate %v Binding 0
OpDecorate %A Block
OpMemberDecorate %A 0 Offset 0
OpDecorate %_arr_float_uint_1000000 ArrayStride 4
OpDecorate %b NonWritable
OpDecorate %b DescriptorSet 0
OpDecorate %b Binding 1
%int = OpTypeInt 32 1
%uint = OpTypeInt 32 0
%uint_1000000 = OpConstant %uint 1000000
%_arr_int_uint_1000000 = OpTypeArray %int %uint_1000000
%v_block = OpTypeStruct %_arr_int_uint_1000000
%_ptr_StorageBuffer_v_block = OpTypePointer StorageBuffer %v_block
%v = OpVariable %_ptr_StorageBuffer_v_block StorageBuffer
%float = OpTypeFloat 32
%_arr_float_uint_1000000 = OpTypeArray %float %uint_1000000
%A = OpTypeStruct %_arr_float_uint_1000000
%_ptr_StorageBuffer_A = OpTypePointer StorageBuffer %A
%b = OpVariable %_ptr_StorageBuffer_A StorageBuffer
%void = OpTypeVoid
%13 = OpTypeFunction %void
%unused_entry_point = OpFunction %void None %13
%16 = OpLabel
OpReturn
OpFunctionEnd

View File

@ -0,0 +1,7 @@
@group(0) @binding(0) var<storage> v : array<i32, 1000000>;
struct A {
a : array<f32, 1000000>,
}
@group(0) @binding(1) var<storage> b : A;