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:
parent
4d96762781
commit
66d3efb669
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
fn f() {
|
||||
var v = array<bool, 65535>();
|
||||
}
|
||||
|
|
@ -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>();
|
||||
^^^^^
|
||||
|
|
@ -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
|
@ -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>{};
|
||||
}
|
||||
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
|||
fn f() {
|
||||
var v = array<bool, 65535>();
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
fn f() {
|
||||
var v : array<bool, 65535>;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
[numthreads(1, 1, 1)]
|
||||
void unused_entry_point() {
|
||||
return;
|
||||
}
|
||||
|
||||
void f() {
|
||||
bool v[65535] = (bool[65535])0;
|
||||
}
|
|
@ -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
|
@ -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 = {};
|
||||
}
|
||||
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
|||
fn f() {
|
||||
var v : array<bool, 65535>;
|
||||
}
|
|
@ -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;
|
|
@ -0,0 +1,8 @@
|
|||
[numthreads(1, 1, 1)]
|
||||
void unused_entry_point() {
|
||||
return;
|
||||
}
|
||||
|
||||
ByteAddressBuffer v : register(t0, space0);
|
||||
|
||||
ByteAddressBuffer b : register(t1, space0);
|
|
@ -0,0 +1,8 @@
|
|||
[numthreads(1, 1, 1)]
|
||||
void unused_entry_point() {
|
||||
return;
|
||||
}
|
||||
|
||||
ByteAddressBuffer v : register(t0, space0);
|
||||
|
||||
ByteAddressBuffer b : register(t1, space0);
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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
|
|
@ -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;
|
Loading…
Reference in New Issue