tint/writer/msl: Move packed_vector hacks to transform

Attempting to paper over all the MSL standard library holes for packed_vector in the MSL writer added complexity to the writer, produced messy output, and didn't actually catch all the cases where casts were needed.

Add a new PackedVec3 transform that applies the packed_vector -> vec casts in a smarter, more precise way.

Fixed: tint:1534
Change-Id: I73ce7e5a62fbc9cb04e1093133070f5fb8965dce
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/107340
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
This commit is contained in:
Ben Clayton
2022-10-27 14:36:49 +00:00
committed by Dawn LUCI CQ
parent 59c0982426
commit a92f4259d5
53 changed files with 1070 additions and 379 deletions

View File

@@ -0,0 +1,194 @@
// Copyright 2022 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/transform/packed_vec3.h"
#include <algorithm>
#include <string>
#include <utility>
#include "src/tint/program_builder.h"
#include "src/tint/sem/index_accessor_expression.h"
#include "src/tint/sem/member_accessor_expression.h"
#include "src/tint/sem/statement.h"
#include "src/tint/sem/variable.h"
#include "src/tint/utils/hashmap.h"
#include "src/tint/utils/hashset.h"
TINT_INSTANTIATE_TYPEINFO(tint::transform::PackedVec3);
TINT_INSTANTIATE_TYPEINFO(tint::transform::PackedVec3::Attribute);
using namespace tint::number_suffixes; // NOLINT
namespace tint::transform {
/// The PIMPL state for the PackedVec3 transform
struct PackedVec3::State {
/// Constructor
/// @param c the CloneContext
explicit State(CloneContext& c) : ctx(c) {}
/// Runs the transform
void Run() {
// Packed vec3<T> struct members
utils::Hashset<const sem::StructMember*, 8> members;
// Find all the packed vector struct members, and apply the @internal(packed_vector)
// attribute.
for (auto* decl : ctx.src->AST().GlobalDeclarations()) {
if (auto* str = sem.Get<sem::Struct>(decl)) {
if (str->IsHostShareable()) {
for (auto* member : str->Members()) {
if (auto* vec = member->Type()->As<sem::Vector>()) {
if (vec->Width() == 3) {
members.Add(member);
// Apply the PackedVec3::Attribute to the member
auto* member_decl = member->Declaration();
auto name = ctx.Clone(member_decl->symbol);
auto* type = ctx.Clone(member_decl->type);
utils::Vector<const ast::Attribute*, 4> attrs{
b.ASTNodes().Create<Attribute>(b.ID(), b.AllocateNodeID()),
};
for (auto* attr : member_decl->attributes) {
attrs.Push(ctx.Clone(attr));
}
ctx.Replace(member_decl, b.Member(name, type, std::move(attrs)));
}
}
}
}
}
}
// Walk the nodes, starting with the most deeply nested, finding all the AST expressions
// that load a whole packed vector (not a scalar / swizzle of the vector).
utils::Hashset<const sem::Expression*, 16> refs;
for (auto* node : ctx.src->ASTNodes().Objects()) {
Switch(
sem.Get(node), //
[&](const sem::StructMemberAccess* access) {
if (members.Contains(access->Member())) {
// Access to a packed vector member. Seed the expression tracking.
refs.Add(access);
}
},
[&](const sem::IndexAccessorExpression* access) {
// Not loading a whole packed vector. Ignore.
refs.Remove(access->Object());
},
[&](const sem::Swizzle* access) {
// Not loading a whole packed vector. Ignore.
refs.Remove(access->Object());
},
[&](const sem::VariableUser* user) {
auto* v = user->Variable();
if (v->Declaration()->Is<ast::Let>() && // if variable is let...
v->Type()->Is<sem::Pointer>() && // and let is a pointer...
refs.Contains(v->Initializer())) { // and pointer is to a packed vector...
refs.Add(user); // then propagate tracking to pointer usage
}
},
[&](const sem::Expression* expr) {
if (auto* unary = expr->Declaration()->As<ast::UnaryOpExpression>()) {
if (unary->op == ast::UnaryOp::kAddressOf ||
unary->op == ast::UnaryOp::kIndirection) {
// Memory access on the packed vector. Track these.
auto* inner = sem.Get(unary->expr);
if (refs.Remove(inner)) {
refs.Add(expr);
}
}
// Note: non-memory ops (e.g. '-') are ignored, leaving any tracked
// reference at the inner expression, so we'd cast, then apply the unary op.
}
},
[&](const sem::Statement* e) {
if (auto* assign = e->Declaration()->As<ast::AssignmentStatement>()) {
// We don't want to cast packed_vectors if they're being assigned to.
refs.Remove(sem.Get(assign->lhs));
}
});
}
// Wrap the load expressions with a cast to the unpacked type.
utils::Hashmap<const sem::Vector*, Symbol, 3> unpack_fns;
for (auto* ref : refs) {
// ref is either a packed vec3 that needs casting, or a pointer to a vec3 which we just
// leave alone.
if (auto* vec_ty = ref->Type()->UnwrapRef()->As<sem::Vector>()) {
auto* expr = ref->Declaration();
ctx.Replace(expr, [this, vec_ty, expr] { //
auto* packed = ctx.CloneWithoutTransform(expr);
return b.Construct(CreateASTTypeFor(ctx, vec_ty), packed);
});
}
}
ctx.Clone();
}
/// @returns true if this transform should be run for the given program
/// @param program the program to inspect
static bool ShouldRun(const Program* program) {
for (auto* decl : program->AST().GlobalDeclarations()) {
if (auto* str = program->Sem().Get<sem::Struct>(decl)) {
if (str->IsHostShareable()) {
for (auto* member : str->Members()) {
if (auto* vec = member->Type()->As<sem::Vector>()) {
if (vec->Width() == 3) {
return true;
}
}
}
}
}
}
return false;
}
private:
/// The clone context
CloneContext& ctx;
/// Alias to the semantic info in ctx.src
const sem::Info& sem = ctx.src->Sem();
/// Alias to the symbols in ctx.src
const SymbolTable& sym = ctx.src->Symbols();
/// Alias to the ctx.dst program builder
ProgramBuilder& b = *ctx.dst;
};
PackedVec3::Attribute::Attribute(ProgramID pid, ast::NodeID nid) : Base(pid, nid) {}
PackedVec3::Attribute::~Attribute() = default;
const PackedVec3::Attribute* PackedVec3::Attribute::Clone(CloneContext* ctx) const {
return ctx->dst->ASTNodes().Create<Attribute>(ctx->dst->ID(), ctx->dst->AllocateNodeID());
}
std::string PackedVec3::Attribute::InternalName() const {
return "packed_vector";
}
PackedVec3::PackedVec3() = default;
PackedVec3::~PackedVec3() = default;
bool PackedVec3::ShouldRun(const Program* program, const DataMap&) const {
return State::ShouldRun(program);
}
void PackedVec3::Run(CloneContext& ctx, const DataMap&, DataMap&) const {
State(ctx).Run();
}
} // namespace tint::transform

View File

@@ -0,0 +1,78 @@
// Copyright 2022 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_TINT_TRANSFORM_PACKED_VEC3_H_
#define SRC_TINT_TRANSFORM_PACKED_VEC3_H_
#include <string>
#include "src/tint/ast/internal_attribute.h"
#include "src/tint/transform/transform.h"
namespace tint::transform {
/// A transform to be used by the MSL backend which will:
/// * Apply the `@internal('packed_vector')` attribute (PackedVec3::Attribute) to all host-sharable
/// structure members that have a vec3<T> type.
/// * Cast all direct (not sub-accessed) loads of these packed vectors to the 'unpacked' vec3<T>
/// type before usage.
///
/// This transform papers over overload holes in the MSL standard library where an MSL
/// `packed_vector` type cannot be interchangable used as a regular `vec` type.
class PackedVec3 final : public Castable<PackedVec3, Transform> {
public:
/// Attribute is the attribute applied to padded vector structure members.
class Attribute final : public Castable<Attribute, ast::InternalAttribute> {
public:
/// Constructor
/// @param pid the identifier of the program that owns this node
/// @param nid the unique node identifier
Attribute(ProgramID pid, ast::NodeID nid);
/// Destructor
~Attribute() override;
/// @returns "packed_vector".
std::string InternalName() const override;
/// Performs a deep clone of this object using the CloneContext `ctx`.
/// @param ctx the clone context
/// @return the newly cloned object
const Attribute* Clone(CloneContext* ctx) const override;
};
/// Constructor
PackedVec3();
/// Destructor
~PackedVec3() override;
/// @param program the program to inspect
/// @param data optional extra transform-specific input data
/// @returns true if this transform should be run for the given program
bool ShouldRun(const Program* program, const DataMap& data = {}) const override;
private:
struct State;
/// Runs the transform using the CloneContext built for transforming a
/// program. Run() is responsible for calling Clone() on the CloneContext.
/// @param ctx the CloneContext primed with the input program and
/// ProgramBuilder
/// @param inputs optional extra transform-specific input data
/// @param outputs optional extra transform-specific output data
void Run(CloneContext& ctx, const DataMap& inputs, DataMap& outputs) const override;
};
} // namespace tint::transform
#endif // SRC_TINT_TRANSFORM_PACKED_VEC3_H_

View File

@@ -0,0 +1,662 @@
// Copyright 2022 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/transform/packed_vec3.h"
#include <string>
#include <utility>
#include <vector>
#include "src/tint/transform/test_helper.h"
#include "src/tint/utils/string.h"
namespace tint::transform {
namespace {
using PackedVec3Test = TransformTest;
TEST_F(PackedVec3Test, ShouldRun_EmptyModule) {
auto* src = R"()";
EXPECT_FALSE(ShouldRun<PackedVec3>(src));
}
TEST_F(PackedVec3Test, ShouldRun_NonHostSharableStruct) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
fn f() {
var v : S; // function address-space - not host sharable
}
)";
EXPECT_FALSE(ShouldRun<PackedVec3>(src));
}
TEST_F(PackedVec3Test, ShouldRun_Vec4Vec2) {
auto* src = R"(
struct S {
v4 : vec4<f32>,
v2 : vec2<f32>,
}
@group(0) @binding(0) var<uniform> P : S; // Host sharable
)";
EXPECT_FALSE(ShouldRun<PackedVec3>(src));
}
TEST_F(PackedVec3Test, ShouldRun_HostSharableStruct) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<uniform> P : S; // Host sharable
)";
EXPECT_TRUE(ShouldRun<PackedVec3>(src));
}
TEST_F(PackedVec3Test, UniformAddressSpace) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<uniform> P : S;
fn f() {
let x = P.v;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<uniform> P : S;
fn f() {
let x = vec3<f32>(P.v);
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, StorageAddressSpace) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = P.v;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = vec3<f32>(P.v);
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, ExistingMemberAttributes) {
auto* src = R"(
struct S {
@align(32) @size(64) v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = P.v;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector) @align(32) @size(64)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = vec3<f32>(P.v);
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, MultipleVectors) {
auto* src = R"(
struct S {
v2_a : vec2<f32>,
v3_a : vec3<f32>,
v4_a : vec4<f32>,
v2_b : vec2<f32>,
v3_b : vec3<f32>,
v4_b : vec4<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let v2_a = P.v2_a;
let v3_a = P.v3_a;
let v4_a = P.v4_a;
let v2_b = P.v2_b;
let v3_b = P.v3_b;
let v4_b = P.v4_b;
}
)";
auto* expect = R"(
struct S {
v2_a : vec2<f32>,
@internal(packed_vector)
v3_a : vec3<f32>,
v4_a : vec4<f32>,
v2_b : vec2<f32>,
@internal(packed_vector)
v3_b : vec3<f32>,
v4_b : vec4<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let v2_a = P.v2_a;
let v3_a = vec3<f32>(P.v3_a);
let v4_a = P.v4_a;
let v2_b = P.v2_b;
let v3_b = vec3<f32>(P.v3_b);
let v4_b = P.v4_b;
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, MixedAddressSpace) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
var f : S;
let x = f.v;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
var f : S;
let x = vec3<f32>(f.v);
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, ReadMemberAccessChain) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = P.v.yz.x;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = P.v.yz.x;
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, ReadVector) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = P.v;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = vec3<f32>(P.v);
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, ReadIndexAccessor) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = P.v[1];
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = P.v[1];
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, ReadViaStructPtrDirect) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = (*(&(*(&P)))).v;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = vec3<f32>((*(&(*(&(P))))).v);
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, ReadViaVectorPtrDirect) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = *(&(*(&(P.v))));
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = vec3<f32>(*(&(*(&(P.v)))));
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, ReadViaStructPtrViaLet) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let p0 = &P;
let p1 = &(*(p0));
let a = (*p1).v;
let p2 = &(*(p1));
let b = (*p2).v;
let c = (*p2).v;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let p0 = &(P);
let p1 = &(*(p0));
let a = vec3<f32>((*(p1)).v);
let p2 = &(*(p1));
let b = vec3<f32>((*(p2)).v);
let c = vec3<f32>((*(p2)).v);
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, ReadViaVectorPtrViaLet) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let p0 = &(P.v);
let p1 = &(*(p0));
let a = *p1;
let p2 = &(*(p1));
let b = *p2;
let c = *p2;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let p0 = &(P.v);
let p1 = &(*(p0));
let a = vec3<f32>(*(p1));
let p2 = &(*(p1));
let b = vec3<f32>(*(p2));
let c = vec3<f32>(*(p2));
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, ReadUnaryOp) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = -P.v;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = -(vec3<f32>(P.v));
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, ReadBinaryOp) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = P.v + P.v;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage> P : S;
fn f() {
let x = (vec3<f32>(P.v) + vec3<f32>(P.v));
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, WriteVector) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage, read_write> P : S;
fn f() {
P.v = vec3(1.23);
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage, read_write> P : S;
fn f() {
P.v = vec3(1.23);
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, WriteMemberAccess) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage, read_write> P : S;
fn f() {
P.v.y = 1.23;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage, read_write> P : S;
fn f() {
P.v.y = 1.23;
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
TEST_F(PackedVec3Test, WriteIndexAccessor) {
auto* src = R"(
struct S {
v : vec3<f32>,
}
@group(0) @binding(0) var<storage, read_write> P : S;
fn f() {
P.v[1] = 1.23;
}
)";
auto* expect = R"(
struct S {
@internal(packed_vector)
v : vec3<f32>,
}
@group(0) @binding(0) var<storage, read_write> P : S;
fn f() {
P.v[1] = 1.23;
}
)";
DataMap data;
auto got = Run<PackedVec3>(src, data);
EXPECT_EQ(expect, str(got));
}
} // namespace
} // namespace tint::transform