[ir][msl] Emit struct types
Add emission of struct types to the MSL IR generator.
Bug: tint:1967
Change-Id: I18eb138e6da812721908d95c07514a2b99eb98a6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/138884
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 8371b8f..e2dd096 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -1130,6 +1130,8 @@
"writer/msl/generator.h",
"writer/msl/generator_impl.cc",
"writer/msl/generator_impl.h",
+ "writer/msl/generator_support.cc",
+ "writer/msl/generator_support.h",
]
deps = [
@@ -2204,6 +2206,7 @@
"writer/msl/generator_impl_type_test.cc",
"writer/msl/generator_impl_unary_op_test.cc",
"writer/msl/generator_impl_variable_decl_statement_test.cc",
+ "writer/msl/generator_support_test.cc",
"writer/msl/test_helper.h",
]
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 1b64b13..0ccef44 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -694,6 +694,8 @@
writer/msl/generator.h
writer/msl/generator_impl.cc
writer/msl/generator_impl.h
+ writer/msl/generator_support.cc
+ writer/msl/generator_support.h
)
if(${TINT_BUILD_IR})
@@ -1449,6 +1451,7 @@
writer/msl/generator_impl_type_test.cc
writer/msl/generator_impl_unary_op_test.cc
writer/msl/generator_impl_variable_decl_statement_test.cc
+ writer/msl/generator_support_test.cc
writer/msl/test_helper.h
)
diff --git a/src/tint/writer/ast_text_generator.cc b/src/tint/writer/ast_text_generator.cc
index dc0530b..65bf618 100644
--- a/src/tint/writer/ast_text_generator.cc
+++ b/src/tint/writer/ast_text_generator.cc
@@ -30,13 +30,4 @@
return builder_.Symbols().New(prefix).Name();
}
-std::string ASTTextGenerator::StructName(const type::Struct* s) {
- auto name = s->Name().Name();
- if (name.size() > 1 && name[0] == '_' && name[1] == '_') {
- name = utils::GetOrCreate(builtin_struct_names_, s,
- [&] { return UniqueIdentifier(name.substr(2)); });
- }
- return name;
-}
-
} // namespace tint::writer
diff --git a/src/tint/writer/ast_text_generator.h b/src/tint/writer/ast_text_generator.h
index 74c42ae..d385e33 100644
--- a/src/tint/writer/ast_text_generator.h
+++ b/src/tint/writer/ast_text_generator.h
@@ -36,14 +36,7 @@
/// @return a new, unique identifier with the given prefix.
/// @param prefix optional prefix to apply to the generated identifier. If
/// empty "tint_symbol" will be used.
- std::string UniqueIdentifier(const std::string& prefix = "");
-
- /// @param s the semantic structure
- /// @returns the name of the structure, taking special care of builtin
- /// structures that start with double underscores. If the structure is a
- /// builtin, then the returned name will be a unique name without the leading
- /// underscores.
- std::string StructName(const type::Struct* s);
+ std::string UniqueIdentifier(const std::string& prefix = "") override;
protected:
/// @returns the resolved type of the ast::Expression `expr`
diff --git a/src/tint/writer/ir_text_generator.cc b/src/tint/writer/ir_text_generator.cc
index c9bbaee..d467dc7 100644
--- a/src/tint/writer/ir_text_generator.cc
+++ b/src/tint/writer/ir_text_generator.cc
@@ -14,6 +14,8 @@
#include "src/tint/writer/ir_text_generator.h"
+#include "src/tint/utils/map.h"
+
namespace tint::writer {
IRTextGenerator::IRTextGenerator(ir::Module* mod) : ir_(mod) {}
diff --git a/src/tint/writer/ir_text_generator.h b/src/tint/writer/ir_text_generator.h
index 5a6e42b..60ccfbc 100644
--- a/src/tint/writer/ir_text_generator.h
+++ b/src/tint/writer/ir_text_generator.h
@@ -33,7 +33,7 @@
/// @return a new, unique identifier with the given prefix.
/// @param prefix optional prefix to apply to the generated identifier. If
/// empty "tint_symbol" will be used.
- std::string UniqueIdentifier(const std::string& prefix = "");
+ std::string UniqueIdentifier(const std::string& prefix = "") override;
/// @returns the generated shader string
std::string Result() const override {
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index ae7591f..83e769a 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -86,6 +86,7 @@
#include "src/tint/utils/string_stream.h"
#include "src/tint/writer/check_supported_extensions.h"
#include "src/tint/writer/float_to_string.h"
+#include "src/tint/writer/msl/generator_support.h"
namespace tint::writer::msl {
namespace {
@@ -1910,73 +1911,6 @@
return true;
}
-std::string GeneratorImpl::builtin_to_attribute(builtin::BuiltinValue builtin) const {
- switch (builtin) {
- case builtin::BuiltinValue::kPosition:
- return "position";
- case builtin::BuiltinValue::kVertexIndex:
- return "vertex_id";
- case builtin::BuiltinValue::kInstanceIndex:
- return "instance_id";
- case builtin::BuiltinValue::kFrontFacing:
- return "front_facing";
- case builtin::BuiltinValue::kFragDepth:
- return "depth(any)";
- case builtin::BuiltinValue::kLocalInvocationId:
- return "thread_position_in_threadgroup";
- case builtin::BuiltinValue::kLocalInvocationIndex:
- return "thread_index_in_threadgroup";
- case builtin::BuiltinValue::kGlobalInvocationId:
- return "thread_position_in_grid";
- case builtin::BuiltinValue::kWorkgroupId:
- return "threadgroup_position_in_grid";
- case builtin::BuiltinValue::kNumWorkgroups:
- return "threadgroups_per_grid";
- case builtin::BuiltinValue::kSampleIndex:
- return "sample_id";
- case builtin::BuiltinValue::kSampleMask:
- return "sample_mask";
- case builtin::BuiltinValue::kPointSize:
- return "point_size";
- default:
- break;
- }
- return "";
-}
-
-std::string GeneratorImpl::interpolation_to_attribute(
- builtin::InterpolationType type,
- builtin::InterpolationSampling sampling) const {
- std::string attr;
- switch (sampling) {
- case builtin::InterpolationSampling::kCenter:
- attr = "center_";
- break;
- case builtin::InterpolationSampling::kCentroid:
- attr = "centroid_";
- break;
- case builtin::InterpolationSampling::kSample:
- attr = "sample_";
- break;
- case builtin::InterpolationSampling::kUndefined:
- break;
- }
- switch (type) {
- case builtin::InterpolationType::kPerspective:
- attr += "perspective";
- break;
- case builtin::InterpolationType::kLinear:
- attr += "no_perspective";
- break;
- case builtin::InterpolationType::kFlat:
- attr += "flat";
- break;
- case builtin::InterpolationType::kUndefined:
- break;
- }
- return attr;
-}
-
bool GeneratorImpl::EmitEntryPointFunction(const ast::Function* func) {
auto* func_sem = builder_.Sem().Get(func);
@@ -2086,7 +2020,7 @@
builtin_found = true;
- auto name = builtin_to_attribute(builtin);
+ auto name = BuiltinToAttribute(builtin);
if (name.empty()) {
diagnostics_.add_error(diag::System::Writer, "unknown builtin");
return false;
@@ -2835,7 +2769,7 @@
auto& attributes = mem->Attributes();
if (auto builtin = attributes.builtin) {
- auto name = builtin_to_attribute(builtin.value());
+ auto name = BuiltinToAttribute(builtin.value());
if (name.empty()) {
diagnostics_.add_error(diag::System::Writer, "unknown builtin");
return false;
@@ -2866,7 +2800,7 @@
}
if (auto interpolation = attributes.interpolation) {
- auto name = interpolation_to_attribute(interpolation->type, interpolation->sampling);
+ auto name = InterpolationToAttribute(interpolation->type, interpolation->sampling);
if (name.empty()) {
diagnostics_.add_error(diag::System::Writer, "unknown interpolation attribute");
return false;
@@ -2883,7 +2817,7 @@
if (is_host_shareable) {
// Calculate new MSL offset
- auto size_align = MslPackedTypeSizeAndAlign(ty);
+ auto size_align = MslPackedTypeSizeAndAlign(diagnostics_, ty);
if (TINT_UNLIKELY(msl_offset % size_align.align)) {
TINT_ICE(Writer, diagnostics_)
<< "Misaligned MSL structure member " << ty->FriendlyName() << " " << mem_name;
@@ -3056,122 +2990,6 @@
return true;
}
-GeneratorImpl::SizeAndAlign GeneratorImpl::MslPackedTypeSizeAndAlign(const type::Type* ty) {
- return Switch(
- ty,
-
- // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
- // 2.1 Scalar Data Types
- [&](const type::U32*) {
- return SizeAndAlign{4, 4};
- },
- [&](const type::I32*) {
- return SizeAndAlign{4, 4};
- },
- [&](const type::F32*) {
- return SizeAndAlign{4, 4};
- },
- [&](const type::F16*) {
- return SizeAndAlign{2, 2};
- },
-
- [&](const type::Vector* vec) {
- auto num_els = vec->Width();
- auto* el_ty = vec->type();
- SizeAndAlign el_size_align = MslPackedTypeSizeAndAlign(el_ty);
- if (el_ty->IsAnyOf<type::U32, type::I32, type::F32, type::F16>()) {
- // Use a packed_vec type for 3-element vectors only.
- if (num_els == 3) {
- // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
- // 2.2.3 Packed Vector Types
- return SizeAndAlign{num_els * el_size_align.size, el_size_align.align};
- } else {
- // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
- // 2.2 Vector Data Types
- // Vector data types are aligned to their size.
- return SizeAndAlign{num_els * el_size_align.size, num_els * el_size_align.size};
- }
- }
- TINT_UNREACHABLE(Writer, diagnostics_)
- << "Unhandled vector element type " << el_ty->TypeInfo().name;
- return SizeAndAlign{};
- },
-
- [&](const type::Matrix* mat) {
- // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
- // 2.3 Matrix Data Types
- auto cols = mat->columns();
- auto rows = mat->rows();
- auto* el_ty = mat->type();
- // Metal only support half and float matrix.
- if (el_ty->IsAnyOf<type::F32, type::F16>()) {
- static constexpr SizeAndAlign table_f32[] = {
- /* float2x2 */ {16, 8},
- /* float2x3 */ {32, 16},
- /* float2x4 */ {32, 16},
- /* float3x2 */ {24, 8},
- /* float3x3 */ {48, 16},
- /* float3x4 */ {48, 16},
- /* float4x2 */ {32, 8},
- /* float4x3 */ {64, 16},
- /* float4x4 */ {64, 16},
- };
- static constexpr SizeAndAlign table_f16[] = {
- /* half2x2 */ {8, 4},
- /* half2x3 */ {16, 8},
- /* half2x4 */ {16, 8},
- /* half3x2 */ {12, 4},
- /* half3x3 */ {24, 8},
- /* half3x4 */ {24, 8},
- /* half4x2 */ {16, 4},
- /* half4x3 */ {32, 8},
- /* half4x4 */ {32, 8},
- };
- if (cols >= 2 && cols <= 4 && rows >= 2 && rows <= 4) {
- if (el_ty->Is<type::F32>()) {
- return table_f32[(3 * (cols - 2)) + (rows - 2)];
- } else {
- return table_f16[(3 * (cols - 2)) + (rows - 2)];
- }
- }
- }
-
- TINT_UNREACHABLE(Writer, diagnostics_)
- << "Unhandled matrix element type " << el_ty->TypeInfo().name;
- return SizeAndAlign{};
- },
-
- [&](const type::Array* arr) {
- if (TINT_UNLIKELY(!arr->IsStrideImplicit())) {
- TINT_ICE(Writer, diagnostics_)
- << "arrays with explicit strides should not exist past the SPIR-V reader";
- return SizeAndAlign{};
- }
- if (arr->Count()->Is<type::RuntimeArrayCount>()) {
- return SizeAndAlign{arr->Stride(), arr->Align()};
- }
- if (auto count = arr->ConstantCount()) {
- return SizeAndAlign{arr->Stride() * count.value(), arr->Align()};
- }
- diagnostics_.add_error(diag::System::Writer, type::Array::kErrExpectedConstantCount);
- return SizeAndAlign{};
- },
-
- [&](const type::Struct* str) {
- // TODO(crbug.com/tint/650): There's an assumption here that MSL's
- // default structure size and alignment matches WGSL's. We need to
- // confirm this.
- return SizeAndAlign{str->Size(), str->Align()};
- },
-
- [&](const type::Atomic* atomic) { return MslPackedTypeSizeAndAlign(atomic->Type()); },
-
- [&](Default) {
- TINT_UNREACHABLE(Writer, diagnostics_) << "Unhandled type " << ty->TypeInfo().name;
- return SizeAndAlign{};
- });
-}
-
template <typename F>
bool GeneratorImpl::CallBuiltinHelper(utils::StringStream& out,
const ast::CallExpression* call,
diff --git a/src/tint/writer/msl/generator_impl.h b/src/tint/writer/msl/generator_impl.h
index ebc3905..42ff42b 100644
--- a/src/tint/writer/msl/generator_impl.h
+++ b/src/tint/writer/msl/generator_impl.h
@@ -360,25 +360,7 @@
/// @returns the name or "" if not valid
std::string generate_builtin_name(const sem::Builtin* builtin);
- /// Converts a builtin to an attribute name
- /// @param builtin the builtin to convert
- /// @returns the string name of the builtin or blank on error
- std::string builtin_to_attribute(builtin::BuiltinValue builtin) const;
-
- /// Converts interpolation attributes to an MSL attribute
- /// @param type the interpolation type
- /// @param sampling the interpolation sampling
- /// @returns the string name of the attribute or blank on error
- std::string interpolation_to_attribute(builtin::InterpolationType type,
- builtin::InterpolationSampling sampling) const;
-
private:
- // A pair of byte size and alignment `uint32_t`s.
- struct SizeAndAlign {
- uint32_t size;
- uint32_t align;
- };
-
/// CallBuiltinHelper will call the builtin helper function, creating it
/// if it hasn't been built already. If the builtin needs to be built then
/// CallBuiltinHelper will generate the function signature and will call
@@ -404,10 +386,6 @@
TextBuffer helpers_; // Helper functions emitted at the top of the output
- /// @returns the MSL packed type size and alignment in bytes for the given
- /// type.
- SizeAndAlign MslPackedTypeSizeAndAlign(const type::Type* ty);
-
std::function<bool()> emit_continuing_;
/// Name of atomicCompareExchangeWeak() helper for the given pointer storage
diff --git a/src/tint/writer/msl/generator_impl_test.cc b/src/tint/writer/msl/generator_impl_test.cc
index 2a627ec..fb5bd6b 100644
--- a/src/tint/writer/msl/generator_impl_test.cc
+++ b/src/tint/writer/msl/generator_impl_test.cc
@@ -61,42 +61,6 @@
)");
}
-struct MslBuiltinData {
- builtin::BuiltinValue builtin;
- const char* attribute_name;
-};
-inline std::ostream& operator<<(std::ostream& out, MslBuiltinData data) {
- utils::StringStream str;
- str << data.builtin;
- out << str.str();
- return out;
-}
-using MslBuiltinConversionTest = TestParamHelper<MslBuiltinData>;
-TEST_P(MslBuiltinConversionTest, Emit) {
- auto params = GetParam();
-
- GeneratorImpl& gen = Build();
-
- EXPECT_EQ(gen.builtin_to_attribute(params.builtin), std::string(params.attribute_name));
-}
-INSTANTIATE_TEST_SUITE_P(
- MslGeneratorImplTest,
- MslBuiltinConversionTest,
- testing::Values(
- MslBuiltinData{builtin::BuiltinValue::kPosition, "position"},
- MslBuiltinData{builtin::BuiltinValue::kVertexIndex, "vertex_id"},
- MslBuiltinData{builtin::BuiltinValue::kInstanceIndex, "instance_id"},
- MslBuiltinData{builtin::BuiltinValue::kFrontFacing, "front_facing"},
- MslBuiltinData{builtin::BuiltinValue::kFragDepth, "depth(any)"},
- MslBuiltinData{builtin::BuiltinValue::kLocalInvocationId, "thread_position_in_threadgroup"},
- MslBuiltinData{builtin::BuiltinValue::kLocalInvocationIndex, "thread_index_in_threadgroup"},
- MslBuiltinData{builtin::BuiltinValue::kGlobalInvocationId, "thread_position_in_grid"},
- MslBuiltinData{builtin::BuiltinValue::kWorkgroupId, "threadgroup_position_in_grid"},
- MslBuiltinData{builtin::BuiltinValue::kNumWorkgroups, "threadgroups_per_grid"},
- MslBuiltinData{builtin::BuiltinValue::kSampleIndex, "sample_id"},
- MslBuiltinData{builtin::BuiltinValue::kSampleMask, "sample_mask"},
- MslBuiltinData{builtin::BuiltinValue::kPointSize, "point_size"}));
-
TEST_F(MslGeneratorImplTest, HasInvariantAttribute_True) {
auto* out = Structure("Out", utils::Vector{
Member("pos", ty.vec4<f32>(),
diff --git a/src/tint/writer/msl/generator_support.cc b/src/tint/writer/msl/generator_support.cc
new file mode 100644
index 0000000..3739cdd
--- /dev/null
+++ b/src/tint/writer/msl/generator_support.cc
@@ -0,0 +1,214 @@
+// Copyright 2023 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/writer/msl/generator_support.h"
+
+#include "src/tint/debug.h"
+#include "src/tint/switch.h"
+#include "src/tint/type/array.h"
+#include "src/tint/type/atomic.h"
+#include "src/tint/type/f16.h"
+#include "src/tint/type/f32.h"
+#include "src/tint/type/i32.h"
+#include "src/tint/type/matrix.h"
+#include "src/tint/type/struct.h"
+#include "src/tint/type/u32.h"
+#include "src/tint/type/vector.h"
+
+namespace tint::writer::msl {
+
+std::string BuiltinToAttribute(builtin::BuiltinValue builtin) {
+ switch (builtin) {
+ case builtin::BuiltinValue::kPosition:
+ return "position";
+ case builtin::BuiltinValue::kVertexIndex:
+ return "vertex_id";
+ case builtin::BuiltinValue::kInstanceIndex:
+ return "instance_id";
+ case builtin::BuiltinValue::kFrontFacing:
+ return "front_facing";
+ case builtin::BuiltinValue::kFragDepth:
+ return "depth(any)";
+ case builtin::BuiltinValue::kLocalInvocationId:
+ return "thread_position_in_threadgroup";
+ case builtin::BuiltinValue::kLocalInvocationIndex:
+ return "thread_index_in_threadgroup";
+ case builtin::BuiltinValue::kGlobalInvocationId:
+ return "thread_position_in_grid";
+ case builtin::BuiltinValue::kWorkgroupId:
+ return "threadgroup_position_in_grid";
+ case builtin::BuiltinValue::kNumWorkgroups:
+ return "threadgroups_per_grid";
+ case builtin::BuiltinValue::kSampleIndex:
+ return "sample_id";
+ case builtin::BuiltinValue::kSampleMask:
+ return "sample_mask";
+ case builtin::BuiltinValue::kPointSize:
+ return "point_size";
+ default:
+ break;
+ }
+ return "";
+}
+
+std::string InterpolationToAttribute(builtin::InterpolationType type,
+ builtin::InterpolationSampling sampling) {
+ std::string attr;
+ switch (sampling) {
+ case builtin::InterpolationSampling::kCenter:
+ attr = "center_";
+ break;
+ case builtin::InterpolationSampling::kCentroid:
+ attr = "centroid_";
+ break;
+ case builtin::InterpolationSampling::kSample:
+ attr = "sample_";
+ break;
+ case builtin::InterpolationSampling::kUndefined:
+ break;
+ }
+ switch (type) {
+ case builtin::InterpolationType::kPerspective:
+ attr += "perspective";
+ break;
+ case builtin::InterpolationType::kLinear:
+ attr += "no_perspective";
+ break;
+ case builtin::InterpolationType::kFlat:
+ attr += "flat";
+ break;
+ case builtin::InterpolationType::kUndefined:
+ break;
+ }
+ return attr;
+}
+
+SizeAndAlign MslPackedTypeSizeAndAlign(diag::List diagnostics, const type::Type* ty) {
+ return tint::Switch(
+ ty,
+
+ // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+ // 2.1 Scalar Data Types
+ [&](const type::U32*) {
+ return SizeAndAlign{4, 4};
+ },
+ [&](const type::I32*) {
+ return SizeAndAlign{4, 4};
+ },
+ [&](const type::F32*) {
+ return SizeAndAlign{4, 4};
+ },
+ [&](const type::F16*) {
+ return SizeAndAlign{2, 2};
+ },
+
+ [&](const type::Vector* vec) {
+ auto num_els = vec->Width();
+ auto* el_ty = vec->type();
+ SizeAndAlign el_size_align = MslPackedTypeSizeAndAlign(diagnostics, el_ty);
+ if (el_ty->IsAnyOf<type::U32, type::I32, type::F32, type::F16>()) {
+ // Use a packed_vec type for 3-element vectors only.
+ if (num_els == 3) {
+ // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+ // 2.2.3 Packed Vector Types
+ return SizeAndAlign{num_els * el_size_align.size, el_size_align.align};
+ } else {
+ // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+ // 2.2 Vector Data Types
+ // Vector data types are aligned to their size.
+ return SizeAndAlign{num_els * el_size_align.size, num_els * el_size_align.size};
+ }
+ }
+ TINT_UNREACHABLE(Writer, diagnostics)
+ << "Unhandled vector element type " << el_ty->TypeInfo().name;
+ return SizeAndAlign{};
+ },
+
+ [&](const type::Matrix* mat) {
+ // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+ // 2.3 Matrix Data Types
+ auto cols = mat->columns();
+ auto rows = mat->rows();
+ auto* el_ty = mat->type();
+ // Metal only support half and float matrix.
+ if (el_ty->IsAnyOf<type::F32, type::F16>()) {
+ static constexpr SizeAndAlign table_f32[] = {
+ /* float2x2 */ {16, 8},
+ /* float2x3 */ {32, 16},
+ /* float2x4 */ {32, 16},
+ /* float3x2 */ {24, 8},
+ /* float3x3 */ {48, 16},
+ /* float3x4 */ {48, 16},
+ /* float4x2 */ {32, 8},
+ /* float4x3 */ {64, 16},
+ /* float4x4 */ {64, 16},
+ };
+ static constexpr SizeAndAlign table_f16[] = {
+ /* half2x2 */ {8, 4},
+ /* half2x3 */ {16, 8},
+ /* half2x4 */ {16, 8},
+ /* half3x2 */ {12, 4},
+ /* half3x3 */ {24, 8},
+ /* half3x4 */ {24, 8},
+ /* half4x2 */ {16, 4},
+ /* half4x3 */ {32, 8},
+ /* half4x4 */ {32, 8},
+ };
+ if (cols >= 2 && cols <= 4 && rows >= 2 && rows <= 4) {
+ if (el_ty->Is<type::F32>()) {
+ return table_f32[(3 * (cols - 2)) + (rows - 2)];
+ } else {
+ return table_f16[(3 * (cols - 2)) + (rows - 2)];
+ }
+ }
+ }
+
+ TINT_UNREACHABLE(Writer, diagnostics)
+ << "Unhandled matrix element type " << el_ty->TypeInfo().name;
+ return SizeAndAlign{};
+ },
+
+ [&](const type::Array* arr) {
+ if (TINT_UNLIKELY(!arr->IsStrideImplicit())) {
+ TINT_ICE(Writer, diagnostics)
+ << "arrays with explicit strides should not exist past the SPIR-V reader";
+ return SizeAndAlign{};
+ }
+ if (arr->Count()->Is<type::RuntimeArrayCount>()) {
+ return SizeAndAlign{arr->Stride(), arr->Align()};
+ }
+ if (auto count = arr->ConstantCount()) {
+ return SizeAndAlign{arr->Stride() * count.value(), arr->Align()};
+ }
+ diagnostics.add_error(diag::System::Writer, type::Array::kErrExpectedConstantCount);
+ return SizeAndAlign{};
+ },
+
+ [&](const type::Struct* str) {
+ // TODO(crbug.com/tint/650): There's an assumption here that MSL's
+ // default structure size and alignment matches WGSL's. We need to
+ // confirm this.
+ return SizeAndAlign{str->Size(), str->Align()};
+ },
+
+ [&](const type::Atomic* atomic) {
+ return MslPackedTypeSizeAndAlign(diagnostics, atomic->Type());
+ },
+
+ [&](Default) {
+ TINT_UNREACHABLE(Writer, diagnostics) << "Unhandled type " << ty->TypeInfo().name;
+ return SizeAndAlign{};
+ });
+}
+} // namespace tint::writer::msl
diff --git a/src/tint/writer/msl/generator_support.h b/src/tint/writer/msl/generator_support.h
new file mode 100644
index 0000000..4f5877f
--- /dev/null
+++ b/src/tint/writer/msl/generator_support.h
@@ -0,0 +1,55 @@
+// Copyright 2023 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_WRITER_MSL_GENERATOR_SUPPORT_H_
+#define SRC_TINT_WRITER_MSL_GENERATOR_SUPPORT_H_
+
+#include <cstdint>
+#include <string>
+
+#include "src/tint/builtin/builtin_value.h"
+#include "src/tint/builtin/interpolation.h"
+#include "src/tint/diagnostic/diagnostic.h"
+#include "src/tint/type/type.h"
+
+namespace tint::writer::msl {
+
+/// A pair of byte size and alignment `uint32_t`s.
+struct SizeAndAlign {
+ /// The size
+ uint32_t size;
+ /// The alignment
+ uint32_t align;
+};
+
+/// @param diagnostics the diagnostics list
+/// @param ty the type to generate size and align for
+/// @returns the MSL packed type size and alignment in bytes for the given type.
+SizeAndAlign MslPackedTypeSizeAndAlign(diag::List diagnostics, const type::Type* ty);
+
+/// Converts a builtin to an attribute name
+/// @param builtin the builtin to convert
+/// @returns the string name of the builtin or blank on error
+std::string BuiltinToAttribute(builtin::BuiltinValue builtin);
+
+/// Converts interpolation attributes to an MSL attribute
+/// @param type the interpolation type
+/// @param sampling the interpolation sampling
+/// @returns the string name of the attribute or blank on error
+std::string InterpolationToAttribute(builtin::InterpolationType type,
+ builtin::InterpolationSampling sampling);
+
+} // namespace tint::writer::msl
+
+#endif // SRC_TINT_WRITER_MSL_GENERATOR_SUPPORT_H_
diff --git a/src/tint/writer/msl/generator_support_test.cc b/src/tint/writer/msl/generator_support_test.cc
new file mode 100644
index 0000000..24f6f92
--- /dev/null
+++ b/src/tint/writer/msl/generator_support_test.cc
@@ -0,0 +1,55 @@
+// Copyright 2023 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/writer/msl/generator_support.h"
+#include "src/tint/writer/msl/test_helper.h"
+
+namespace tint::writer::msl {
+namespace {
+
+struct MslBuiltinData {
+ builtin::BuiltinValue builtin;
+ const char* attribute_name;
+};
+inline std::ostream& operator<<(std::ostream& out, MslBuiltinData data) {
+ utils::StringStream str;
+ str << data.builtin;
+ out << str.str();
+ return out;
+}
+using MslBuiltinConversionTest = TestParamHelper<MslBuiltinData>;
+TEST_P(MslBuiltinConversionTest, Emit) {
+ auto params = GetParam();
+ EXPECT_EQ(BuiltinToAttribute(params.builtin), std::string(params.attribute_name));
+}
+INSTANTIATE_TEST_SUITE_P(
+ MslGeneratorImplTest,
+ MslBuiltinConversionTest,
+ testing::Values(
+ MslBuiltinData{builtin::BuiltinValue::kPosition, "position"},
+ MslBuiltinData{builtin::BuiltinValue::kVertexIndex, "vertex_id"},
+ MslBuiltinData{builtin::BuiltinValue::kInstanceIndex, "instance_id"},
+ MslBuiltinData{builtin::BuiltinValue::kFrontFacing, "front_facing"},
+ MslBuiltinData{builtin::BuiltinValue::kFragDepth, "depth(any)"},
+ MslBuiltinData{builtin::BuiltinValue::kLocalInvocationId, "thread_position_in_threadgroup"},
+ MslBuiltinData{builtin::BuiltinValue::kLocalInvocationIndex, "thread_index_in_threadgroup"},
+ MslBuiltinData{builtin::BuiltinValue::kGlobalInvocationId, "thread_position_in_grid"},
+ MslBuiltinData{builtin::BuiltinValue::kWorkgroupId, "threadgroup_position_in_grid"},
+ MslBuiltinData{builtin::BuiltinValue::kNumWorkgroups, "threadgroups_per_grid"},
+ MslBuiltinData{builtin::BuiltinValue::kSampleIndex, "sample_id"},
+ MslBuiltinData{builtin::BuiltinValue::kSampleMask, "sample_mask"},
+ MslBuiltinData{builtin::BuiltinValue::kPointSize, "point_size"}));
+
+} // namespace
+} // namespace tint::writer::msl
diff --git a/src/tint/writer/msl/ir/generator_impl_ir.cc b/src/tint/writer/msl/ir/generator_impl_ir.cc
index 9b6f84f..d5e9ac9 100644
--- a/src/tint/writer/msl/ir/generator_impl_ir.cc
+++ b/src/tint/writer/msl/ir/generator_impl_ir.cc
@@ -36,6 +36,7 @@
#include "src/tint/type/vector.h"
#include "src/tint/type/void.h"
#include "src/tint/utils/scoped_assignment.h"
+#include "src/tint/writer/msl/generator_support.h"
namespace tint::writer::msl {
namespace {
@@ -283,7 +284,149 @@
diagnostics_.add_error(diag::System::Writer, "invalid texture type");
});
},
+ [&](const type::Struct* str) {
+ out << StructName(str);
+
+ TINT_SCOPED_ASSIGNMENT(current_buffer_, &preamble_buffer_);
+ EmitStructType(str);
+ },
[&](Default) { UNHANDLED_CASE(ty); });
}
+void GeneratorImplIr::EmitStructType(const type::Struct* str) {
+ auto it = emitted_structs_.emplace(str);
+ if (!it.second) {
+ return;
+ }
+
+ // This does not append directly to the preamble because a struct may require other structs, or
+ // the array template, to get emitted before it. So, the struct emits into a temporary text
+ // buffer, then anything it depends on will emit to the preamble first, and then it copies the
+ // text buffer into the preamble.
+ TextBuffer str_buf;
+ Line(&str_buf) << "struct " << StructName(str) << " {";
+
+ bool is_host_shareable = str->IsHostShareable();
+
+ // Emits a `/* 0xnnnn */` byte offset comment for a struct member.
+ auto add_byte_offset_comment = [&](utils::StringStream& out, uint32_t offset) {
+ std::ios_base::fmtflags saved_flag_state(out.flags());
+ out << "/* 0x" << std::hex << std::setfill('0') << std::setw(4) << offset << " */ ";
+ out.flags(saved_flag_state);
+ };
+
+ auto add_padding = [&](uint32_t size, uint32_t msl_offset) {
+ std::string name;
+ do {
+ name = UniqueIdentifier("tint_pad");
+ } while (str->FindMember(ir_->symbols.Get(name)));
+
+ auto out = Line(&str_buf);
+ add_byte_offset_comment(out, msl_offset);
+ out << ArrayTemplateName() << "<int8_t, " << size << "> " << name << ";";
+ };
+
+ str_buf.IncrementIndent();
+
+ uint32_t msl_offset = 0;
+ for (auto* mem : str->Members()) {
+ auto out = Line(&str_buf);
+ auto mem_name = mem->Name().Name();
+ auto ir_offset = mem->Offset();
+
+ if (is_host_shareable) {
+ if (TINT_UNLIKELY(ir_offset < msl_offset)) {
+ // Unimplementable layout
+ TINT_ICE(Writer, diagnostics_) << "Structure member offset (" << ir_offset
+ << ") is behind MSL offset (" << msl_offset << ")";
+ return;
+ }
+
+ // Generate padding if required
+ if (auto padding = ir_offset - msl_offset) {
+ add_padding(padding, msl_offset);
+ msl_offset += padding;
+ }
+
+ add_byte_offset_comment(out, msl_offset);
+ }
+
+ auto* ty = mem->Type();
+
+ EmitType(out, ty);
+ out << " " << mem_name;
+
+ // Emit attributes
+ auto& attributes = mem->Attributes();
+
+ if (auto builtin = attributes.builtin) {
+ auto name = BuiltinToAttribute(builtin.value());
+ if (name.empty()) {
+ diagnostics_.add_error(diag::System::Writer, "unknown builtin");
+ return;
+ }
+ out << " [[" << name << "]]";
+ }
+
+ if (auto location = attributes.location) {
+ auto& pipeline_stage_uses = str->PipelineStageUses();
+ if (TINT_UNLIKELY(pipeline_stage_uses.size() != 1)) {
+ TINT_ICE(Writer, diagnostics_) << "invalid entry point IO struct uses";
+ return;
+ }
+
+ if (pipeline_stage_uses.count(type::PipelineStageUsage::kVertexInput)) {
+ out << " [[attribute(" + std::to_string(location.value()) + ")]]";
+ } else if (pipeline_stage_uses.count(type::PipelineStageUsage::kVertexOutput)) {
+ out << " [[user(locn" + std::to_string(location.value()) + ")]]";
+ } else if (pipeline_stage_uses.count(type::PipelineStageUsage::kFragmentInput)) {
+ out << " [[user(locn" + std::to_string(location.value()) + ")]]";
+ } else if (TINT_LIKELY(
+ pipeline_stage_uses.count(type::PipelineStageUsage::kFragmentOutput))) {
+ out << " [[color(" + std::to_string(location.value()) + ")]]";
+ } else {
+ TINT_ICE(Writer, diagnostics_) << "invalid use of location decoration";
+ return;
+ }
+ }
+
+ if (auto interpolation = attributes.interpolation) {
+ auto name = InterpolationToAttribute(interpolation->type, interpolation->sampling);
+ if (name.empty()) {
+ diagnostics_.add_error(diag::System::Writer, "unknown interpolation attribute");
+ return;
+ }
+ out << " [[" << name << "]]";
+ }
+
+ if (attributes.invariant) {
+ invariant_define_name_ = UniqueIdentifier("TINT_INVARIANT");
+ out << " " << invariant_define_name_;
+ }
+
+ out << ";";
+
+ if (is_host_shareable) {
+ // Calculate new MSL offset
+ auto size_align = MslPackedTypeSizeAndAlign(diagnostics_, ty);
+ if (TINT_UNLIKELY(msl_offset % size_align.align)) {
+ TINT_ICE(Writer, diagnostics_)
+ << "Misaligned MSL structure member " << mem_name << " : " << ty->FriendlyName()
+ << " offset: " << msl_offset << " align: " << size_align.align;
+ return;
+ }
+ msl_offset += size_align.size;
+ }
+ }
+
+ if (is_host_shareable && str->Size() != msl_offset) {
+ add_padding(str->Size() - msl_offset, msl_offset);
+ }
+
+ str_buf.DecrementIndent();
+ Line(&str_buf) << "};";
+
+ preamble_buffer_.Append(str_buf);
+}
+
} // namespace tint::writer::msl
diff --git a/src/tint/writer/msl/ir/generator_impl_ir.h b/src/tint/writer/msl/ir/generator_impl_ir.h
index 200c24b..70e5d15 100644
--- a/src/tint/writer/msl/ir/generator_impl_ir.h
+++ b/src/tint/writer/msl/ir/generator_impl_ir.h
@@ -16,6 +16,7 @@
#define SRC_TINT_WRITER_MSL_IR_GENERATOR_IMPL_IR_H_
#include <string>
+#include <unordered_set>
#include "src/tint/diagnostic/diagnostic.h"
#include "src/tint/ir/module.h"
@@ -44,6 +45,11 @@
/// @param ty the type to emit
void EmitType(utils::StringStream& out, const type::Type* ty);
+ /// Handles generating a struct declaration. If the structure has already been emitted, then
+ /// this function will simply return without emitting anything.
+ /// @param str the struct to generate
+ void EmitStructType(const type::Struct* str);
+
/// Handles generating a address space
/// @param out the output of the type stream
/// @param sc the address space to generate
@@ -55,6 +61,13 @@
/// Unique name of the tint_array<T, N> template.
/// Non-empty only if the template has been generated.
std::string array_template_name_;
+
+ private:
+ /// Unique name of the 'TINT_INVARIANT' preprocessor define.
+ /// Non-empty only if an invariant attribute has been generated.
+ std::string invariant_define_name_;
+
+ std::unordered_set<const type::Struct*> emitted_structs_;
};
} // namespace tint::writer::msl
diff --git a/src/tint/writer/msl/ir/generator_impl_ir_type_test.cc b/src/tint/writer/msl/ir/generator_impl_ir_type_test.cc
index 237de35..3e82c1b 100644
--- a/src/tint/writer/msl/ir/generator_impl_ir_type_test.cc
+++ b/src/tint/writer/msl/ir/generator_impl_ir_type_test.cc
@@ -21,6 +21,7 @@
#include "src/tint/type/multisampled_texture.h"
#include "src/tint/type/sampled_texture.h"
#include "src/tint/type/storage_texture.h"
+#include "src/tint/type/struct.h"
#include "src/tint/utils/string.h"
#include "src/tint/writer/msl/ir/test_helper_ir.h"
@@ -30,60 +31,6 @@
using namespace tint::builtin::fluent_types; // NOLINT
using namespace tint::number_suffixes; // NOLINT
-// void FormatMSLField(utils::StringStream& out,
-// const char* addr,
-// const char* type,
-// size_t array_count,
-// const char* name) {
-// out << " /* " << std::string(addr) << " */ ";
-// if (array_count == 0) {
-// out << type << " ";
-// } else {
-// out << "tint_array<" << type << ", " << std::to_string(array_count) << "> ";
-// }
-// out << name << ";\n";
-// }
-//
-// #define CHECK_TYPE_SIZE_AND_ALIGN(TYPE, SIZE, ALIGN) \ //
-// static_assert(sizeof(TYPE) == SIZE, "Bad type size"); \ //
-// static_assert(alignof(TYPE) == ALIGN, "Bad type alignment")
-//
-// // Declare C++ types that match the size and alignment of the types of the same
-// // name in MSL.
-// #define DECLARE_TYPE(NAME, SIZE, ALIGN) \ //
-// struct alignas(ALIGN) NAME { \ //
-// uint8_t _[SIZE]; \ //
-// }; \ //
-// CHECK_TYPE_SIZE_AND_ALIGN(NAME, SIZE, ALIGN)
-//
-// // Size and alignments taken from the MSL spec:
-// // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
-// DECLARE_TYPE(float2, 8, 8);
-// DECLARE_TYPE(float3, 12, 4);
-// DECLARE_TYPE(float4, 16, 16);
-// DECLARE_TYPE(float2x2, 16, 8);
-// DECLARE_TYPE(float2x3, 32, 16);
-// DECLARE_TYPE(float2x4, 32, 16);
-// DECLARE_TYPE(float3x2, 24, 8);
-// DECLARE_TYPE(float3x3, 48, 16);
-// DECLARE_TYPE(float3x4, 48, 16);
-// DECLARE_TYPE(float4x2, 32, 8);
-// DECLARE_TYPE(float4x3, 64, 16);
-// DECLARE_TYPE(float4x4, 64, 16);
-// DECLARE_TYPE(half2, 4, 4);
-// DECLARE_TYPE(packed_half3, 6, 2);
-// DECLARE_TYPE(half4, 8, 8);
-// DECLARE_TYPE(half2x2, 8, 4);
-// DECLARE_TYPE(half2x3, 16, 8);
-// DECLARE_TYPE(half2x4, 16, 8);
-// DECLARE_TYPE(half3x2, 12, 4);
-// DECLARE_TYPE(half3x3, 24, 8);
-// DECLARE_TYPE(half3x4, 24, 8);
-// DECLARE_TYPE(half4x2, 16, 4);
-// DECLARE_TYPE(half4x3, 32, 8);
-// DECLARE_TYPE(half4x4, 32, 8);
-// using uint = unsigned int;
-
TEST_F(MslGeneratorImplIrTest, EmitType_Array) {
generator_.EmitType(generator_.Line(), ty.array<bool, 4>());
ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
@@ -266,495 +213,600 @@
MslAddressSpaceData{builtin::AddressSpace::kStorage, "device"},
MslAddressSpaceData{builtin::AddressSpace::kUniform, "constant"}));
-// TEST_F(MslGeneratorImplTest, EmitType_Struct) {
-// auto* s = Structure("S", utils::Vector{
-// Member("a", ty.i32()),
-// Member("b", ty.f32()),
-// });
-//
-// GeneratorImpl& gen = Build();
-//
-// utils::StringStream out;
-// ASSERT_TRUE(gen.EmitType(out, program->TypeOf(s), "")) << gen.Diagnostics();
-// EXPECT_EQ(out.str(), "S");
-// }
-//
-// TEST_F(MslGeneratorImplTest, EmitType_StructDecl) {
-// auto* s = Structure("S", utils::Vector{
-// Member("a", ty.i32()),
-// Member("b", ty.f32()),
-// });
-//
-// GeneratorImpl& gen = Build();
-//
-// TextGenerator::TextBuffer buf;
-// auto* str = program->TypeOf(s)->As<type::Struct>();
-// ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
-// EXPECT_EQ(buf.String(), R"(struct S {
-// int a;
-// float b;
-// };
-// )");
-// }
-//
-// TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_NonComposites) {
-// auto* s = Structure(
-// "S", utils::Vector{
-// Member("a", ty.i32(), utils::Vector{MemberSize(32_a)}),
-// Member("b", ty.f32(), utils::Vector{MemberAlign(128_i), MemberSize(128_a)}),
-// Member("c", ty.vec2<f32>()),
-// Member("d", ty.u32()),
-// Member("e", ty.vec3<f32>()),
-// Member("f", ty.u32()),
-// Member("g", ty.vec4<f32>()),
-// Member("h", ty.u32()),
-// Member("i", ty.mat2x2<f32>()),
-// Member("j", ty.u32()),
-// Member("k", ty.mat2x3<f32>()),
-// Member("l", ty.u32()),
-// Member("m", ty.mat2x4<f32>()),
-// Member("n", ty.u32()),
-// Member("o", ty.mat3x2<f32>()),
-// Member("p", ty.u32()),
-// Member("q", ty.mat3x3<f32>()),
-// Member("r", ty.u32()),
-// Member("s", ty.mat3x4<f32>()),
-// Member("t", ty.u32()),
-// Member("u", ty.mat4x2<f32>()),
-// Member("v", ty.u32()),
-// Member("w", ty.mat4x3<f32>()),
-// Member("x", ty.u32()),
-// Member("y", ty.mat4x4<f32>()),
-// Member("z", ty.f32()),
-// });
-//
-// ast::Type type = GlobalVar("G", ty.Of(s), builtin::AddressSpace::kStorage,
-// builtin::Access::kRead, Binding(0_a), Group(0_a))
-// ->type;
-//
-// GeneratorImpl& gen = Build();
-//
-// TextGenerator::TextBuffer buf;
-// auto* str = program->TypeOf(type)->As<type::Struct>();
-// ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
-//
-// // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
-// // for each field of the structure s.
-// #define ALL_FIELDS() \ //
-// FIELD(0x0000, int, 0, a) \ //
-// FIELD(0x0004, int8_t, 124, tint_pad) \ //
-// FIELD(0x0080, float, 0, b) \ //
-// FIELD(0x0084, int8_t, 124, tint_pad_1) \ //
-// FIELD(0x0100, float2, 0, c) \ //
-// FIELD(0x0108, uint, 0, d) \ //
-// FIELD(0x010c, int8_t, 4, tint_pad_2) \ //
-// FIELD(0x0110, float3, 0, e) \ //
-// FIELD(0x011c, uint, 0, f) \ //
-// FIELD(0x0120, float4, 0, g) \ //
-// FIELD(0x0130, uint, 0, h) \ //
-// FIELD(0x0134, int8_t, 4, tint_pad_3) \ //
-// FIELD(0x0138, float2x2, 0, i) \ //
-// FIELD(0x0148, uint, 0, j) \ //
-// FIELD(0x014c, int8_t, 4, tint_pad_4) \ //
-// FIELD(0x0150, float2x3, 0, k) \ //
-// FIELD(0x0170, uint, 0, l) \ //
-// FIELD(0x0174, int8_t, 12, tint_pad_5) \ //
-// FIELD(0x0180, float2x4, 0, m) \ //
-// FIELD(0x01a0, uint, 0, n) \ //
-// FIELD(0x01a4, int8_t, 4, tint_pad_6) \ //
-// FIELD(0x01a8, float3x2, 0, o) \ //
-// FIELD(0x01c0, uint, 0, p) \ //
-// FIELD(0x01c4, int8_t, 12, tint_pad_7) \ //
-// FIELD(0x01d0, float3x3, 0, q) \ //
-// FIELD(0x0200, uint, 0, r) \ //
-// FIELD(0x0204, int8_t, 12, tint_pad_8) \ //
-// FIELD(0x0210, float3x4, 0, s) \ //
-// FIELD(0x0240, uint, 0, t) \ //
-// FIELD(0x0244, int8_t, 4, tint_pad_9) \ //
-// FIELD(0x0248, float4x2, 0, u) \ //
-// FIELD(0x0268, uint, 0, v) \ //
-// FIELD(0x026c, int8_t, 4, tint_pad_10) \ //
-// FIELD(0x0270, float4x3, 0, w) \ //
-// FIELD(0x02b0, uint, 0, x) \ //
-// FIELD(0x02b4, int8_t, 12, tint_pad_11) \ //
-// FIELD(0x02c0, float4x4, 0, y) \ //
-// FIELD(0x0300, float, 0, z) \ //
-// FIELD(0x0304, int8_t, 124, tint_pad_12)
-//
-// // Check that the generated string is as expected.
-// utils::StringStream expect;
-// expect << "struct S {\n";
-// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \ //
-// FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
-// ALL_FIELDS()
-// #undef FIELD
-// expect << "};\n";
-// EXPECT_EQ(buf.String(), expect.str());
-//
-// // 1.4 Metal and C++14
-// // The Metal programming language is a C++14-based Specification with
-// // extensions and restrictions. Refer to the C++14 Specification (also known
-// // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
-// // description of the language grammar.
-// //
-// // Tint is written in C++14, so use the compiler to verify the generated
-// // layout is as expected for C++14 / MSL.
-// {
-// struct S {
-// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) std::array<TYPE, ARRAY_COUNT ? ARRAY_COUNT : 1>
-// NAME;
-// ALL_FIELDS()
-// #undef FIELD
-// };
-//
-// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \ //
-// EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
-// ALL_FIELDS()
-// #undef FIELD
-// }
-// #undef ALL_FIELDS
-// }
-//
-// TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_Structures) {
-// // inner_x: size(1024), align(512)
-// auto* inner_x =
-// Structure("inner_x", utils::Vector{
-// Member("a", ty.i32()),
-// Member("b", ty.f32(), utils::Vector{MemberAlign(512_i)}),
-// });
-//
-// // inner_y: size(516), align(4)
-// auto* inner_y =
-// Structure("inner_y", utils::Vector{
-// Member("a", ty.i32(), utils::Vector{MemberSize(512_a)}),
-// Member("b", ty.f32()),
-// });
-//
-// auto* s = Structure("S", utils::Vector{
-// Member("a", ty.i32()),
-// Member("b", ty.Of(inner_x)),
-// Member("c", ty.f32()),
-// Member("d", ty.Of(inner_y)),
-// Member("e", ty.f32()),
-// });
-//
-// ast::Type type = GlobalVar("G", ty.Of(s), builtin::AddressSpace::kStorage,
-// builtin::Access::kRead, Binding(0_a), Group(0_a))
-// ->type;
-//
-// GeneratorImpl& gen = Build();
-//
-// TextGenerator::TextBuffer buf;
-// auto* str = program->TypeOf(type)->As<type::Struct>();
-// ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
-//
-// // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
-// // for each field of the structure s.
-// #define ALL_FIELDS() \ //
-// FIELD(0x0000, int, 0, a) \ //
-// FIELD(0x0004, int8_t, 508, tint_pad) \ //
-// FIELD(0x0200, inner_x, 0, b) \ //
-// FIELD(0x0600, float, 0, c) \ //
-// FIELD(0x0604, inner_y, 0, d) \ //
-// FIELD(0x0808, float, 0, e) \ //
-// FIELD(0x080c, int8_t, 500, tint_pad_1)
-//
-// // Check that the generated string is as expected.
-// utils::StringStream expect;
-// expect << "struct S {\n";
-// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \ //
-// FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
-// ALL_FIELDS()
-// #undef FIELD
-// expect << "};\n";
-// EXPECT_EQ(buf.String(), expect.str());
-//
-// // 1.4 Metal and C++14
-// // The Metal programming language is a C++14-based Specification with
-// // extensions and restrictions. Refer to the C++14 Specification (also known
-// // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
-// // description of the language grammar.
-// //
-// // Tint is written in C++14, so use the compiler to verify the generated
-// // layout is as expected for C++14 / MSL.
-// {
-// struct inner_x {
-// uint32_t a;
-// alignas(512) float b;
-// };
-// CHECK_TYPE_SIZE_AND_ALIGN(inner_x, 1024, 512);
-//
-// struct inner_y {
-// uint32_t a[128];
-// float b;
-// };
-// CHECK_TYPE_SIZE_AND_ALIGN(inner_y, 516, 4);
-//
-// struct S {
-// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) std::array<TYPE, ARRAY_COUNT ? ARRAY_COUNT : 1>
-// NAME;
-// ALL_FIELDS()
-// #undef FIELD
-// };
-//
-// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \ //
-// EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
-// ALL_FIELDS()
-// #undef FIELD
-// }
-//
-// #undef ALL_FIELDS
-// }
-//
-// TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayDefaultStride) {
-// // inner: size(1024), align(512)
-// auto* inner = Structure("inner", utils::Vector{
-// Member("a", ty.i32()),
-// Member("b", ty.f32(),
-// utils::Vector{MemberAlign(512_i)}),
-// });
-//
-// // array_x: size(28), align(4)
-// auto array_x = ty.array<f32, 7>();
-//
-// // array_y: size(4096), align(512)
-// auto array_y = ty.array(ty.Of(inner), 4_u);
-//
-// // array_z: size(4), align(4)
-// auto array_z = ty.array<f32>();
-//
-// auto* s = Structure("S", utils::Vector{
-// Member("a", ty.i32()),
-// Member("b", array_x),
-// Member("c", ty.f32()),
-// Member("d", array_y),
-// Member("e", ty.f32()),
-// Member("f", array_z),
-// });
-//
-// ast::Type type = GlobalVar("G", ty.Of(s), builtin::AddressSpace::kStorage,
-// builtin::Access::kRead, Binding(0_a), Group(0_a))
-// ->type;
-//
-// GeneratorImpl& gen = Build();
-//
-// TextGenerator::TextBuffer buf;
-// auto* str = program->TypeOf(type)->As<type::Struct>();
-// ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
-//
-// // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
-// // for each field of the structure s.
-// #define ALL_FIELDS() \ //
-// FIELD(0x0000, int, 0, a) \ //
-// FIELD(0x0004, float, 7, b) \ //
-// FIELD(0x0020, float, 0, c) \ //
-// FIELD(0x0024, int8_t, 476, tint_pad) \ //
-// FIELD(0x0200, inner, 4, d) \ //
-// FIELD(0x1200, float, 0, e) \ //
-// FIELD(0x1204, float, 1, f) \ //
-// FIELD(0x1208, int8_t, 504, tint_pad_1)
-//
-// // Check that the generated string is as expected.
-// utils::StringStream expect;
-// expect << "struct S {\n";
-// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \ //
-// FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
-// ALL_FIELDS()
-// #undef FIELD
-// expect << "};\n";
-// EXPECT_EQ(buf.String(), expect.str());
-//
-// // 1.4 Metal and C++14
-// // The Metal programming language is a C++14-based Specification with
-// // extensions and restrictions. Refer to the C++14 Specification (also known
-// // as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a detailed
-// // description of the language grammar.
-// //
-// // Tint is written in C++14, so use the compiler to verify the generated
-// // layout is as expected for C++14 / MSL.
-// {
-// struct inner {
-// uint32_t a;
-// alignas(512) float b;
-// };
-// CHECK_TYPE_SIZE_AND_ALIGN(inner, 1024, 512);
-//
-// // array_x: size(28), align(4)
-// using array_x = std::array<float, 7>;
-// CHECK_TYPE_SIZE_AND_ALIGN(array_x, 28, 4);
-//
-// // array_y: size(4096), align(512)
-// using array_y = std::array<inner, 4>;
-// CHECK_TYPE_SIZE_AND_ALIGN(array_y, 4096, 512);
-//
-// // array_z: size(4), align(4)
-// using array_z = std::array<float, 1>;
-// CHECK_TYPE_SIZE_AND_ALIGN(array_z, 4, 4);
-//
-// struct S {
-// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) std::array<TYPE, ARRAY_COUNT ? ARRAY_COUNT : 1>
-// NAME;
-// ALL_FIELDS()
-// #undef FIELD
-// };
-//
-// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \ //
-// EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
-// ALL_FIELDS()
-// #undef FIELD
-// }
-//
-// #undef ALL_FIELDS
-// }
-//
-// TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayVec3DefaultStride) {
-// // array: size(64), align(16)
-// auto array = ty.array<vec3<f32>, 4>();
-//
-// auto* s = Structure("S", utils::Vector{
-// Member("a", ty.i32()),
-// Member("b", array),
-// Member("c", ty.i32()),
-// });
-//
-// ast::Type type = GlobalVar("G", ty.Of(s), builtin::AddressSpace::kStorage,
-// builtin::Access::kRead, Binding(0_a), Group(0_a))
-// ->type;
-//
-// GeneratorImpl& gen = Build();
-//
-// TextGenerator::TextBuffer buf;
-// auto* str = program->TypeOf(type)->As<type::Struct>();
-// ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
-//
-// // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
-// // for each field of the structure s.
-// #define ALL_FIELDS() \ //
-// FIELD(0x0000, int, 0, a) \ //
-// FIELD(0x0004, int8_t, 12, tint_pad) \ //
-// FIELD(0x0010, float3, 4, b) \ //
-// FIELD(0x0050, int, 0, c) \ //
-// FIELD(0x0054, int8_t, 12, tint_pad_1)
-//
-// // Check that the generated string is as expected.
-// utils::StringStream expect;
-// expect << "struct S {\n";
-// #define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \ //
-// FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
-// ALL_FIELDS()
-// #undef FIELD
-// expect << "};\n";
-// EXPECT_EQ(buf.String(), expect.str());
-// }
-//
-// TEST_F(MslGeneratorImplTest, AttemptTintPadSymbolCollision) {
-// auto* s = Structure("S", utils::Vector{
-// // uses symbols tint_pad_[0..9] and tint_pad_[20..35]
-// Member("tint_pad_2", ty.i32(),
-// utils::Vector{MemberSize(32_a)}), Member("tint_pad_20",
-// ty.f32(),
-// utils::Vector{MemberAlign(128_i),
-// MemberSize(128_u)}),
-// Member("tint_pad_33", ty.vec2<f32>()),
-// Member("tint_pad_1", ty.u32()),
-// Member("tint_pad_3", ty.vec3<f32>()),
-// Member("tint_pad_7", ty.u32()),
-// Member("tint_pad_25", ty.vec4<f32>()),
-// Member("tint_pad_5", ty.u32()),
-// Member("tint_pad_27", ty.mat2x2<f32>()),
-// Member("tint_pad_24", ty.u32()),
-// Member("tint_pad_23", ty.mat2x3<f32>()),
-// Member("tint_pad", ty.u32()),
-// Member("tint_pad_8", ty.mat2x4<f32>()),
-// Member("tint_pad_26", ty.u32()),
-// Member("tint_pad_29", ty.mat3x2<f32>()),
-// Member("tint_pad_6", ty.u32()),
-// Member("tint_pad_22", ty.mat3x3<f32>()),
-// Member("tint_pad_32", ty.u32()),
-// Member("tint_pad_34", ty.mat3x4<f32>()),
-// Member("tint_pad_35", ty.u32()),
-// Member("tint_pad_30", ty.mat4x2<f32>()),
-// Member("tint_pad_9", ty.u32()),
-// Member("tint_pad_31", ty.mat4x3<f32>()),
-// Member("tint_pad_28", ty.u32()),
-// Member("tint_pad_4", ty.mat4x4<f32>()),
-// Member("tint_pad_21", ty.f32()),
-// });
-//
-// ast::Type type = GlobalVar("G", ty.Of(s), builtin::AddressSpace::kStorage,
-// builtin::Access::kRead, Binding(0_a), Group(0_a))
-// ->type;
-//
-// GeneratorImpl& gen = Build();
-//
-// TextGenerator::TextBuffer buf;
-// auto* str = program->TypeOf(type)->As<type::Struct>();
-// ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
-// EXPECT_EQ(buf.String(), R"(struct S {
-// /* 0x0000 */ int tint_pad_2;
-// /* 0x0004 */ tint_array<int8_t, 124> tint_pad_10;
-// /* 0x0080 */ float tint_pad_20;
-// /* 0x0084 */ tint_array<int8_t, 124> tint_pad_11;
-// /* 0x0100 */ float2 tint_pad_33;
-// /* 0x0108 */ uint tint_pad_1;
-// /* 0x010c */ tint_array<int8_t, 4> tint_pad_12;
-// /* 0x0110 */ float3 tint_pad_3;
-// /* 0x011c */ uint tint_pad_7;
-// /* 0x0120 */ float4 tint_pad_25;
-// /* 0x0130 */ uint tint_pad_5;
-// /* 0x0134 */ tint_array<int8_t, 4> tint_pad_13;
-// /* 0x0138 */ float2x2 tint_pad_27;
-// /* 0x0148 */ uint tint_pad_24;
-// /* 0x014c */ tint_array<int8_t, 4> tint_pad_14;
-// /* 0x0150 */ float2x3 tint_pad_23;
-// /* 0x0170 */ uint tint_pad;
-// /* 0x0174 */ tint_array<int8_t, 12> tint_pad_15;
-// /* 0x0180 */ float2x4 tint_pad_8;
-// /* 0x01a0 */ uint tint_pad_26;
-// /* 0x01a4 */ tint_array<int8_t, 4> tint_pad_16;
-// /* 0x01a8 */ float3x2 tint_pad_29;
-// /* 0x01c0 */ uint tint_pad_6;
-// /* 0x01c4 */ tint_array<int8_t, 12> tint_pad_17;
-// /* 0x01d0 */ float3x3 tint_pad_22;
-// /* 0x0200 */ uint tint_pad_32;
-// /* 0x0204 */ tint_array<int8_t, 12> tint_pad_18;
-// /* 0x0210 */ float3x4 tint_pad_34;
-// /* 0x0240 */ uint tint_pad_35;
-// /* 0x0244 */ tint_array<int8_t, 4> tint_pad_19;
-// /* 0x0248 */ float4x2 tint_pad_30;
-// /* 0x0268 */ uint tint_pad_9;
-// /* 0x026c */ tint_array<int8_t, 4> tint_pad_36;
-// /* 0x0270 */ float4x3 tint_pad_31;
-// /* 0x02b0 */ uint tint_pad_28;
-// /* 0x02b4 */ tint_array<int8_t, 12> tint_pad_37;
-// /* 0x02c0 */ float4x4 tint_pad_4;
-// /* 0x0300 */ float tint_pad_21;
-// /* 0x0304 */ tint_array<int8_t, 124> tint_pad_38;
-// };
-// )");
-// }
-//
-// TEST_F(MslGeneratorImplTest, EmitType_Struct_WithAttribute) {
-// auto* s = Structure("S", utils::Vector{
-// Member("a", ty.i32()),
-// Member("b", ty.f32()),
-// });
-//
-// ast::Type type = GlobalVar("G", ty.Of(s), builtin::AddressSpace::kStorage,
-// builtin::Access::kRead, Binding(0_a), Group(0_a))
-// ->type;
-//
-// GeneratorImpl& gen = Build();
-//
-// TextGenerator::TextBuffer buf;
-// auto* str = program->TypeOf(type)->As<type::Struct>();
-// ASSERT_TRUE(gen.EmitStructType(&buf, str)) << gen.Diagnostics();
-// EXPECT_EQ(buf.String(), R"(struct S {
-// /* 0x0000 */ int a;
-// /* 0x0004 */ float b;
-// };
-// )");
-// }
+TEST_F(MslGeneratorImplIrTest, EmitType_Struct) {
+ auto* s = ty.Struct(mod.symbols.New("S"), {
+ {mod.symbols.Register("a"), ty.i32()},
+ {mod.symbols.Register("b"), ty.f32()},
+ });
+ generator_.EmitType(generator_.Line(), s);
+ ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+ EXPECT_STREQ(std::string(utils::TrimSpace(generator_.Result())).c_str(), R"(struct S {
+ int a;
+ float b;
+};
+
+S)");
+}
+
+TEST_F(MslGeneratorImplIrTest, EmitType_Struct_Dedup) {
+ auto* s = ty.Struct(mod.symbols.New("S"), {
+ {mod.symbols.Register("a"), ty.i32()},
+ {mod.symbols.Register("b"), ty.f32()},
+ });
+ generator_.EmitType(generator_.Line(), s);
+ generator_.EmitType(generator_.Line(), s);
+ ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+ EXPECT_STREQ(std::string(utils::TrimSpace(generator_.Result())).c_str(), R"(struct S {
+ int a;
+ float b;
+};
+
+S
+S)");
+}
+
+void FormatMSLField(utils::StringStream& out,
+ const char* addr,
+ const char* type,
+ size_t array_count,
+ const char* name) {
+ out << " /* " << std::string(addr) << " */ ";
+ if (array_count == 0) {
+ out << type << " ";
+ } else {
+ out << "tint_array<" << type << ", " << std::to_string(array_count) << "> ";
+ }
+ out << name << ";\n";
+}
+
+#define CHECK_TYPE_SIZE_AND_ALIGN(TYPE, SIZE, ALIGN) \
+ static_assert(sizeof(TYPE) == SIZE, "Bad type size"); \
+ static_assert(alignof(TYPE) == ALIGN, "Bad type alignment")
+
+// Declare C++ types that match the size and alignment of the types of the same
+// name in MSL.
+#define DECLARE_TYPE(NAME, SIZE, ALIGN) \
+ struct alignas(ALIGN) NAME { \
+ uint8_t _[SIZE]; \
+ }; \
+ CHECK_TYPE_SIZE_AND_ALIGN(NAME, SIZE, ALIGN)
+
+// Size and alignments taken from the MSL spec:
+// https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+DECLARE_TYPE(float2, 8, 8);
+DECLARE_TYPE(float3, 12, 4);
+DECLARE_TYPE(float4, 16, 16);
+DECLARE_TYPE(float2x2, 16, 8);
+DECLARE_TYPE(float2x3, 32, 16);
+DECLARE_TYPE(float2x4, 32, 16);
+DECLARE_TYPE(float3x2, 24, 8);
+DECLARE_TYPE(float3x3, 48, 16);
+DECLARE_TYPE(float3x4, 48, 16);
+DECLARE_TYPE(float4x2, 32, 8);
+DECLARE_TYPE(float4x3, 64, 16);
+DECLARE_TYPE(float4x4, 64, 16);
+DECLARE_TYPE(half2, 4, 4);
+DECLARE_TYPE(packed_half3, 6, 2);
+DECLARE_TYPE(half4, 8, 8);
+DECLARE_TYPE(half2x2, 8, 4);
+DECLARE_TYPE(half2x3, 16, 8);
+DECLARE_TYPE(half2x4, 16, 8);
+DECLARE_TYPE(half3x2, 12, 4);
+DECLARE_TYPE(half3x3, 24, 8);
+DECLARE_TYPE(half3x4, 24, 8);
+DECLARE_TYPE(half4x2, 16, 4);
+DECLARE_TYPE(half4x3, 32, 8);
+DECLARE_TYPE(half4x4, 32, 8);
+using uint = unsigned int;
+
+struct MemberData {
+ Symbol name;
+ const type::Type* type;
+ uint32_t size = 0;
+ uint32_t align = 0;
+};
+type::Struct* MkStruct(ir::Module& mod,
+ type::Manager& ty,
+ std::string_view name,
+ utils::VectorRef<MemberData> data) {
+ utils::Vector<const type::StructMember*, 26> members;
+ uint32_t align = 0;
+ uint32_t size = 0;
+ for (uint32_t i = 0; i < data.Length(); ++i) {
+ auto& d = data[i];
+
+ uint32_t mem_align = d.align == 0 ? d.type->Align() : d.align;
+ uint32_t mem_size = d.size == 0 ? d.type->Size() : d.size;
+
+ uint32_t offset = utils::RoundUp(mem_align, size);
+ members.Push(ty.Get<type::StructMember>(d.name, d.type, i, offset, mem_align, mem_size,
+ type::StructMemberAttributes{}));
+
+ align = std::max(align, mem_align);
+ size = offset + mem_size;
+ }
+
+ return ty.Get<type::Struct>(mod.symbols.New(name), std::move(members), align,
+ utils::RoundUp(align, size), size);
+}
+
+TEST_F(MslGeneratorImplIrTest, EmitType_Struct_Layout_NonComposites) {
+ utils::Vector<MemberData, 26> data = {{mod.symbols.Register("a"), ty.i32(), 32}, //
+ {mod.symbols.Register("b"), ty.f32(), 128, 128}, //
+ {mod.symbols.Register("c"), ty.vec2<f32>()}, //
+ {mod.symbols.Register("d"), ty.u32()}, //
+ {mod.symbols.Register("e"), ty.vec3<f32>()}, //
+ {mod.symbols.Register("f"), ty.u32()}, //
+ {mod.symbols.Register("g"), ty.vec4<f32>()}, //
+ {mod.symbols.Register("h"), ty.u32()}, //
+ {mod.symbols.Register("i"), ty.mat2x2<f32>()}, //
+ {mod.symbols.Register("j"), ty.u32()}, //
+ {mod.symbols.Register("k"), ty.mat2x3<f32>()}, //
+ {mod.symbols.Register("l"), ty.u32()}, //
+ {mod.symbols.Register("m"), ty.mat2x4<f32>()}, //
+ {mod.symbols.Register("n"), ty.u32()}, //
+ {mod.symbols.Register("o"), ty.mat3x2<f32>()}, //
+ {mod.symbols.Register("p"), ty.u32()}, //
+ {mod.symbols.Register("q"), ty.mat3x3<f32>()}, //
+ {mod.symbols.Register("r"), ty.u32()}, //
+ {mod.symbols.Register("s"), ty.mat3x4<f32>()}, //
+ {mod.symbols.Register("t"), ty.u32()}, //
+ {mod.symbols.Register("u"), ty.mat4x2<f32>()}, //
+ {mod.symbols.Register("v"), ty.u32()}, //
+ {mod.symbols.Register("w"), ty.mat4x3<f32>()}, //
+ {mod.symbols.Register("x"), ty.u32()}, //
+ {mod.symbols.Register("y"), ty.mat4x4<f32>()}, //
+ {mod.symbols.Register("z"), ty.f32()}};
+
+ auto* s = MkStruct(mod, ty, "S", data);
+ s->AddUsage(builtin::AddressSpace::kStorage);
+
+ // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
+ // for each field of the structure s.
+#define ALL_FIELDS() \
+ FIELD(0x0000, int, 0, a) \
+ FIELD(0x0004, int8_t, 124, tint_pad) \
+ FIELD(0x0080, float, 0, b) \
+ FIELD(0x0084, int8_t, 124, tint_pad_1) \
+ FIELD(0x0100, float2, 0, c) \
+ FIELD(0x0108, uint, 0, d) \
+ FIELD(0x010c, int8_t, 4, tint_pad_2) \
+ FIELD(0x0110, float3, 0, e) \
+ FIELD(0x011c, uint, 0, f) \
+ FIELD(0x0120, float4, 0, g) \
+ FIELD(0x0130, uint, 0, h) \
+ FIELD(0x0134, int8_t, 4, tint_pad_3) \
+ FIELD(0x0138, float2x2, 0, i) \
+ FIELD(0x0148, uint, 0, j) \
+ FIELD(0x014c, int8_t, 4, tint_pad_4) \
+ FIELD(0x0150, float2x3, 0, k) \
+ FIELD(0x0170, uint, 0, l) \
+ FIELD(0x0174, int8_t, 12, tint_pad_5) \
+ FIELD(0x0180, float2x4, 0, m) \
+ FIELD(0x01a0, uint, 0, n) \
+ FIELD(0x01a4, int8_t, 4, tint_pad_6) \
+ FIELD(0x01a8, float3x2, 0, o) \
+ FIELD(0x01c0, uint, 0, p) \
+ FIELD(0x01c4, int8_t, 12, tint_pad_7) \
+ FIELD(0x01d0, float3x3, 0, q) \
+ FIELD(0x0200, uint, 0, r) \
+ FIELD(0x0204, int8_t, 12, tint_pad_8) \
+ FIELD(0x0210, float3x4, 0, s) \
+ FIELD(0x0240, uint, 0, t) \
+ FIELD(0x0244, int8_t, 4, tint_pad_9) \
+ FIELD(0x0248, float4x2, 0, u) \
+ FIELD(0x0268, uint, 0, v) \
+ FIELD(0x026c, int8_t, 4, tint_pad_10) \
+ FIELD(0x0270, float4x3, 0, w) \
+ FIELD(0x02b0, uint, 0, x) \
+ FIELD(0x02b4, int8_t, 12, tint_pad_11) \
+ FIELD(0x02c0, float4x4, 0, y) \
+ FIELD(0x0300, float, 0, z) \
+ FIELD(0x0304, int8_t, 124, tint_pad_12)
+
+ // Check that the generated string is as expected.
+ utils::StringStream expect;
+ expect << R"(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];
+};
+
+)";
+
+ expect << "struct S {\n";
+#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
+ FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
+ ALL_FIELDS()
+#undef FIELD
+ expect << "};\n\nS";
+
+ generator_.EmitType(generator_.Line(), s);
+ ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+ EXPECT_EQ(utils::TrimSpace(generator_.Result()), expect.str());
+
+ // 1.4 Metal and C++14
+ // The Metal programming language is a C++14-based Specification with
+ // extensions and restrictions. Refer to the C++14 Specification (also
+ // known as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a
+ // detailed description of the language grammar.
+ //
+ // Tint is written in C++14, so use the compiler to verify the generated
+ // layout is as expected for C++14 / MSL.
+ {
+ struct S {
+#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) std::array<TYPE, ARRAY_COUNT ? ARRAY_COUNT : 1> NAME;
+ ALL_FIELDS()
+#undef FIELD
+ };
+
+#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
+ EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
+ ALL_FIELDS()
+#undef FIELD
+ }
+#undef ALL_FIELDS
+}
+
+TEST_F(MslGeneratorImplIrTest, EmitType_Struct_Layout_Structures) {
+ // inner_x: size(1024), align(512)
+ utils::Vector<MemberData, 2> inner_x_data = {{{mod.symbols.Register("a"), ty.i32()}, //
+ {mod.symbols.Register("b"), ty.f32(), 0, 512}}};
+ auto* inner_x = MkStruct(mod, ty, "inner_x", inner_x_data);
+
+ // inner_y: size(516), align(4)
+ utils::Vector<MemberData, 2> inner_y_data = {{mod.symbols.Register("a"), ty.i32(), 512},
+ {mod.symbols.Register("b"), ty.f32()}};
+
+ auto* inner_y = MkStruct(mod, ty, "inner_y", inner_y_data);
+
+ auto* s = ty.Struct(mod.symbols.New("S"), {{mod.symbols.Register("a"), ty.i32()},
+ {mod.symbols.Register("b"), inner_x},
+ {mod.symbols.Register("c"), ty.f32()},
+ {mod.symbols.Register("d"), inner_y},
+ {mod.symbols.Register("e"), ty.f32()}});
+ const_cast<type::Struct*>(s)->AddUsage(builtin::AddressSpace::kStorage);
+
+// ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
+// for each field of the structure s.
+#define ALL_FIELDS() \
+ FIELD(0x0000, int, 0, a) \
+ FIELD(0x0004, int8_t, 508, tint_pad) \
+ FIELD(0x0200, inner_x, 0, b) \
+ FIELD(0x0600, float, 0, c) \
+ FIELD(0x0604, inner_y, 0, d) \
+ FIELD(0x0808, float, 0, e) \
+ FIELD(0x080c, int8_t, 500, tint_pad_1)
+
+ // Check that the generated string is as expected.
+ utils::StringStream expect;
+ expect << R"(template<typename T, size_t N>
+struct tint_array {
+ const constant T& operator[](size_t i) const constant { return elements[i]; }
+ device T& operator[](size_t i) device { return elements[i]; }
+ const device T& operator[](size_t i) const device { return elements[i]; }
+ thread T& operator[](size_t i) thread { return elements[i]; }
+ const thread T& operator[](size_t i) const thread { return elements[i]; }
+ threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+ const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+ T elements[N];
+};
+
+struct inner_x {
+ int a;
+ float b;
+};
+struct inner_y {
+ int a;
+ float b;
+};
+)";
+
+ expect << "struct S {\n";
+#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
+ FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
+ ALL_FIELDS()
+#undef FIELD
+ expect << "};\n\nS";
+
+ generator_.EmitType(generator_.Line(), s);
+ ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+ EXPECT_EQ(utils::TrimSpace(generator_.Result()), expect.str());
+
+ // 1.4 Metal and C++14
+ // The Metal programming language is a C++14-based Specification with
+ // extensions and restrictions. Refer to the C++14 Specification (also
+ // known as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a
+ // detailed description of the language grammar.
+ //
+ // Tint is written in C++14, so use the compiler to verify the generated
+ // layout is as expected for C++14 / MSL.
+ {
+ struct inner_x {
+ uint32_t a;
+ alignas(512) float b;
+ };
+ CHECK_TYPE_SIZE_AND_ALIGN(inner_x, 1024, 512);
+
+ struct inner_y {
+ uint32_t a[128];
+ float b;
+ };
+ CHECK_TYPE_SIZE_AND_ALIGN(inner_y, 516, 4);
+
+ struct S {
+#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) std::array<TYPE, ARRAY_COUNT ? ARRAY_COUNT : 1> NAME;
+ ALL_FIELDS()
+#undef FIELD
+ };
+
+#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
+ EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
+ ALL_FIELDS()
+#undef FIELD
+ }
+
+#undef ALL_FIELDS
+}
+
+TEST_F(MslGeneratorImplIrTest, EmitType_Struct_Layout_ArrayDefaultStride) {
+ // inner: size(1024), align(512)
+ utils::Vector<MemberData, 2> inner_data = {{mod.symbols.Register("a"), ty.i32()},
+ {mod.symbols.Register("b"), ty.f32(), 0, 512}};
+
+ auto* inner = MkStruct(mod, ty, "inner", inner_data);
+
+ // array_x: size(28), align(4)
+ auto array_x = ty.array<f32, 7>();
+
+ // array_y: size(4096), align(512)
+ auto array_y = ty.array(inner, 4_u);
+
+ // array_z: size(4), align(4)
+ auto array_z = ty.array<f32>();
+
+ auto* s = ty.Struct(mod.symbols.New("S"), {{mod.symbols.Register("a"), ty.i32()},
+ {mod.symbols.Register("b"), array_x},
+ {mod.symbols.Register("c"), ty.f32()},
+ {mod.symbols.Register("d"), array_y},
+ {mod.symbols.Register("e"), ty.f32()},
+ {mod.symbols.Register("f"), array_z}});
+ const_cast<type::Struct*>(s)->AddUsage(builtin::AddressSpace::kStorage);
+
+ // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
+ // for each field of the structure s.
+#define ALL_FIELDS() \
+ FIELD(0x0000, int, 0, a) \
+ FIELD(0x0004, float, 7, b) \
+ FIELD(0x0020, float, 0, c) \
+ FIELD(0x0024, int8_t, 476, tint_pad) \
+ FIELD(0x0200, inner, 4, d) \
+ FIELD(0x1200, float, 0, e) \
+ FIELD(0x1204, float, 1, f) \
+ FIELD(0x1208, int8_t, 504, tint_pad_1)
+
+ // Check that the generated string is as expected.
+ utils::StringStream expect;
+
+ expect << R"(template<typename T, size_t N>
+struct tint_array {
+ const constant T& operator[](size_t i) const constant { return elements[i]; }
+ device T& operator[](size_t i) device { return elements[i]; }
+ const device T& operator[](size_t i) const device { return elements[i]; }
+ thread T& operator[](size_t i) thread { return elements[i]; }
+ const thread T& operator[](size_t i) const thread { return elements[i]; }
+ threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+ const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+ T elements[N];
+};
+
+struct inner {
+ int a;
+ float b;
+};
+)";
+
+ expect << "struct S {\n";
+#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
+ FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
+ ALL_FIELDS()
+#undef FIELD
+ expect << "};\n\nS";
+
+ generator_.EmitType(generator_.Line(), s);
+ ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+ EXPECT_EQ(utils::TrimSpace(generator_.Result()), expect.str());
+
+ // 1.4 Metal and C++14
+ // The Metal programming language is a C++14-based Specification with
+ // extensions and restrictions. Refer to the C++14 Specification (also
+ // known as the ISO/IEC JTC1/SC22/WG21 N4431 Language Specification) for a
+ // detailed description of the language grammar.
+ //
+ // Tint is written in C++14, so use the compiler to verify the generated
+ // layout is as expected for C++14 / MSL.
+ {
+ struct inner {
+ uint32_t a;
+ alignas(512) float b;
+ };
+ CHECK_TYPE_SIZE_AND_ALIGN(inner, 1024, 512);
+
+ // array_x: size(28), align(4)
+ using array_x = std::array<float, 7>;
+ CHECK_TYPE_SIZE_AND_ALIGN(array_x, 28, 4);
+
+ // array_y: size(4096), align(512)
+ using array_y = std::array<inner, 4>;
+ CHECK_TYPE_SIZE_AND_ALIGN(array_y, 4096, 512);
+
+ // array_z: size(4), align(4)
+ using array_z = std::array<float, 1>;
+ CHECK_TYPE_SIZE_AND_ALIGN(array_z, 4, 4);
+
+ struct S {
+#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) std::array<TYPE, ARRAY_COUNT ? ARRAY_COUNT : 1> NAME;
+ ALL_FIELDS()
+#undef FIELD
+ };
+
+#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
+ EXPECT_EQ(ADDR, static_cast<int>(offsetof(S, NAME))) << "Field " << #NAME;
+ ALL_FIELDS()
+#undef FIELD
+ }
+
+#undef ALL_FIELDS
+}
+
+TEST_F(MslGeneratorImplIrTest, EmitType_Struct_Layout_ArrayVec3DefaultStride) {
+ // array: size(64), align(16)
+ auto array = ty.array<vec3<f32>, 4>();
+
+ auto* s = ty.Struct(mod.symbols.New("S"), {
+ {mod.symbols.Register("a"), ty.i32()},
+ {mod.symbols.Register("b"), array},
+ {mod.symbols.Register("c"), ty.i32()},
+ });
+ const_cast<type::Struct*>(s)->AddUsage(builtin::AddressSpace::kStorage);
+
+ // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, ARRAY_COUNT, NAME)
+ // for each field of the structure s.
+#define ALL_FIELDS() \
+ FIELD(0x0000, int, 0, a) \
+ FIELD(0x0004, int8_t, 12, tint_pad) \
+ FIELD(0x0010, float3, 4, b) \
+ FIELD(0x0050, int, 0, c) \
+ FIELD(0x0054, int8_t, 12, tint_pad_1)
+
+ // Check that the generated string is as expected.
+ utils::StringStream expect;
+
+ expect << R"(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];
+};
+
+)";
+
+ expect << "struct S {\n";
+#define FIELD(ADDR, TYPE, ARRAY_COUNT, NAME) \
+ FormatMSLField(expect, #ADDR, #TYPE, ARRAY_COUNT, #NAME);
+ ALL_FIELDS()
+#undef FIELD
+ expect << "};\n\nS";
+
+ generator_.EmitType(generator_.Line(), s);
+ ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+ EXPECT_EQ(utils::TrimSpace(generator_.Result()), expect.str());
+}
+
+TEST_F(MslGeneratorImplIrTest, AttemptTintPadSymbolCollision) {
+ utils::Vector<MemberData, 26> data = {
+ // uses symbols tint_pad_[0..9] and tint_pad_[20..35]
+ {mod.symbols.Register("tint_pad_2"), ty.i32(), 32}, //
+ {mod.symbols.Register("tint_pad_20"), ty.f32(), 128, 128}, //
+ {mod.symbols.Register("tint_pad_33"), ty.vec2<f32>()}, //
+ {mod.symbols.Register("tint_pad_1"), ty.u32()}, //
+ {mod.symbols.Register("tint_pad_3"), ty.vec3<f32>()}, //
+ {mod.symbols.Register("tint_pad_7"), ty.u32()}, //
+ {mod.symbols.Register("tint_pad_25"), ty.vec4<f32>()}, //
+ {mod.symbols.Register("tint_pad_5"), ty.u32()}, //
+ {mod.symbols.Register("tint_pad_27"), ty.mat2x2<f32>()}, //
+ {mod.symbols.Register("tint_pad_24"), ty.u32()}, //
+ {mod.symbols.Register("tint_pad_23"), ty.mat2x3<f32>()}, //
+ {mod.symbols.Register("tint_pad"), ty.u32()}, //
+ {mod.symbols.Register("tint_pad_8"), ty.mat2x4<f32>()}, //
+ {mod.symbols.Register("tint_pad_26"), ty.u32()}, //
+ {mod.symbols.Register("tint_pad_29"), ty.mat3x2<f32>()}, //
+ {mod.symbols.Register("tint_pad_6"), ty.u32()}, //
+ {mod.symbols.Register("tint_pad_22"), ty.mat3x3<f32>()}, //
+ {mod.symbols.Register("tint_pad_32"), ty.u32()}, //
+ {mod.symbols.Register("tint_pad_34"), ty.mat3x4<f32>()}, //
+ {mod.symbols.Register("tint_pad_35"), ty.u32()}, //
+ {mod.symbols.Register("tint_pad_30"), ty.mat4x2<f32>()}, //
+ {mod.symbols.Register("tint_pad_9"), ty.u32()}, //
+ {mod.symbols.Register("tint_pad_31"), ty.mat4x3<f32>()}, //
+ {mod.symbols.Register("tint_pad_28"), ty.u32()}, //
+ {mod.symbols.Register("tint_pad_4"), ty.mat4x4<f32>()}, //
+ {mod.symbols.Register("tint_pad_21"), ty.f32()}};
+
+ auto* s = MkStruct(mod, ty, "S", data);
+ s->AddUsage(builtin::AddressSpace::kStorage);
+
+ auto expect = R"(template<typename T, size_t N>
+struct tint_array {
+ const constant T& operator[](size_t i) const constant { return elements[i]; }
+ device T& operator[](size_t i) device { return elements[i]; }
+ const device T& operator[](size_t i) const device { return elements[i]; }
+ thread T& operator[](size_t i) thread { return elements[i]; }
+ const thread T& operator[](size_t i) const thread { return elements[i]; }
+ threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+ const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+ T elements[N];
+};
+
+struct S {
+ /* 0x0000 */ int tint_pad_2;
+ /* 0x0004 */ tint_array<int8_t, 124> tint_pad_10;
+ /* 0x0080 */ float tint_pad_20;
+ /* 0x0084 */ tint_array<int8_t, 124> tint_pad_11;
+ /* 0x0100 */ float2 tint_pad_33;
+ /* 0x0108 */ uint tint_pad_1;
+ /* 0x010c */ tint_array<int8_t, 4> tint_pad_12;
+ /* 0x0110 */ float3 tint_pad_3;
+ /* 0x011c */ uint tint_pad_7;
+ /* 0x0120 */ float4 tint_pad_25;
+ /* 0x0130 */ uint tint_pad_5;
+ /* 0x0134 */ tint_array<int8_t, 4> tint_pad_13;
+ /* 0x0138 */ float2x2 tint_pad_27;
+ /* 0x0148 */ uint tint_pad_24;
+ /* 0x014c */ tint_array<int8_t, 4> tint_pad_14;
+ /* 0x0150 */ float2x3 tint_pad_23;
+ /* 0x0170 */ uint tint_pad;
+ /* 0x0174 */ tint_array<int8_t, 12> tint_pad_15;
+ /* 0x0180 */ float2x4 tint_pad_8;
+ /* 0x01a0 */ uint tint_pad_26;
+ /* 0x01a4 */ tint_array<int8_t, 4> tint_pad_16;
+ /* 0x01a8 */ float3x2 tint_pad_29;
+ /* 0x01c0 */ uint tint_pad_6;
+ /* 0x01c4 */ tint_array<int8_t, 12> tint_pad_17;
+ /* 0x01d0 */ float3x3 tint_pad_22;
+ /* 0x0200 */ uint tint_pad_32;
+ /* 0x0204 */ tint_array<int8_t, 12> tint_pad_18;
+ /* 0x0210 */ float3x4 tint_pad_34;
+ /* 0x0240 */ uint tint_pad_35;
+ /* 0x0244 */ tint_array<int8_t, 4> tint_pad_19;
+ /* 0x0248 */ float4x2 tint_pad_30;
+ /* 0x0268 */ uint tint_pad_9;
+ /* 0x026c */ tint_array<int8_t, 4> tint_pad_36;
+ /* 0x0270 */ float4x3 tint_pad_31;
+ /* 0x02b0 */ uint tint_pad_28;
+ /* 0x02b4 */ tint_array<int8_t, 12> tint_pad_37;
+ /* 0x02c0 */ float4x4 tint_pad_4;
+ /* 0x0300 */ float tint_pad_21;
+ /* 0x0304 */ tint_array<int8_t, 124> tint_pad_38;
+};
+
+S)";
+
+ generator_.EmitType(generator_.Line(), s);
+ ASSERT_TRUE(generator_.Diagnostics().empty()) << generator_.Diagnostics().str();
+ EXPECT_STREQ(std::string(utils::TrimSpace(generator_.Result())).c_str(), expect);
+}
TEST_F(MslGeneratorImplIrTest, EmitType_Sampler) {
generator_.EmitType(generator_.Line(), ty.sampler());
diff --git a/src/tint/writer/text_generator.cc b/src/tint/writer/text_generator.cc
index 417f7bd..1585e79 100644
--- a/src/tint/writer/text_generator.cc
+++ b/src/tint/writer/text_generator.cc
@@ -18,6 +18,7 @@
#include <limits>
#include "src/tint/debug.h"
+#include "src/tint/utils/map.h"
namespace tint::writer {
@@ -25,6 +26,15 @@
TextGenerator::~TextGenerator() = default;
+std::string TextGenerator::StructName(const type::Struct* s) {
+ auto name = s->Name().Name();
+ if (name.size() > 1 && name[0] == '_' && name[1] == '_') {
+ name = utils::GetOrCreate(builtin_struct_names_, s,
+ [&] { return UniqueIdentifier(name.substr(2)); });
+ }
+ return name;
+}
+
TextGenerator::LineWriter::LineWriter(TextBuffer* buf) : buffer(buf) {}
TextGenerator::LineWriter::LineWriter(LineWriter&& other) {
diff --git a/src/tint/writer/text_generator.h b/src/tint/writer/text_generator.h
index bb9867e..348a31b 100644
--- a/src/tint/writer/text_generator.h
+++ b/src/tint/writer/text_generator.h
@@ -21,6 +21,7 @@
#include <vector>
#include "src/tint/diagnostic/diagnostic.h"
+#include "src/tint/type/struct.h"
#include "src/tint/utils/string_stream.h"
namespace tint::writer {
@@ -130,6 +131,17 @@
/// the end of `buffer`.
static LineWriter Line(TextBuffer* buffer) { return LineWriter(buffer); }
+ /// @return a new, unique identifier with the given prefix.
+ /// @param prefix optional prefix to apply to the generated identifier. If
+ /// empty "tint_symbol" will be used.
+ virtual std::string UniqueIdentifier(const std::string& prefix = "") = 0;
+
+ /// @param s the structure
+ /// @returns the name of the structure, taking special care of builtin structures that start
+ /// with double underscores. If the structure is a builtin, then the returned name will be a
+ /// unique name without the leading underscores.
+ std::string StructName(const type::Struct* s);
+
/// @returns the result data
virtual std::string Result() const { return main_buffer_.String(); }
@@ -183,6 +195,10 @@
/// The primary text buffer that the generator will emit
TextBuffer main_buffer_;
+
+ private:
+ /// Map of builtin structure to unique generated name
+ std::unordered_map<const type::Struct*, std::string> builtin_struct_names_;
};
} // namespace tint::writer