tint: add syntax sugar for dereferencing pointers
Adds support to Tint for syntax sugar for dereferencing pointers for
member or index access as per:
https://github.com/gpuweb/gpuweb/pull/4311
- Resolver: when the lhs of a accessor expression is a pointer, it is
now resolved to a sem::Reference.
- Added "pointer_composite_access" feature as experimental, hooked up
validation in Resolver, and added tests.
- Added resolver tests for the new syntax to resolver/ptr_ref_test.cc.
- Fixed multiple transforms to deal with the fact that the lhs of
accessor expressions can now be pointers, including: Robustness,
Renamer, Std140, and SimplifyPointers.
- In transforms that rely on other transforms, such as
SimplifyPointers, to remove/inline pointers, I added asserts that
the type is not a pointer.
- Added unit tests for transforms that use pointer-dot/index for
accessor expressions.
- Fixed uniformity analysis code so that ProcessLValueExpression
correctly deals with accessor expressions where the object is a
pointer, in the same way we do for UnaryOp::kIndirection, including
partial pointer checks. Added many tests for these new cases.
- Fixed ProgramToIR so that EmitAccess handles the new syntax. Added
multiple tests.
- Added end2end tests under test/tint/ptr_sugar
For Googlers, see my work log at
go/add-syntax-sugar-for-dereferencing-composites for more details.
Bug: tint:2113
Change-Id: I7a0093f52ca2237be598e44245b45049f21d056c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/164900
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/dawn/common/WGSLFeatureMapping.h b/src/dawn/common/WGSLFeatureMapping.h
index 14ba2b6..4810cee 100644
--- a/src/dawn/common/WGSLFeatureMapping.h
+++ b/src/dawn/common/WGSLFeatureMapping.h
@@ -32,6 +32,7 @@
X(kUndefined, Undefined) \
X(kReadonlyAndReadwriteStorageTextures, ReadonlyAndReadwriteStorageTextures) \
X(kPacked4X8IntegerDotProduct, Packed4x8IntegerDotProduct) \
+ X(kPointerCompositeAccess, PointerCompositeAccess) \
X(kChromiumTestingUnimplemented, ChromiumTestingUnimplemented) \
X(kChromiumTestingUnsafeExperimental, ChromiumTestingUnsafeExperimental) \
X(kChromiumTestingExperimental, ChromiumTestingExperimental) \
diff --git a/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
index 452abc5..38219bd 100644
--- a/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
@@ -652,7 +652,7 @@
break;
default: {
auto* vec = TypeOf(lhs_row_access->object)
- ->UnwrapRef()
+ ->UnwrapPtrOrRef()
->As<core::type::Vector>();
TINT_UNREACHABLE() << "invalid vector size " << vec->Width();
break;
@@ -908,10 +908,24 @@
bool ASTPrinter::EmitAssign(const ast::AssignmentStatement* stmt) {
if (auto* lhs_access = stmt->lhs->As<ast::IndexAccessorExpression>()) {
+ auto validate_obj_not_pointer = [&](const core::type::Type* object_ty) {
+ if (TINT_UNLIKELY(object_ty->Is<core::type::Pointer>())) {
+ TINT_ICE() << "lhs of index accessor should not be a pointer. These should have "
+ "been removed by transforms such as SimplifyPointers, "
+ "DecomposeMemoryAccess, and DirectVariableAccess";
+ return false;
+ }
+ return true;
+ };
+
// BUG(crbug.com/tint/1333): work around assignment of scalar to matrices
// with at least one dynamic index
if (auto* lhs_sub_access = lhs_access->object->As<ast::IndexAccessorExpression>()) {
- if (auto* mat = TypeOf(lhs_sub_access->object)->UnwrapRef()->As<core::type::Matrix>()) {
+ const auto* lhs_sub_access_type = TypeOf(lhs_sub_access->object);
+ if (!validate_obj_not_pointer(lhs_sub_access_type)) {
+ return false;
+ }
+ if (auto* mat = lhs_sub_access_type->UnwrapRef()->As<core::type::Matrix>()) {
auto* rhs_row_idx_sem = builder_.Sem().GetVal(lhs_access->index);
auto* rhs_col_idx_sem = builder_.Sem().GetVal(lhs_sub_access->index);
if (!rhs_row_idx_sem->ConstantValue() || !rhs_col_idx_sem->ConstantValue()) {
@@ -921,8 +935,11 @@
}
// BUG(crbug.com/tint/1333): work around assignment of vector to matrices
// with dynamic indices
- const auto* lhs_access_type = TypeOf(lhs_access->object)->UnwrapRef();
- if (auto* mat = lhs_access_type->As<core::type::Matrix>()) {
+ const auto* lhs_access_type = TypeOf(lhs_access->object);
+ if (!validate_obj_not_pointer(lhs_access_type)) {
+ return false;
+ }
+ if (auto* mat = lhs_access_type->UnwrapRef()->As<core::type::Matrix>()) {
auto* lhs_index_sem = builder_.Sem().GetVal(lhs_access->index);
if (!lhs_index_sem->ConstantValue()) {
return EmitDynamicMatrixVectorAssignment(stmt, mat);
@@ -930,7 +947,7 @@
}
// BUG(crbug.com/tint/534): work around assignment to vectors with dynamic
// indices
- if (auto* vec = lhs_access_type->As<core::type::Vector>()) {
+ if (auto* vec = lhs_access_type->UnwrapRef()->As<core::type::Vector>()) {
auto* rhs_sem = builder_.Sem().GetVal(lhs_access->index);
if (!rhs_sem->ConstantValue()) {
return EmitDynamicVectorAssignment(stmt, vec);
diff --git a/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length.cc b/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length.cc
index 0dacf5e..3dd8c8a 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length.cc
@@ -178,6 +178,12 @@
"&struct_var.array_member";
break;
}
+ if (TINT_UNLIKELY(storage_buffer_sem->Type()->Is<core::type::Pointer>())) {
+ TINT_ICE()
+ << "storage buffer variable should not be a pointer. These should have "
+ "been removed by the SimplifyPointers transform";
+ break;
+ }
auto* storage_buffer_var = storage_buffer_sem->Variable();
auto* storage_buffer_type =
storage_buffer_sem->Type()->As<core::type::Reference>();
diff --git a/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length_test.cc b/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length_test.cc
index 4768b3a..60e769b 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length_test.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/calculate_array_length_test.cc
@@ -149,6 +149,47 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(CalculateArrayLengthTest, BasicInStruct_ViaPointerDot) {
+ auto* src = R"(
+struct SB {
+ x : i32,
+ arr : array<i32>,
+};
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@compute @workgroup_size(1)
+fn main() {
+ let p = &sb;
+ var len : u32 = arrayLength(&p.arr);
+}
+)";
+
+ auto* expect = R"(
+@internal(intrinsic_buffer_size)
+fn tint_symbol(@internal(disable_validation__function_parameter) buffer : ptr<storage, SB, read>, result : ptr<function, u32>)
+
+struct SB {
+ x : i32,
+ arr : array<i32>,
+}
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@compute @workgroup_size(1)
+fn main() {
+ var tint_symbol_1 : u32 = 0u;
+ tint_symbol(&(sb), &(tint_symbol_1));
+ let tint_symbol_2 : u32 = ((tint_symbol_1 - 4u) / 4u);
+ var len : u32 = tint_symbol_2;
+}
+)";
+
+ auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(CalculateArrayLengthTest, ArrayOfStruct) {
auto* src = R"(
struct S {
diff --git a/src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access_test.cc b/src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access_test.cc
index 32414a1..d9addc3 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access_test.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access_test.cc
@@ -26,8 +26,8 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/tint/lang/hlsl/writer/ast_raise/decompose_memory_access.h"
-
#include "src/tint/lang/wgsl/ast/transform/helper_test.h"
+#include "src/tint/lang/wgsl/ast/transform/simplify_pointers.h"
namespace tint::hlsl::writer {
namespace {
@@ -3198,6 +3198,81 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(DecomposeMemoryAccessTest, ComplexStaticAccessChain_ViaPointerDot) {
+ auto* src = R"(
+// sizeof(S1) == 32
+// alignof(S1) == 16
+struct S1 {
+ a : i32,
+ b : vec3<f32>,
+ c : i32,
+};
+
+// sizeof(S2) == 116
+// alignof(S2) == 16
+struct S2 {
+ a : i32,
+ b : array<S1, 3>,
+ c : i32,
+};
+
+struct SB {
+ @size(128)
+ a : i32,
+ b : array<S2>,
+};
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@compute @workgroup_size(1)
+fn main() {
+ let p = &sb;
+ var x : f32 = (*p).b[4].b[1].b.z;
+}
+)";
+
+ // sb.b[4].b[1].b.z
+ // ^ ^ ^ ^ ^ ^
+ // | | | | | |
+ // 128 | |688 | 712
+ // | | |
+ // 640 656 704
+
+ auto* expect = R"(
+struct S1 {
+ a : i32,
+ b : vec3<f32>,
+ c : i32,
+}
+
+struct S2 {
+ a : i32,
+ b : array<S1, 3>,
+ c : i32,
+}
+
+struct SB {
+ @size(128)
+ a : i32,
+ b : array<S2>,
+}
+
+@group(0) @binding(0) var<storage, read_write> sb : SB;
+
+@internal(intrinsic_load_storage_f32) @internal(disable_validation__function_has_no_body)
+fn sb_load(offset : u32) -> f32
+
+@compute @workgroup_size(1)
+fn main() {
+ var x : f32 = sb_load(712u);
+}
+)";
+
+ auto got = Run<ast::transform::SimplifyPointers, DecomposeMemoryAccess>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(DecomposeMemoryAccessTest, ComplexStaticAccessChain_OutOfOrder) {
auto* src = R"(
@compute @workgroup_size(1)
diff --git a/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment.cc b/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment.cc
index c52041d..84574c8 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment.cc
@@ -183,8 +183,15 @@
if (!idx_sem->ConstantValue()) {
// Indexing a member access expr?
if (auto* ma = ia->object->As<ast::MemberAccessorExpression>()) {
+ const auto* ma_ty = src.TypeOf(ma);
+ if (TINT_UNLIKELY(ma_ty->Is<core::type::Pointer>())) {
+ TINT_ICE()
+ << "lhs of index accessor expression should not be a pointer. These "
+ "should have been removed by the SimplifyPointers transform";
+ return ast::TraverseAction::Stop;
+ }
// That accesses an array?
- if (src.TypeOf(ma)->UnwrapRef()->Is<core::type::Array>()) {
+ if (ma_ty->UnwrapRef()->Is<core::type::Array>()) {
result = true;
return ast::TraverseAction::Stop;
}
diff --git a/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment_test.cc b/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment_test.cc
index 0ad39aa..c6a6f6b 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment_test.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/localize_struct_array_assignment_test.cc
@@ -699,6 +699,67 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(LocalizeStructArrayAssignmentTest, ViaPointerArg_PointerDot) {
+ auto* src = R"(
+struct Uniforms {
+ i : u32,
+};
+struct InnerS {
+ v : i32,
+};
+struct OuterS {
+ a1 : array<InnerS, 8>,
+};
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+fn f(p : ptr<function, OuterS>) {
+ var v : InnerS;
+ p.a1[uniforms.i] = v;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ var s1 : OuterS;
+ f(&s1);
+}
+)";
+
+ auto* expect = R"(
+struct Uniforms {
+ i : u32,
+}
+
+struct InnerS {
+ v : i32,
+}
+
+struct OuterS {
+ a1 : array<InnerS, 8>,
+}
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+fn f(p : ptr<function, OuterS>) {
+ var v : InnerS;
+ {
+ let tint_symbol = &((*(p)).a1);
+ var tint_symbol_1 = *(tint_symbol);
+ tint_symbol_1[uniforms.i] = v;
+ *(tint_symbol) = tint_symbol_1;
+ }
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ var s1 : OuterS;
+ f(&(s1));
+}
+)";
+
+ auto got = Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(LocalizeStructArrayAssignmentTest, ViaPointerArg_OutOfOrder) {
auto* src = R"(
@compute @workgroup_size(1)
@@ -858,5 +919,91 @@
EXPECT_FALSE(ShouldRun<LocalizeStructArrayAssignment>(src));
}
+TEST_F(LocalizeStructArrayAssignmentTest, ArrayStructArray) {
+ auto* src = R"(
+struct Uniforms {
+ i : u32,
+};
+
+struct InnerS {
+ v : i32,
+};
+
+struct OuterS {
+ a1 : array<InnerS, 8>,
+};
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@compute @workgroup_size(1)
+fn main() {
+ var v : InnerS;
+ var s1 : array<OuterS, 2>;
+ s1[uniforms.i].a1[uniforms.i] = v;
+}
+)";
+
+ // Transform does nothing as the struct-of-array is in an array, which FXC has no problem with.
+ EXPECT_FALSE(ShouldRun<LocalizeStructArrayAssignment>(src));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, ArrayStructArray_ViaPointerDerefIndex) {
+ auto* src = R"(
+struct Uniforms {
+ i : u32,
+};
+
+struct InnerS {
+ v : i32,
+};
+
+struct OuterS {
+ a1 : array<InnerS, 8>,
+};
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@compute @workgroup_size(1)
+fn main() {
+ var v : InnerS;
+ var s1 : array<OuterS, 2>;
+ let p = &s1;
+ (*p)[uniforms.i].a1[uniforms.i] = v;
+}
+)";
+
+ // Transform does nothing as the struct-of-array is in an array, which FXC has no problem with.
+ EXPECT_FALSE(ShouldRun<LocalizeStructArrayAssignment>(src));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, ArrayStructArray_ViaPointerIndex) {
+ auto* src = R"(
+struct Uniforms {
+ i : u32,
+};
+
+struct InnerS {
+ v : i32,
+};
+
+struct OuterS {
+ a1 : array<InnerS, 8>,
+};
+
+@group(1) @binding(4) var<uniform> uniforms : Uniforms;
+
+@compute @workgroup_size(1)
+fn main() {
+ var v : InnerS;
+ var s1 : array<OuterS, 2>;
+ let p = &s1;
+ p[uniforms.i].a1[uniforms.i] = v;
+}
+)";
+
+ // Transform does nothing as the struct-of-array is in an array, which FXC has no problem with.
+ EXPECT_FALSE(ShouldRun<LocalizeStructArrayAssignment>(src));
+}
+
} // namespace
} // namespace tint::hlsl::writer
diff --git a/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform_test.cc b/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform_test.cc
index a8fed98..d27b132 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform_test.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform_test.cc
@@ -111,6 +111,124 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(NumWorkgroupsFromUniformTest, Basic_VarCopy) {
+ auto* src = R"(
+@compute @workgroup_size(1)
+fn main(@builtin(num_workgroups) num_wgs : vec3<u32>) {
+ var a = num_wgs;
+ let groups_x = a.x;
+ let groups_y = a.y;
+ let groups_z = a.z;
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol_2 {
+ num_workgroups : vec3<u32>,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
+
+fn main_inner(num_wgs : vec3<u32>) {
+ var a = num_wgs;
+ let groups_x = a.x;
+ let groups_y = a.y;
+ let groups_z = a.z;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ main_inner(tint_symbol_3.num_workgroups);
+}
+)";
+
+ ast::transform::DataMap data;
+ data.Add<CanonicalizeEntryPointIO::Config>(CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+ data.Add<NumWorkgroupsFromUniform::Config>(BindingPoint{0, 30u});
+ auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(src, data);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(NumWorkgroupsFromUniformTest, Basic_VarCopy_ViaPointerDerefDot) {
+ auto* src = R"(
+@compute @workgroup_size(1)
+fn main(@builtin(num_workgroups) num_wgs : vec3<u32>) {
+ var a = num_wgs;
+ let p = &a;
+ let groups_x = (*p).x;
+ let groups_y = (*p).y;
+ let groups_z = (*p).z;
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol_2 {
+ num_workgroups : vec3<u32>,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
+
+fn main_inner(num_wgs : vec3<u32>) {
+ var a = num_wgs;
+ let p = &(a);
+ let groups_x = (*(p)).x;
+ let groups_y = (*(p)).y;
+ let groups_z = (*(p)).z;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ main_inner(tint_symbol_3.num_workgroups);
+}
+)";
+
+ ast::transform::DataMap data;
+ data.Add<CanonicalizeEntryPointIO::Config>(CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+ data.Add<NumWorkgroupsFromUniform::Config>(BindingPoint{0, 30u});
+ auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(src, data);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(NumWorkgroupsFromUniformTest, Basic_VarCopy_ViaPointerDot) {
+ auto* src = R"(
+@compute @workgroup_size(1)
+fn main(@builtin(num_workgroups) num_wgs : vec3<u32>) {
+ var a = num_wgs;
+ let p = &a;
+ let groups_x = p.x;
+ let groups_y = p.y;
+ let groups_z = p.z;
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol_2 {
+ num_workgroups : vec3<u32>,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
+
+fn main_inner(num_wgs : vec3<u32>) {
+ var a = num_wgs;
+ let p = &(a);
+ let groups_x = p.x;
+ let groups_y = p.y;
+ let groups_z = p.z;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ main_inner(tint_symbol_3.num_workgroups);
+}
+)";
+
+ ast::transform::DataMap data;
+ data.Add<CanonicalizeEntryPointIO::Config>(CanonicalizeEntryPointIO::ShaderStyle::kHlsl);
+ data.Add<NumWorkgroupsFromUniform::Config>(BindingPoint{0, 30u});
+ auto got = Run<Unshadow, CanonicalizeEntryPointIO, NumWorkgroupsFromUniform>(src, data);
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(NumWorkgroupsFromUniformTest, StructOnlyMember) {
auto* src = R"(
struct Builtins {
diff --git a/src/tint/lang/msl/writer/ast_raise/packed_vec3_test.cc b/src/tint/lang/msl/writer/ast_raise/packed_vec3_test.cc
index b3f3fb1..325f528 100644
--- a/src/tint/lang/msl/writer/ast_raise/packed_vec3_test.cc
+++ b/src/tint/lang/msl/writer/ast_raise/packed_vec3_test.cc
@@ -291,6 +291,60 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(PackedVec3Test, Vec3_ReadComponent_IndexAccessor_ViaDerefPointerIndex) {
+ auto* src = R"(
+@group(0) @binding(0) var<storage> v : vec3<f32>;
+
+fn f() {
+ let p = &v;
+ let x = (*p)[1];
+}
+)";
+
+ auto* expect = R"(
+enable chromium_internal_relaxed_uniform_layout;
+
+@group(0) @binding(0) var<storage> v : __packed_vec3<f32>;
+
+fn f() {
+ let p = &(v);
+ let x = (*(p))[1];
+}
+)";
+
+ ast::transform::DataMap data;
+ auto got = Run<PackedVec3>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PackedVec3Test, Vec3_ReadComponent_IndexAccessor_ViaPointerIndex) {
+ auto* src = R"(
+@group(0) @binding(0) var<storage> v : vec3<f32>;
+
+fn f() {
+ let p = &v;
+ let x = p[1];
+}
+)";
+
+ auto* expect = R"(
+enable chromium_internal_relaxed_uniform_layout;
+
+@group(0) @binding(0) var<storage> v : __packed_vec3<f32>;
+
+fn f() {
+ let p = &(v);
+ let x = p[1];
+}
+)";
+
+ ast::transform::DataMap data;
+ auto got = Run<PackedVec3>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(PackedVec3Test, Vec3_WriteVector_ValueRHS) {
auto* src = R"(
@group(0) @binding(0) var<storage, read_write> v : vec3<f32>;
@@ -369,6 +423,60 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(PackedVec3Test, Vec3_WriteComponent_MemberAccessor_ViaDerefPointerDot) {
+ auto* src = R"(
+@group(0) @binding(0) var<storage, read_write> v : vec3<f32>;
+
+fn f() {
+ let p = &v;
+ (*p).y = 1.23;
+}
+)";
+
+ auto* expect = R"(
+enable chromium_internal_relaxed_uniform_layout;
+
+@group(0) @binding(0) var<storage, read_write> v : __packed_vec3<f32>;
+
+fn f() {
+ let p = &(v);
+ (*(p)).y = 1.22999999999999998224;
+}
+)";
+
+ ast::transform::DataMap data;
+ auto got = Run<PackedVec3>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PackedVec3Test, Vec3_WriteComponent_MemberAccessor_ViaPointerDot) {
+ auto* src = R"(
+@group(0) @binding(0) var<storage, read_write> v : vec3<f32>;
+
+fn f() {
+ let p = &v;
+ p.y = 1.23;
+}
+)";
+
+ auto* expect = R"(
+enable chromium_internal_relaxed_uniform_layout;
+
+@group(0) @binding(0) var<storage, read_write> v : __packed_vec3<f32>;
+
+fn f() {
+ let p = &(v);
+ p.y = 1.22999999999999998224;
+}
+)";
+
+ ast::transform::DataMap data;
+ auto got = Run<PackedVec3>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(PackedVec3Test, Vec3_WriteComponent_IndexAccessor) {
auto* src = R"(
@group(0) @binding(0) var<storage, read_write> v : vec3<f32>;
diff --git a/src/tint/lang/spirv/reader/ast_lower/atomics_test.cc b/src/tint/lang/spirv/reader/ast_lower/atomics_test.cc
index 54a1f9d..818297c 100644
--- a/src/tint/lang/spirv/reader/ast_lower/atomics_test.cc
+++ b/src/tint/lang/spirv/reader/ast_lower/atomics_test.cc
@@ -200,6 +200,54 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(AtomicsTest, ArrayOfU32_ViaDerefPointerIndex) {
+ auto* src = R"(
+var<workgroup> wg : array<u32, 4>;
+
+fn f() {
+ let p = &wg;
+ stub_atomicStore_u32((*p)[1], 1u);
+}
+)";
+
+ auto* expect = R"(
+var<workgroup> wg : array<atomic<u32>, 4u>;
+
+fn f() {
+ let p = &(wg);
+ atomicStore(&((*(p))[1]), 1u);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AtomicsTest, ArrayOfU32_ViaPointerIndex) {
+ auto* src = R"(
+var<workgroup> wg : array<u32, 4>;
+
+fn f() {
+ let p = &wg;
+ stub_atomicStore_u32(p[1], 1u);
+}
+)";
+
+ auto* expect = R"(
+var<workgroup> wg : array<atomic<u32>, 4u>;
+
+fn f() {
+ let p = &(wg);
+ atomicStore(&(p[1]), 1u);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(AtomicsTest, ArraysOfU32) {
auto* src = R"(
var<workgroup> wg : array<array<array<u32, 1>, 2>, 3>;
@@ -294,6 +342,78 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(AtomicsTest, FlatStructSingleAtomic_ViaDerefPointerDot) {
+ auto* src = R"(
+struct S {
+ a : u32,
+}
+
+var<workgroup> wg : S;
+
+fn f() {
+ let p = &wg;
+ stub_atomicStore_u32((*p).a, 1u);
+}
+)";
+
+ auto* expect = R"(
+struct S_atomic {
+ a : atomic<u32>,
+}
+
+struct S {
+ a : u32,
+}
+
+var<workgroup> wg : S_atomic;
+
+fn f() {
+ let p = &(wg);
+ atomicStore(&((*(p)).a), 1u);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AtomicsTest, FlatStructSingleAtomic_ViaPointerDot) {
+ auto* src = R"(
+struct S {
+ a : u32,
+}
+
+var<workgroup> wg : S;
+
+fn f() {
+ let p = &wg;
+ stub_atomicStore_u32(p.a, 1u);
+}
+)";
+
+ auto* expect = R"(
+struct S_atomic {
+ a : atomic<u32>,
+}
+
+struct S {
+ a : u32,
+}
+
+var<workgroup> wg : S_atomic;
+
+fn f() {
+ let p = &(wg);
+ atomicStore(&(p.a), 1u);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(AtomicsTest, FlatStructMultipleAtomic) {
auto* src = R"(
struct S {
@@ -455,6 +575,90 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(AtomicsTest, ArrayOfStruct_ViaDerefPointerIndex) {
+ auto* src = R"(
+struct S {
+ a : u32,
+ b : i32,
+ c : u32,
+}
+
+@group(0) @binding(1) var<storage, read_write> arr : array<S>;
+
+fn f() {
+ let p = &arr;
+ stub_atomicStore_i32((*p)[4].b, 1i);
+}
+)";
+
+ auto* expect = R"(
+struct S_atomic {
+ a : u32,
+ b : atomic<i32>,
+ c : u32,
+}
+
+struct S {
+ a : u32,
+ b : i32,
+ c : u32,
+}
+
+@group(0) @binding(1) var<storage, read_write> arr : array<S_atomic>;
+
+fn f() {
+ let p = &(arr);
+ atomicStore(&((*(p))[4].b), 1i);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(AtomicsTest, ArrayOfStruct_ViaPointerIndex) {
+ auto* src = R"(
+struct S {
+ a : u32,
+ b : i32,
+ c : u32,
+}
+
+@group(0) @binding(1) var<storage, read_write> arr : array<S>;
+
+fn f() {
+ let p = &arr;
+ stub_atomicStore_i32(p[4].b, 1i);
+}
+)";
+
+ auto* expect = R"(
+struct S_atomic {
+ a : u32,
+ b : atomic<i32>,
+ c : u32,
+}
+
+struct S {
+ a : u32,
+ b : i32,
+ c : u32,
+}
+
+@group(0) @binding(1) var<storage, read_write> arr : array<S_atomic>;
+
+fn f() {
+ let p = &(arr);
+ atomicStore(&(p[4].b), 1i);
+}
+)";
+
+ auto got = Run(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(AtomicsTest, StructOfArray) {
auto* src = R"(
struct S {
diff --git a/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array.cc b/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array.cc
index e26ac38..47e242b8 100644
--- a/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array.cc
+++ b/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array.cc
@@ -143,6 +143,11 @@
// Example: `arr[i]` -> `arr[i].el`
ctx.ReplaceAll([&](const ast::IndexAccessorExpression* idx) -> const ast::Expression* {
if (auto* ty = src.TypeOf(idx->object)) {
+ if (TINT_UNLIKELY(ty->Is<core::type::Pointer>())) {
+ TINT_ICE() << "lhs of index accessor expression should not be a pointer. These "
+ "should have been removed by the SimplifyPointers transform";
+ return nullptr;
+ }
if (auto* arr = ty->UnwrapRef()->As<core::type::Array>()) {
if (!arr->IsStrideImplicit()) {
auto* expr = ctx.CloneWithoutTransform(idx);
diff --git a/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array_test.cc b/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array_test.cc
index 87d7ceb..84103ce 100644
--- a/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array_test.cc
+++ b/src/tint/lang/spirv/reader/ast_lower/decompose_strided_array_test.cc
@@ -100,7 +100,7 @@
//
// @compute @workgroup_size(1)
// fn f() {
- // let a : @stride(4) array<f32, 4u> = a;
+ // let a : @stride(4) array<f32, 4u> = arr;
// let b : f32 = arr[1];
// }
@@ -139,6 +139,52 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(DecomposeStridedArrayTest, PrivateDefaultStridedArray_ViaPointerIndex) {
+ // var<private> arr : @stride(4) array<f32, 4u>
+ //
+ // @compute @workgroup_size(1)
+ // fn f() {
+ // let a : @stride(4) array<f32, 4u> = arr;
+ // let p = &arr;
+ // let b : f32 = p[1];
+ // }
+
+ ProgramBuilder b;
+ b.GlobalVar("arr",
+ b.ty.array<f32, 4u>(Vector{
+ b.Stride(4),
+ }),
+ core::AddressSpace::kPrivate);
+ b.Func("f", tint::Empty, b.ty.void_(),
+ Vector{
+ b.Decl(b.Let("a",
+ b.ty.array<f32, 4u>(Vector{
+ b.Stride(4),
+ }),
+ b.Expr("arr"))),
+ b.Decl(b.Let("p", b.AddressOf(b.Expr("arr")))),
+ b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor("p", 1_i))),
+ },
+ Vector{
+ b.Stage(ast::PipelineStage::kCompute),
+ b.WorkgroupSize(1_i),
+ });
+
+ auto* expect = R"(
+var<private> arr : array<f32, 4u>;
+
+@compute @workgroup_size(1i)
+fn f() {
+ let a : array<f32, 4u> = arr;
+ let b : f32 = arr[1i];
+}
+)";
+
+ auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(resolver::Resolve(b));
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(DecomposeStridedArrayTest, PrivateStridedArray) {
// var<private> arr : @stride(32) array<f32, 4u>
//
diff --git a/src/tint/lang/spirv/writer/ast_printer/accessor_expression_test.cc b/src/tint/lang/spirv/writer/ast_printer/accessor_expression_test.cc
index f09814b..1dde2c8 100644
--- a/src/tint/lang/spirv/writer/ast_printer/accessor_expression_test.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/accessor_expression_test.cc
@@ -138,6 +138,84 @@
Validate(b);
}
+TEST_F(SpirvASTPrinterTest, Runtime_IndexAccessor_Vector_ViaDerefPointerIndex) {
+ // var ary : vec3<u32>;
+ // let p = &ary;
+ // var x = (*p)[1i];
+
+ auto* ary = Var("ary", ty.vec3<u32>());
+ auto* p = Let("p", AddressOf("ary"));
+ auto* x = Var("x", IndexAccessor(Deref(p), 1_i));
+ WrapInFunction(ary, p, x);
+
+ Builder& b = SanitizeAndBuild();
+
+ ASSERT_TRUE(b.Build()) << b.Diagnostics();
+
+ EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%8 = OpTypeInt 32 0
+%7 = OpTypeVector %8 3
+%6 = OpTypePointer Function %7
+%9 = OpConstantNull %7
+%10 = OpTypeInt 32 1
+%11 = OpConstant %10 1
+%12 = OpTypePointer Function %8
+%16 = OpConstantNull %8
+)");
+ EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+ R"(%5 = OpVariable %6 Function %9
+%15 = OpVariable %12 Function %16
+)");
+ EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
+ R"(%13 = OpAccessChain %12 %5 %11
+%14 = OpLoad %8 %13
+OpStore %15 %14
+OpReturn
+)");
+
+ Validate(b);
+}
+
+TEST_F(SpirvASTPrinterTest, Runtime_IndexAccessor_Vector_ViaPointerIndex) {
+ // var ary : vec3<u32>;
+ // let p = &ary;
+ // var x = p[1i];
+
+ auto* ary = Var("ary", ty.vec3<u32>());
+ auto* p = Let("p", AddressOf("ary"));
+ auto* x = Var("x", IndexAccessor(p, 1_i));
+ WrapInFunction(ary, p, x);
+
+ Builder& b = SanitizeAndBuild();
+
+ ASSERT_TRUE(b.Build()) << b.Diagnostics();
+
+ EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%8 = OpTypeInt 32 0
+%7 = OpTypeVector %8 3
+%6 = OpTypePointer Function %7
+%9 = OpConstantNull %7
+%10 = OpTypeInt 32 1
+%11 = OpConstant %10 1
+%12 = OpTypePointer Function %8
+%16 = OpConstantNull %8
+)");
+ EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+ R"(%5 = OpVariable %6 Function %9
+%15 = OpVariable %12 Function %16
+)");
+ EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
+ R"(%13 = OpAccessChain %12 %5 %11
+%14 = OpLoad %8 %13
+OpStore %15 %14
+OpReturn
+)");
+
+ Validate(b);
+}
+
TEST_F(SpirvASTPrinterTest, Dynamic_IndexAccessor_Vector) {
// var ary : vec3<f32>;
// var idx : i32;
@@ -978,6 +1056,96 @@
Validate(b);
}
+TEST_F(SpirvASTPrinterTest, MemberAccessor_ViaDerefPointerDot) {
+ // my_struct {
+ // a : f32
+ // b : f32
+ // }
+ // var ident : my_struct
+ // let p = &ident;
+ // (*p).b
+
+ auto* s = Structure("my_struct", Vector{
+ Member("a", ty.f32()),
+ Member("b", ty.f32()),
+ });
+
+ auto* var = Var("ident", ty.Of(s));
+ auto* p = Let("p", AddressOf(var));
+ auto* expr = MemberAccessor(Deref(p), "b");
+ WrapInFunction(var, p, expr);
+
+ Builder& b = SanitizeAndBuild();
+
+ ASSERT_TRUE(b.Build()) << b.Diagnostics();
+
+ EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%8 = OpTypeFloat 32
+%7 = OpTypeStruct %8 %8
+%6 = OpTypePointer Function %7
+%9 = OpConstantNull %7
+%10 = OpTypeInt 32 0
+%11 = OpConstant %10 1
+%12 = OpTypePointer Function %8
+)");
+ EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+ R"(%5 = OpVariable %6 Function %9
+)");
+ EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
+ R"(%13 = OpAccessChain %12 %5 %11
+%14 = OpLoad %8 %13
+OpReturn
+)");
+
+ Validate(b);
+}
+
+TEST_F(SpirvASTPrinterTest, MemberAccessor_ViaPointerDot) {
+ // my_struct {
+ // a : f32
+ // b : f32
+ // }
+ // var ident : my_struct
+ // let p = &ident;
+ // p.b
+
+ auto* s = Structure("my_struct", Vector{
+ Member("a", ty.f32()),
+ Member("b", ty.f32()),
+ });
+
+ auto* var = Var("ident", ty.Of(s));
+ auto* p = Let("p", AddressOf(var));
+ auto* expr = MemberAccessor(p, "b");
+ WrapInFunction(var, p, expr);
+
+ Builder& b = SanitizeAndBuild();
+
+ ASSERT_TRUE(b.Build()) << b.Diagnostics();
+
+ EXPECT_EQ(DumpInstructions(b.Module().Types()), R"(%2 = OpTypeVoid
+%1 = OpTypeFunction %2
+%8 = OpTypeFloat 32
+%7 = OpTypeStruct %8 %8
+%6 = OpTypePointer Function %7
+%9 = OpConstantNull %7
+%10 = OpTypeInt 32 0
+%11 = OpConstant %10 1
+%12 = OpTypePointer Function %8
+)");
+ EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].variables()),
+ R"(%5 = OpVariable %6 Function %9
+)");
+ EXPECT_EQ(DumpInstructions(b.Module().Functions()[0].instructions()),
+ R"(%13 = OpAccessChain %12 %5 %11
+%14 = OpLoad %8 %13
+OpReturn
+)");
+
+ Validate(b);
+}
+
TEST_F(SpirvASTPrinterTest, MemberAccessor_Nested) {
// inner_struct {
// a : f32
diff --git a/src/tint/lang/spirv/writer/ast_printer/builder.cc b/src/tint/lang/spirv/writer/ast_printer/builder.cc
index 19a5662..b736b28 100644
--- a/src/tint/lang/spirv/writer/ast_printer/builder.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/builder.cc
@@ -866,12 +866,14 @@
}
// If the source is a reference, we access chain into it.
- // In the future, pointers may support access-chaining.
- // See https://github.com/gpuweb/gpuweb/pull/1580
if (info->source_type->Is<core::type::Reference>()) {
info->access_chain_indices.push_back(idx_id);
info->source_type = builder_.Sem().Get(expr)->UnwrapLoad()->Type();
return true;
+ } else if (TINT_UNLIKELY(info->source_type->Is<core::type::Pointer>())) {
+ TINT_ICE() << "lhs of index accesor expression should not be a pointer. These should have "
+ "been removed by the SimplifyPointers transform";
+ return false;
}
auto result_type_id = GenerateTypeIfNeeded(TypeOf(expr));
diff --git a/src/tint/lang/spirv/writer/ast_raise/var_for_dynamic_index_test.cc b/src/tint/lang/spirv/writer/ast_raise/var_for_dynamic_index_test.cc
index 91654cd..5d95703 100644
--- a/src/tint/lang/spirv/writer/ast_raise/var_for_dynamic_index_test.cc
+++ b/src/tint/lang/spirv/writer/ast_raise/var_for_dynamic_index_test.cc
@@ -470,6 +470,76 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(VarForDynamicIndexTest, VarArrayIndexDynamic) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ var p = array<i32, 4>(1, 2, 3, 4);
+ let x = p[i];
+}
+)";
+
+ auto* expect = src;
+
+ ast::transform::DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, VarMatrixIndexDynamic) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ var p = mat2x2(1.0, 2.0, 3.0, 4.0);
+ let x = p[i];
+}
+)";
+
+ auto* expect = src;
+
+ ast::transform::DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, VarArrayIndexDynamic_ViaPointerIndex) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ var v = array<i32, 4>(1, 2, 3, 4);
+ let p = &(v);
+ let x = p[i];
+}
+)";
+
+ auto* expect = src;
+
+ ast::transform::DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(VarForDynamicIndexTest, VarMatrixIndexDynamic_ViaPointerIndex) {
+ auto* src = R"(
+fn f() {
+ var i : i32;
+ var v = mat2x2(1.0, 2.0, 3.0, 4.0);
+ let p = &(v);
+ let x = p[i];
+}
+)";
+
+ auto* expect = src;
+
+ ast::transform::DataMap data;
+ auto got = Run<VarForDynamicIndex>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(VarForDynamicIndexTest, ArrayIndexLiteral) {
auto* src = R"(
fn f() {
diff --git a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc
index d98068d..27b354c 100644
--- a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc
+++ b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc
@@ -162,6 +162,11 @@
// array_length = ----------------------------------------
// array_stride
const Expression* total_size = total_storage_buffer_size;
+ if (TINT_UNLIKELY(storage_buffer_sem->Type()->Is<core::type::Pointer>())) {
+ TINT_ICE() << "storage buffer variable should not be a pointer. These should have "
+ "been removed by the SimplifyPointers transform";
+ return;
+ }
auto* storage_buffer_type = storage_buffer_sem->Type()->UnwrapRef();
const core::type::Array* array_type = nullptr;
if (auto* str = storage_buffer_type->As<core::type::Struct>()) {
diff --git a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform_test.cc b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform_test.cc
index b6b4ab2..2a9c6ac 100644
--- a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform_test.cc
@@ -206,6 +206,57 @@
got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
}
+// Should output the same as BasicInStruct because SimplifyPointers outputs the same AST for
+// explicit and implicit pointer dereference.
+TEST_F(ArrayLengthFromUniformTest, BasicInStruct_ViaPointerDot) {
+ auto* src = R"(
+struct SB {
+ x : i32,
+ arr : array<i32>,
+};
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@compute @workgroup_size(1)
+fn main() {
+ let p = &sb;
+ var len : u32 = arrayLength(&p.arr);
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol {
+ buffer_size : array<vec4<u32>, 1u>,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+struct SB {
+ x : i32,
+ arr : array<i32>,
+}
+
+@group(0) @binding(0) var<storage, read> sb : SB;
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
+}
+)";
+
+ ArrayLengthFromUniform::Config cfg({0, 30u});
+ cfg.bindpoint_to_size_index.emplace(BindingPoint{0, 0}, 0);
+
+ DataMap data;
+ data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
+
+ auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+ EXPECT_EQ(std::unordered_set<uint32_t>({0}),
+ got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
+}
+
TEST_F(ArrayLengthFromUniformTest, MultipleStorageBuffers) {
auto* src = R"(
struct SB1 {
diff --git a/src/tint/lang/wgsl/ast/transform/demote_to_helper_test.cc b/src/tint/lang/wgsl/ast/transform/demote_to_helper_test.cc
index 181a010..de590a4 100644
--- a/src/tint/lang/wgsl/ast/transform/demote_to_helper_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/demote_to_helper_test.cc
@@ -334,6 +334,56 @@
EXPECT_EQ(expect, str(got));
}
+// Test that write via sugared pointer also discards
+TEST_F(DemoteToHelperTest, WriteInEntryPoint_DiscardInEntryPoint_ViaPointerDot) {
+ auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+@group(0) @binding(2) var<storage, read_write> v : vec4<f32>;
+
+@fragment
+fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
+ if (in == 0.0) {
+ discard;
+ }
+ let ret = textureSample(t, s, coord);
+ let p = &v;
+ p.x = ret.x;
+}
+)";
+
+ auto* expect = R"(
+var<private> tint_discarded = false;
+
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+@group(0) @binding(2) var<storage, read_write> v : vec4<f32>;
+
+@fragment
+fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
+ if ((in == 0.0)) {
+ tint_discarded = true;
+ }
+ let ret = textureSample(t, s, coord);
+ let p = &(v);
+ if (!(tint_discarded)) {
+ p.x = ret.x;
+ }
+ if (tint_discarded) {
+ discard;
+ }
+}
+)";
+
+ auto got = Run<DemoteToHelper>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
// Test that no additional discards are inserted when the function unconditionally returns in a
// nested block.
TEST_F(DemoteToHelperTest, EntryPointReturn_NestedInBlock) {
@@ -667,6 +717,58 @@
EXPECT_EQ(expect, str(got));
}
+// Test that we do not mask writes to invocation-private address spaces via a sugared pointer write
+TEST_F(DemoteToHelperTest, InvocationPrivateWritesViaPointerDot) {
+ auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+var<private> vp : vec4<f32>;
+
+@fragment
+fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
+ if (in == 0.0) {
+ discard;
+ }
+ let ret = textureSample(t, s, coord);
+ var vf : f32;
+ vf = ret.x;
+ let p = &vp;
+ p.x = ret.x;
+}
+)";
+
+ auto* expect = R"(
+var<private> tint_discarded = false;
+
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@group(0) @binding(1) var s : sampler;
+
+var<private> vp : vec4<f32>;
+
+@fragment
+fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
+ if ((in == 0.0)) {
+ tint_discarded = true;
+ }
+ let ret = textureSample(t, s, coord);
+ var vf : f32;
+ vf = ret.x;
+ let p = &(vp);
+ p.x = ret.x;
+ if (tint_discarded) {
+ discard;
+ }
+}
+)";
+
+ auto got = Run<DemoteToHelper>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(DemoteToHelperTest, TextureStoreInEntryPoint) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
diff --git a/src/tint/lang/wgsl/ast/transform/direct_variable_access_test.cc b/src/tint/lang/wgsl/ast/transform/direct_variable_access_test.cc
index 33c68c1..cb5598f 100644
--- a/src/tint/lang/wgsl/ast/transform/direct_variable_access_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/direct_variable_access_test.cc
@@ -287,6 +287,74 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(DirectVariableAccessPtrChainsTest, ConstantIndices_ViaPointerIndex) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8>, 8>, 8>;
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+ return *p;
+}
+
+fn b() {
+ let p0 = &U;
+ let p1 = &p0[1];
+ let p2 = &p1[1+1];
+ let p3 = &p2[2*2 - 1];
+ a(10, p3, 20);
+}
+
+fn c(p : ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>>) {
+ let p0 = p;
+ let p1 = &p0[1];
+ let p2 = &p1[1+1];
+ let p3 = &p2[2*2 - 1];
+ a(10, p3, 20);
+}
+
+fn d() {
+ c(&U);
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8>, 8>, 8>;
+
+alias U_X_X_X = array<u32, 3u>;
+
+fn a_U_X_X_X(pre : i32, p : U_X_X_X, post : i32) -> vec4<i32> {
+ return U[p[0]][p[1]][p[2]];
+}
+
+fn b() {
+ let p0 = &(U);
+ let p1 = &(p0[1]);
+ let p2 = &(p1[(1 + 1)]);
+ let p3 = &(p2[((2 * 2) - 1)]);
+ a_U_X_X_X(10, U_X_X_X(1, 2, 3), 20);
+}
+
+fn c_U() {
+ let p0 = &(U);
+ let p1 = &(U[1]);
+ let p2 = &(U[1][2]);
+ let p3 = &(U[1][2][3]);
+ a_U_X_X_X(10, U_X_X_X(1, 2, 3), 20);
+}
+
+fn d() {
+ c_U();
+}
+)";
+
+ auto got = Run<DirectVariableAccess>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(DirectVariableAccessPtrChainsTest, HoistIndices) {
auto* src = R"(
enable chromium_experimental_full_ptr_parameters;
@@ -839,6 +907,46 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(DirectVariableAccessUniformASTest, Param_ptr_vec4i32_Via_array_DynamicRead_ViaPointerDot) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+ return *p;
+}
+
+fn b() {
+ var I = vec2<i32>(3, 3);
+ let p = &I;
+ a(10, &U[p.x], 20);
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<vec4<i32>, 8>;
+
+alias U_X = array<u32, 1u>;
+
+fn a_U_X(pre : i32, p : U_X, post : i32) -> vec4<i32> {
+ return U[p[0]];
+}
+
+fn b() {
+ var I = vec2<i32>(3, 3);
+ let p = &(I);
+ a_U_X(10, U_X(u32(p.x)), 20);
+}
+)";
+
+ auto got = Run<DirectVariableAccess>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(DirectVariableAccessUniformASTest, CallChaining) {
auto* src = R"(
enable chromium_experimental_full_ptr_parameters;
@@ -1002,6 +1110,169 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(DirectVariableAccessUniformASTest, CallChaining_ViaPointerDotOrIndex) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+ mat : mat3x4<f32>,
+};
+
+alias InnerArr = array<Inner, 4>;
+
+struct Outer {
+ arr : InnerArr,
+ mat : mat3x4<f32>,
+};
+
+@group(0) @binding(0) var<uniform> U : Outer;
+
+fn f0(p : ptr<uniform, vec4<f32>>) -> f32 {
+ return p.x;
+}
+
+fn f1(p : ptr<uniform, mat3x4<f32>>) -> f32 {
+ var res : f32;
+ {
+ // call f0() with inline usage of p
+ res += f0(&p[1]);
+ }
+ {
+ // call f0() with pointer-let usage of p
+ let p_vec = &p[1];
+ res += f0(p_vec);
+ }
+ {
+ // call f0() with inline usage of U
+ res += f0(&U.arr[2].mat[1]);
+ }
+ {
+ // call f0() with pointer-let usage of U
+ let p_vec = &U.arr[2].mat[1];
+ res += f0(p_vec);
+ }
+ return res;
+}
+
+fn f2(p : ptr<uniform, Inner>) -> f32 {
+ let p_mat = &p.mat;
+ return f1(p_mat);
+}
+
+fn f3(p0 : ptr<uniform, InnerArr>, p1 : ptr<uniform, mat3x4<f32>>) -> f32 {
+ let p0_inner = &(*p0)[3];
+ return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<uniform, Outer>) -> f32 {
+ return f3(&p.arr, &U.mat);
+}
+
+fn b() {
+ f4(&U);
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+ mat : mat3x4<f32>,
+}
+
+alias InnerArr = array<Inner, 4>;
+
+struct Outer {
+ arr : InnerArr,
+ mat : mat3x4<f32>,
+}
+
+@group(0) @binding(0) var<uniform> U : Outer;
+
+alias U_mat_X = array<u32, 1u>;
+
+fn f0_U_mat_X(p : U_mat_X) -> f32 {
+ return (&(U.mat[p[0]])).x;
+}
+
+alias U_arr_X_mat_X = array<u32, 2u>;
+
+fn f0_U_arr_X_mat_X(p : U_arr_X_mat_X) -> f32 {
+ return (&(U.arr[p[0]].mat[p[0]])).x;
+}
+
+alias U_arr_X_mat_X_1 = array<u32, 2u>;
+
+fn f0_U_arr_X_mat_X_1(p : U_arr_X_mat_X_1) -> f32 {
+ return (&(U.arr[p[0]].mat[p[1]])).x;
+}
+
+fn f1_U_mat() -> f32 {
+ var res : f32;
+ {
+ res += f0_U_mat_X(U_mat_X(1));
+ }
+ {
+ let p_vec = &(U.mat[1]);
+ res += f0_U_mat_X(U_mat_X(1));
+ }
+ {
+ res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+ }
+ {
+ let p_vec = &(U.arr[2].mat[1]);
+ res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+ }
+ return res;
+}
+
+alias U_arr_X_mat = array<u32, 1u>;
+
+fn f1_U_arr_X_mat(p : U_arr_X_mat) -> f32 {
+ var res : f32;
+ {
+ res += f0_U_arr_X_mat_X(U_arr_X_mat_X(p[0u], 1));
+ }
+ {
+ let p_vec = &(U.arr[p[0]].mat[1]);
+ res += f0_U_arr_X_mat_X(U_arr_X_mat_X(p[0u], 1));
+ }
+ {
+ res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+ }
+ {
+ let p_vec = &(U.arr[2].mat[1]);
+ res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+ }
+ return res;
+}
+
+alias U_arr_X = array<u32, 1u>;
+
+fn f2_U_arr_X(p : U_arr_X) -> f32 {
+ let p_mat = &(U.arr[p[0]].mat);
+ return f1_U_arr_X_mat(U_arr_X_mat(p[0u]));
+}
+
+fn f3_U_arr_U_mat() -> f32 {
+ let p0_inner = &(U.arr[3]);
+ return (f2_U_arr_X(U_arr_X(3)) + f1_U_mat());
+}
+
+fn f4_U() -> f32 {
+ return f3_U_arr_U_mat();
+}
+
+fn b() {
+ f4_U();
+}
+)";
+
+ auto got = Run<DirectVariableAccess>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
} // namespace uniform_as_tests
////////////////////////////////////////////////////////////////////////////////
@@ -1296,6 +1567,169 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(DirectVariableAccessStorageASTest, CallChaining_ViaPointerDotOrIndex) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+ mat : mat3x4<f32>,
+};
+
+alias InnerArr = array<Inner, 4>;
+
+struct Outer {
+ arr : InnerArr,
+ mat : mat3x4<f32>,
+};
+
+@group(0) @binding(0) var<storage> S : Outer;
+
+fn f0(p : ptr<storage, vec4<f32>>) -> f32 {
+ return p.x;
+}
+
+fn f1(p : ptr<storage, mat3x4<f32>>) -> f32 {
+ var res : f32;
+ {
+ // call f0() with inline usage of p
+ res += f0(&p[1]);
+ }
+ {
+ // call f0() with pointer-let usage of p
+ let p_vec = &p[1];
+ res += f0(p_vec);
+ }
+ {
+ // call f0() with inline usage of S
+ res += f0(&S.arr[2].mat[1]);
+ }
+ {
+ // call f0() with pointer-let usage of S
+ let p_vec = &S.arr[2].mat[1];
+ res += f0(p_vec);
+ }
+ return res;
+}
+
+fn f2(p : ptr<storage, Inner>) -> f32 {
+ let p_mat = &p.mat;
+ return f1(p_mat);
+}
+
+fn f3(p0 : ptr<storage, InnerArr>, p1 : ptr<storage, mat3x4<f32>>) -> f32 {
+ let p0_inner = &p0[3];
+ return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<storage, Outer>) -> f32 {
+ return f3(&p.arr, &S.mat);
+}
+
+fn b() {
+ f4(&S);
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+ mat : mat3x4<f32>,
+}
+
+alias InnerArr = array<Inner, 4>;
+
+struct Outer {
+ arr : InnerArr,
+ mat : mat3x4<f32>,
+}
+
+@group(0) @binding(0) var<storage> S : Outer;
+
+alias S_mat_X = array<u32, 1u>;
+
+fn f0_S_mat_X(p : S_mat_X) -> f32 {
+ return (&(S.mat[p[0]])).x;
+}
+
+alias S_arr_X_mat_X = array<u32, 2u>;
+
+fn f0_S_arr_X_mat_X(p : S_arr_X_mat_X) -> f32 {
+ return (&(S.arr[p[0]].mat[p[0]])).x;
+}
+
+alias S_arr_X_mat_X_1 = array<u32, 2u>;
+
+fn f0_S_arr_X_mat_X_1(p : S_arr_X_mat_X_1) -> f32 {
+ return (&(S.arr[p[0]].mat[p[1]])).x;
+}
+
+fn f1_S_mat() -> f32 {
+ var res : f32;
+ {
+ res += f0_S_mat_X(S_mat_X(1));
+ }
+ {
+ let p_vec = &(S.mat[1]);
+ res += f0_S_mat_X(S_mat_X(1));
+ }
+ {
+ res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+ }
+ {
+ let p_vec = &(S.arr[2].mat[1]);
+ res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+ }
+ return res;
+}
+
+alias S_arr_X_mat = array<u32, 1u>;
+
+fn f1_S_arr_X_mat(p : S_arr_X_mat) -> f32 {
+ var res : f32;
+ {
+ res += f0_S_arr_X_mat_X(S_arr_X_mat_X(p[0u], 1));
+ }
+ {
+ let p_vec = &(S.arr[p[0]].mat[1]);
+ res += f0_S_arr_X_mat_X(S_arr_X_mat_X(p[0u], 1));
+ }
+ {
+ res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+ }
+ {
+ let p_vec = &(S.arr[2].mat[1]);
+ res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+ }
+ return res;
+}
+
+alias S_arr_X = array<u32, 1u>;
+
+fn f2_S_arr_X(p : S_arr_X) -> f32 {
+ let p_mat = &(S.arr[p[0]].mat);
+ return f1_S_arr_X_mat(S_arr_X_mat(p[0u]));
+}
+
+fn f3_S_arr_S_mat() -> f32 {
+ let p0_inner = &(S.arr[3]);
+ return (f2_S_arr_X(S_arr_X(3)) + f1_S_mat());
+}
+
+fn f4_S() -> f32 {
+ return f3_S_arr_S_mat();
+}
+
+fn b() {
+ f4_S();
+}
+)";
+
+ auto got = Run<DirectVariableAccess>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
} // namespace storage_as_tests
////////////////////////////////////////////////////////////////////////////////
@@ -1540,6 +1974,169 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(DirectVariableAccessWorkgroupASTest, CallChaining_ViaPointerDotOrIndex) {
+ auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+ mat : mat3x4<f32>,
+};
+
+alias InnerArr = array<Inner, 4>;
+
+struct Outer {
+ arr : InnerArr,
+ mat : mat3x4<f32>,
+};
+
+var<workgroup> W : Outer;
+
+fn f0(p : ptr<workgroup, vec4<f32>>) -> f32 {
+ return p.x;
+}
+
+fn f1(p : ptr<workgroup, mat3x4<f32>>) -> f32 {
+ var res : f32;
+ {
+ // call f0() with inline usage of p
+ res += f0(&p[1]);
+ }
+ {
+ // call f0() with pointer-let usage of p
+ let p_vec = &p[1];
+ res += f0(p_vec);
+ }
+ {
+ // call f0() with inline usage of W
+ res += f0(&W.arr[2].mat[1]);
+ }
+ {
+ // call f0() with pointer-let usage of W
+ let p_vec = &W.arr[2].mat[1];
+ res += f0(p_vec);
+ }
+ return res;
+}
+
+fn f2(p : ptr<workgroup, Inner>) -> f32 {
+ let p_mat = &p.mat;
+ return f1(p_mat);
+}
+
+fn f3(p0 : ptr<workgroup, InnerArr>, p1 : ptr<workgroup, mat3x4<f32>>) -> f32 {
+ let p0_inner = &p0[3];
+ return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<workgroup, Outer>) -> f32 {
+ return f3(&p.arr, &W.mat);
+}
+
+fn b() {
+ f4(&W);
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+ mat : mat3x4<f32>,
+}
+
+alias InnerArr = array<Inner, 4>;
+
+struct Outer {
+ arr : InnerArr,
+ mat : mat3x4<f32>,
+}
+
+var<workgroup> W : Outer;
+
+alias W_mat_X = array<u32, 1u>;
+
+fn f0_W_mat_X(p : W_mat_X) -> f32 {
+ return (&(W.mat[p[0]])).x;
+}
+
+alias W_arr_X_mat_X = array<u32, 2u>;
+
+fn f0_W_arr_X_mat_X(p : W_arr_X_mat_X) -> f32 {
+ return (&(W.arr[p[0]].mat[p[0]])).x;
+}
+
+alias W_arr_X_mat_X_1 = array<u32, 2u>;
+
+fn f0_W_arr_X_mat_X_1(p : W_arr_X_mat_X_1) -> f32 {
+ return (&(W.arr[p[0]].mat[p[1]])).x;
+}
+
+fn f1_W_mat() -> f32 {
+ var res : f32;
+ {
+ res += f0_W_mat_X(W_mat_X(1));
+ }
+ {
+ let p_vec = &(W.mat[1]);
+ res += f0_W_mat_X(W_mat_X(1));
+ }
+ {
+ res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+ }
+ {
+ let p_vec = &(W.arr[2].mat[1]);
+ res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+ }
+ return res;
+}
+
+alias W_arr_X_mat = array<u32, 1u>;
+
+fn f1_W_arr_X_mat(p : W_arr_X_mat) -> f32 {
+ var res : f32;
+ {
+ res += f0_W_arr_X_mat_X(W_arr_X_mat_X(p[0u], 1));
+ }
+ {
+ let p_vec = &(W.arr[p[0]].mat[1]);
+ res += f0_W_arr_X_mat_X(W_arr_X_mat_X(p[0u], 1));
+ }
+ {
+ res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+ }
+ {
+ let p_vec = &(W.arr[2].mat[1]);
+ res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+ }
+ return res;
+}
+
+alias W_arr_X = array<u32, 1u>;
+
+fn f2_W_arr_X(p : W_arr_X) -> f32 {
+ let p_mat = &(W.arr[p[0]].mat);
+ return f1_W_arr_X_mat(W_arr_X_mat(p[0u]));
+}
+
+fn f3_W_arr_W_mat() -> f32 {
+ let p0_inner = &(W.arr[3]);
+ return (f2_W_arr_X(W_arr_X(3)) + f1_W_mat());
+}
+
+fn f4_W() -> f32 {
+ return f3_W_arr_W_mat();
+}
+
+fn b() {
+ f4_W();
+}
+)";
+
+ auto got = Run<DirectVariableAccess>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
} // namespace workgroup_as_tests
////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tint/lang/wgsl/ast/transform/expand_compound_assignment.cc b/src/tint/lang/wgsl/ast/transform/expand_compound_assignment.cc
index 06da9de..d4c66c1 100644
--- a/src/tint/lang/wgsl/ast/transform/expand_compound_assignment.cc
+++ b/src/tint/lang/wgsl/ast/transform/expand_compound_assignment.cc
@@ -85,8 +85,10 @@
// Helper function to create a variable that is a pointer to `expr`.
auto hoist_pointer_to = [&](const Expression* expr) {
+ // Lhs may already be a pointer, in which case we don't take it's address
+ bool is_pointer = ctx.src->Sem().GetVal(expr)->Type()->Is<core::type::Pointer>();
auto name = b.Sym();
- auto* ptr = b.AddressOf(ctx.Clone(expr));
+ auto* ptr = is_pointer ? ctx.Clone(expr) : b.AddressOf(ctx.Clone(expr));
auto* decl = b.Decl(b.Let(name, ptr));
hoist_to_decl_before.InsertBefore(ctx.src->Sem().Get(stmt), decl);
return name;
@@ -103,7 +105,7 @@
// Helper function that returns `true` if the type of `expr` is a vector.
auto is_vec = [&](const Expression* expr) {
if (auto* val_expr = ctx.src->Sem().GetVal(expr)) {
- return val_expr->Type()->UnwrapRef()->Is<core::type::Vector>();
+ return val_expr->Type()->UnwrapPtrOrRef()->Is<core::type::Vector>();
}
return false;
};
@@ -116,9 +118,11 @@
auto* member_accessor = lhs->As<MemberAccessorExpression>();
if (lhs->Is<IdentifierExpression>() ||
(member_accessor && member_accessor->object->Is<IdentifierExpression>())) {
- // This is the simple case with no side effects, so we can just use the
- // original LHS expression directly.
- // Before:
+ // TODO(crbug.com/tint/2115): This branch should also handle (recursive) deref'd
+ // identifiers (e.g. (*p).bar += rhs)).
+
+ // This is the simple case with no side effects, so we can just use
+ // the original LHS expression directly. Before:
// foo.bar += rhs;
// After:
// foo.bar = foo.bar + rhs;
diff --git a/src/tint/lang/wgsl/ast/transform/expand_compound_assignment_test.cc b/src/tint/lang/wgsl/ast/transform/expand_compound_assignment_test.cc
index 83d154c..8ffc045 100644
--- a/src/tint/lang/wgsl/ast/transform/expand_compound_assignment_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/expand_compound_assignment_test.cc
@@ -222,6 +222,122 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(ExpandCompoundAssignmentTest, LhsArrayOfVectorComponent_MemberAccessor_ViaArrayIndex) {
+ auto* src = R"(
+fn main() {
+ var v : array<vec4<i32>, 3>;
+ v[0].y += 1;
+}
+)";
+
+ auto* expect = R"(
+fn main() {
+ var v : array<vec4<i32>, 3>;
+ let tint_symbol = &(v[0]);
+ (*(tint_symbol)).y = ((*(tint_symbol)).y + 1);
+}
+)";
+
+ auto got = Run<ExpandCompoundAssignment>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ExpandCompoundAssignmentTest, LhsVectorComponent_MemberAccessor_ViaDerefPointerDot) {
+ auto* src = R"(
+fn main() {
+ var v : vec4<i32>;
+ let p = &v;
+ (*p).y += 1;
+}
+)";
+
+ // TODO(crbug.com/tint/2115): we currently needlessly hoist pointer-deref to another pointer.
+ auto* expect = R"(
+fn main() {
+ var v : vec4<i32>;
+ let p = &(v);
+ let tint_symbol = &(*(p));
+ (*(tint_symbol)).y = ((*(tint_symbol)).y + 1);
+}
+)";
+
+ auto got = Run<ExpandCompoundAssignment>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ExpandCompoundAssignmentTest, LhsVectorComponent_MemberAccessor_ViaPointerDot) {
+ auto* src = R"(
+fn main() {
+ var v : vec4<i32>;
+ let p = &v;
+ p.y += 1;
+}
+)";
+
+ auto* expect = R"(
+fn main() {
+ var v : vec4<i32>;
+ let p = &(v);
+ p.y = (p.y + 1);
+}
+)";
+
+ auto got = Run<ExpandCompoundAssignment>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ExpandCompoundAssignmentTest, LhsVectorComponent_MemberAccessor_ViaDerefPointerIndex) {
+ auto* src = R"(
+fn main() {
+ var v : vec4<i32>;
+ let p = &v;
+ (*p)[0] += 1;
+}
+)";
+
+ // TODO(crbug.com/tint/2115): we currently needlessly hoist pointer-deref to another pointer.
+ auto* expect = R"(
+fn main() {
+ var v : vec4<i32>;
+ let p = &(v);
+ let tint_symbol = &(*(p));
+ let tint_symbol_1 = 0;
+ (*(tint_symbol))[tint_symbol_1] = ((*(tint_symbol))[tint_symbol_1] + 1);
+}
+)";
+
+ auto got = Run<ExpandCompoundAssignment>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ExpandCompoundAssignmentTest, LhsVectorComponent_MemberAccessor_ViaPointerIndex) {
+ auto* src = R"(
+fn main() {
+ var v : vec4<i32>;
+ let p = &v;
+ p[0] += 1;
+}
+)";
+
+ auto* expect = R"(
+fn main() {
+ var v : vec4<i32>;
+ let p = &(v);
+ let tint_symbol = p;
+ let tint_symbol_1 = 0;
+ (*(tint_symbol))[tint_symbol_1] = ((*(tint_symbol))[tint_symbol_1] + 1);
+}
+)";
+
+ auto got = Run<ExpandCompoundAssignment>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(ExpandCompoundAssignmentTest, LhsMatrixColumn) {
auto* src = R"(
var<private> m : mat4x4<f32>;
@@ -678,6 +794,79 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(ExpandCompoundAssignmentTest,
+ Increment_LhsVectorComponent_ArrayAccessor_ViaDerefPointerIndex) {
+ auto* src = R"(
+var<private> v : vec4<i32>;
+
+fn idx() -> i32 {
+ v.y = 42;
+ return 1;
+}
+
+fn main() {
+ let p = &v;
+ (*p)[idx()]++;
+}
+)";
+
+ auto* expect = R"(
+var<private> v : vec4<i32>;
+
+fn idx() -> i32 {
+ v.y = 42;
+ return 1;
+}
+
+fn main() {
+ let p = &(v);
+ let tint_symbol = &(*(p));
+ let tint_symbol_1 = idx();
+ (*(tint_symbol))[tint_symbol_1] = ((*(tint_symbol))[tint_symbol_1] + 1);
+}
+)";
+
+ auto got = Run<ExpandCompoundAssignment>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ExpandCompoundAssignmentTest, Increment_LhsVectorComponent_ArrayAccessor_ViaPointerIndex) {
+ auto* src = R"(
+var<private> v : vec4<i32>;
+
+fn idx() -> i32 {
+ v.y = 42;
+ return 1;
+}
+
+fn main() {
+ let p = &v;
+ p[idx()]++;
+}
+)";
+
+ auto* expect = R"(
+var<private> v : vec4<i32>;
+
+fn idx() -> i32 {
+ v.y = 42;
+ return 1;
+}
+
+fn main() {
+ let p = &(v);
+ let tint_symbol = p;
+ let tint_symbol_1 = idx();
+ (*(tint_symbol))[tint_symbol_1] = ((*(tint_symbol))[tint_symbol_1] + 1);
+}
+)";
+
+ auto got = Run<ExpandCompoundAssignment>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(ExpandCompoundAssignmentTest, Increment_LhsVectorComponent_MemberAccessor) {
auto* src = R"(
fn main() {
@@ -698,6 +887,53 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(ExpandCompoundAssignmentTest,
+ Increment_LhsVectorComponent_MemberAccessor_ViaDerefPointerDot) {
+ auto* src = R"(
+fn main() {
+ var v : vec4<i32>;
+ let p = &v;
+ (*p).y++;
+}
+)";
+
+ // TODO(crbug.com/tint/2115): we currently needlessly hoist pointer-deref to another pointer.
+ auto* expect = R"(
+fn main() {
+ var v : vec4<i32>;
+ let p = &(v);
+ let tint_symbol = &(*(p));
+ (*(tint_symbol)).y = ((*(tint_symbol)).y + 1);
+}
+)";
+
+ auto got = Run<ExpandCompoundAssignment>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(ExpandCompoundAssignmentTest, Increment_LhsVectorComponent_MemberAccessor_ViaPointerDot) {
+ auto* src = R"(
+fn main() {
+ var v : vec4<i32>;
+ let p = &v;
+ p.y++;
+}
+)";
+
+ auto* expect = R"(
+fn main() {
+ var v : vec4<i32>;
+ let p = &(v);
+ p.y = (p.y + 1);
+}
+)";
+
+ auto got = Run<ExpandCompoundAssignment>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(ExpandCompoundAssignmentTest, Increment_ForLoopCont) {
auto* src = R"(
var<private> a : array<vec4<i32>, 4>;
diff --git a/src/tint/lang/wgsl/ast/transform/preserve_padding_test.cc b/src/tint/lang/wgsl/ast/transform/preserve_padding_test.cc
index c5f43f3..469448b 100644
--- a/src/tint/lang/wgsl/ast/transform/preserve_padding_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/preserve_padding_test.cc
@@ -150,6 +150,67 @@
EXPECT_EQ(expect, str(got));
}
+// Same should happen via a sugared pointer write
+TEST_F(PreservePaddingTest, StructTrailingPadding_ViaPointerDot) {
+ auto* src = R"(
+struct S {
+ a : u32,
+ b : u32,
+ c : u32,
+ d : u32,
+ e : vec3<u32>,
+}
+
+struct Outer {
+ s : S,
+}
+
+@group(0) @binding(0) var<storage, read_write> v : Outer;
+
+@compute @workgroup_size(1)
+fn foo() {
+ let p = &v;
+ p.s = S();
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct S {
+ a : u32,
+ b : u32,
+ c : u32,
+ d : u32,
+ e : vec3<u32>,
+}
+
+struct Outer {
+ s : S,
+}
+
+@group(0) @binding(0) var<storage, read_write> v : Outer;
+
+fn assign_and_preserve_padding(dest : ptr<storage, S, read_write>, value : S) {
+ (*(dest)).a = value.a;
+ (*(dest)).b = value.b;
+ (*(dest)).c = value.c;
+ (*(dest)).d = value.d;
+ (*(dest)).e = value.e;
+}
+
+@compute @workgroup_size(1)
+fn foo() {
+ let p = &(v);
+ assign_and_preserve_padding(&(p.s), S());
+}
+)";
+
+ auto got = Run<PreservePadding>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(PreservePaddingTest, StructInternalPadding) {
auto* src = R"(
struct S {
@@ -745,6 +806,34 @@
EXPECT_EQ(expect, str(got));
}
+// Same should happen via a sugared pointer write.
+TEST_F(PreservePaddingTest, NoModify_Workgroup_ViaPointerDot) {
+ auto* src = R"(
+struct S {
+ a : u32,
+ b : vec3<u32>,
+}
+
+struct Outer {
+ s : S,
+}
+
+var<workgroup> v : Outer;
+
+@compute @workgroup_size(1)
+fn foo() {
+ let p = &(v);
+ p.s = S();
+}
+)";
+
+ auto* expect = src;
+
+ auto got = Run<PreservePadding>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(PreservePaddingTest, NoModify_Private) {
auto* src = R"(
struct S {
diff --git a/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl_test.cc b/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl_test.cc
index ea506c0..f3238ee 100644
--- a/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl_test.cc
@@ -2725,6 +2725,107 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(PromoteSideEffectsToDeclTest, IndexAccessor_2D_LeftSE_ViaPointerIndex) {
+ auto* src = R"(
+fn a(i : i32) -> i32 {
+ return 1;
+}
+
+fn f() {
+ var b = array<array<i32, 10>, 10>();
+ let p = &b;
+ var c = 1;
+ var r = p[a(0)][c];
+}
+)";
+
+ auto* expect = R"(
+fn a(i : i32) -> i32 {
+ return 1;
+}
+
+fn f() {
+ var b = array<array<i32, 10>, 10>();
+ let p = &(b);
+ var c = 1;
+ let tint_symbol : i32 = a(0);
+ var r = p[tint_symbol][c];
+}
+)";
+
+ auto got = Run<PromoteSideEffectsToDecl>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteSideEffectsToDeclTest, IndexAccessor_2D_RightSE_ViaPointerIndex) {
+ auto* src = R"(
+fn a(i : i32) -> i32 {
+ return 1;
+}
+
+fn f() {
+ var b = array<array<i32, 10>, 10>();
+ let p = &b;
+ var c = 1;
+ let tint_symbol = c;
+ let tint_symbol_1 = a(0);
+ var r = p[tint_symbol][tint_symbol_1];
+}
+)";
+
+ auto* expect = R"(
+fn a(i : i32) -> i32 {
+ return 1;
+}
+
+fn f() {
+ var b = array<array<i32, 10>, 10>();
+ let p = &(b);
+ var c = 1;
+ let tint_symbol = c;
+ let tint_symbol_1 = a(0);
+ var r = p[tint_symbol][tint_symbol_1];
+}
+)";
+
+ auto got = Run<PromoteSideEffectsToDecl>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PromoteSideEffectsToDeclTest, IndexAccessor_2D_BothSE_ViaPointerIndex) {
+ auto* src = R"(
+fn a(i : i32) -> i32 {
+ return 1;
+}
+
+fn f() {
+ var b = array<array<i32, 10>, 10>();
+ let p = &b;
+ var r = p[a(0)][a(1)];
+}
+)";
+
+ auto* expect = R"(
+fn a(i : i32) -> i32 {
+ return 1;
+}
+
+fn f() {
+ var b = array<array<i32, 10>, 10>();
+ let p = &(b);
+ let tint_symbol : i32 = a(0);
+ let tint_symbol_1 : i32 = a(1);
+ var r = p[tint_symbol][tint_symbol_1];
+}
+)";
+
+ auto got = Run<PromoteSideEffectsToDecl>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(PromoteSideEffectsToDeclTest, Assignment_ToPhony) {
auto* src = R"(
fn a(i : i32) -> i32 {
@@ -3643,6 +3744,43 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(PromoteSideEffectsToDeclTest, BinaryMemberAccessorPlusSE_ViaPointerDot) {
+ // bclayton@'s example:
+ // https://dawn-review.googlesource.com/c/tint/+/78620/6..8/src/transform/promote_side_effects_to_decl.cc#b490
+ auto* src = R"(
+fn modify_vec(p : ptr<function, vec4<i32>>) -> i32 {
+ (*p).x = 42;
+ return 0;
+}
+
+fn f() {
+ var v = vec4<i32>();
+ let p = &v;
+ let l = p.x + modify_vec(&v);
+ // l should be 0, not 42
+}
+)";
+
+ auto* expect = R"(
+fn modify_vec(p : ptr<function, vec4<i32>>) -> i32 {
+ (*(p)).x = 42;
+ return 0;
+}
+
+fn f() {
+ var v = vec4<i32>();
+ let p = &(v);
+ let tint_symbol : i32 = p.x;
+ let tint_symbol_1 : i32 = modify_vec(&(v));
+ let l = (tint_symbol + tint_symbol_1);
+}
+)";
+
+ auto got = Run<PromoteSideEffectsToDecl>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(PromoteSideEffectsToDeclTest, Call_ReadOnlyArgAndSE) {
// Make sure that read-only args don't get hoisted (tex and samp)
auto* src = R"(
diff --git a/src/tint/lang/wgsl/ast/transform/renamer.cc b/src/tint/lang/wgsl/ast/transform/renamer.cc
index 0183f76..7233498 100644
--- a/src/tint/lang/wgsl/ast/transform/renamer.cc
+++ b/src/tint/lang/wgsl/ast/transform/renamer.cc
@@ -1298,7 +1298,7 @@
if (sem->Is<sem::Swizzle>()) {
preserved_identifiers.Add(accessor->member);
} else if (auto* str_expr = src.Sem().GetVal(accessor->object)) {
- if (auto* ty = str_expr->Type()->UnwrapRef()->As<core::type::Struct>()) {
+ if (auto* ty = str_expr->Type()->UnwrapPtrOrRef()->As<core::type::Struct>()) {
if (!ty->Is<sem::Struct>()) { // Builtin structure
preserved_identifiers.Add(accessor->member);
}
diff --git a/src/tint/lang/wgsl/ast/transform/renamer_test.cc b/src/tint/lang/wgsl/ast/transform/renamer_test.cc
index fca0b22..b24a440 100644
--- a/src/tint/lang/wgsl/ast/transform/renamer_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/renamer_test.cc
@@ -274,6 +274,51 @@
EXPECT_THAT(data->remappings, ContainerEq(expected_remappings));
}
+TEST_F(RenamerTest, PreserveBuiltinTypes_ViaPointerDot) {
+ auto* src = R"(
+@compute @workgroup_size(1)
+fn entry() {
+ var m = modf(1.0);
+ let p1 = &m;
+ var f = frexp(1.0);
+ let p2 = &f;
+
+ var a = p1.whole;
+ var b = p1.fract;
+ var c = p2.fract;
+ var d = p2.exp;
+}
+)";
+
+ auto* expect = R"(
+@compute @workgroup_size(1)
+fn tint_symbol() {
+ var tint_symbol_1 = modf(1.0);
+ let tint_symbol_2 = &(tint_symbol_1);
+ var tint_symbol_3 = frexp(1.0);
+ let tint_symbol_4 = &(tint_symbol_3);
+ var tint_symbol_5 = tint_symbol_2.whole;
+ var tint_symbol_6 = tint_symbol_2.fract;
+ var tint_symbol_7 = tint_symbol_4.fract;
+ var tint_symbol_8 = tint_symbol_4.exp;
+}
+)";
+
+ auto got = Run<Renamer>(src);
+
+ EXPECT_EQ(expect, str(got));
+
+ auto* data = got.data.Get<Renamer::Data>();
+
+ ASSERT_NE(data, nullptr);
+ Renamer::Remappings expected_remappings = {
+ {"entry", "tint_symbol"}, {"m", "tint_symbol_1"}, {"p1", "tint_symbol_2"},
+ {"f", "tint_symbol_3"}, {"p2", "tint_symbol_4"}, {"a", "tint_symbol_5"},
+ {"b", "tint_symbol_6"}, {"c", "tint_symbol_7"}, {"d", "tint_symbol_8"},
+ };
+ EXPECT_THAT(data->remappings, ContainerEq(expected_remappings));
+}
+
TEST_F(RenamerTest, PreserveCoreDiagnosticRuleName) {
auto* src = R"(
diagnostic(off, chromium.unreachable_code);
diff --git a/src/tint/lang/wgsl/ast/transform/robustness.cc b/src/tint/lang/wgsl/ast/transform/robustness.cc
index 0bc1575..8169d28 100644
--- a/src/tint/lang/wgsl/ast/transform/robustness.cc
+++ b/src/tint/lang/wgsl/ast/transform/robustness.cc
@@ -31,6 +31,7 @@
#include <limits>
#include <utility>
+#include "src/tint/lang/core/type/memory_view.h"
#include "src/tint/lang/core/type/reference.h"
#include "src/tint/lang/wgsl/ast/transform/hoist_to_decl_before.h"
#include "src/tint/lang/wgsl/program/clone_context.h"
@@ -194,7 +195,7 @@
if (auto pred = predicates.Get(expr)) {
// Expression is predicated
auto* sem_expr = sem.GetVal(expr);
- if (!sem_expr->Type()->IsAnyOf<core::type::Reference, core::type::Pointer>()) {
+ if (!sem_expr->Type()->Is<core::type::MemoryView>()) {
auto pred_load = b.Symbols().New("predicated_expr");
auto ty = CreateASTTypeFor(ctx, sem_expr->Type());
hoist.InsertBefore(sem_expr->Stmt(), b.Decl(b.Var(pred_load, ty)));
@@ -236,7 +237,7 @@
const Expression* DynamicLimitFor(const sem::IndexAccessorExpression* expr) {
auto* obj_type = expr->Object()->Type();
return Switch(
- obj_type->UnwrapRef(), //
+ obj_type->UnwrapPtrOrRef(), //
[&](const core::type::Vector* vec) -> const Expression* {
if (expr->Index()->ConstantValue() || expr->Index()->Is<sem::Swizzle>()) {
// Index and size is constant.
@@ -708,7 +709,7 @@
/// @returns true if expr is an IndexAccessorExpression whose object is a runtime-sized array.
bool IsIndexAccessingRuntimeSizedArray(const sem::IndexAccessorExpression* expr) {
- auto* array_type = expr->Object()->Type()->UnwrapRef()->As<core::type::Array>();
+ auto* array_type = expr->Object()->Type()->UnwrapPtrOrRef()->As<core::type::Array>();
return array_type != nullptr && array_type->Count()->Is<core::type::RuntimeArrayCount>();
}
diff --git a/src/tint/lang/wgsl/ast/transform/robustness_test.cc b/src/tint/lang/wgsl/ast/transform/robustness_test.cc
index f1e733b..647e669 100644
--- a/src/tint/lang/wgsl/ast/transform/robustness_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/robustness_test.cc
@@ -464,6 +464,67 @@
EXPECT_EQ(expect, str(got));
}
+TEST_P(RobustnessTest, Read_ConstantSizedArrayRef_IndexWithRuntimeArrayIndexViaPointerIndex) {
+ auto* src = R"(
+var<private> a : array<f32, 3>;
+
+var<private> b : array<i32, 5>;
+
+var<private> i : u32;
+
+fn f() {
+ let p1 = &(a);
+ let p2 = &(b);
+ var c : f32 = p1[p2[i]];
+}
+)";
+
+ auto* expect = Expect(GetParam(),
+ /* ignore */ src,
+ /* clamp */ R"(
+var<private> a : array<f32, 3>;
+
+var<private> b : array<i32, 5>;
+
+var<private> i : u32;
+
+fn f() {
+ let p1 = &(a);
+ let p2 = &(b);
+ var c : f32 = p1[min(u32(p2[min(i, 4u)]), 2u)];
+}
+)",
+ /* predicate */ R"(
+var<private> a : array<f32, 3>;
+
+var<private> b : array<i32, 5>;
+
+var<private> i : u32;
+
+fn f() {
+ let p1 = &(a);
+ let p2 = &(b);
+ let index = i;
+ let predicate = (u32(index) <= 4u);
+ var predicated_expr : i32;
+ if (predicate) {
+ predicated_expr = p2[index];
+ }
+ let index_1 = predicated_expr;
+ let predicate_1 = (u32(index_1) <= 2u);
+ var predicated_expr_1 : f32;
+ if (predicate_1) {
+ predicated_expr_1 = p1[index_1];
+ }
+ var c : f32 = predicated_expr_1;
+}
+)");
+
+ auto got = Run<Robustness>(src, Config(GetParam()));
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_P(RobustnessTest, Read_ConstantSizedArrayRef_IndexWithRuntimeExpression) {
auto* src = R"(
var<private> a : array<f32, 3>;
@@ -1379,6 +1440,52 @@
EXPECT_EQ(expect, str(got));
}
+TEST_P(RobustnessTest, Read_Vector_IndexWithRuntimeExpression_ViaPointerIndex) {
+ auto* src = R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+ let p = &(a);
+ var b : f32 = p[((c + 2) - 3)];
+}
+)";
+
+ auto* expect = Expect(GetParam(),
+ /* ignore */ src,
+ /* clamp */ R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+ let p = &(a);
+ var b : f32 = p[min(u32(((c + 2) - 3)), 2u)];
+}
+)",
+ /* predicate */ R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+ let p = &(a);
+ let index = ((c + 2) - 3);
+ let predicate = (u32(index) <= 2u);
+ var predicated_expr : f32;
+ if (predicate) {
+ predicated_expr = p[index];
+ }
+ var b : f32 = predicated_expr;
+}
+)");
+
+ auto got = Run<Robustness>(src, Config(GetParam()));
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_P(RobustnessTest, Read_Vector_SwizzleIndexWithGlobalVar) {
auto* src = R"(
var<private> a : vec3<f32>;
@@ -1465,6 +1572,52 @@
EXPECT_EQ(expect, str(got));
}
+TEST_P(RobustnessTest, Read_Vector_SwizzleIndexWithRuntimeExpression_ViaPointerDot) {
+ auto* src = R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+ let p = &(a);
+ var b : f32 = p.xy[((c + 2) - 3)];
+}
+)";
+
+ auto* expect = Expect(GetParam(),
+ /* ignore */ src,
+ /* clamp */ R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+ let p = &(a);
+ var b : f32 = p.xy[min(u32(((c + 2) - 3)), 1u)];
+}
+)",
+ /* predicate */ R"(
+var<private> a : vec3<f32>;
+
+var<private> c : i32;
+
+fn f() {
+ let p = &(a);
+ let index = ((c + 2) - 3);
+ let predicate = (u32(index) <= 1u);
+ var predicated_expr : f32;
+ if (predicate) {
+ predicated_expr = p.xy[index];
+ }
+ var b : f32 = predicated_expr;
+}
+)");
+
+ auto got = Run<Robustness>(src, Config(GetParam()));
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_P(RobustnessTest, Read_Vector_IndexWithOverride) {
auto* src = R"(
@id(1300) override idx : i32;
@@ -5571,6 +5724,30 @@
EXPECT_EQ(expect, str(got));
}
+TEST_P(RobustnessTest, Read_disable_unsized_array_index_clamping_abstract_int_ViaPointerIndex) {
+ auto* src = R"(
+@group(0) @binding(0) var<storage, read> s : array<f32>;
+
+fn f() {
+ let p = &(s);
+ var d : f32 = p[25];
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<storage, read> s : array<f32>;
+
+fn f() {
+ let p = &(s);
+ var d : f32 = p[u32(25)];
+}
+)";
+
+ auto got = Run<Robustness>(src, Config(GetParam(), true));
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_P(RobustnessTest, Assign_disable_unsized_array_index_clamping_i32) {
auto* src = R"(
@group(0) @binding(0) var<storage, read_write> s : array<f32>;
@@ -5634,6 +5811,30 @@
EXPECT_EQ(expect, str(got));
}
+TEST_P(RobustnessTest, Assign_disable_unsized_array_index_clamping_abstract_int_ViaPointerIndex) {
+ auto* src = R"(
+@group(0) @binding(0) var<storage, read_write> s : array<f32>;
+
+fn f() {
+ let p = &(s);
+ p[25] = 0.5f;
+}
+)";
+
+ auto* expect = R"(
+@group(0) @binding(0) var<storage, read_write> s : array<f32>;
+
+fn f() {
+ let p = &(s);
+ p[u32(25)] = 0.5f;
+}
+)";
+
+ auto got = Run<Robustness>(src, Config(GetParam(), true));
+
+ EXPECT_EQ(expect, str(got));
+}
+
INSTANTIATE_TEST_SUITE_P(,
RobustnessTest,
testing::Values(Robustness::Action::kIgnore,
diff --git a/src/tint/lang/wgsl/ast/transform/simplify_pointers.cc b/src/tint/lang/wgsl/ast/transform/simplify_pointers.cc
index 771b954..3095252 100644
--- a/src/tint/lang/wgsl/ast/transform/simplify_pointers.cc
+++ b/src/tint/lang/wgsl/ast/transform/simplify_pointers.cc
@@ -27,15 +27,14 @@
#include "src/tint/lang/wgsl/ast/transform/simplify_pointers.h"
-#include <memory>
-#include <unordered_map>
-#include <utility>
-#include <vector>
+#include <unordered_set>
+#include "src/tint/utils/containers/hashset.h"
#include "src/tint/lang/wgsl/ast/transform/unshadow.h"
#include "src/tint/lang/wgsl/program/clone_context.h"
#include "src/tint/lang/wgsl/program/program_builder.h"
#include "src/tint/lang/wgsl/resolver/resolve.h"
+#include "src/tint/lang/wgsl/sem/accessor_expression.h"
#include "src/tint/lang/wgsl/sem/block_statement.h"
#include "src/tint/lang/wgsl/sem/function.h"
#include "src/tint/lang/wgsl/sem/statement.h"
@@ -69,6 +68,9 @@
ProgramBuilder b;
/// The clone context
program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true};
+ /// Set of accessor expression objects that are pointers, used to handle
+ /// pointer-index/dot sugar syntax.
+ Hashset<const Expression*, 4> is_accessor_object_pointer;
/// Constructor
/// @param program the source program
@@ -110,9 +112,14 @@
/// indirection ops into a PointerOp.
/// @param in the expression to walk
/// @returns the reduced PointerOp
- PointerOp Reduce(const Expression* in) const {
+ PointerOp Reduce(const Expression* in) {
PointerOp op{0, in};
while (true) {
+ if (is_accessor_object_pointer.Contains(op.expr)) {
+ // Object is an implicitly dereferenced pointer (i.e. syntax sugar).
+ op.indirections++;
+ }
+
if (auto* unary = op.expr->As<UnaryOpExpression>()) {
switch (unary->op) {
case core::UnaryOp::kIndirection:
@@ -226,6 +233,14 @@
// will be no pointers that can be inlined.
needs_transform = true;
}
+ },
+ [&](const AccessorExpression* accessor) {
+ if (auto* a = ctx.src->Sem().Get<sem::ValueExpression>(accessor->object)) {
+ if (a->Type()->Is<core::type::Pointer>()) {
+ // Object is an implicitly dereferenced pointer (i.e. syntax sugar).
+ is_accessor_object_pointer.Add(accessor->object);
+ }
+ }
});
}
diff --git a/src/tint/lang/wgsl/ast/transform/simplify_pointers_test.cc b/src/tint/lang/wgsl/ast/transform/simplify_pointers_test.cc
index e2c7efd..1c92b18 100644
--- a/src/tint/lang/wgsl/ast/transform/simplify_pointers_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/simplify_pointers_test.cc
@@ -144,6 +144,92 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(SimplifyPointersTest, PointerDerefIndex) {
+ auto* src = R"(
+fn f() {
+ var a : array<f32, 2>;
+ let p = &a;
+ let v = (*p)[1];
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var a : array<f32, 2>;
+ let v = a[1];
+}
+)";
+
+ auto got = Run<Unshadow, SimplifyPointers>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, PointerIndex) {
+ auto* src = R"(
+fn f() {
+ var a : array<f32, 2>;
+ let p = &a;
+ let v = p[1];
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var a : array<f32, 2>;
+ let v = a[1];
+}
+)";
+
+ auto got = Run<Unshadow, SimplifyPointers>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, SimpleChain) {
+ auto* src = R"(
+fn f() {
+ var a : array<f32, 2>;
+ let ap : ptr<function, array<f32, 2>> = &a;
+ let vp : ptr<function, f32> = &(*ap)[1];
+ let v : f32 = *vp;
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var a : array<f32, 2>;
+ let v : f32 = a[1];
+}
+)";
+
+ auto got = Run<Unshadow, SimplifyPointers>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(SimplifyPointersTest, SimpleChain_ViaPointerIndex) {
+ auto* src = R"(
+fn f() {
+ var a : array<f32, 2>;
+ let ap : ptr<function, array<f32, 2>> = &a;
+ let vp : ptr<function, f32> = &ap[1];
+ let v : f32 = *vp;
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var a : array<f32, 2>;
+ let v : f32 = a[1];
+}
+)";
+
+ auto got = Run<Unshadow, SimplifyPointers>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(SimplifyPointersTest, ComplexChain) {
auto* src = R"(
fn f() {
@@ -167,6 +253,29 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(SimplifyPointersTest, ComplexChain_ViaPointerIndex) {
+ auto* src = R"(
+fn f() {
+ var a : array<mat4x4<f32>, 4>;
+ let ap : ptr<function, array<mat4x4<f32>, 4>> = &a;
+ let mp : ptr<function, mat4x4<f32>> = &ap[3];
+ let vp : ptr<function, vec4<f32>> = &mp[2];
+ let v : vec4<f32> = *vp;
+}
+)";
+
+ auto* expect = R"(
+fn f() {
+ var a : array<mat4x4<f32>, 4>;
+ let v : vec4<f32> = a[3][2];
+}
+)";
+
+ auto got = Run<Unshadow, SimplifyPointers>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(SimplifyPointersTest, SavedVars) {
auto* src = R"(
struct S {
diff --git a/src/tint/lang/wgsl/ast/transform/std140.cc b/src/tint/lang/wgsl/ast/transform/std140.cc
index d933ec6..b28e5a7 100644
--- a/src/tint/lang/wgsl/ast/transform/std140.cc
+++ b/src/tint/lang/wgsl/ast/transform/std140.cc
@@ -551,7 +551,7 @@
expr = a->Object();
// Is the object a std140 decomposed matrix?
- if (auto* mat = expr->Type()->UnwrapRef()->As<core::type::Matrix>()) {
+ if (auto* mat = expr->Type()->UnwrapPtrOrRef()->As<core::type::Matrix>()) {
if (std140_mats.Contains(mat)) {
// Record this on the access.
access.std140_mat_idx = access.indices.Length();
diff --git a/src/tint/lang/wgsl/ast/transform/std140_exhaustive_test.cc b/src/tint/lang/wgsl/ast/transform/std140_exhaustive_test.cc
index 2e9c0a1..f9dca89 100644
--- a/src/tint/lang/wgsl/ast/transform/std140_exhaustive_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/std140_exhaustive_test.cc
@@ -2458,6 +2458,217 @@
EXPECT_EQ(expect, str(got));
}
+TEST_P(Std140Test_Matrix, ArrayStructArrayStructMatUniform_LoadsViaImplicitDerefPtrs) {
+ auto matrix = GetParam();
+
+ std::string src = R"(
+enable f16;
+
+struct Inner {
+ @size(64)
+ m : ${mat},
+}
+
+struct Outer {
+ a : array<Inner, 4>,
+}
+
+@group(0) @binding(0) var<uniform> a : array<Outer, 4>;
+
+fn f() {
+ let I = 1;
+ let J = 2;
+ let K = 0;
+ let p_a = &(a);
+ let p_a_3 = &(p_a[3]);
+ let p_a_I = &(p_a[I]);
+ let p_a_3_a = &((*(p_a_3)).a);
+ let p_a_I_a = &((*(p_a_I)).a);
+ let p_a_3_a_2 = &(p_a_3_a[2]);
+ let p_a_3_a_I = &(p_a_3_a[I]);
+ let p_a_I_a_2 = &(p_a_I_a[2]);
+ let p_a_I_a_J = &(p_a_I_a[J]);
+ let p_a_3_a_2_m = &((*(p_a_3_a_2)).m);
+ let p_a_3_a_I_m = &((*(p_a_3_a_I)).m);
+ let p_a_I_a_2_m = &((*(p_a_I_a_2)).m);
+ let p_a_I_a_J_m = &((*(p_a_I_a_J)).m);
+ let p_a_3_a_2_m_1 = &(p_a_3_a_2_m[1]);
+ let p_a_I_a_J_m_K = &(p_a_I_a_J_m[K]);
+ let l_a : array<Outer, 4> = *(p_a);
+ let l_a_3 : Outer = *(p_a_3);
+ let l_a_I : Outer = *(p_a_I);
+ let l_a_3_a : array<Inner, 4> = *(p_a_3_a);
+ let l_a_I_a : array<Inner, 4> = *(p_a_I_a);
+ let l_a_3_a_2 : Inner = *(p_a_3_a_2);
+ let l_a_3_a_I : Inner = *(p_a_3_a_I);
+ let l_a_I_a_2 : Inner = *(p_a_I_a_2);
+ let l_a_I_a_J : Inner = *(p_a_I_a_J);
+ let l_a_3_a_2_m : ${mat} = *(p_a_3_a_2_m);
+ let l_a_3_a_I_m : ${mat} = *(p_a_3_a_I_m);
+ let l_a_I_a_2_m : ${mat} = *(p_a_I_a_2_m);
+ let l_a_I_a_J_m : ${mat} = *(p_a_I_a_J_m);
+ let l_a_3_a_2_m_1 : ${col_vector_type} = *(p_a_3_a_2_m_1);
+ let l_a_I_a_J_m_K : ${col_vector_type} = *(p_a_I_a_J_m_K);
+ let l_a_2_a_0_m_1_0 : ${elem_type} = p_a_3_a_2_m_1[0];
+ let l_a_I_a_J_m_K_I : ${elem_type} = p_a_I_a_J_m_K[I];
+}
+)";
+ src = matrix.ReplaceFieldsInString(src);
+
+ std::string expect;
+ if (matrix.NotStd140Compatible()) {
+ expect = R"(
+enable f16;
+
+struct Inner {
+ @size(64)
+ m : ${mat},
+}
+
+struct Inner_std140 {
+${col_vectors}
+}
+
+struct Outer {
+ a : array<Inner, 4>,
+}
+
+struct Outer_std140 {
+ a : array<Inner_std140, 4u>,
+}
+
+@group(0) @binding(0) var<uniform> a : array<Outer_std140, 4u>;
+
+fn conv_Inner(val : Inner_std140) -> Inner {
+ return Inner(${mat}(${col_vectors_inline_conv_Inner}));
+}
+
+fn conv_arr4_Inner(val : array<Inner_std140, 4u>) -> array<Inner, 4u> {
+ var arr : array<Inner, 4u>;
+ for(var i : u32; (i < 4u); i = (i + 1)) {
+ arr[i] = conv_Inner(val[i]);
+ }
+ return arr;
+}
+
+fn conv_Outer(val : Outer_std140) -> Outer {
+ return Outer(conv_arr4_Inner(val.a));
+}
+
+fn conv_arr4_Outer(val : array<Outer_std140, 4u>) -> array<Outer, 4u> {
+ var arr : array<Outer, 4u>;
+ for(var i : u32; (i < 4u); i = (i + 1)) {
+ arr[i] = conv_Outer(val[i]);
+ }
+ return arr;
+}
+
+fn load_a_3_a_2_m() -> ${mat} {
+ let s = &(a[3u].a[2u]);
+ return ${mat}(${col_vectors_inline_load_matrix});
+}
+
+fn load_a_3_a_p0_m(p0 : u32) -> ${mat} {
+ let s = &(a[3u].a[p0]);
+ return ${mat}(${col_vectors_inline_load_matrix});
+}
+
+fn load_a_p0_a_2_m(p0 : u32) -> ${mat} {
+ let s = &(a[p0].a[2u]);
+ return ${mat}(${col_vectors_inline_load_matrix});
+}
+
+fn load_a_p0_a_p1_m(p0 : u32, p1 : u32) -> ${mat} {
+ let s = &(a[p0].a[p1]);
+ return ${mat}(${col_vectors_inline_load_matrix});
+}
+
+fn load_a_p0_a_p1_m_p2(p0 : u32, p1 : u32, p2 : u32) -> ${col_vector_type} {
+ switch(p2) {
+${col_table_load_column}
+ default: {
+ return ${col_vector_type}();
+ }
+ }
+}
+
+fn load_a_p0_a_p1_m_p2_p3(p0 : u32, p1 : u32, p2 : u32, p3 : u32) -> ${elem_type} {
+ switch(p2) {
+${col_table_load_element}
+ default: {
+ return ${elem_type}();
+ }
+ }
+}
+
+fn f() {
+ let I = 1;
+ let J = 2;
+ let K = 0;
+ let p_a = conv_arr4_Outer(a);
+ let p_a_3 = conv_Outer(a[3u]);
+ let p_a_I = conv_Outer(a[I]);
+ let p_a_3_a = conv_arr4_Inner(a[3u].a);
+ let p_a_I_a = conv_arr4_Inner(a[I].a);
+ let p_a_3_a_2 = conv_Inner(a[3u].a[2u]);
+ let p_a_3_a_I = conv_Inner(a[3u].a[I]);
+ let p_a_I_a_2 = conv_Inner(a[I].a[2u]);
+ let p_a_I_a_J = conv_Inner(a[I].a[J]);
+ let p_a_3_a_2_m = load_a_3_a_2_m();
+ let p_a_3_a_I_m = load_a_3_a_p0_m(u32(I));
+ let p_a_I_a_2_m = load_a_p0_a_2_m(u32(I));
+ let p_a_I_a_J_m = load_a_p0_a_p1_m(u32(I), u32(J));
+ let p_a_3_a_2_m_1 = a[3u].a[2u].m_1;
+ let p_a_I_a_J_m_K = load_a_p0_a_p1_m_p2(u32(I), u32(J), u32(K));
+ let l_a : array<Outer, 4> = conv_arr4_Outer(a);
+ let l_a_3 : Outer = conv_Outer(a[3u]);
+ let l_a_I : Outer = conv_Outer(a[I]);
+ let l_a_3_a : array<Inner, 4> = conv_arr4_Inner(a[3u].a);
+ let l_a_I_a : array<Inner, 4> = conv_arr4_Inner(a[I].a);
+ let l_a_3_a_2 : Inner = conv_Inner(a[3u].a[2u]);
+ let l_a_3_a_I : Inner = conv_Inner(a[3u].a[I]);
+ let l_a_I_a_2 : Inner = conv_Inner(a[I].a[2u]);
+ let l_a_I_a_J : Inner = conv_Inner(a[I].a[J]);
+ let l_a_3_a_2_m : ${mat} = load_a_3_a_2_m();
+ let l_a_3_a_I_m : ${mat} = load_a_3_a_p0_m(u32(I));
+ let l_a_I_a_2_m : ${mat} = load_a_p0_a_2_m(u32(I));
+ let l_a_I_a_J_m : ${mat} = load_a_p0_a_p1_m(u32(I), u32(J));
+ let l_a_3_a_2_m_1 : ${col_vector_type} = a[3u].a[2u].m_1;
+ let l_a_I_a_J_m_K : ${col_vector_type} = load_a_p0_a_p1_m_p2(u32(I), u32(J), u32(K));
+ let l_a_2_a_0_m_1_0 : ${elem_type} = a[3u].a[2u].m_1[0u];
+ let l_a_I_a_J_m_K_I : ${elem_type} = load_a_p0_a_p1_m_p2_p3(u32(I), u32(J), u32(K), u32(I));
+}
+)";
+ std::string col_tableLoadColumn = matrix.JoinTemplatedStringForEachMatrixColumn( //
+ R"( case ${col_id_for_tmpl}u: {
+ return a[p0].a[p1].m_${col_id_for_tmpl};
+ })",
+ "\n");
+ std::string col_tableLoadElement = matrix.JoinTemplatedStringForEachMatrixColumn( //
+ R"( case ${col_id_for_tmpl}u: {
+ return a[p0].a[p1].m_${col_id_for_tmpl}[p3];
+ })",
+ "\n");
+ uint32_t last_size =
+ 64 - static_cast<uint32_t>(matrix.ColumnVectorAlign() * (matrix.columns - 1));
+ expect = matrix.ReplaceFieldsInString(
+ expect,
+ {{"${col_vectors}", matrix.ExpendedColumnVectorsWithLastSize(2, "m_", last_size)},
+ {"${col_vectors_inline_conv_Inner}",
+ matrix.ExpendedColumnVectorsInline("val.m_", ", ")},
+ {"${col_vectors_inline_load_matrix}",
+ matrix.ExpendedColumnVectorsInline("(*(s)).m_", ", ")},
+ {"${col_table_load_column}", col_tableLoadColumn},
+ {"${col_table_load_element}", col_tableLoadElement}});
+ } else {
+ expect = src;
+ }
+
+ auto got = Run<Std140>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_P(Std140Test_Matrix, ArrayStructMatUniform_CopyArray_UniformToStorage) {
auto matrix = GetParam();
diff --git a/src/tint/lang/wgsl/ast/transform/std140_f32_test.cc b/src/tint/lang/wgsl/ast/transform/std140_f32_test.cc
index f5a7d19..ed81103 100644
--- a/src/tint/lang/wgsl/ast/transform/std140_f32_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/std140_f32_test.cc
@@ -693,6 +693,360 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(Std140Test_F32, MatUniform_LoadColumn_ConstIndex_Mat3x2F32_ViaPointerIndex) {
+ auto* src = R"(
+@group(0) @binding(0) var<uniform> a : mat3x2<f32>;
+
+fn f() {
+ let p = &a;
+ let l = p[1];
+}
+)";
+
+ auto* expect = R"(
+struct mat3x2_f32 {
+ col0 : vec2<f32>,
+ col1 : vec2<f32>,
+ col2 : vec2<f32>,
+}
+
+@group(0) @binding(0) var<uniform> a : mat3x2_f32;
+
+fn conv_mat3x2_f32(val : mat3x2_f32) -> mat3x2<f32> {
+ return mat3x2<f32>(val.col0, val.col1, val.col2);
+}
+
+fn f() {
+ let p = conv_mat3x2_f32(a);
+ let l = a.col1;
+}
+)";
+
+ auto got = Run<Std140>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Std140Test_F32, MatUniform_LoadColumn_VariableIndex_Mat3x2F32_ViaPointerIndex) {
+ auto* src = R"(
+@group(0) @binding(0) var<uniform> a : mat3x2<f32>;
+
+fn f() {
+ let I = 1;
+ let p = &a;
+ let l = p[I];
+}
+)";
+
+ auto* expect = R"(
+struct mat3x2_f32 {
+ col0 : vec2<f32>,
+ col1 : vec2<f32>,
+ col2 : vec2<f32>,
+}
+
+@group(0) @binding(0) var<uniform> a : mat3x2_f32;
+
+fn conv_mat3x2_f32(val : mat3x2_f32) -> mat3x2<f32> {
+ return mat3x2<f32>(val.col0, val.col1, val.col2);
+}
+
+fn load_a_p0(p0 : u32) -> vec2<f32> {
+ switch(p0) {
+ case 0u: {
+ return a.col0;
+ }
+ case 1u: {
+ return a.col1;
+ }
+ case 2u: {
+ return a.col2;
+ }
+ default: {
+ return vec2<f32>();
+ }
+ }
+}
+
+fn f() {
+ let I = 1;
+ let p = conv_mat3x2_f32(a);
+ let l = load_a_p0(u32(I));
+}
+)";
+
+ auto got = Run<Std140>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Std140Test_F32, MatUniform_LoadColumnSwizzle_ConstIndex_Mat3x2F32_ViaPointerIndex) {
+ auto* src = R"(
+@group(0) @binding(0) var<uniform> a : mat3x2<f32>;
+
+fn f() {
+ let p = &a;
+ let l = p[1].yx;
+}
+)";
+
+ auto* expect = R"(
+struct mat3x2_f32 {
+ col0 : vec2<f32>,
+ col1 : vec2<f32>,
+ col2 : vec2<f32>,
+}
+
+@group(0) @binding(0) var<uniform> a : mat3x2_f32;
+
+fn conv_mat3x2_f32(val : mat3x2_f32) -> mat3x2<f32> {
+ return mat3x2<f32>(val.col0, val.col1, val.col2);
+}
+
+fn f() {
+ let p = conv_mat3x2_f32(a);
+ let l = a.col1.yx;
+}
+)";
+
+ auto got = Run<Std140>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Std140Test_F32, MatUniform_LoadColumnSwizzle_VariableIndex_Mat3x2F32_ViaPointerIndex) {
+ auto* src = R"(
+@group(0) @binding(0) var<uniform> a : mat3x2<f32>;
+
+fn f() {
+ let I = 1;
+ let p = &a;
+ let l = p[I].yx;
+}
+)";
+
+ auto* expect = R"(
+struct mat3x2_f32 {
+ col0 : vec2<f32>,
+ col1 : vec2<f32>,
+ col2 : vec2<f32>,
+}
+
+@group(0) @binding(0) var<uniform> a : mat3x2_f32;
+
+fn conv_mat3x2_f32(val : mat3x2_f32) -> mat3x2<f32> {
+ return mat3x2<f32>(val.col0, val.col1, val.col2);
+}
+
+fn load_a_p0_yx(p0 : u32) -> vec2<f32> {
+ switch(p0) {
+ case 0u: {
+ return a.col0.yx;
+ }
+ case 1u: {
+ return a.col1.yx;
+ }
+ case 2u: {
+ return a.col2.yx;
+ }
+ default: {
+ return vec2<f32>();
+ }
+ }
+}
+
+fn f() {
+ let I = 1;
+ let p = conv_mat3x2_f32(a);
+ let l = load_a_p0_yx(u32(I));
+}
+)";
+
+ auto got = Run<Std140>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Std140Test_F32,
+ MatUniform_LoadScalar_ConstColumnIndex_ConstRowIndex_Mat3x2F32_ViaPointerIndex) {
+ auto* src = R"(
+@group(0) @binding(0) var<uniform> a : mat3x2<f32>;
+
+fn f() {
+ let p = &a;
+ let l = p[1][0];
+}
+)";
+
+ auto* expect = R"(
+struct mat3x2_f32 {
+ col0 : vec2<f32>,
+ col1 : vec2<f32>,
+ col2 : vec2<f32>,
+}
+
+@group(0) @binding(0) var<uniform> a : mat3x2_f32;
+
+fn conv_mat3x2_f32(val : mat3x2_f32) -> mat3x2<f32> {
+ return mat3x2<f32>(val.col0, val.col1, val.col2);
+}
+
+fn f() {
+ let p = conv_mat3x2_f32(a);
+ let l = a.col1[0u];
+}
+)";
+
+ auto got = Run<Std140>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Std140Test_F32,
+ MatUniform_LoadScalar_VariableColumnIndex_ConstRowIndex_Mat3x2F32_ViaPointerIndex) {
+ auto* src = R"(
+@group(0) @binding(0) var<uniform> a : mat3x2<f32>;
+
+fn f() {
+ let I = 0;
+ let p = &a;
+ let l = p[I][0];
+}
+)";
+
+ auto* expect = R"(
+struct mat3x2_f32 {
+ col0 : vec2<f32>,
+ col1 : vec2<f32>,
+ col2 : vec2<f32>,
+}
+
+@group(0) @binding(0) var<uniform> a : mat3x2_f32;
+
+fn conv_mat3x2_f32(val : mat3x2_f32) -> mat3x2<f32> {
+ return mat3x2<f32>(val.col0, val.col1, val.col2);
+}
+
+fn load_a_p0_0(p0 : u32) -> f32 {
+ switch(p0) {
+ case 0u: {
+ return a.col0[0u];
+ }
+ case 1u: {
+ return a.col1[0u];
+ }
+ case 2u: {
+ return a.col2[0u];
+ }
+ default: {
+ return f32();
+ }
+ }
+}
+
+fn f() {
+ let I = 0;
+ let p = conv_mat3x2_f32(a);
+ let l = load_a_p0_0(u32(I));
+}
+)";
+
+ auto got = Run<Std140>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Std140Test_F32,
+ MatUniform_LoadScalar_ConstColumnIndex_VariableRowIndex_Mat3x2F32_ViaPointerIndex) {
+ auto* src = R"(
+@group(0) @binding(0) var<uniform> a : mat3x2<f32>;
+
+fn f() {
+ let I = 0;
+ let p = &a;
+ let l = p[1][I];
+}
+)";
+
+ auto* expect = R"(
+struct mat3x2_f32 {
+ col0 : vec2<f32>,
+ col1 : vec2<f32>,
+ col2 : vec2<f32>,
+}
+
+@group(0) @binding(0) var<uniform> a : mat3x2_f32;
+
+fn conv_mat3x2_f32(val : mat3x2_f32) -> mat3x2<f32> {
+ return mat3x2<f32>(val.col0, val.col1, val.col2);
+}
+
+fn f() {
+ let I = 0;
+ let p = conv_mat3x2_f32(a);
+ let l = a.col1[I];
+}
+)";
+
+ auto got = Run<Std140>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(Std140Test_F32,
+ MatUniform_LoadScalar_VariableColumnIndex_VariableRowIndex_Mat3x2F32_ViaPointerIndex) {
+ auto* src = R"(
+@group(0) @binding(0) var<uniform> a : mat3x2<f32>;
+
+fn f() {
+ let I = 0;
+ let p = &a;
+ let l = p[I][I];
+}
+)";
+
+ auto* expect = R"(
+struct mat3x2_f32 {
+ col0 : vec2<f32>,
+ col1 : vec2<f32>,
+ col2 : vec2<f32>,
+}
+
+@group(0) @binding(0) var<uniform> a : mat3x2_f32;
+
+fn conv_mat3x2_f32(val : mat3x2_f32) -> mat3x2<f32> {
+ return mat3x2<f32>(val.col0, val.col1, val.col2);
+}
+
+fn load_a_p0_p1(p0 : u32, p1 : u32) -> f32 {
+ switch(p0) {
+ case 0u: {
+ return a.col0[p1];
+ }
+ case 1u: {
+ return a.col1[p1];
+ }
+ case 2u: {
+ return a.col2[p1];
+ }
+ default: {
+ return f32();
+ }
+ }
+}
+
+fn f() {
+ let I = 0;
+ let p = conv_mat3x2_f32(a);
+ let l = load_a_p0_p1(u32(I), u32(I));
+}
+)";
+
+ auto got = Run<Std140>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(Std140Test_F32, StructMatUniform_NameCollision_Mat3x2F32) {
auto* src = R"(
struct S {
@@ -841,6 +1195,48 @@
EXPECT_EQ(expect, str(got));
}
+TEST_F(Std140Test_F32, StructMatUniform_LoadColumn_ConstIndex_Mat3x2F32_ViaPointerDot) {
+ auto* src = R"(
+struct S {
+ m : mat3x2<f32>,
+}
+
+@group(0) @binding(0) var<uniform> s : S;
+
+fn f() {
+ let p = &s;
+ let l = p.m[1];
+}
+)";
+
+ auto* expect = R"(
+struct S {
+ m : mat3x2<f32>,
+}
+
+struct S_std140 {
+ m_0 : vec2<f32>,
+ m_1 : vec2<f32>,
+ m_2 : vec2<f32>,
+}
+
+@group(0) @binding(0) var<uniform> s : S_std140;
+
+fn conv_S(val : S_std140) -> S {
+ return S(mat3x2<f32>(val.m_0, val.m_1, val.m_2));
+}
+
+fn f() {
+ let p = conv_S(s);
+ let l = s.m_1;
+}
+)";
+
+ auto got = Run<Std140>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_F(Std140Test_F32, StructMatUniform_LoadColumn_VariableIndex_Mat3x2F32) {
auto* src = R"(
struct S {
diff --git a/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers_test.cc b/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers_test.cc
index 88e3eb6..fd69553 100644
--- a/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers_test.cc
@@ -86,7 +86,7 @@
EXPECT_EQ(expect, str(got));
}
-TEST_P(VectorizeScalarMatrixInitializersTest, MultipleScalarsReference) {
+TEST_P(VectorizeScalarMatrixInitializersTest, MultipleScalarsViaReference) {
uint32_t cols = GetParam().first;
uint32_t rows = GetParam().second;
std::string mat_type = "mat" + std::to_string(cols) + "x" + std::to_string(rows) + "<f32>";
@@ -129,6 +129,50 @@
EXPECT_EQ(expect, str(got));
}
+TEST_P(VectorizeScalarMatrixInitializersTest, MultipleScalarsViaPointerIndex) {
+ uint32_t cols = GetParam().first;
+ uint32_t rows = GetParam().second;
+ std::string mat_type = "mat" + std::to_string(cols) + "x" + std::to_string(rows) + "<f32>";
+ std::string vec_type = "vec" + std::to_string(rows) + "<f32>";
+ std::string scalar_values;
+ std::string vector_values;
+ for (uint32_t c = 0; c < cols; c++) {
+ if (c > 0) {
+ vector_values += ", ";
+ scalar_values += ", ";
+ }
+ vector_values += vec_type + "(";
+ for (uint32_t r = 0; r < rows; r++) {
+ if (r > 0) {
+ scalar_values += ", ";
+ vector_values += ", ";
+ }
+ auto value = "p[" + std::to_string((c * rows + r) % 4) + "]";
+ scalar_values += value;
+ vector_values += value;
+ }
+ vector_values += ")";
+ }
+
+ std::string tmpl = R"(
+@fragment
+fn main() {
+ var v = vec4<f32>(1.0, 2.0, 3.0, 8.0);
+ let p = &(v);
+ let m = ${matrix}(${values});
+}
+)";
+ tmpl = tint::ReplaceAll(tmpl, "${matrix}", mat_type);
+ auto src = tint::ReplaceAll(tmpl, "${values}", scalar_values);
+ auto expect = tint::ReplaceAll(tmpl, "${values}", vector_values);
+
+ EXPECT_TRUE(ShouldRun<VectorizeScalarMatrixInitializers>(src));
+
+ auto got = Run<VectorizeScalarMatrixInitializers>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
TEST_P(VectorizeScalarMatrixInitializersTest, NonScalarInitializers) {
uint32_t cols = GetParam().first;
uint32_t rows = GetParam().second;
diff --git a/src/tint/lang/wgsl/features/language_feature.cc b/src/tint/lang/wgsl/features/language_feature.cc
index 0dc4b3a..4561eb0 100644
--- a/src/tint/lang/wgsl/features/language_feature.cc
+++ b/src/tint/lang/wgsl/features/language_feature.cc
@@ -60,6 +60,9 @@
if (str == "packed_4x8_integer_dot_product") {
return LanguageFeature::kPacked4X8IntegerDotProduct;
}
+ if (str == "pointer_composite_access") {
+ return LanguageFeature::kPointerCompositeAccess;
+ }
if (str == "readonly_and_readwrite_storage_textures") {
return LanguageFeature::kReadonlyAndReadwriteStorageTextures;
}
@@ -82,6 +85,8 @@
return "chromium_testing_unsafe_experimental";
case LanguageFeature::kPacked4X8IntegerDotProduct:
return "packed_4x8_integer_dot_product";
+ case LanguageFeature::kPointerCompositeAccess:
+ return "pointer_composite_access";
case LanguageFeature::kReadonlyAndReadwriteStorageTextures:
return "readonly_and_readwrite_storage_textures";
}
diff --git a/src/tint/lang/wgsl/features/language_feature.h b/src/tint/lang/wgsl/features/language_feature.h
index 0f1d2c9..aa7315b 100644
--- a/src/tint/lang/wgsl/features/language_feature.h
+++ b/src/tint/lang/wgsl/features/language_feature.h
@@ -52,6 +52,7 @@
kChromiumTestingUnimplemented,
kChromiumTestingUnsafeExperimental,
kPacked4X8IntegerDotProduct,
+ kPointerCompositeAccess,
kReadonlyAndReadwriteStorageTextures,
};
@@ -71,6 +72,7 @@
"chromium_testing_unimplemented",
"chromium_testing_unsafe_experimental",
"packed_4x8_integer_dot_product",
+ "pointer_composite_access",
"readonly_and_readwrite_storage_textures",
};
@@ -82,6 +84,7 @@
LanguageFeature::kChromiumTestingUnimplemented,
LanguageFeature::kChromiumTestingUnsafeExperimental,
LanguageFeature::kPacked4X8IntegerDotProduct,
+ LanguageFeature::kPointerCompositeAccess,
LanguageFeature::kReadonlyAndReadwriteStorageTextures,
};
diff --git a/src/tint/lang/wgsl/features/status.cc b/src/tint/lang/wgsl/features/status.cc
index b0c4bb3..f8297ea 100644
--- a/src/tint/lang/wgsl/features/status.cc
+++ b/src/tint/lang/wgsl/features/status.cc
@@ -35,6 +35,7 @@
switch (f) {
case LanguageFeature::kReadonlyAndReadwriteStorageTextures:
case LanguageFeature::kPacked4X8IntegerDotProduct:
+ case LanguageFeature::kPointerCompositeAccess:
return FeatureStatus::kExperimental;
case LanguageFeature::kUndefined:
return FeatureStatus::kUnknown;
diff --git a/src/tint/lang/wgsl/language_feature_test.cc b/src/tint/lang/wgsl/language_feature_test.cc
index 6a103ad..991f5db 100644
--- a/src/tint/lang/wgsl/language_feature_test.cc
+++ b/src/tint/lang/wgsl/language_feature_test.cc
@@ -64,6 +64,7 @@
{"chromium_testing_unimplemented", LanguageFeature::kChromiumTestingUnimplemented},
{"chromium_testing_unsafe_experimental", LanguageFeature::kChromiumTestingUnsafeExperimental},
{"packed_4x8_integer_dot_product", LanguageFeature::kPacked4X8IntegerDotProduct},
+ {"pointer_composite_access", LanguageFeature::kPointerCompositeAccess},
{"readonly_and_readwrite_storage_textures",
LanguageFeature::kReadonlyAndReadwriteStorageTextures},
};
@@ -87,9 +88,12 @@
{"packed_4x8_integer_Eot_product", LanguageFeature::kUndefined},
{"paked_4x8_integePP_dTTt_product", LanguageFeature::kUndefined},
{"packed_4x8_integxxrdot_pddoduct", LanguageFeature::kUndefined},
- {"readon44y_and_readwrite_storage_textures", LanguageFeature::kUndefined},
- {"readonly_and_readwrite_storageVVSSextures", LanguageFeature::kUndefined},
- {"rRadonly_an_rea22write_storRge_textures", LanguageFeature::kUndefined},
+ {"44ointer_composite_access", LanguageFeature::kUndefined},
+ {"VVSSinter_composite_access", LanguageFeature::kUndefined},
+ {"poinR2er_compRsite_acess", LanguageFeature::kUndefined},
+ {"readonlF_and_readwrite_st9rage_textues", LanguageFeature::kUndefined},
+ {"readonly_and_radwrite_storage_textures", LanguageFeature::kUndefined},
+ {"readonly_and_readwrite_sOOrage_tVxRRures", LanguageFeature::kUndefined},
};
using LanguageFeatureParseTest = testing::TestWithParam<Case>;
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/accessor_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/accessor_test.cc
index 9e0ffa9..b763ef9 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/accessor_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/accessor_test.cc
@@ -66,6 +66,60 @@
)");
}
+TEST_F(ProgramToIRAccessorTest, Accessor_Var_ArraySingleIndex_ViaDerefPointerIndex) {
+ // var a: array<u32, 3>
+ // let p = &a;
+ // let b = (*p)[2]
+
+ auto* a = Var("a", ty.array<u32, 3>(), core::AddressSpace::kFunction);
+ auto* p = Let("p", AddressOf(a));
+ auto* expr = Decl(Let("b", IndexAccessor(Deref(p), 2_u)));
+ WrapInFunction(Decl(a), Decl(p), expr);
+
+ auto m = Build();
+ ASSERT_EQ(m, Success);
+
+ EXPECT_EQ(Disassemble(m.Get()),
+ R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+ %b1 = block {
+ %a:ptr<function, array<u32, 3>, read_write> = var
+ %p:ptr<function, array<u32, 3>, read_write> = let %a
+ %4:ptr<function, u32, read_write> = access %p, 2u
+ %5:u32 = load %4
+ %b:u32 = let %5
+ ret
+ }
+}
+)");
+}
+
+TEST_F(ProgramToIRAccessorTest, Accessor_Var_ArraySingleIndex_ViaPointerIndex) {
+ // var a: array<u32, 3>
+ // let p = &a;
+ // let b = p[2]
+
+ auto* a = Var("a", ty.array<u32, 3>(), core::AddressSpace::kFunction);
+ auto* p = Let("p", AddressOf(a));
+ auto* expr = Decl(Let("b", IndexAccessor(p, 2_u)));
+ WrapInFunction(Decl(a), Decl(p), expr);
+
+ auto m = Build();
+ ASSERT_EQ(m, Success);
+
+ EXPECT_EQ(Disassemble(m.Get()),
+ R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+ %b1 = block {
+ %a:ptr<function, array<u32, 3>, read_write> = var
+ %p:ptr<function, array<u32, 3>, read_write> = let %a
+ %4:ptr<function, u32, read_write> = access %p, 2u
+ %5:u32 = load %4
+ %b:u32 = let %5
+ ret
+ }
+}
+)");
+}
+
TEST_F(ProgramToIRAccessorTest, Accessor_Multiple) {
// let a: vec4<u32> = vec4();
// let b = a[2]
@@ -117,6 +171,58 @@
)");
}
+TEST_F(ProgramToIRAccessorTest, Accessor_Var_VectorSingleIndex_ViaDerefPointerIndex) {
+ // var a: vec3<u32>
+ // let p = &a;
+ // let b = (*p)[2]
+
+ auto* a = Var("a", ty.vec3<u32>(), core::AddressSpace::kFunction);
+ auto* p = Let("p", AddressOf(a));
+ auto* expr = Decl(Let("b", IndexAccessor(Deref(p), 2_u)));
+ WrapInFunction(Decl(a), Decl(p), expr);
+
+ auto m = Build();
+ ASSERT_EQ(m, Success);
+
+ EXPECT_EQ(Disassemble(m.Get()),
+ R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+ %b1 = block {
+ %a:ptr<function, vec3<u32>, read_write> = var
+ %p:ptr<function, vec3<u32>, read_write> = let %a
+ %4:u32 = load_vector_element %p, 2u
+ %b:u32 = let %4
+ ret
+ }
+}
+)");
+}
+
+TEST_F(ProgramToIRAccessorTest, Accessor_Var_VectorSingleIndex_ViaPointerIndex) {
+ // var a: vec3<u32>
+ // let p = &a;
+ // let b = p[2]
+
+ auto* a = Var("a", ty.vec3<u32>(), core::AddressSpace::kFunction);
+ auto* p = Let("p", AddressOf(a));
+ auto* expr = Decl(Let("b", IndexAccessor(p, 2_u)));
+ WrapInFunction(Decl(a), Decl(p), expr);
+
+ auto m = Build();
+ ASSERT_EQ(m, Success);
+
+ EXPECT_EQ(Disassemble(m.Get()),
+ R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+ %b1 = block {
+ %a:ptr<function, vec3<u32>, read_write> = var
+ %p:ptr<function, vec3<u32>, read_write> = let %a
+ %4:u32 = load_vector_element %p, 2u
+ %b:u32 = let %4
+ ret
+ }
+}
+)");
+}
+
TEST_F(ProgramToIRAccessorTest, Accessor_Var_ArraysMultiIndex) {
// var a: array<array<f32, 4>, 3>
// let b = a[2][3]
@@ -141,6 +247,60 @@
)");
}
+TEST_F(ProgramToIRAccessorTest, Accessor_Var_ArraysMultiIndex_ViaDerefPointerIndex) {
+ // var a: array<array<f32, 4>, 3>
+ // let p = &a;
+ // let b = (*p)[2][3]
+
+ auto* a = Var("a", ty.array<array<f32, 4>, 3>(), core::AddressSpace::kFunction);
+ auto* p = Let("p", AddressOf(a));
+ auto* expr = Decl(Let("b", IndexAccessor(IndexAccessor(Deref(p), 2_u), 3_u)));
+ WrapInFunction(Decl(a), Decl(p), expr);
+
+ auto m = Build();
+ ASSERT_EQ(m, Success);
+
+ EXPECT_EQ(Disassemble(m.Get()),
+ R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+ %b1 = block {
+ %a:ptr<function, array<array<f32, 4>, 3>, read_write> = var
+ %p:ptr<function, array<array<f32, 4>, 3>, read_write> = let %a
+ %4:ptr<function, f32, read_write> = access %p, 2u, 3u
+ %5:f32 = load %4
+ %b:f32 = let %5
+ ret
+ }
+}
+)");
+}
+
+TEST_F(ProgramToIRAccessorTest, Accessor_Var_ArraysMultiIndex_ViaPointerIndex) {
+ // var a: array<array<f32, 4>, 3>
+ // let p = &a;
+ // let b = p[2][3]
+
+ auto* a = Var("a", ty.array<array<f32, 4>, 3>(), core::AddressSpace::kFunction);
+ auto* p = Let("p", AddressOf(a));
+ auto* expr = Decl(Let("b", IndexAccessor(IndexAccessor(p, 2_u), 3_u)));
+ WrapInFunction(Decl(a), Decl(p), expr);
+
+ auto m = Build();
+ ASSERT_EQ(m, Success);
+
+ EXPECT_EQ(Disassemble(m.Get()),
+ R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+ %b1 = block {
+ %a:ptr<function, array<array<f32, 4>, 3>, read_write> = var
+ %p:ptr<function, array<array<f32, 4>, 3>, read_write> = let %a
+ %4:ptr<function, f32, read_write> = access %p, 2u, 3u
+ %5:f32 = load %4
+ %b:f32 = let %5
+ ret
+ }
+}
+)");
+}
+
TEST_F(ProgramToIRAccessorTest, Accessor_Var_MatrixMultiIndex) {
// var a: mat3x4<f32>
// let b = a[2][3]
@@ -197,6 +357,76 @@
)");
}
+TEST_F(ProgramToIRAccessorTest, Accessor_Var_SingleMember_ViaDerefPointerIndex) {
+ // struct MyStruct { foo: i32 }
+ // var a: MyStruct;
+ // let p = &a;
+ // let b = (*p).foo
+
+ auto* s = Structure("MyStruct", Vector{
+ Member("foo", ty.i32()),
+ });
+ auto* a = Var("a", ty.Of(s), core::AddressSpace::kFunction);
+ auto* p = Let("p", AddressOf(a));
+ auto* expr = Decl(Let("b", MemberAccessor(Deref(p), "foo")));
+ WrapInFunction(Decl(a), Decl(p), expr);
+
+ auto m = Build();
+ ASSERT_EQ(m, Success);
+
+ EXPECT_EQ(Disassemble(m.Get()),
+ R"(MyStruct = struct @align(4) {
+ foo:i32 @offset(0)
+}
+
+%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+ %b1 = block {
+ %a:ptr<function, MyStruct, read_write> = var
+ %p:ptr<function, MyStruct, read_write> = let %a
+ %4:ptr<function, i32, read_write> = access %p, 0u
+ %5:i32 = load %4
+ %b:i32 = let %5
+ ret
+ }
+}
+)");
+}
+
+TEST_F(ProgramToIRAccessorTest, Accessor_Var_SingleMember_ViaPointerIndex) {
+ // struct MyStruct { foo: i32 }
+ // var a: MyStruct;
+ // let p = &a;
+ // let b = p.foo
+
+ auto* s = Structure("MyStruct", Vector{
+ Member("foo", ty.i32()),
+ });
+ auto* a = Var("a", ty.Of(s), core::AddressSpace::kFunction);
+ auto* p = Let("p", AddressOf(a));
+ auto* expr = Decl(Let("b", MemberAccessor(p, "foo")));
+ WrapInFunction(Decl(a), Decl(p), expr);
+
+ auto m = Build();
+ ASSERT_EQ(m, Success);
+
+ EXPECT_EQ(Disassemble(m.Get()),
+ R"(MyStruct = struct @align(4) {
+ foo:i32 @offset(0)
+}
+
+%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+ %b1 = block {
+ %a:ptr<function, MyStruct, read_write> = var
+ %p:ptr<function, MyStruct, read_write> = let %a
+ %4:ptr<function, i32, read_write> = access %p, 0u
+ %5:i32 = load %4
+ %b:i32 = let %5
+ ret
+ }
+}
+)");
+}
+
TEST_F(ProgramToIRAccessorTest, Accessor_Var_MultiMember) {
// struct Inner { bar: f32 }
// struct Outer { a: i32, foo: Inner }
@@ -333,6 +563,58 @@
)");
}
+TEST_F(ProgramToIRAccessorTest, Accessor_Var_VectorElementSwizzle_ViaDerefPointerIndex) {
+ // var a: vec2<f32>
+ // let p = &a;
+ // let b = (*p).y
+
+ auto* a = Var("a", ty.vec2<f32>(), core::AddressSpace::kFunction);
+ auto* p = Let("p", AddressOf(a));
+ auto* expr = Decl(Let("b", MemberAccessor(Deref(p), "y")));
+ WrapInFunction(Decl(a), Decl(p), expr);
+
+ auto m = Build();
+ ASSERT_EQ(m, Success);
+
+ EXPECT_EQ(Disassemble(m.Get()),
+ R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+ %b1 = block {
+ %a:ptr<function, vec2<f32>, read_write> = var
+ %p:ptr<function, vec2<f32>, read_write> = let %a
+ %4:f32 = load_vector_element %p, 1u
+ %b:f32 = let %4
+ ret
+ }
+}
+)");
+}
+
+TEST_F(ProgramToIRAccessorTest, Accessor_Var_VectorElementSwizzle_ViaPointerIndex) {
+ // var a: vec2<f32>
+ // let p = &a;
+ // let b = p.y
+
+ auto* a = Var("a", ty.vec2<f32>(), core::AddressSpace::kFunction);
+ auto* p = Let("p", AddressOf(a));
+ auto* expr = Decl(Let("b", MemberAccessor(p, "y")));
+ WrapInFunction(Decl(a), Decl(p), expr);
+
+ auto m = Build();
+ ASSERT_EQ(m, Success);
+
+ EXPECT_EQ(Disassemble(m.Get()),
+ R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
+ %b1 = block {
+ %a:ptr<function, vec2<f32>, read_write> = var
+ %p:ptr<function, vec2<f32>, read_write> = let %a
+ %4:f32 = load_vector_element %p, 1u
+ %b:f32 = let %4
+ ret
+ }
+}
+)");
+}
+
TEST_F(ProgramToIRAccessorTest, Accessor_Var_MultiElementSwizzle) {
// var a: vec3<f32>
// let b = a.zyxz
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
index 740c918..cc5a137 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
@@ -1098,12 +1098,12 @@
return std::nullopt;
}
- auto* ref = access->Object()->Type()->As<core::type::Reference>();
- if (!ref) {
+ auto* memory_view = access->Object()->Type()->As<core::type::MemoryView>();
+ if (!memory_view) {
return std::nullopt;
}
- if (!ref->StoreType()->Is<core::type::Vector>()) {
+ if (!memory_view->StoreType()->Is<core::type::Vector>()) {
return std::nullopt;
}
return tint::Switch(
diff --git a/src/tint/lang/wgsl/resolver/array_accessor_test.cc b/src/tint/lang/wgsl/resolver/array_accessor_test.cc
index aece64f..937568d 100644
--- a/src/tint/lang/wgsl/resolver/array_accessor_test.cc
+++ b/src/tint/lang/wgsl/resolver/array_accessor_test.cc
@@ -41,7 +41,7 @@
using ResolverIndexAccessorTest = ResolverTest;
-TEST_F(ResolverIndexAccessorTest, Matrix_Dynamic_F32) {
+TEST_F(ResolverIndexAccessorTest, Matrix_F32) {
GlobalVar("my_var", ty.mat2x3<f32>(), core::AddressSpace::kPrivate);
auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, 1_f));
WrapInFunction(acc);
@@ -82,7 +82,7 @@
TEST_F(ResolverIndexAccessorTest, Matrix_Dynamic) {
GlobalConst("my_const", ty.mat2x3<f32>(), Call<mat2x3<f32>>());
auto* idx = Var("idx", ty.i32(), Call<i32>());
- auto* acc = IndexAccessor("my_const", Expr(Source{{12, 34}}, idx));
+ auto* acc = IndexAccessor("my_const", idx);
WrapInFunction(Decl(idx), acc);
EXPECT_TRUE(r()->Resolve());
@@ -97,7 +97,7 @@
TEST_F(ResolverIndexAccessorTest, Matrix_XDimension_Dynamic) {
GlobalConst("my_const", ty.mat4x4<f32>(), Call<mat4x4<f32>>());
auto* idx = Var("idx", ty.u32(), Expr(3_u));
- auto* acc = IndexAccessor("my_const", Expr(Source{{12, 34}}, idx));
+ auto* acc = IndexAccessor("my_const", idx);
WrapInFunction(Decl(idx), acc);
EXPECT_TRUE(r()->Resolve());
@@ -106,9 +106,10 @@
TEST_F(ResolverIndexAccessorTest, Matrix_BothDimension_Dynamic) {
GlobalConst("my_const", ty.mat4x4<f32>(), Call<mat4x4<f32>>());
- auto* idx = Var("idy", ty.u32(), Expr(2_u));
- auto* acc = IndexAccessor(IndexAccessor("my_const", Expr(Source{{12, 34}}, idx)), 1_i);
- WrapInFunction(Decl(idx), acc);
+ auto* idx = Var("idx", ty.u32(), Expr(3_u));
+ auto* idy = Var("idy", ty.u32(), Expr(2_u));
+ auto* acc = IndexAccessor(IndexAccessor("my_const", idx), idy);
+ WrapInFunction(Decl(idx), Decl(idy), acc);
EXPECT_TRUE(r()->Resolve());
EXPECT_EQ(r()->error(), "");
@@ -175,16 +176,16 @@
TEST_F(ResolverIndexAccessorTest, Vector_Dynamic) {
GlobalConst("my_const", ty.vec3<f32>(), Call<vec3<f32>>());
auto* idx = Var("idx", ty.i32(), Expr(2_i));
- auto* acc = IndexAccessor("my_const", Expr(Source{{12, 34}}, idx));
+ auto* acc = IndexAccessor("my_const", idx);
WrapInFunction(Decl(idx), acc);
EXPECT_TRUE(r()->Resolve());
}
TEST_F(ResolverIndexAccessorTest, Vector) {
- GlobalVar("my_var", ty.vec3<f32>(), core::AddressSpace::kPrivate);
+ GlobalConst("my_const", ty.vec3<f32>(), Call<vec3<f32>>());
- auto* acc = IndexAccessor("my_var", 2_i);
+ auto* acc = IndexAccessor("my_const", 2_i);
WrapInFunction(acc);
EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -346,6 +347,26 @@
EXPECT_EQ(idx_sem->Object()->Declaration(), acc->object);
}
+TEST_F(ResolverIndexAccessorTest, Expr_ImplicitDeref_FuncGoodParent) {
+ // fn func(p: ptr<function, vec4<f32>>) -> f32 {
+ // let idx: u32 = u32();
+ // let x: f32 = p[idx];
+ // return x;
+ // }
+ auto* p = Param("p", ty.ptr<function, vec4<f32>>());
+ auto* idx = Let("idx", ty.u32(), Call<u32>());
+ auto* acc = IndexAccessor(Source{{12, 34}}, p, idx);
+ auto* x = Var("x", ty.f32(), acc);
+ Func("func", Vector{p}, ty.f32(), Vector{Decl(idx), Decl(x), Return(x)});
+
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto idx_sem = Sem().Get(acc)->UnwrapLoad()->As<sem::IndexAccessorExpression>();
+ ASSERT_NE(idx_sem, nullptr);
+ EXPECT_EQ(idx_sem->Index()->Declaration(), acc->index);
+ EXPECT_EQ(idx_sem->Object()->Declaration(), acc->object);
+}
+
TEST_F(ResolverIndexAccessorTest, Expr_Deref_FuncBadParent) {
// fn func(p: ptr<function, vec4<f32>>) -> f32 {
// let idx: u32 = u32();
@@ -360,11 +381,10 @@
Func("func", Vector{p}, ty.f32(), Vector{Decl(idx), Decl(x), Return(x)});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "12:34 error: cannot index type 'ptr<function, vec4<f32>, read_write>'");
+ EXPECT_EQ(r()->error(), "12:34 error: cannot dereference expression of type 'f32'");
}
-TEST_F(ResolverIndexAccessorTest, Exr_Deref_BadParent) {
+TEST_F(ResolverIndexAccessorTest, Expr_Deref_BadParent) {
// var param: vec4<f32>
// let x: f32 = *(¶m)[0];
auto* param = Var("param", ty.vec4<f32>());
@@ -376,8 +396,7 @@
WrapInFunction(param, idx, x);
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "12:34 error: cannot index type 'ptr<function, vec4<f32>, read_write>'");
+ EXPECT_EQ(r()->error(), "12:34 error: cannot dereference expression of type 'f32'");
}
} // namespace
diff --git a/src/tint/lang/wgsl/resolver/ptr_ref_test.cc b/src/tint/lang/wgsl/resolver/ptr_ref_test.cc
index 70c5b7b..892dd90 100644
--- a/src/tint/lang/wgsl/resolver/ptr_ref_test.cc
+++ b/src/tint/lang/wgsl/resolver/ptr_ref_test.cc
@@ -35,6 +35,7 @@
namespace tint::resolver {
namespace {
+using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
struct ResolverPtrRefTest : public resolver::TestHelper, public testing::Test {};
@@ -122,5 +123,320 @@
EXPECT_EQ(TypeOf(storage_ptr)->As<core::type::Pointer>()->Access(), core::Access::kRead);
}
+TEST_F(ResolverPtrRefTest, ArrayIndexAccessorViaDerefPointer) {
+ // var a : array<i32, 3>;
+ // let p = &a;
+ // (*p)[0]
+
+ auto* v = Var("v", ty.array<i32, 3>());
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = IndexAccessor(Deref(p), 0_i);
+
+ WrapInFunction(v, p, expr);
+
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto* load = Sem().Get<sem::Load>(expr);
+ ASSERT_NE(load, nullptr);
+
+ auto* ref = load->Reference();
+ ASSERT_NE(ref, nullptr);
+
+ ASSERT_TRUE(ref->Type()->Is<core::type::Reference>());
+ EXPECT_TRUE(ref->Type()->As<core::type::Reference>()->StoreType()->Is<core::type::I32>());
+}
+
+TEST_F(ResolverPtrRefTest, ArrayIndexAccessorViaPointer) {
+ // var a : array<i32, 3>;
+ // let p = &a;
+ // p[0]
+
+ auto* v = Var("v", ty.array<i32, 3>());
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = IndexAccessor(p, 0_i);
+ WrapInFunction(v, p, expr);
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto* load = Sem().Get<sem::Load>(expr);
+ ASSERT_NE(load, nullptr);
+
+ auto* ref = load->Reference();
+ ASSERT_NE(ref, nullptr);
+
+ ASSERT_TRUE(ref->Type()->Is<core::type::Reference>());
+ EXPECT_TRUE(ref->Type()->As<core::type::Reference>()->StoreType()->Is<core::type::I32>());
+}
+
+TEST_F(ResolverPtrRefTest, ArrayIndexAccessorViaPointer_FeatureDisallowed) {
+ // var a : array<i32, 3>;
+ // let p = &a;
+ // p[0]
+
+ auto* v = Var("v", ty.array<i32, 3>());
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = IndexAccessor(Source{Source::Location{12, 34}}, p, 0_i);
+ WrapInFunction(v, p, expr);
+
+ auto resolver = Resolver(this, {});
+ EXPECT_FALSE(resolver.Resolve());
+ EXPECT_EQ(resolver.error(),
+ "12:34 error: pointer composite access requires the pointer_composite_access "
+ "language feature, which is not allowed in the current environment");
+}
+
+TEST_F(ResolverPtrRefTest, VectorIndexAccessorViaDerefPointer) {
+ // var a : vec3<i32>;
+ // let p = &a;
+ // (*p)[0]
+
+ auto* v = Var("v", ty.vec3<i32>());
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = IndexAccessor(Deref(p), 0_i);
+ WrapInFunction(v, p, expr);
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto* load = Sem().Get<sem::Load>(expr);
+ ASSERT_NE(load, nullptr);
+
+ auto* ref = load->Reference();
+ ASSERT_NE(ref, nullptr);
+
+ ASSERT_TRUE(ref->Type()->Is<core::type::Reference>());
+ EXPECT_TRUE(ref->Type()->As<core::type::Reference>()->StoreType()->Is<core::type::I32>());
+}
+
+TEST_F(ResolverPtrRefTest, VectorIndexAccessorViaPointer) {
+ // var a : vec3<i32>;
+ // let p = &a;
+ // p[0]
+
+ auto* v = Var("v", ty.vec3<i32>());
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = IndexAccessor(p, 0_i);
+ WrapInFunction(v, p, expr);
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto* load = Sem().Get<sem::Load>(expr);
+ ASSERT_NE(load, nullptr);
+
+ auto* ref = load->Reference();
+ ASSERT_NE(ref, nullptr);
+
+ ASSERT_TRUE(ref->Type()->Is<core::type::Reference>());
+ EXPECT_TRUE(ref->Type()->As<core::type::Reference>()->StoreType()->Is<core::type::I32>());
+}
+
+TEST_F(ResolverPtrRefTest, VectorIndexAccessorViaPointer_FeatureDisallowed) {
+ // var a : vec3<i32>;
+ // let p = &a;
+ // p[0]
+
+ auto* v = Var("v", ty.vec3<i32>());
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = IndexAccessor(Source{Source::Location{12, 34}}, p, 0_i);
+ WrapInFunction(v, p, expr);
+
+ auto resolver = Resolver(this, {});
+ EXPECT_FALSE(resolver.Resolve());
+ EXPECT_EQ(resolver.error(),
+ "12:34 error: pointer composite access requires the pointer_composite_access "
+ "language feature, which is not allowed in the current environment");
+}
+
+TEST_F(ResolverPtrRefTest, VectorMemberAccessorViaDerefPointer) {
+ // var a : vec3<i32>;
+ // let p = &a;
+ // (*p).x
+
+ auto* v = Var("v", ty.vec3<i32>());
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = MemberAccessor(Deref(p), "x");
+ WrapInFunction(v, p, expr);
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto* load = Sem().Get<sem::Load>(expr);
+ ASSERT_NE(load, nullptr);
+
+ auto* ref = load->Reference();
+ ASSERT_NE(ref, nullptr);
+
+ ASSERT_TRUE(ref->Type()->Is<core::type::Reference>());
+ EXPECT_TRUE(ref->Type()->As<core::type::Reference>()->StoreType()->Is<core::type::I32>());
+}
+
+TEST_F(ResolverPtrRefTest, VectorMemberAccessorViaPointer) {
+ // var a : vec3<i32>;
+ // let p = &a;
+ // p.x
+
+ auto* v = Var("v", ty.vec3<i32>());
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = MemberAccessor(p, "x");
+ WrapInFunction(v, p, expr);
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto* load = Sem().Get<sem::Load>(expr);
+ ASSERT_NE(load, nullptr);
+
+ auto* ref = load->Reference();
+ ASSERT_NE(ref, nullptr);
+
+ ASSERT_TRUE(ref->Type()->Is<core::type::Reference>());
+ EXPECT_TRUE(ref->Type()->As<core::type::Reference>()->StoreType()->Is<core::type::I32>());
+}
+
+TEST_F(ResolverPtrRefTest, VectorMemberAccessorViaPointer_FeatureDisallowed) {
+ // var a : vec3<i32>;
+ // let p = &a;
+ // p.x
+
+ auto* v = Var("v", ty.vec3<i32>());
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = MemberAccessor(Source{Source::Location{12, 34}}, p, "x");
+ WrapInFunction(v, p, expr);
+
+ auto resolver = Resolver(this, {});
+ EXPECT_FALSE(resolver.Resolve());
+ EXPECT_EQ(resolver.error(),
+ "12:34 error: pointer composite access requires the pointer_composite_access "
+ "language feature, which is not allowed in the current environment");
+}
+
+TEST_F(ResolverPtrRefTest, MatrixIndexAccessorViaDerefPointer) {
+ // var a : mat2x3<f32>;
+ // let p = &a;
+ // (*p)[0]
+
+ auto* v = Var("v", ty.mat2x3<f32>());
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = IndexAccessor(Deref(p), 0_i);
+ WrapInFunction(v, p, expr);
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto* load = Sem().Get<sem::Load>(expr);
+ ASSERT_NE(load, nullptr);
+
+ auto* ref = load->Reference();
+ ASSERT_NE(ref, nullptr);
+
+ ASSERT_TRUE(ref->Type()->Is<core::type::Reference>());
+ auto* vec = ref->Type()->As<core::type::Reference>()->StoreType()->As<core::type::Vector>();
+ ASSERT_TRUE(vec);
+ EXPECT_EQ(vec->Elements().count, 3u);
+ EXPECT_TRUE(vec->Elements().type->Is<core::type::F32>());
+}
+
+TEST_F(ResolverPtrRefTest, MatrixIndexAccessorViaPointer) {
+ // var a : mat2x3<f32>;
+ // let p = &a;
+ // p[0]
+
+ auto* v = Var("v", ty.mat2x3<f32>());
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = IndexAccessor(p, 0_i);
+ WrapInFunction(v, p, expr);
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto* load = Sem().Get<sem::Load>(expr);
+ ASSERT_NE(load, nullptr);
+
+ auto* ref = load->Reference();
+ ASSERT_NE(ref, nullptr);
+
+ ASSERT_TRUE(ref->Type()->Is<core::type::Reference>());
+ auto* vec = ref->Type()->As<core::type::Reference>()->StoreType()->As<core::type::Vector>();
+ ASSERT_TRUE(vec);
+ EXPECT_EQ(vec->Elements().count, 3u);
+ EXPECT_TRUE(vec->Elements().type->Is<core::type::F32>());
+}
+
+TEST_F(ResolverPtrRefTest, MatrixIndexAccessorViaPointer_FeatureDisallowed) {
+ // var a : mat2x3<f32>;
+ // let p = &a;
+ // p[0]
+
+ auto* v = Var("v", ty.mat2x3<f32>());
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = IndexAccessor(Source{Source::Location{12, 34}}, p, 0_i);
+ WrapInFunction(v, p, expr);
+
+ auto resolver = Resolver(this, {});
+ EXPECT_FALSE(resolver.Resolve());
+ EXPECT_EQ(resolver.error(),
+ "12:34 error: pointer composite access requires the pointer_composite_access "
+ "language feature, which is not allowed in the current environment");
+}
+
+TEST_F(ResolverPtrRefTest, StructMemberAccessorViaDerefPointer) {
+ // struct S { a : i32, }
+ // var a : S;
+ // let p = &a;
+ // (*p).a
+
+ auto* s = Structure("S", Vector{
+ Member("a", ty.i32()),
+ });
+ auto* v = Var("v", ty.Of(s));
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = MemberAccessor(Deref(p), "a");
+ WrapInFunction(v, p, expr);
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto* load = Sem().Get<sem::Load>(expr);
+ ASSERT_NE(load, nullptr);
+
+ auto* ref = load->Reference();
+ ASSERT_NE(ref, nullptr);
+
+ ASSERT_TRUE(ref->Type()->Is<core::type::Reference>());
+ EXPECT_TRUE(ref->Type()->As<core::type::Reference>()->StoreType()->Is<core::type::I32>());
+}
+
+TEST_F(ResolverPtrRefTest, StructMemberAccessorViaPointer) {
+ // struct S { a : i32, }
+ // var a : S;
+ // let p = &a;
+ // p.a
+
+ auto* s = Structure("S", Vector{
+ Member("a", ty.i32()),
+ });
+ auto* v = Var("v", ty.Of(s));
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = MemberAccessor(p, "a");
+ WrapInFunction(v, p, expr);
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto* load = Sem().Get<sem::Load>(expr);
+ ASSERT_NE(load, nullptr);
+
+ auto* ref = load->Reference();
+ ASSERT_NE(ref, nullptr);
+
+ ASSERT_TRUE(ref->Type()->Is<core::type::Reference>());
+ EXPECT_TRUE(ref->Type()->As<core::type::Reference>()->StoreType()->Is<core::type::I32>());
+}
+
+TEST_F(ResolverPtrRefTest, StructMemberAccessorViaPointer_FeatureDisallowed) {
+ // struct S { a : i32, }
+ // var a : S;
+ // let p = &a;
+ // p.a
+
+ auto* s = Structure("S", Vector{
+ Member("a", ty.i32()),
+ });
+ auto* v = Var("v", ty.Of(s));
+ auto* p = Let("p", AddressOf(v));
+ auto* expr = MemberAccessor(Source{Source::Location{12, 34}}, p, "a");
+ WrapInFunction(v, p, expr);
+
+ auto resolver = Resolver(this, {});
+ EXPECT_FALSE(resolver.Resolve());
+ EXPECT_EQ(resolver.error(),
+ "12:34 error: pointer composite access requires the pointer_composite_access "
+ "language feature, which is not allowed in the current environment");
+}
+
} // namespace
} // namespace tint::resolver
diff --git a/src/tint/lang/wgsl/resolver/resolver.cc b/src/tint/lang/wgsl/resolver/resolver.cc
index a94a59f..454e928 100644
--- a/src/tint/lang/wgsl/resolver/resolver.cc
+++ b/src/tint/lang/wgsl/resolver/resolver.cc
@@ -44,6 +44,7 @@
#include "src/tint/lang/core/type/depth_multisampled_texture.h"
#include "src/tint/lang/core/type/depth_texture.h"
#include "src/tint/lang/core/type/external_texture.h"
+#include "src/tint/lang/core/type/memory_view.h"
#include "src/tint/lang/core/type/multisampled_texture.h"
#include "src/tint/lang/core/type/pointer.h"
#include "src/tint/lang/core/type/reference.h"
@@ -1979,17 +1980,30 @@
if (!obj) {
return nullptr;
}
- auto* obj_raw_ty = obj->Type();
- auto* obj_ty = obj_raw_ty->UnwrapRef();
+ auto* object_ty = obj->Type();
+ auto* const memory_view = object_ty->As<core::type::MemoryView>();
+ const core::type::Type* storage_ty = object_ty->UnwrapRef();
+ if (memory_view) {
+ if (memory_view->Is<core::type::Pointer>() &&
+ !allowed_features_.features.count(wgsl::LanguageFeature::kPointerCompositeAccess)) {
+ AddError(
+ "pointer composite access requires the pointer_composite_access language feature, "
+ "which is not allowed in the current environment",
+ expr->source);
+ return nullptr;
+ }
+ storage_ty = memory_view->StoreType();
+ }
+
auto* ty = Switch(
- obj_ty, //
+ storage_ty, //
[&](const sem::Array* arr) { return arr->ElemType(); },
[&](const core::type::Vector* vec) { return vec->type(); },
[&](const core::type::Matrix* mat) {
return b.create<core::type::Vector>(mat->type(), mat->rows());
},
[&](Default) {
- AddError("cannot index type '" + sem_.TypeNameOf(obj_ty) + "'", expr->source);
+ AddError("cannot index type '" + sem_.TypeNameOf(storage_ty) + "'", expr->source);
return nullptr;
});
if (ty == nullptr) {
@@ -2003,9 +2017,10 @@
return nullptr;
}
- // If we're extracting from a reference, we return a reference.
- if (auto* ref = obj_raw_ty->As<core::type::Reference>()) {
- ty = b.create<core::type::Reference>(ref->AddressSpace(), ty, ref->Access());
+ // If we're extracting from a memory view, we return a reference.
+ if (memory_view) {
+ ty =
+ b.create<core::type::Reference>(memory_view->AddressSpace(), ty, memory_view->Access());
}
const core::constant::Value* val = nullptr;
@@ -3398,7 +3413,20 @@
}
auto* object_ty = object->Type();
- auto* storage_ty = object_ty->UnwrapRef();
+
+ auto* const memory_view = object_ty->As<core::type::MemoryView>();
+ const core::type::Type* storage_ty = object_ty->UnwrapRef();
+ if (memory_view) {
+ if (memory_view->Is<core::type::Pointer>() &&
+ !allowed_features_.features.count(wgsl::LanguageFeature::kPointerCompositeAccess)) {
+ AddError(
+ "pointer composite access requires the pointer_composite_access language feature, "
+ "which is not allowed in the current environment",
+ expr->source);
+ return nullptr;
+ }
+ storage_ty = memory_view->StoreType();
+ }
auto* root_ident = object->RootIdentifier();
@@ -3429,9 +3457,10 @@
ty = member->Type();
- // If we're extracting from a reference, we return a reference.
- if (auto* ref = object_ty->As<core::type::Reference>()) {
- ty = b.create<core::type::Reference>(ref->AddressSpace(), ty, ref->Access());
+ // If we're extracting from a memory view, we return a reference.
+ if (memory_view) {
+ ty = b.create<core::type::Reference>(memory_view->AddressSpace(), ty,
+ memory_view->Access());
}
const core::constant::Value* val = nullptr;
@@ -3497,9 +3526,10 @@
if (size == 1) {
// A single element swizzle is just the type of the vector.
ty = vec->type();
- // If we're extracting from a reference, we return a reference.
- if (auto* ref = object_ty->As<core::type::Reference>()) {
- ty = b.create<core::type::Reference>(ref->AddressSpace(), ty, ref->Access());
+ // If we're extracting from a memory view, we return a reference.
+ if (memory_view) {
+ ty = b.create<core::type::Reference>(memory_view->AddressSpace(), ty,
+ memory_view->Access());
}
} else {
// The vector will have a number of components equal to the length of
diff --git a/src/tint/lang/wgsl/resolver/uniformity.cc b/src/tint/lang/wgsl/resolver/uniformity.cc
index 0cea0be..236c458 100644
--- a/src/tint/lang/wgsl/resolver/uniformity.cc
+++ b/src/tint/lang/wgsl/resolver/uniformity.cc
@@ -1445,14 +1445,43 @@
},
[&](const ast::IndexAccessorExpression* i) {
- auto [cf1, l1, root_ident] =
- ProcessLValueExpression(cf, i->object, /*is_partial_reference*/ true);
+ auto* sem_object = sem_.GetVal(i->object);
+
+ LValue object_result;
+ if (sem_object->Type()->Is<core::type::Pointer>()) {
+ // Sugared pointer access, treat as indirection
+ auto* root_ident = sem_object->RootIdentifier();
+ auto* deref = CreateNode({NameFor(root_ident), "_deref"});
+ if (auto* old_value = current_function_->variables.Get(root_ident)) {
+ // We're dereferecing a partial pointer, so link back to the variable's
+ // previous value.
+ deref->AddEdge(old_value);
+ }
+ object_result = LValue{cf, deref, root_ident};
+ } else {
+ object_result =
+ ProcessLValueExpression(cf, i->object, /*is_partial_reference*/ true);
+ }
+ auto [cf1, l1, root_ident] = object_result;
auto [cf2, v2] = ProcessExpression(cf1, i->index);
l1->AddEdge(v2);
return LValue{cf2, l1, root_ident};
},
[&](const ast::MemberAccessorExpression* m) {
+ auto* sem_object = sem_.GetVal(m->object);
+ if (sem_object->Type()->Is<core::type::Pointer>()) {
+ // Sugared pointer access, treat as indirection
+ auto* root_ident = sem_object->RootIdentifier();
+ auto* deref = CreateNode({NameFor(root_ident), "_deref"});
+ if (auto* old_value = current_function_->variables.Get(root_ident)) {
+ // We're dereferecing a partial pointer, so link back to the variable's
+ // previous value.
+ deref->AddEdge(old_value);
+ }
+ return LValue{cf, deref, root_ident};
+ }
+
return ProcessLValueExpression(cf, m->object, /*is_partial_reference*/ true);
},
diff --git a/src/tint/lang/wgsl/resolver/uniformity_test.cc b/src/tint/lang/wgsl/resolver/uniformity_test.cc
index 769ab82..fe1389e 100644
--- a/src/tint/lang/wgsl/resolver/uniformity_test.cc
+++ b/src/tint/lang/wgsl/resolver/uniformity_test.cc
@@ -5631,6 +5631,70 @@
)");
}
+TEST_F(UniformityAnalysisTest,
+ VectorElement_VectorBecomesUniform_PartialAssignment_ViaPointerDerefIndex) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ let p = &v;
+ (*p)[1] = rw;
+ v = vec4(1, 2, 3, v[3]);
+ if (v[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on possibly non-uniform value
+ if (v[1] == 0) {
+ ^^
+
+test:7:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ (*p)[1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ VectorElement_VectorBecomesUniform_PartialAssignment_ViaPointerIndex) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ let p = &v;
+ p[1] = rw;
+ v = vec4(1, 2, 3, v[3]);
+ if (v[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on possibly non-uniform value
+ if (v[1] == 0) {
+ ^^
+
+test:7:10 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ p[1] = rw;
+ ^^
+)");
+}
+
TEST_F(UniformityAnalysisTest, VectorElementViaMember_VectorBecomesUniform_PartialAssignment) {
std::string src = R"(
@group(0) @binding(0) var<storage, read_write> rw : i32;
@@ -5661,6 +5725,70 @@
)");
}
+TEST_F(UniformityAnalysisTest,
+ VectorElementViaMember_VectorBecomesUniform_PartialAssignment_ViaPointerDerefDot) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ let p = &v;
+ (*p).y = rw;
+ v = vec4(1, 2, 3, v.w);
+ if (v.y == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on possibly non-uniform value
+ if (v.y == 0) {
+ ^^
+
+test:7:12 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ (*p).y = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ VectorElementViaMember_VectorBecomesUniform_PartialAssignment_ViaPointerDot) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ let p = &v;
+ p.y = rw;
+ v = vec4(1, 2, 3, v.w);
+ if (v.y == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on possibly non-uniform value
+ if (v.y == 0) {
+ ^^
+
+test:7:9 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ p.y = rw;
+ ^^
+)");
+}
+
TEST_F(UniformityAnalysisTest, VectorElement_DifferentElementBecomesUniform) {
// For aggregate types, we conservatively consider them to be non-uniform once they
// become non-uniform. Test that after assigning a uniform value to an element, the whole vector
@@ -5961,6 +6089,146 @@
}
TEST_F(UniformityAnalysisTest,
+ MatrixElement_ColumnBecomesUniform_ThroughCapturedPartialPointer_PointerDerefIndex) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ let p = &m[1];
+ m[1][1] = rw;
+ (*p)[0] = 0.0;
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on possibly non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:7:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ MatrixElement_ColumnBecomesUniform_ThroughCapturedPartialPointer_PointerIndex) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ let p = &m[1];
+ m[1][1] = rw;
+ p[0] = 0.0;
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on possibly non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:7:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ MatrixElement_ColumnBecomesUniform_ThroughCapturedPartialPointer_PointerDerefDot) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ let p = &m[1];
+ m[1][1] = rw;
+ (*p).x = 0.0;
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on possibly non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:7:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ MatrixElement_ColumnBecomesUniform_ThroughCapturedPartialPointer_PointerDot) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ let p = &m[1];
+ m[1][1] = rw;
+ p.x = 0.0;
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on possibly non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:7:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
MatrixElement_ColumnBecomesUniform_ThroughCapturedPartialPointerChain) {
// For aggregate types, we conservatively consider them to be non-uniform once they
// become non-uniform. Test that after assigning a uniform value to an element, that element is
diff --git a/src/tint/lang/wgsl/resolver/validation_test.cc b/src/tint/lang/wgsl/resolver/validation_test.cc
index 9d369b8..6bc6fc2 100644
--- a/src/tint/lang/wgsl/resolver/validation_test.cc
+++ b/src/tint/lang/wgsl/resolver/validation_test.cc
@@ -383,12 +383,10 @@
WrapInFunction(Decl(param), Decl(ret));
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "12:34 error: invalid member accessor expression. Expected vector or struct, got "
- "'ptr<function, vec4<f32>, read_write>'");
+ EXPECT_EQ(r()->error(), "error: cannot dereference expression of type 'f32'");
}
-TEST_F(ResolverValidationTest, EXpr_MemberAccessor_FuncGoodParent) {
+TEST_F(ResolverValidationTest, Expr_MemberAccessor_FuncGoodParent) {
// fn func(p: ptr<function, vec4<f32>>) -> f32 {
// let x: f32 = (*p).z;
// return x;
@@ -405,7 +403,7 @@
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
-TEST_F(ResolverValidationTest, EXpr_MemberAccessor_FuncBadParent) {
+TEST_F(ResolverValidationTest, Expr_MemberAccessor_FuncBadParent) {
// fn func(p: ptr<function, vec4<f32>>) -> f32 {
// let x: f32 = *p.z;
// return x;
@@ -421,9 +419,7 @@
});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "12:34 error: invalid member accessor expression. Expected vector or struct, got "
- "'ptr<function, vec4<f32>, read_write>'");
+ EXPECT_EQ(r()->error(), "error: cannot dereference expression of type 'f32'");
}
TEST_F(ResolverValidationTest,
@@ -1276,24 +1272,6 @@
EXPECT_EQ(r()->error(), R"(12:24 error: value 4294967296 cannot be represented as 'u32')");
}
-// var a: array<i32,2>;
-// *&a[0] = 1;
-TEST_F(ResolverTest, PointerIndexing_Fail) {
- // var a: array<i32,2>;
- // let p = &a;
- // *p[0] = 0;
-
- auto* a = Var("a", ty.array<i32, 2>());
- auto* p = AddressOf("a");
- auto* idx = Assign(Deref(IndexAccessor(p, 0_u)), 0_u);
-
- WrapInFunction(a, idx);
-
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- R"(error: cannot index type 'ptr<function, array<i32, 2>, read_write>')");
-}
-
} // namespace
} // namespace tint::resolver
diff --git a/src/tint/lang/wgsl/wgsl.def b/src/tint/lang/wgsl/wgsl.def
index 8b40557..41cd202 100644
--- a/src/tint/lang/wgsl/wgsl.def
+++ b/src/tint/lang/wgsl/wgsl.def
@@ -93,6 +93,7 @@
enum language_feature {
readonly_and_readwrite_storage_textures
packed_4x8_integer_dot_product
+ pointer_composite_access
// Language features used only for testing whose status will never change.
chromium_testing_unimplemented
diff --git a/test/tint/ptr_sugar/array.wgsl b/test/tint/ptr_sugar/array.wgsl
new file mode 100644
index 0000000..d48a62b
--- /dev/null
+++ b/test/tint/ptr_sugar/array.wgsl
@@ -0,0 +1,55 @@
+fn deref_const() {
+ var a : array<i32, 10>;
+ let p = &a;
+ var b = (*p)[0];
+ (*p)[0] = 42;
+}
+
+fn no_deref_const() {
+ var a : array<i32, 10>;
+ let p = &a;
+ var b = p[0];
+ p[0] = 42;
+}
+
+fn deref_let() {
+ var a : array<i32, 10>;
+ let p = &a;
+ let i = 0;
+ var b = (*p)[i];
+ (*p)[0] = 42;
+}
+
+fn no_deref_let() {
+ var a : array<i32, 10>;
+ let p = &a;
+ let i = 0;
+ var b = p[i];
+ p[0] = 42;
+}
+
+fn deref_var() {
+ var a : array<i32, 10>;
+ let p = &a;
+ var i = 0;
+ var b = (*p)[i];
+ (*p)[0] = 42;
+}
+
+fn no_deref_var() {
+ var a : array<i32, 10>;
+ let p = &a;
+ var i = 0;
+ var b = p[i];
+ p[0] = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref_const();
+ no_deref_const();
+ deref_let();
+ no_deref_let();
+ deref_var();
+ no_deref_var();
+}
diff --git a/test/tint/ptr_sugar/array.wgsl.expected.dxc.hlsl b/test/tint/ptr_sugar/array.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..c876ea4
--- /dev/null
+++ b/test/tint/ptr_sugar/array.wgsl.expected.dxc.hlsl
@@ -0,0 +1,50 @@
+void deref_const() {
+ int a[10] = (int[10])0;
+ int b = a[0];
+ a[0] = 42;
+}
+
+void no_deref_const() {
+ int a[10] = (int[10])0;
+ int b = a[0];
+ a[0] = 42;
+}
+
+void deref_let() {
+ int a[10] = (int[10])0;
+ const int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_let() {
+ int a[10] = (int[10])0;
+ const int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void deref_var() {
+ int a[10] = (int[10])0;
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_var() {
+ int a[10] = (int[10])0;
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref_const();
+ no_deref_const();
+ deref_let();
+ no_deref_let();
+ deref_var();
+ no_deref_var();
+ return;
+}
diff --git a/test/tint/ptr_sugar/array.wgsl.expected.fxc.hlsl b/test/tint/ptr_sugar/array.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..c876ea4
--- /dev/null
+++ b/test/tint/ptr_sugar/array.wgsl.expected.fxc.hlsl
@@ -0,0 +1,50 @@
+void deref_const() {
+ int a[10] = (int[10])0;
+ int b = a[0];
+ a[0] = 42;
+}
+
+void no_deref_const() {
+ int a[10] = (int[10])0;
+ int b = a[0];
+ a[0] = 42;
+}
+
+void deref_let() {
+ int a[10] = (int[10])0;
+ const int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_let() {
+ int a[10] = (int[10])0;
+ const int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void deref_var() {
+ int a[10] = (int[10])0;
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_var() {
+ int a[10] = (int[10])0;
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref_const();
+ no_deref_const();
+ deref_let();
+ no_deref_let();
+ deref_var();
+ no_deref_var();
+ return;
+}
diff --git a/test/tint/ptr_sugar/array.wgsl.expected.glsl b/test/tint/ptr_sugar/array.wgsl.expected.glsl
new file mode 100644
index 0000000..48419fc
--- /dev/null
+++ b/test/tint/ptr_sugar/array.wgsl.expected.glsl
@@ -0,0 +1,56 @@
+#version 310 es
+
+void deref_const() {
+ int a[10] = int[10](0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ int b = a[0];
+ a[0] = 42;
+}
+
+void no_deref_const() {
+ int a[10] = int[10](0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ int b = a[0];
+ a[0] = 42;
+}
+
+void deref_let() {
+ int a[10] = int[10](0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_let() {
+ int a[10] = int[10](0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void deref_var() {
+ int a[10] = int[10](0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_var() {
+ int a[10] = int[10](0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void tint_symbol() {
+ deref_const();
+ no_deref_const();
+ deref_let();
+ no_deref_let();
+ deref_var();
+ no_deref_var();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+ tint_symbol();
+ return;
+}
diff --git a/test/tint/ptr_sugar/array.wgsl.expected.msl b/test/tint/ptr_sugar/array.wgsl.expected.msl
new file mode 100644
index 0000000..9bd0861
--- /dev/null
+++ b/test/tint/ptr_sugar/array.wgsl.expected.msl
@@ -0,0 +1,66 @@
+#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 deref_const() {
+ tint_array<int, 10> a = {};
+ int b = a[0];
+ a[0] = 42;
+}
+
+void no_deref_const() {
+ tint_array<int, 10> a = {};
+ int b = a[0];
+ a[0] = 42;
+}
+
+void deref_let() {
+ tint_array<int, 10> a = {};
+ int const i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_let() {
+ tint_array<int, 10> a = {};
+ int const i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void deref_var() {
+ tint_array<int, 10> a = {};
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_var() {
+ tint_array<int, 10> a = {};
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+kernel void tint_symbol() {
+ deref_const();
+ no_deref_const();
+ deref_let();
+ no_deref_let();
+ deref_var();
+ no_deref_var();
+ return;
+}
+
diff --git a/test/tint/ptr_sugar/array.wgsl.expected.spvasm b/test/tint/ptr_sugar/array.wgsl.expected.spvasm
new file mode 100644
index 0000000..fd7b149
--- /dev/null
+++ b/test/tint/ptr_sugar/array.wgsl.expected.spvasm
@@ -0,0 +1,124 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 66
+; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %deref_const "deref_const"
+ OpName %a "a"
+ OpName %b "b"
+ OpName %no_deref_const "no_deref_const"
+ OpName %a_0 "a"
+ OpName %b_0 "b"
+ OpName %deref_let "deref_let"
+ OpName %a_1 "a"
+ OpName %b_1 "b"
+ OpName %no_deref_let "no_deref_let"
+ OpName %a_2 "a"
+ OpName %b_2 "b"
+ OpName %deref_var "deref_var"
+ OpName %a_3 "a"
+ OpName %i "i"
+ OpName %b_3 "b"
+ OpName %no_deref_var "no_deref_var"
+ OpName %a_4 "a"
+ OpName %i_0 "i"
+ OpName %b_4 "b"
+ OpName %main "main"
+ OpDecorate %_arr_int_uint_10 ArrayStride 4
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %uint = OpTypeInt 32 0
+ %uint_10 = OpConstant %uint 10
+%_arr_int_uint_10 = OpTypeArray %int %uint_10
+%_ptr_Function__arr_int_uint_10 = OpTypePointer Function %_arr_int_uint_10
+ %11 = OpConstantNull %_arr_int_uint_10
+ %12 = OpConstantNull %int
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_42 = OpConstant %int 42
+%deref_const = OpFunction %void None %1
+ %4 = OpLabel
+ %a = OpVariable %_ptr_Function__arr_int_uint_10 Function %11
+ %b = OpVariable %_ptr_Function_int Function %12
+ %14 = OpAccessChain %_ptr_Function_int %a %12
+ %15 = OpLoad %int %14
+ OpStore %b %15
+ %17 = OpAccessChain %_ptr_Function_int %a %12
+ OpStore %17 %int_42
+ OpReturn
+ OpFunctionEnd
+%no_deref_const = OpFunction %void None %1
+ %20 = OpLabel
+ %a_0 = OpVariable %_ptr_Function__arr_int_uint_10 Function %11
+ %b_0 = OpVariable %_ptr_Function_int Function %12
+ %22 = OpAccessChain %_ptr_Function_int %a_0 %12
+ %23 = OpLoad %int %22
+ OpStore %b_0 %23
+ %25 = OpAccessChain %_ptr_Function_int %a_0 %12
+ OpStore %25 %int_42
+ OpReturn
+ OpFunctionEnd
+ %deref_let = OpFunction %void None %1
+ %27 = OpLabel
+ %a_1 = OpVariable %_ptr_Function__arr_int_uint_10 Function %11
+ %b_1 = OpVariable %_ptr_Function_int Function %12
+ %29 = OpAccessChain %_ptr_Function_int %a_1 %12
+ %30 = OpLoad %int %29
+ OpStore %b_1 %30
+ %32 = OpAccessChain %_ptr_Function_int %a_1 %12
+ OpStore %32 %int_42
+ OpReturn
+ OpFunctionEnd
+%no_deref_let = OpFunction %void None %1
+ %34 = OpLabel
+ %a_2 = OpVariable %_ptr_Function__arr_int_uint_10 Function %11
+ %b_2 = OpVariable %_ptr_Function_int Function %12
+ %36 = OpAccessChain %_ptr_Function_int %a_2 %12
+ %37 = OpLoad %int %36
+ OpStore %b_2 %37
+ %39 = OpAccessChain %_ptr_Function_int %a_2 %12
+ OpStore %39 %int_42
+ OpReturn
+ OpFunctionEnd
+ %deref_var = OpFunction %void None %1
+ %41 = OpLabel
+ %a_3 = OpVariable %_ptr_Function__arr_int_uint_10 Function %11
+ %i = OpVariable %_ptr_Function_int Function %12
+ %b_3 = OpVariable %_ptr_Function_int Function %12
+ OpStore %i %12
+ %44 = OpLoad %int %i
+ %45 = OpAccessChain %_ptr_Function_int %a_3 %44
+ %46 = OpLoad %int %45
+ OpStore %b_3 %46
+ %48 = OpAccessChain %_ptr_Function_int %a_3 %12
+ OpStore %48 %int_42
+ OpReturn
+ OpFunctionEnd
+%no_deref_var = OpFunction %void None %1
+ %50 = OpLabel
+ %a_4 = OpVariable %_ptr_Function__arr_int_uint_10 Function %11
+ %i_0 = OpVariable %_ptr_Function_int Function %12
+ %b_4 = OpVariable %_ptr_Function_int Function %12
+ OpStore %i_0 %12
+ %53 = OpLoad %int %i_0
+ %54 = OpAccessChain %_ptr_Function_int %a_4 %53
+ %55 = OpLoad %int %54
+ OpStore %b_4 %55
+ %57 = OpAccessChain %_ptr_Function_int %a_4 %12
+ OpStore %57 %int_42
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %1
+ %59 = OpLabel
+ %60 = OpFunctionCall %void %deref_const
+ %61 = OpFunctionCall %void %no_deref_const
+ %62 = OpFunctionCall %void %deref_let
+ %63 = OpFunctionCall %void %no_deref_let
+ %64 = OpFunctionCall %void %deref_var
+ %65 = OpFunctionCall %void %no_deref_var
+ OpReturn
+ OpFunctionEnd
diff --git a/test/tint/ptr_sugar/array.wgsl.expected.wgsl b/test/tint/ptr_sugar/array.wgsl.expected.wgsl
new file mode 100644
index 0000000..da29411
--- /dev/null
+++ b/test/tint/ptr_sugar/array.wgsl.expected.wgsl
@@ -0,0 +1,55 @@
+fn deref_const() {
+ var a : array<i32, 10>;
+ let p = &(a);
+ var b = (*(p))[0];
+ (*(p))[0] = 42;
+}
+
+fn no_deref_const() {
+ var a : array<i32, 10>;
+ let p = &(a);
+ var b = p[0];
+ p[0] = 42;
+}
+
+fn deref_let() {
+ var a : array<i32, 10>;
+ let p = &(a);
+ let i = 0;
+ var b = (*(p))[i];
+ (*(p))[0] = 42;
+}
+
+fn no_deref_let() {
+ var a : array<i32, 10>;
+ let p = &(a);
+ let i = 0;
+ var b = p[i];
+ p[0] = 42;
+}
+
+fn deref_var() {
+ var a : array<i32, 10>;
+ let p = &(a);
+ var i = 0;
+ var b = (*(p))[i];
+ (*(p))[0] = 42;
+}
+
+fn no_deref_var() {
+ var a : array<i32, 10>;
+ let p = &(a);
+ var i = 0;
+ var b = p[i];
+ p[0] = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref_const();
+ no_deref_const();
+ deref_let();
+ no_deref_let();
+ deref_var();
+ no_deref_var();
+}
diff --git a/test/tint/ptr_sugar/builtin_struct.wgsl b/test/tint/ptr_sugar/builtin_struct.wgsl
new file mode 100644
index 0000000..b44cbc4
--- /dev/null
+++ b/test/tint/ptr_sugar/builtin_struct.wgsl
@@ -0,0 +1,35 @@
+fn deref_modf() {
+ var a = modf(1.5);
+ let p = &a;
+ var fract = (*p).fract;
+ var whole = (*p).whole;
+}
+
+fn no_deref_modf() {
+ var a = modf(1.5);
+ let p = &a;
+ var fract = p.fract;
+ var whole = p.whole;
+}
+
+fn deref_frexp() {
+ var a = frexp(1.5);
+ let p = &a;
+ var fract = (*p).fract;
+ var exp = (*p).exp;
+}
+
+fn no_deref_frexp() {
+ var a = frexp(1.5);
+ let p = &a;
+ var fract = p.fract;
+ var exp = p.exp;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref_modf();
+ no_deref_modf();
+ deref_frexp();
+ no_deref_frexp();
+}
diff --git a/test/tint/ptr_sugar/builtin_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_sugar/builtin_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..ec96b03
--- /dev/null
+++ b/test/tint/ptr_sugar/builtin_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,40 @@
+struct modf_result_f32 {
+ float fract;
+ float whole;
+};
+struct frexp_result_f32 {
+ float fract;
+ int exp;
+};
+void deref_modf() {
+ modf_result_f32 a = {0.5f, 1.0f};
+ float fract = a.fract;
+ float whole = a.whole;
+}
+
+void no_deref_modf() {
+ modf_result_f32 a = {0.5f, 1.0f};
+ float fract = a.fract;
+ float whole = a.whole;
+}
+
+void deref_frexp() {
+ frexp_result_f32 a = {0.75f, 1};
+ float fract = a.fract;
+ int exp = a.exp;
+}
+
+void no_deref_frexp() {
+ frexp_result_f32 a = {0.75f, 1};
+ float fract = a.fract;
+ int exp = a.exp;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref_modf();
+ no_deref_modf();
+ deref_frexp();
+ no_deref_frexp();
+ return;
+}
diff --git a/test/tint/ptr_sugar/builtin_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_sugar/builtin_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..ec96b03
--- /dev/null
+++ b/test/tint/ptr_sugar/builtin_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,40 @@
+struct modf_result_f32 {
+ float fract;
+ float whole;
+};
+struct frexp_result_f32 {
+ float fract;
+ int exp;
+};
+void deref_modf() {
+ modf_result_f32 a = {0.5f, 1.0f};
+ float fract = a.fract;
+ float whole = a.whole;
+}
+
+void no_deref_modf() {
+ modf_result_f32 a = {0.5f, 1.0f};
+ float fract = a.fract;
+ float whole = a.whole;
+}
+
+void deref_frexp() {
+ frexp_result_f32 a = {0.75f, 1};
+ float fract = a.fract;
+ int exp = a.exp;
+}
+
+void no_deref_frexp() {
+ frexp_result_f32 a = {0.75f, 1};
+ float fract = a.fract;
+ int exp = a.exp;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref_modf();
+ no_deref_modf();
+ deref_frexp();
+ no_deref_frexp();
+ return;
+}
diff --git a/test/tint/ptr_sugar/builtin_struct.wgsl.expected.glsl b/test/tint/ptr_sugar/builtin_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..e852152
--- /dev/null
+++ b/test/tint/ptr_sugar/builtin_struct.wgsl.expected.glsl
@@ -0,0 +1,49 @@
+#version 310 es
+
+struct modf_result_f32 {
+ float fract;
+ float whole;
+};
+
+struct frexp_result_f32 {
+ float fract;
+ int exp;
+};
+
+
+void deref_modf() {
+ modf_result_f32 a = modf_result_f32(0.5f, 1.0f);
+ float tint_symbol = a.fract;
+ float whole = a.whole;
+}
+
+void no_deref_modf() {
+ modf_result_f32 a = modf_result_f32(0.5f, 1.0f);
+ float tint_symbol = a.fract;
+ float whole = a.whole;
+}
+
+void deref_frexp() {
+ frexp_result_f32 a = frexp_result_f32(0.75f, 1);
+ float tint_symbol = a.fract;
+ int tint_symbol_1 = a.exp;
+}
+
+void no_deref_frexp() {
+ frexp_result_f32 a = frexp_result_f32(0.75f, 1);
+ float tint_symbol = a.fract;
+ int tint_symbol_1 = a.exp;
+}
+
+void tint_symbol_2() {
+ deref_modf();
+ no_deref_modf();
+ deref_frexp();
+ no_deref_frexp();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+ tint_symbol_2();
+ return;
+}
diff --git a/test/tint/ptr_sugar/builtin_struct.wgsl.expected.msl b/test/tint/ptr_sugar/builtin_struct.wgsl.expected.msl
new file mode 100644
index 0000000..13aa15f
--- /dev/null
+++ b/test/tint/ptr_sugar/builtin_struct.wgsl.expected.msl
@@ -0,0 +1,44 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+struct modf_result_f32 {
+ float fract;
+ float whole;
+};
+struct frexp_result_f32 {
+ float fract;
+ int exp;
+};
+void deref_modf() {
+ modf_result_f32 a = modf_result_f32{.fract=0.5f, .whole=1.0f};
+ float fract = a.fract;
+ float whole = a.whole;
+}
+
+void no_deref_modf() {
+ modf_result_f32 a = modf_result_f32{.fract=0.5f, .whole=1.0f};
+ float fract = a.fract;
+ float whole = a.whole;
+}
+
+void deref_frexp() {
+ frexp_result_f32 a = frexp_result_f32{.fract=0.75f, .exp=1};
+ float fract = a.fract;
+ int exp = a.exp;
+}
+
+void no_deref_frexp() {
+ frexp_result_f32 a = frexp_result_f32{.fract=0.75f, .exp=1};
+ float fract = a.fract;
+ int exp = a.exp;
+}
+
+kernel void tint_symbol() {
+ deref_modf();
+ no_deref_modf();
+ deref_frexp();
+ no_deref_frexp();
+ return;
+}
+
diff --git a/test/tint/ptr_sugar/builtin_struct.wgsl.expected.spvasm b/test/tint/ptr_sugar/builtin_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..5459222
--- /dev/null
+++ b/test/tint/ptr_sugar/builtin_struct.wgsl.expected.spvasm
@@ -0,0 +1,123 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 66
+; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %deref_modf "deref_modf"
+ OpName %__modf_result_f32 "__modf_result_f32"
+ OpMemberName %__modf_result_f32 0 "fract"
+ OpMemberName %__modf_result_f32 1 "whole"
+ OpName %a "a"
+ OpName %fract "fract"
+ OpName %whole "whole"
+ OpName %no_deref_modf "no_deref_modf"
+ OpName %a_0 "a"
+ OpName %fract_0 "fract"
+ OpName %whole_0 "whole"
+ OpName %deref_frexp "deref_frexp"
+ OpName %__frexp_result_f32 "__frexp_result_f32"
+ OpMemberName %__frexp_result_f32 0 "fract"
+ OpMemberName %__frexp_result_f32 1 "exp"
+ OpName %a_1 "a"
+ OpName %fract_1 "fract"
+ OpName %exp "exp"
+ OpName %no_deref_frexp "no_deref_frexp"
+ OpName %a_2 "a"
+ OpName %fract_2 "fract"
+ OpName %exp_0 "exp"
+ OpName %main "main"
+ OpMemberDecorate %__modf_result_f32 0 Offset 0
+ OpMemberDecorate %__modf_result_f32 1 Offset 4
+ OpMemberDecorate %__frexp_result_f32 0 Offset 0
+ OpMemberDecorate %__frexp_result_f32 1 Offset 4
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+%__modf_result_f32 = OpTypeStruct %float %float
+ %float_0_5 = OpConstant %float 0.5
+ %float_1 = OpConstant %float 1
+ %9 = OpConstantComposite %__modf_result_f32 %float_0_5 %float_1
+%_ptr_Function___modf_result_f32 = OpTypePointer Function %__modf_result_f32
+ %12 = OpConstantNull %__modf_result_f32
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+ %19 = OpConstantNull %float
+ %uint_1 = OpConstant %uint 1
+ %int = OpTypeInt 32 1
+%__frexp_result_f32 = OpTypeStruct %float %int
+ %float_0_75 = OpConstant %float 0.75
+ %int_1 = OpConstant %int 1
+ %39 = OpConstantComposite %__frexp_result_f32 %float_0_75 %int_1
+%_ptr_Function___frexp_result_f32 = OpTypePointer Function %__frexp_result_f32
+ %42 = OpConstantNull %__frexp_result_f32
+%_ptr_Function_int = OpTypePointer Function %int
+ %50 = OpConstantNull %int
+ %deref_modf = OpFunction %void None %1
+ %4 = OpLabel
+ %a = OpVariable %_ptr_Function___modf_result_f32 Function %12
+ %fract = OpVariable %_ptr_Function_float Function %19
+ %whole = OpVariable %_ptr_Function_float Function %19
+ OpStore %a %9
+ %16 = OpAccessChain %_ptr_Function_float %a %uint_0
+ %17 = OpLoad %float %16
+ OpStore %fract %17
+ %21 = OpAccessChain %_ptr_Function_float %a %uint_1
+ %22 = OpLoad %float %21
+ OpStore %whole %22
+ OpReturn
+ OpFunctionEnd
+%no_deref_modf = OpFunction %void None %1
+ %25 = OpLabel
+ %a_0 = OpVariable %_ptr_Function___modf_result_f32 Function %12
+ %fract_0 = OpVariable %_ptr_Function_float Function %19
+ %whole_0 = OpVariable %_ptr_Function_float Function %19
+ OpStore %a_0 %9
+ %27 = OpAccessChain %_ptr_Function_float %a_0 %uint_0
+ %28 = OpLoad %float %27
+ OpStore %fract_0 %28
+ %30 = OpAccessChain %_ptr_Function_float %a_0 %uint_1
+ %31 = OpLoad %float %30
+ OpStore %whole_0 %31
+ OpReturn
+ OpFunctionEnd
+%deref_frexp = OpFunction %void None %1
+ %34 = OpLabel
+ %a_1 = OpVariable %_ptr_Function___frexp_result_f32 Function %42
+ %fract_1 = OpVariable %_ptr_Function_float Function %19
+ %exp = OpVariable %_ptr_Function_int Function %50
+ OpStore %a_1 %39
+ %43 = OpAccessChain %_ptr_Function_float %a_1 %uint_0
+ %44 = OpLoad %float %43
+ OpStore %fract_1 %44
+ %47 = OpAccessChain %_ptr_Function_int %a_1 %uint_1
+ %48 = OpLoad %int %47
+ OpStore %exp %48
+ OpReturn
+ OpFunctionEnd
+%no_deref_frexp = OpFunction %void None %1
+ %52 = OpLabel
+ %a_2 = OpVariable %_ptr_Function___frexp_result_f32 Function %42
+ %fract_2 = OpVariable %_ptr_Function_float Function %19
+ %exp_0 = OpVariable %_ptr_Function_int Function %50
+ OpStore %a_2 %39
+ %54 = OpAccessChain %_ptr_Function_float %a_2 %uint_0
+ %55 = OpLoad %float %54
+ OpStore %fract_2 %55
+ %57 = OpAccessChain %_ptr_Function_int %a_2 %uint_1
+ %58 = OpLoad %int %57
+ OpStore %exp_0 %58
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %1
+ %61 = OpLabel
+ %62 = OpFunctionCall %void %deref_modf
+ %63 = OpFunctionCall %void %no_deref_modf
+ %64 = OpFunctionCall %void %deref_frexp
+ %65 = OpFunctionCall %void %no_deref_frexp
+ OpReturn
+ OpFunctionEnd
diff --git a/test/tint/ptr_sugar/builtin_struct.wgsl.expected.wgsl b/test/tint/ptr_sugar/builtin_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..7349c16
--- /dev/null
+++ b/test/tint/ptr_sugar/builtin_struct.wgsl.expected.wgsl
@@ -0,0 +1,35 @@
+fn deref_modf() {
+ var a = modf(1.5);
+ let p = &(a);
+ var fract = (*(p)).fract;
+ var whole = (*(p)).whole;
+}
+
+fn no_deref_modf() {
+ var a = modf(1.5);
+ let p = &(a);
+ var fract = p.fract;
+ var whole = p.whole;
+}
+
+fn deref_frexp() {
+ var a = frexp(1.5);
+ let p = &(a);
+ var fract = (*(p)).fract;
+ var exp = (*(p)).exp;
+}
+
+fn no_deref_frexp() {
+ var a = frexp(1.5);
+ let p = &(a);
+ var fract = p.fract;
+ var exp = p.exp;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref_modf();
+ no_deref_modf();
+ deref_frexp();
+ no_deref_frexp();
+}
diff --git a/test/tint/ptr_sugar/compound_assign_index.wgsl b/test/tint/ptr_sugar/compound_assign_index.wgsl
new file mode 100644
index 0000000..ff01d4c
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_index.wgsl
@@ -0,0 +1,31 @@
+fn deref() {
+ var a : vec3<i32>;
+ let p = &a;
+ (*p)[0] += 42;
+}
+
+fn no_deref() {
+ var a : vec3<i32>;
+ let p = &a;
+ p[0] += 42;
+}
+
+fn deref_inc() {
+ var a : vec3<i32>;
+ let p = &a;
+ (*p)[0]++;
+}
+
+fn no_deref_inc() {
+ var a : vec3<i32>;
+ let p = &a;
+ p[0]++;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref();
+ no_deref();
+ deref_inc();
+ no_deref_inc();
+}
diff --git a/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.dxc.hlsl b/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..25853e3
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.dxc.hlsl
@@ -0,0 +1,36 @@
+void set_int3(inout int3 vec, int idx, int val) {
+ vec = (idx.xxx == int3(0, 1, 2)) ? val.xxx : vec;
+}
+
+void deref() {
+ int3 a = int3(0, 0, 0);
+ const int tint_symbol_1 = 0;
+ set_int3(a, tint_symbol_1, (a[tint_symbol_1] + 42));
+}
+
+void no_deref() {
+ int3 a = int3(0, 0, 0);
+ const int tint_symbol_3 = 0;
+ set_int3(a, tint_symbol_3, (a[tint_symbol_3] + 42));
+}
+
+void deref_inc() {
+ int3 a = int3(0, 0, 0);
+ const int tint_symbol_5 = 0;
+ set_int3(a, tint_symbol_5, (a[tint_symbol_5] + 1));
+}
+
+void no_deref_inc() {
+ int3 a = int3(0, 0, 0);
+ const int tint_symbol_7 = 0;
+ set_int3(a, tint_symbol_7, (a[tint_symbol_7] + 1));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref();
+ no_deref();
+ deref_inc();
+ no_deref_inc();
+ return;
+}
diff --git a/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.fxc.hlsl b/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..25853e3
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.fxc.hlsl
@@ -0,0 +1,36 @@
+void set_int3(inout int3 vec, int idx, int val) {
+ vec = (idx.xxx == int3(0, 1, 2)) ? val.xxx : vec;
+}
+
+void deref() {
+ int3 a = int3(0, 0, 0);
+ const int tint_symbol_1 = 0;
+ set_int3(a, tint_symbol_1, (a[tint_symbol_1] + 42));
+}
+
+void no_deref() {
+ int3 a = int3(0, 0, 0);
+ const int tint_symbol_3 = 0;
+ set_int3(a, tint_symbol_3, (a[tint_symbol_3] + 42));
+}
+
+void deref_inc() {
+ int3 a = int3(0, 0, 0);
+ const int tint_symbol_5 = 0;
+ set_int3(a, tint_symbol_5, (a[tint_symbol_5] + 1));
+}
+
+void no_deref_inc() {
+ int3 a = int3(0, 0, 0);
+ const int tint_symbol_7 = 0;
+ set_int3(a, tint_symbol_7, (a[tint_symbol_7] + 1));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref();
+ no_deref();
+ deref_inc();
+ no_deref_inc();
+ return;
+}
diff --git a/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.glsl b/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.glsl
new file mode 100644
index 0000000..dd04e0f
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.glsl
@@ -0,0 +1,38 @@
+#version 310 es
+
+void deref() {
+ ivec3 a = ivec3(0, 0, 0);
+ int tint_symbol_2 = 0;
+ a[tint_symbol_2] = (a[tint_symbol_2] + 42);
+}
+
+void no_deref() {
+ ivec3 a = ivec3(0, 0, 0);
+ int tint_symbol_4 = 0;
+ a[tint_symbol_4] = (a[tint_symbol_4] + 42);
+}
+
+void deref_inc() {
+ ivec3 a = ivec3(0, 0, 0);
+ int tint_symbol_6 = 0;
+ a[tint_symbol_6] = (a[tint_symbol_6] + 1);
+}
+
+void no_deref_inc() {
+ ivec3 a = ivec3(0, 0, 0);
+ int tint_symbol_8 = 0;
+ a[tint_symbol_8] = (a[tint_symbol_8] + 1);
+}
+
+void tint_symbol() {
+ deref();
+ no_deref();
+ deref_inc();
+ no_deref_inc();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+ tint_symbol();
+ return;
+}
diff --git a/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.msl b/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.msl
new file mode 100644
index 0000000..0a18439
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.msl
@@ -0,0 +1,35 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void deref() {
+ int3 a = 0;
+ int const tint_symbol_2 = 0;
+ a[tint_symbol_2] = as_type<int>((as_type<uint>(a[tint_symbol_2]) + as_type<uint>(42)));
+}
+
+void no_deref() {
+ int3 a = 0;
+ int const tint_symbol_4 = 0;
+ a[tint_symbol_4] = as_type<int>((as_type<uint>(a[tint_symbol_4]) + as_type<uint>(42)));
+}
+
+void deref_inc() {
+ int3 a = 0;
+ int const tint_symbol_6 = 0;
+ a[tint_symbol_6] = as_type<int>((as_type<uint>(a[tint_symbol_6]) + as_type<uint>(1)));
+}
+
+void no_deref_inc() {
+ int3 a = 0;
+ int const tint_symbol_8 = 0;
+ a[tint_symbol_8] = as_type<int>((as_type<uint>(a[tint_symbol_8]) + as_type<uint>(1)));
+}
+
+kernel void tint_symbol() {
+ deref();
+ no_deref();
+ deref_inc();
+ no_deref_inc();
+ return;
+}
+
diff --git a/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.spvasm b/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.spvasm
new file mode 100644
index 0000000..237c95a
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.spvasm
@@ -0,0 +1,76 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 45
+; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %deref "deref"
+ OpName %a "a"
+ OpName %no_deref "no_deref"
+ OpName %a_0 "a"
+ OpName %deref_inc "deref_inc"
+ OpName %a_1 "a"
+ OpName %no_deref_inc "no_deref_inc"
+ OpName %a_2 "a"
+ OpName %main "main"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %v3int = OpTypeVector %int 3
+%_ptr_Function_v3int = OpTypePointer Function %v3int
+ %9 = OpConstantNull %v3int
+ %10 = OpConstantNull %int
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_42 = OpConstant %int 42
+ %int_1 = OpConstant %int 1
+ %deref = OpFunction %void None %1
+ %4 = OpLabel
+ %a = OpVariable %_ptr_Function_v3int Function %9
+ %12 = OpAccessChain %_ptr_Function_int %a %10
+ %13 = OpAccessChain %_ptr_Function_int %a %10
+ %14 = OpLoad %int %13
+ %16 = OpIAdd %int %14 %int_42
+ OpStore %12 %16
+ OpReturn
+ OpFunctionEnd
+ %no_deref = OpFunction %void None %1
+ %18 = OpLabel
+ %a_0 = OpVariable %_ptr_Function_v3int Function %9
+ %20 = OpAccessChain %_ptr_Function_int %a_0 %10
+ %21 = OpAccessChain %_ptr_Function_int %a_0 %10
+ %22 = OpLoad %int %21
+ %23 = OpIAdd %int %22 %int_42
+ OpStore %20 %23
+ OpReturn
+ OpFunctionEnd
+ %deref_inc = OpFunction %void None %1
+ %25 = OpLabel
+ %a_1 = OpVariable %_ptr_Function_v3int Function %9
+ %27 = OpAccessChain %_ptr_Function_int %a_1 %10
+ %28 = OpAccessChain %_ptr_Function_int %a_1 %10
+ %29 = OpLoad %int %28
+ %31 = OpIAdd %int %29 %int_1
+ OpStore %27 %31
+ OpReturn
+ OpFunctionEnd
+%no_deref_inc = OpFunction %void None %1
+ %33 = OpLabel
+ %a_2 = OpVariable %_ptr_Function_v3int Function %9
+ %35 = OpAccessChain %_ptr_Function_int %a_2 %10
+ %36 = OpAccessChain %_ptr_Function_int %a_2 %10
+ %37 = OpLoad %int %36
+ %38 = OpIAdd %int %37 %int_1
+ OpStore %35 %38
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %1
+ %40 = OpLabel
+ %41 = OpFunctionCall %void %deref
+ %42 = OpFunctionCall %void %no_deref
+ %43 = OpFunctionCall %void %deref_inc
+ %44 = OpFunctionCall %void %no_deref_inc
+ OpReturn
+ OpFunctionEnd
diff --git a/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.wgsl b/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.wgsl
new file mode 100644
index 0000000..9fad9aa
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_index.wgsl.expected.wgsl
@@ -0,0 +1,31 @@
+fn deref() {
+ var a : vec3<i32>;
+ let p = &(a);
+ (*(p))[0] += 42;
+}
+
+fn no_deref() {
+ var a : vec3<i32>;
+ let p = &(a);
+ p[0] += 42;
+}
+
+fn deref_inc() {
+ var a : vec3<i32>;
+ let p = &(a);
+ (*(p))[0]++;
+}
+
+fn no_deref_inc() {
+ var a : vec3<i32>;
+ let p = &(a);
+ p[0]++;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref();
+ no_deref();
+ deref_inc();
+ no_deref_inc();
+}
diff --git a/test/tint/ptr_sugar/compound_assign_member.wgsl b/test/tint/ptr_sugar/compound_assign_member.wgsl
new file mode 100644
index 0000000..2785bd5
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_member.wgsl
@@ -0,0 +1,17 @@
+fn deref() {
+ var a : vec3<i32>;
+ let p = &a;
+ (*p).x += 42;
+}
+
+fn no_deref() {
+ var a : vec3<i32>;
+ let p = &a;
+ p.x += 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref();
+ no_deref();
+}
diff --git a/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.dxc.hlsl b/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..630d44f
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.dxc.hlsl
@@ -0,0 +1,16 @@
+void deref() {
+ int3 a = int3(0, 0, 0);
+ a.x = (a.x + 42);
+}
+
+void no_deref() {
+ int3 a = int3(0, 0, 0);
+ a.x = (a.x + 42);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref();
+ no_deref();
+ return;
+}
diff --git a/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.fxc.hlsl b/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..630d44f
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.fxc.hlsl
@@ -0,0 +1,16 @@
+void deref() {
+ int3 a = int3(0, 0, 0);
+ a.x = (a.x + 42);
+}
+
+void no_deref() {
+ int3 a = int3(0, 0, 0);
+ a.x = (a.x + 42);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref();
+ no_deref();
+ return;
+}
diff --git a/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.glsl b/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.glsl
new file mode 100644
index 0000000..62a3b1f
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.glsl
@@ -0,0 +1,22 @@
+#version 310 es
+
+void deref() {
+ ivec3 a = ivec3(0, 0, 0);
+ a.x = (a.x + 42);
+}
+
+void no_deref() {
+ ivec3 a = ivec3(0, 0, 0);
+ a.x = (a.x + 42);
+}
+
+void tint_symbol() {
+ deref();
+ no_deref();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+ tint_symbol();
+ return;
+}
diff --git a/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.msl b/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.msl
new file mode 100644
index 0000000..65e3c9e
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.msl
@@ -0,0 +1,19 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void deref() {
+ int3 a = 0;
+ a[0] = as_type<int>((as_type<uint>(a[0]) + as_type<uint>(42)));
+}
+
+void no_deref() {
+ int3 a = 0;
+ a[0] = as_type<int>((as_type<uint>(a[0]) + as_type<uint>(42)));
+}
+
+kernel void tint_symbol() {
+ deref();
+ no_deref();
+ return;
+}
+
diff --git a/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.spvasm b/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.spvasm
new file mode 100644
index 0000000..7e3b8ff
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.spvasm
@@ -0,0 +1,50 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %deref "deref"
+ OpName %a "a"
+ OpName %no_deref "no_deref"
+ OpName %a_0 "a"
+ OpName %main "main"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %v3int = OpTypeVector %int 3
+%_ptr_Function_v3int = OpTypePointer Function %v3int
+ %9 = OpConstantNull %v3int
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_42 = OpConstant %int 42
+ %deref = OpFunction %void None %1
+ %4 = OpLabel
+ %a = OpVariable %_ptr_Function_v3int Function %9
+ %13 = OpAccessChain %_ptr_Function_int %a %uint_0
+ %14 = OpAccessChain %_ptr_Function_int %a %uint_0
+ %15 = OpLoad %int %14
+ %17 = OpIAdd %int %15 %int_42
+ OpStore %13 %17
+ OpReturn
+ OpFunctionEnd
+ %no_deref = OpFunction %void None %1
+ %19 = OpLabel
+ %a_0 = OpVariable %_ptr_Function_v3int Function %9
+ %21 = OpAccessChain %_ptr_Function_int %a_0 %uint_0
+ %22 = OpAccessChain %_ptr_Function_int %a_0 %uint_0
+ %23 = OpLoad %int %22
+ %24 = OpIAdd %int %23 %int_42
+ OpStore %21 %24
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %1
+ %26 = OpLabel
+ %27 = OpFunctionCall %void %deref
+ %28 = OpFunctionCall %void %no_deref
+ OpReturn
+ OpFunctionEnd
diff --git a/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.wgsl b/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.wgsl
new file mode 100644
index 0000000..3a01d23
--- /dev/null
+++ b/test/tint/ptr_sugar/compound_assign_member.wgsl.expected.wgsl
@@ -0,0 +1,17 @@
+fn deref() {
+ var a : vec3<i32>;
+ let p = &(a);
+ (*(p)).x += 42;
+}
+
+fn no_deref() {
+ var a : vec3<i32>;
+ let p = &(a);
+ p.x += 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref();
+ no_deref();
+}
diff --git a/test/tint/ptr_sugar/matrix.wgsl b/test/tint/ptr_sugar/matrix.wgsl
new file mode 100644
index 0000000..5a768d9
--- /dev/null
+++ b/test/tint/ptr_sugar/matrix.wgsl
@@ -0,0 +1,19 @@
+fn deref() {
+ var a : mat2x3<f32>;
+ let p = &a;
+ var b = (*p)[0];
+ (*p)[0] = vec3<f32>(1.0, 2.0, 3.0);
+}
+
+fn no_deref() {
+ var a : mat2x3<f32>;
+ let p = &a;
+ var b = p[0];
+ p[0] = vec3<f32>(1.0, 2.0, 3.0);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref();
+ no_deref();
+}
diff --git a/test/tint/ptr_sugar/matrix.wgsl.expected.dxc.hlsl b/test/tint/ptr_sugar/matrix.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..7790b20
--- /dev/null
+++ b/test/tint/ptr_sugar/matrix.wgsl.expected.dxc.hlsl
@@ -0,0 +1,18 @@
+void deref() {
+ float2x3 a = float2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+ float3 b = a[0];
+ a[0] = float3(1.0f, 2.0f, 3.0f);
+}
+
+void no_deref() {
+ float2x3 a = float2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+ float3 b = a[0];
+ a[0] = float3(1.0f, 2.0f, 3.0f);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref();
+ no_deref();
+ return;
+}
diff --git a/test/tint/ptr_sugar/matrix.wgsl.expected.fxc.hlsl b/test/tint/ptr_sugar/matrix.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..7790b20
--- /dev/null
+++ b/test/tint/ptr_sugar/matrix.wgsl.expected.fxc.hlsl
@@ -0,0 +1,18 @@
+void deref() {
+ float2x3 a = float2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+ float3 b = a[0];
+ a[0] = float3(1.0f, 2.0f, 3.0f);
+}
+
+void no_deref() {
+ float2x3 a = float2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+ float3 b = a[0];
+ a[0] = float3(1.0f, 2.0f, 3.0f);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref();
+ no_deref();
+ return;
+}
diff --git a/test/tint/ptr_sugar/matrix.wgsl.expected.glsl b/test/tint/ptr_sugar/matrix.wgsl.expected.glsl
new file mode 100644
index 0000000..25cae38
--- /dev/null
+++ b/test/tint/ptr_sugar/matrix.wgsl.expected.glsl
@@ -0,0 +1,24 @@
+#version 310 es
+
+void deref() {
+ mat2x3 a = mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+ vec3 b = a[0];
+ a[0] = vec3(1.0f, 2.0f, 3.0f);
+}
+
+void no_deref() {
+ mat2x3 a = mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+ vec3 b = a[0];
+ a[0] = vec3(1.0f, 2.0f, 3.0f);
+}
+
+void tint_symbol() {
+ deref();
+ no_deref();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+ tint_symbol();
+ return;
+}
diff --git a/test/tint/ptr_sugar/matrix.wgsl.expected.msl b/test/tint/ptr_sugar/matrix.wgsl.expected.msl
new file mode 100644
index 0000000..65ab860
--- /dev/null
+++ b/test/tint/ptr_sugar/matrix.wgsl.expected.msl
@@ -0,0 +1,21 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void deref() {
+ float2x3 a = float2x3(0.0f);
+ float3 b = a[0];
+ a[0] = float3(1.0f, 2.0f, 3.0f);
+}
+
+void no_deref() {
+ float2x3 a = float2x3(0.0f);
+ float3 b = a[0];
+ a[0] = float3(1.0f, 2.0f, 3.0f);
+}
+
+kernel void tint_symbol() {
+ deref();
+ no_deref();
+ return;
+}
+
diff --git a/test/tint/ptr_sugar/matrix.wgsl.expected.spvasm b/test/tint/ptr_sugar/matrix.wgsl.expected.spvasm
new file mode 100644
index 0000000..4f1e62d
--- /dev/null
+++ b/test/tint/ptr_sugar/matrix.wgsl.expected.spvasm
@@ -0,0 +1,59 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 34
+; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %deref "deref"
+ OpName %a "a"
+ OpName %b "b"
+ OpName %no_deref "no_deref"
+ OpName %a_0 "a"
+ OpName %b_0 "b"
+ OpName %main "main"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v3float = OpTypeVector %float 3
+%mat2v3float = OpTypeMatrix %v3float 2
+%_ptr_Function_mat2v3float = OpTypePointer Function %mat2v3float
+ %10 = OpConstantNull %mat2v3float
+ %int = OpTypeInt 32 1
+ %12 = OpConstantNull %int
+%_ptr_Function_v3float = OpTypePointer Function %v3float
+ %17 = OpConstantNull %v3float
+ %float_1 = OpConstant %float 1
+ %float_2 = OpConstant %float 2
+ %float_3 = OpConstant %float 3
+ %22 = OpConstantComposite %v3float %float_1 %float_2 %float_3
+ %deref = OpFunction %void None %1
+ %4 = OpLabel
+ %a = OpVariable %_ptr_Function_mat2v3float Function %10
+ %b = OpVariable %_ptr_Function_v3float Function %17
+ %14 = OpAccessChain %_ptr_Function_v3float %a %12
+ %15 = OpLoad %v3float %14
+ OpStore %b %15
+ %18 = OpAccessChain %_ptr_Function_v3float %a %12
+ OpStore %18 %22
+ OpReturn
+ OpFunctionEnd
+ %no_deref = OpFunction %void None %1
+ %24 = OpLabel
+ %a_0 = OpVariable %_ptr_Function_mat2v3float Function %10
+ %b_0 = OpVariable %_ptr_Function_v3float Function %17
+ %26 = OpAccessChain %_ptr_Function_v3float %a_0 %12
+ %27 = OpLoad %v3float %26
+ OpStore %b_0 %27
+ %29 = OpAccessChain %_ptr_Function_v3float %a_0 %12
+ OpStore %29 %22
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %1
+ %31 = OpLabel
+ %32 = OpFunctionCall %void %deref
+ %33 = OpFunctionCall %void %no_deref
+ OpReturn
+ OpFunctionEnd
diff --git a/test/tint/ptr_sugar/matrix.wgsl.expected.wgsl b/test/tint/ptr_sugar/matrix.wgsl.expected.wgsl
new file mode 100644
index 0000000..3b18d24
--- /dev/null
+++ b/test/tint/ptr_sugar/matrix.wgsl.expected.wgsl
@@ -0,0 +1,19 @@
+fn deref() {
+ var a : mat2x3<f32>;
+ let p = &(a);
+ var b = (*(p))[0];
+ (*(p))[0] = vec3<f32>(1.0, 2.0, 3.0);
+}
+
+fn no_deref() {
+ var a : mat2x3<f32>;
+ let p = &(a);
+ var b = p[0];
+ p[0] = vec3<f32>(1.0, 2.0, 3.0);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref();
+ no_deref();
+}
diff --git a/test/tint/ptr_sugar/struct.wgsl b/test/tint/ptr_sugar/struct.wgsl
new file mode 100644
index 0000000..1c6c7fe
--- /dev/null
+++ b/test/tint/ptr_sugar/struct.wgsl
@@ -0,0 +1,23 @@
+struct S {
+ x : i32,
+}
+
+fn deref() {
+ var a : S;
+ let p = &a;
+ var b = (*p).x;
+ (*p).x = 42;
+}
+
+fn no_deref() {
+ var a : S;
+ let p = &a;
+ var b = p.x;
+ p.x = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref();
+ no_deref();
+}
diff --git a/test/tint/ptr_sugar/struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_sugar/struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..628808f
--- /dev/null
+++ b/test/tint/ptr_sugar/struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,22 @@
+struct S {
+ int x;
+};
+
+void deref() {
+ S a = (S)0;
+ int b = a.x;
+ a.x = 42;
+}
+
+void no_deref() {
+ S a = (S)0;
+ int b = a.x;
+ a.x = 42;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref();
+ no_deref();
+ return;
+}
diff --git a/test/tint/ptr_sugar/struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_sugar/struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..628808f
--- /dev/null
+++ b/test/tint/ptr_sugar/struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,22 @@
+struct S {
+ int x;
+};
+
+void deref() {
+ S a = (S)0;
+ int b = a.x;
+ a.x = 42;
+}
+
+void no_deref() {
+ S a = (S)0;
+ int b = a.x;
+ a.x = 42;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref();
+ no_deref();
+ return;
+}
diff --git a/test/tint/ptr_sugar/struct.wgsl.expected.glsl b/test/tint/ptr_sugar/struct.wgsl.expected.glsl
new file mode 100644
index 0000000..dceeb2c
--- /dev/null
+++ b/test/tint/ptr_sugar/struct.wgsl.expected.glsl
@@ -0,0 +1,28 @@
+#version 310 es
+
+struct S {
+ int x;
+};
+
+void deref() {
+ S a = S(0);
+ int b = a.x;
+ a.x = 42;
+}
+
+void no_deref() {
+ S a = S(0);
+ int b = a.x;
+ a.x = 42;
+}
+
+void tint_symbol() {
+ deref();
+ no_deref();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+ tint_symbol();
+ return;
+}
diff --git a/test/tint/ptr_sugar/struct.wgsl.expected.msl b/test/tint/ptr_sugar/struct.wgsl.expected.msl
new file mode 100644
index 0000000..1b6ebcd
--- /dev/null
+++ b/test/tint/ptr_sugar/struct.wgsl.expected.msl
@@ -0,0 +1,25 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct S {
+ int x;
+};
+
+void deref() {
+ S a = {};
+ int b = a.x;
+ a.x = 42;
+}
+
+void no_deref() {
+ S a = {};
+ int b = a.x;
+ a.x = 42;
+}
+
+kernel void tint_symbol() {
+ deref();
+ no_deref();
+ return;
+}
+
diff --git a/test/tint/ptr_sugar/struct.wgsl.expected.spvasm b/test/tint/ptr_sugar/struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..d4c38c9
--- /dev/null
+++ b/test/tint/ptr_sugar/struct.wgsl.expected.spvasm
@@ -0,0 +1,58 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 30
+; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %deref "deref"
+ OpName %S "S"
+ OpMemberName %S 0 "x"
+ OpName %a "a"
+ OpName %b "b"
+ OpName %no_deref "no_deref"
+ OpName %a_0 "a"
+ OpName %b_0 "b"
+ OpName %main "main"
+ OpMemberDecorate %S 0 Offset 0
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %S = OpTypeStruct %int
+%_ptr_Function_S = OpTypePointer Function %S
+ %9 = OpConstantNull %S
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Function_int = OpTypePointer Function %int
+ %16 = OpConstantNull %int
+ %int_42 = OpConstant %int 42
+ %deref = OpFunction %void None %1
+ %4 = OpLabel
+ %a = OpVariable %_ptr_Function_S Function %9
+ %b = OpVariable %_ptr_Function_int Function %16
+ %13 = OpAccessChain %_ptr_Function_int %a %uint_0
+ %14 = OpLoad %int %13
+ OpStore %b %14
+ %17 = OpAccessChain %_ptr_Function_int %a %uint_0
+ OpStore %17 %int_42
+ OpReturn
+ OpFunctionEnd
+ %no_deref = OpFunction %void None %1
+ %20 = OpLabel
+ %a_0 = OpVariable %_ptr_Function_S Function %9
+ %b_0 = OpVariable %_ptr_Function_int Function %16
+ %22 = OpAccessChain %_ptr_Function_int %a_0 %uint_0
+ %23 = OpLoad %int %22
+ OpStore %b_0 %23
+ %25 = OpAccessChain %_ptr_Function_int %a_0 %uint_0
+ OpStore %25 %int_42
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %1
+ %27 = OpLabel
+ %28 = OpFunctionCall %void %deref
+ %29 = OpFunctionCall %void %no_deref
+ OpReturn
+ OpFunctionEnd
diff --git a/test/tint/ptr_sugar/struct.wgsl.expected.wgsl b/test/tint/ptr_sugar/struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..e01aecd
--- /dev/null
+++ b/test/tint/ptr_sugar/struct.wgsl.expected.wgsl
@@ -0,0 +1,23 @@
+struct S {
+ x : i32,
+}
+
+fn deref() {
+ var a : S;
+ let p = &(a);
+ var b = (*(p)).x;
+ (*(p)).x = 42;
+}
+
+fn no_deref() {
+ var a : S;
+ let p = &(a);
+ var b = p.x;
+ p.x = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref();
+ no_deref();
+}
diff --git a/test/tint/ptr_sugar/vector_index.wgsl b/test/tint/ptr_sugar/vector_index.wgsl
new file mode 100644
index 0000000..c4a549a
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_index.wgsl
@@ -0,0 +1,55 @@
+fn deref_const() {
+ var a : vec3<i32>;
+ let p = &a;
+ var b = (*p)[0];
+ (*p)[0] = 42;
+}
+
+fn no_deref_const() {
+ var a : vec3<i32>;
+ let p = &a;
+ var b = p[0];
+ p[0] = 42;
+}
+
+fn deref_let() {
+ var a : vec3<i32>;
+ let p = &a;
+ let i = 0;
+ var b = (*p)[i];
+ (*p)[0] = 42;
+}
+
+fn no_deref_let() {
+ var a : vec3<i32>;
+ let p = &a;
+ let i = 0;
+ var b = p[i];
+ p[0] = 42;
+}
+
+fn deref_var() {
+ var a : vec3<i32>;
+ let p = &a;
+ var i = 0;
+ var b = (*p)[i];
+ (*p)[0] = 42;
+}
+
+fn no_deref_var() {
+ var a : vec3<i32>;
+ let p = &a;
+ let i = 0;
+ var b = p[i];
+ p[0] = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref_const();
+ no_deref_const();
+ deref_let();
+ no_deref_let();
+ deref_var();
+ no_deref_var();
+}
diff --git a/test/tint/ptr_sugar/vector_index.wgsl.expected.dxc.hlsl b/test/tint/ptr_sugar/vector_index.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..7d0de2e
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_index.wgsl.expected.dxc.hlsl
@@ -0,0 +1,50 @@
+void deref_const() {
+ int3 a = int3(0, 0, 0);
+ int b = a[0];
+ a[0] = 42;
+}
+
+void no_deref_const() {
+ int3 a = int3(0, 0, 0);
+ int b = a[0];
+ a[0] = 42;
+}
+
+void deref_let() {
+ int3 a = int3(0, 0, 0);
+ const int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_let() {
+ int3 a = int3(0, 0, 0);
+ const int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void deref_var() {
+ int3 a = int3(0, 0, 0);
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_var() {
+ int3 a = int3(0, 0, 0);
+ const int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref_const();
+ no_deref_const();
+ deref_let();
+ no_deref_let();
+ deref_var();
+ no_deref_var();
+ return;
+}
diff --git a/test/tint/ptr_sugar/vector_index.wgsl.expected.fxc.hlsl b/test/tint/ptr_sugar/vector_index.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..7d0de2e
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_index.wgsl.expected.fxc.hlsl
@@ -0,0 +1,50 @@
+void deref_const() {
+ int3 a = int3(0, 0, 0);
+ int b = a[0];
+ a[0] = 42;
+}
+
+void no_deref_const() {
+ int3 a = int3(0, 0, 0);
+ int b = a[0];
+ a[0] = 42;
+}
+
+void deref_let() {
+ int3 a = int3(0, 0, 0);
+ const int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_let() {
+ int3 a = int3(0, 0, 0);
+ const int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void deref_var() {
+ int3 a = int3(0, 0, 0);
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_var() {
+ int3 a = int3(0, 0, 0);
+ const int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref_const();
+ no_deref_const();
+ deref_let();
+ no_deref_let();
+ deref_var();
+ no_deref_var();
+ return;
+}
diff --git a/test/tint/ptr_sugar/vector_index.wgsl.expected.glsl b/test/tint/ptr_sugar/vector_index.wgsl.expected.glsl
new file mode 100644
index 0000000..b78430b
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_index.wgsl.expected.glsl
@@ -0,0 +1,56 @@
+#version 310 es
+
+void deref_const() {
+ ivec3 a = ivec3(0, 0, 0);
+ int b = a[0];
+ a[0] = 42;
+}
+
+void no_deref_const() {
+ ivec3 a = ivec3(0, 0, 0);
+ int b = a[0];
+ a[0] = 42;
+}
+
+void deref_let() {
+ ivec3 a = ivec3(0, 0, 0);
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_let() {
+ ivec3 a = ivec3(0, 0, 0);
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void deref_var() {
+ ivec3 a = ivec3(0, 0, 0);
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_var() {
+ ivec3 a = ivec3(0, 0, 0);
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void tint_symbol() {
+ deref_const();
+ no_deref_const();
+ deref_let();
+ no_deref_let();
+ deref_var();
+ no_deref_var();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+ tint_symbol();
+ return;
+}
diff --git a/test/tint/ptr_sugar/vector_index.wgsl.expected.msl b/test/tint/ptr_sugar/vector_index.wgsl.expected.msl
new file mode 100644
index 0000000..2d99f38
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_index.wgsl.expected.msl
@@ -0,0 +1,53 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void deref_const() {
+ int3 a = 0;
+ int b = a[0];
+ a[0] = 42;
+}
+
+void no_deref_const() {
+ int3 a = 0;
+ int b = a[0];
+ a[0] = 42;
+}
+
+void deref_let() {
+ int3 a = 0;
+ int const i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_let() {
+ int3 a = 0;
+ int const i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void deref_var() {
+ int3 a = 0;
+ int i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+void no_deref_var() {
+ int3 a = 0;
+ int const i = 0;
+ int b = a[i];
+ a[0] = 42;
+}
+
+kernel void tint_symbol() {
+ deref_const();
+ no_deref_const();
+ deref_let();
+ no_deref_let();
+ deref_var();
+ no_deref_var();
+ return;
+}
+
diff --git a/test/tint/ptr_sugar/vector_index.wgsl.expected.spvasm b/test/tint/ptr_sugar/vector_index.wgsl.expected.spvasm
new file mode 100644
index 0000000..7706621
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_index.wgsl.expected.spvasm
@@ -0,0 +1,117 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 62
+; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %deref_const "deref_const"
+ OpName %a "a"
+ OpName %b "b"
+ OpName %no_deref_const "no_deref_const"
+ OpName %a_0 "a"
+ OpName %b_0 "b"
+ OpName %deref_let "deref_let"
+ OpName %a_1 "a"
+ OpName %b_1 "b"
+ OpName %no_deref_let "no_deref_let"
+ OpName %a_2 "a"
+ OpName %b_2 "b"
+ OpName %deref_var "deref_var"
+ OpName %a_3 "a"
+ OpName %i "i"
+ OpName %b_3 "b"
+ OpName %no_deref_var "no_deref_var"
+ OpName %a_4 "a"
+ OpName %b_4 "b"
+ OpName %main "main"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %v3int = OpTypeVector %int 3
+%_ptr_Function_v3int = OpTypePointer Function %v3int
+ %9 = OpConstantNull %v3int
+ %10 = OpConstantNull %int
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_42 = OpConstant %int 42
+%deref_const = OpFunction %void None %1
+ %4 = OpLabel
+ %a = OpVariable %_ptr_Function_v3int Function %9
+ %b = OpVariable %_ptr_Function_int Function %10
+ %12 = OpAccessChain %_ptr_Function_int %a %10
+ %13 = OpLoad %int %12
+ OpStore %b %13
+ %15 = OpAccessChain %_ptr_Function_int %a %10
+ OpStore %15 %int_42
+ OpReturn
+ OpFunctionEnd
+%no_deref_const = OpFunction %void None %1
+ %18 = OpLabel
+ %a_0 = OpVariable %_ptr_Function_v3int Function %9
+ %b_0 = OpVariable %_ptr_Function_int Function %10
+ %20 = OpAccessChain %_ptr_Function_int %a_0 %10
+ %21 = OpLoad %int %20
+ OpStore %b_0 %21
+ %23 = OpAccessChain %_ptr_Function_int %a_0 %10
+ OpStore %23 %int_42
+ OpReturn
+ OpFunctionEnd
+ %deref_let = OpFunction %void None %1
+ %25 = OpLabel
+ %a_1 = OpVariable %_ptr_Function_v3int Function %9
+ %b_1 = OpVariable %_ptr_Function_int Function %10
+ %27 = OpAccessChain %_ptr_Function_int %a_1 %10
+ %28 = OpLoad %int %27
+ OpStore %b_1 %28
+ %30 = OpAccessChain %_ptr_Function_int %a_1 %10
+ OpStore %30 %int_42
+ OpReturn
+ OpFunctionEnd
+%no_deref_let = OpFunction %void None %1
+ %32 = OpLabel
+ %a_2 = OpVariable %_ptr_Function_v3int Function %9
+ %b_2 = OpVariable %_ptr_Function_int Function %10
+ %34 = OpAccessChain %_ptr_Function_int %a_2 %10
+ %35 = OpLoad %int %34
+ OpStore %b_2 %35
+ %37 = OpAccessChain %_ptr_Function_int %a_2 %10
+ OpStore %37 %int_42
+ OpReturn
+ OpFunctionEnd
+ %deref_var = OpFunction %void None %1
+ %39 = OpLabel
+ %a_3 = OpVariable %_ptr_Function_v3int Function %9
+ %i = OpVariable %_ptr_Function_int Function %10
+ %b_3 = OpVariable %_ptr_Function_int Function %10
+ OpStore %i %10
+ %42 = OpLoad %int %i
+ %43 = OpAccessChain %_ptr_Function_int %a_3 %42
+ %44 = OpLoad %int %43
+ OpStore %b_3 %44
+ %46 = OpAccessChain %_ptr_Function_int %a_3 %10
+ OpStore %46 %int_42
+ OpReturn
+ OpFunctionEnd
+%no_deref_var = OpFunction %void None %1
+ %48 = OpLabel
+ %a_4 = OpVariable %_ptr_Function_v3int Function %9
+ %b_4 = OpVariable %_ptr_Function_int Function %10
+ %50 = OpAccessChain %_ptr_Function_int %a_4 %10
+ %51 = OpLoad %int %50
+ OpStore %b_4 %51
+ %53 = OpAccessChain %_ptr_Function_int %a_4 %10
+ OpStore %53 %int_42
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %1
+ %55 = OpLabel
+ %56 = OpFunctionCall %void %deref_const
+ %57 = OpFunctionCall %void %no_deref_const
+ %58 = OpFunctionCall %void %deref_let
+ %59 = OpFunctionCall %void %no_deref_let
+ %60 = OpFunctionCall %void %deref_var
+ %61 = OpFunctionCall %void %no_deref_var
+ OpReturn
+ OpFunctionEnd
diff --git a/test/tint/ptr_sugar/vector_index.wgsl.expected.wgsl b/test/tint/ptr_sugar/vector_index.wgsl.expected.wgsl
new file mode 100644
index 0000000..28f45ae
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_index.wgsl.expected.wgsl
@@ -0,0 +1,55 @@
+fn deref_const() {
+ var a : vec3<i32>;
+ let p = &(a);
+ var b = (*(p))[0];
+ (*(p))[0] = 42;
+}
+
+fn no_deref_const() {
+ var a : vec3<i32>;
+ let p = &(a);
+ var b = p[0];
+ p[0] = 42;
+}
+
+fn deref_let() {
+ var a : vec3<i32>;
+ let p = &(a);
+ let i = 0;
+ var b = (*(p))[i];
+ (*(p))[0] = 42;
+}
+
+fn no_deref_let() {
+ var a : vec3<i32>;
+ let p = &(a);
+ let i = 0;
+ var b = p[i];
+ p[0] = 42;
+}
+
+fn deref_var() {
+ var a : vec3<i32>;
+ let p = &(a);
+ var i = 0;
+ var b = (*(p))[i];
+ (*(p))[0] = 42;
+}
+
+fn no_deref_var() {
+ var a : vec3<i32>;
+ let p = &(a);
+ let i = 0;
+ var b = p[i];
+ p[0] = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref_const();
+ no_deref_const();
+ deref_let();
+ no_deref_let();
+ deref_var();
+ no_deref_var();
+}
diff --git a/test/tint/ptr_sugar/vector_member.wgsl b/test/tint/ptr_sugar/vector_member.wgsl
new file mode 100644
index 0000000..cd2eab8
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_member.wgsl
@@ -0,0 +1,19 @@
+fn deref() {
+ var a : vec3<i32>;
+ let p = &a;
+ var b = (*p).x;
+ (*p).x = 42;
+}
+
+fn no_deref() {
+ var a : vec3<i32>;
+ let p = &a;
+ var b = p.x;
+ p.x = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref();
+ no_deref();
+}
diff --git a/test/tint/ptr_sugar/vector_member.wgsl.expected.dxc.hlsl b/test/tint/ptr_sugar/vector_member.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..b9ff0dc
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_member.wgsl.expected.dxc.hlsl
@@ -0,0 +1,18 @@
+void deref() {
+ int3 a = int3(0, 0, 0);
+ int b = a.x;
+ a.x = 42;
+}
+
+void no_deref() {
+ int3 a = int3(0, 0, 0);
+ int b = a.x;
+ a.x = 42;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref();
+ no_deref();
+ return;
+}
diff --git a/test/tint/ptr_sugar/vector_member.wgsl.expected.fxc.hlsl b/test/tint/ptr_sugar/vector_member.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..b9ff0dc
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_member.wgsl.expected.fxc.hlsl
@@ -0,0 +1,18 @@
+void deref() {
+ int3 a = int3(0, 0, 0);
+ int b = a.x;
+ a.x = 42;
+}
+
+void no_deref() {
+ int3 a = int3(0, 0, 0);
+ int b = a.x;
+ a.x = 42;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+ deref();
+ no_deref();
+ return;
+}
diff --git a/test/tint/ptr_sugar/vector_member.wgsl.expected.glsl b/test/tint/ptr_sugar/vector_member.wgsl.expected.glsl
new file mode 100644
index 0000000..4380434
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_member.wgsl.expected.glsl
@@ -0,0 +1,24 @@
+#version 310 es
+
+void deref() {
+ ivec3 a = ivec3(0, 0, 0);
+ int b = a.x;
+ a.x = 42;
+}
+
+void no_deref() {
+ ivec3 a = ivec3(0, 0, 0);
+ int b = a.x;
+ a.x = 42;
+}
+
+void tint_symbol() {
+ deref();
+ no_deref();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+ tint_symbol();
+ return;
+}
diff --git a/test/tint/ptr_sugar/vector_member.wgsl.expected.msl b/test/tint/ptr_sugar/vector_member.wgsl.expected.msl
new file mode 100644
index 0000000..b4b88f1
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_member.wgsl.expected.msl
@@ -0,0 +1,21 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void deref() {
+ int3 a = 0;
+ int b = a[0];
+ a[0] = 42;
+}
+
+void no_deref() {
+ int3 a = 0;
+ int b = a[0];
+ a[0] = 42;
+}
+
+kernel void tint_symbol() {
+ deref();
+ no_deref();
+ return;
+}
+
diff --git a/test/tint/ptr_sugar/vector_member.wgsl.expected.spvasm b/test/tint/ptr_sugar/vector_member.wgsl.expected.spvasm
new file mode 100644
index 0000000..90d445f
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_member.wgsl.expected.spvasm
@@ -0,0 +1,55 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 30
+; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %deref "deref"
+ OpName %a "a"
+ OpName %b "b"
+ OpName %no_deref "no_deref"
+ OpName %a_0 "a"
+ OpName %b_0 "b"
+ OpName %main "main"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %v3int = OpTypeVector %int 3
+%_ptr_Function_v3int = OpTypePointer Function %v3int
+ %9 = OpConstantNull %v3int
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Function_int = OpTypePointer Function %int
+ %16 = OpConstantNull %int
+ %int_42 = OpConstant %int 42
+ %deref = OpFunction %void None %1
+ %4 = OpLabel
+ %a = OpVariable %_ptr_Function_v3int Function %9
+ %b = OpVariable %_ptr_Function_int Function %16
+ %13 = OpAccessChain %_ptr_Function_int %a %uint_0
+ %14 = OpLoad %int %13
+ OpStore %b %14
+ %17 = OpAccessChain %_ptr_Function_int %a %uint_0
+ OpStore %17 %int_42
+ OpReturn
+ OpFunctionEnd
+ %no_deref = OpFunction %void None %1
+ %20 = OpLabel
+ %a_0 = OpVariable %_ptr_Function_v3int Function %9
+ %b_0 = OpVariable %_ptr_Function_int Function %16
+ %22 = OpAccessChain %_ptr_Function_int %a_0 %uint_0
+ %23 = OpLoad %int %22
+ OpStore %b_0 %23
+ %25 = OpAccessChain %_ptr_Function_int %a_0 %uint_0
+ OpStore %25 %int_42
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %1
+ %27 = OpLabel
+ %28 = OpFunctionCall %void %deref
+ %29 = OpFunctionCall %void %no_deref
+ OpReturn
+ OpFunctionEnd
diff --git a/test/tint/ptr_sugar/vector_member.wgsl.expected.wgsl b/test/tint/ptr_sugar/vector_member.wgsl.expected.wgsl
new file mode 100644
index 0000000..4c48f2b
--- /dev/null
+++ b/test/tint/ptr_sugar/vector_member.wgsl.expected.wgsl
@@ -0,0 +1,19 @@
+fn deref() {
+ var a : vec3<i32>;
+ let p = &(a);
+ var b = (*(p)).x;
+ (*(p)).x = 42;
+}
+
+fn no_deref() {
+ var a : vec3<i32>;
+ let p = &(a);
+ var b = p.x;
+ p.x = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ deref();
+ no_deref();
+}