[spirv-reader] Emit struct types and constants
Add a map to uniquify type declarations.
Empty structures are disallowed, as per WGSL.
Test OpConstantNull too.
Bug: tint:1907, tint:2123
Change-Id: I20c73d441f89ca13d2d18019863b4fbc0d2b0084
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/168205
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.bazel b/src/tint/lang/spirv/reader/parser/BUILD.bazel
index 1d0b9ee..6990fb1 100644
--- a/src/tint/lang/spirv/reader/parser/BUILD.bazel
+++ b/src/tint/lang/spirv/reader/parser/BUILD.bazel
@@ -82,6 +82,7 @@
"constant_test.cc",
"function_test.cc",
"helper_test.h",
+ "struct_test.cc",
"var_test.cc",
],
deps = [
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.cmake b/src/tint/lang/spirv/reader/parser/BUILD.cmake
index 524b09d..ac508eb 100644
--- a/src/tint/lang/spirv/reader/parser/BUILD.cmake
+++ b/src/tint/lang/spirv/reader/parser/BUILD.cmake
@@ -88,6 +88,7 @@
lang/spirv/reader/parser/constant_test.cc
lang/spirv/reader/parser/function_test.cc
lang/spirv/reader/parser/helper_test.h
+ lang/spirv/reader/parser/struct_test.cc
lang/spirv/reader/parser/var_test.cc
)
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.gn b/src/tint/lang/spirv/reader/parser/BUILD.gn
index fa9f30c..8ffc520 100644
--- a/src/tint/lang/spirv/reader/parser/BUILD.gn
+++ b/src/tint/lang/spirv/reader/parser/BUILD.gn
@@ -89,6 +89,7 @@
"constant_test.cc",
"function_test.cc",
"helper_test.h",
+ "struct_test.cc",
"var_test.cc",
]
deps = [
diff --git a/src/tint/lang/spirv/reader/parser/constant_test.cc b/src/tint/lang/spirv/reader/parser/constant_test.cc
index 7d3542b..862db10 100644
--- a/src/tint/lang/spirv/reader/parser/constant_test.cc
+++ b/src/tint/lang/spirv/reader/parser/constant_test.cc
@@ -624,5 +624,106 @@
)");
}
+TEST_F(SpirvParserTest, Constant_Struct) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ %void = OpTypeVoid
+ %i32 = OpTypeInt 32 1
+ %f32 = OpTypeFloat 32
+ %str = OpTypeStruct %i32 %f32
+ %i32_42 = OpConstant %i32 42
+ %f32_n1 = OpConstant %f32 -1
+ %str_const = OpConstantComposite %str %i32_42 %f32_n1
+ %null = OpConstantNull %str
+ %void_fn = OpTypeFunction %void
+ %fn_type = OpTypeFunction %str %str
+
+ %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+ OpReturn
+ OpFunctionEnd
+
+ %foo = OpFunction %str None %fn_type
+ %param = OpFunctionParameter %str
+ %foo_start = OpLabel
+ OpReturnValue %param
+ OpFunctionEnd
+
+ %bar = OpFunction %void None %void_fn
+ %bar_start = OpLabel
+ %1 = OpFunctionCall %str %foo %str_const
+ %2 = OpFunctionCall %str %foo %null
+ OpReturn
+ OpFunctionEnd
+)",
+ R"(
+%4 = func():void -> %b3 {
+ %b3 = block {
+ %5:tint_symbol_2 = call %2, tint_symbol_2(42i, -1.0f)
+ %6:tint_symbol_2 = call %2, tint_symbol_2(0i, 0.0f)
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, Constant_Struct_Nested) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ %void = OpTypeVoid
+ %i32 = OpTypeInt 32 1
+ %f32 = OpTypeFloat 32
+ %i32_2 = OpConstant %i32 2
+ %inner = OpTypeStruct %i32 %f32
+ %arr = OpTypeArray %inner %i32_2
+ %outer = OpTypeStruct %arr %arr
+ %i32_42 = OpConstant %i32 42
+ %i32_n1 = OpConstant %i32 -1
+ %f32_n1 = OpConstant %f32 -1
+ %f32_42 = OpConstant %f32 42
+%inner_const_0 = OpConstantComposite %inner %i32_42 %f32_n1
+%inner_const_1 = OpConstantComposite %inner %i32_n1 %f32_42
+ %arr_const_0 = OpConstantComposite %arr %inner_const_0 %inner_const_1
+ %arr_const_1 = OpConstantComposite %arr %inner_const_1 %inner_const_0
+ %outer_const = OpConstantComposite %outer %arr_const_0 %arr_const_1
+ %null = OpConstantNull %outer
+ %void_fn = OpTypeFunction %void
+ %fn_type = OpTypeFunction %outer %outer
+
+ %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+ OpReturn
+ OpFunctionEnd
+
+ %foo = OpFunction %outer None %fn_type
+ %param = OpFunctionParameter %outer
+ %foo_start = OpLabel
+ OpReturnValue %param
+ OpFunctionEnd
+
+ %bar = OpFunction %void None %void_fn
+ %bar_start = OpLabel
+ %1 = OpFunctionCall %outer %foo %outer_const
+ %2 = OpFunctionCall %outer %foo %null
+ OpReturn
+ OpFunctionEnd
+)",
+ R"(
+%4 = func():void -> %b3 {
+ %b3 = block {
+ %5:tint_symbol_5 = call %2, tint_symbol_5(array<tint_symbol_2, 2>(tint_symbol_2(42i, -1.0f), tint_symbol_2(-1i, 42.0f)), array<tint_symbol_2, 2>(tint_symbol_2(-1i, 42.0f), tint_symbol_2(42i, -1.0f)))
+ %6:tint_symbol_5 = call %2, tint_symbol_5(array<tint_symbol_2, 2>(tint_symbol_2(0i, 0.0f)))
+ ret
+ }
+}
+)");
+}
+
} // namespace
} // namespace tint::spirv::reader
diff --git a/src/tint/lang/spirv/reader/parser/helper_test.h b/src/tint/lang/spirv/reader/parser/helper_test.h
index af16699..489484e 100644
--- a/src/tint/lang/spirv/reader/parser/helper_test.h
+++ b/src/tint/lang/spirv/reader/parser/helper_test.h
@@ -28,6 +28,7 @@
#ifndef SRC_TINT_LANG_SPIRV_READER_PARSER_HELPER_TEST_H_
#define SRC_TINT_LANG_SPIRV_READER_PARSER_HELPER_TEST_H_
+#include <iostream>
#include <string>
#include <vector>
@@ -53,11 +54,10 @@
template <typename BASE>
class SpirvParserTestHelperBase : public BASE {
protected:
- /// Run the parser on a SPIR-V module and return the Tint IR or an error string.
- /// @param spirv_asm the SPIR-V assembly to parse
- /// @returns the disassembled Tint IR
- std::string Run(std::string spirv_asm) {
- // Assemble the SPIR-V input.
+ /// Assemble a textual SPIR-V module into a SPIR-V binary.
+ /// @param spirv_asm the textual SPIR-V assembly
+ /// @returns the SPIR-V binary data, or an error string
+ static Result<std::vector<uint32_t>, std::string> Assemble(std::string spirv_asm) {
StringStream err;
std::vector<uint32_t> binary;
spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
@@ -65,14 +65,24 @@
[&err](spv_message_level_t, const char*, const spv_position_t& pos, const char* msg) {
err << "SPIR-V assembly failed:" << pos.line << ":" << pos.column << ": " << msg;
});
- auto assembled =
- tools.Assemble(spirv_asm, &binary, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
- if (!assembled) {
+ if (!tools.Assemble(spirv_asm, &binary, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS)) {
return err.str();
}
+ return binary;
+ }
+
+ /// Run the parser on a SPIR-V module and return the Tint IR or an error string.
+ /// @param spirv_asm the SPIR-V assembly to parse
+ /// @returns the disassembled Tint IR
+ std::string Run(std::string spirv_asm) {
+ // Assemble the SPIR-V input.
+ auto binary = Assemble(spirv_asm);
+ if (binary != Success) {
+ return binary.Failure();
+ }
// Parse the SPIR-V to produce an IR module.
- auto parsed = Parse(Slice(binary.data(), binary.size()));
+ auto parsed = Parse(Slice(binary.Get().data(), binary.Get().size()));
if (parsed != Success) {
return parsed.Failure().reason.str();
}
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index e19d9f1..5b0d9be 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -27,6 +27,7 @@
#include "src/tint/lang/spirv/reader/parser/parser.h"
+#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
@@ -105,53 +106,58 @@
/// @param type a SPIR-V type object
/// @returns a Tint type object
const core::type::Type* Type(const spvtools::opt::analysis::Type* type) {
- switch (type->kind()) {
- case spvtools::opt::analysis::Type::kVoid:
- return ty_.void_();
- case spvtools::opt::analysis::Type::kBool:
- return ty_.bool_();
- case spvtools::opt::analysis::Type::kInteger: {
- auto* int_ty = type->AsInteger();
- TINT_ASSERT_OR_RETURN_VALUE(int_ty->width() == 32, ty_.void_());
- if (int_ty->IsSigned()) {
- return ty_.i32();
- } else {
- return ty_.u32();
- }
- }
- case spvtools::opt::analysis::Type::kFloat: {
- auto* float_ty = type->AsFloat();
- if (float_ty->width() == 16) {
- return ty_.f16();
- } else if (float_ty->width() == 32) {
- return ty_.f32();
- } else {
- TINT_UNREACHABLE()
- << "unsupported floating point type width: " << float_ty->width();
+ return types_.GetOrCreate(type, [&]() -> const core::type::Type* {
+ switch (type->kind()) {
+ case spvtools::opt::analysis::Type::kVoid:
return ty_.void_();
+ case spvtools::opt::analysis::Type::kBool:
+ return ty_.bool_();
+ case spvtools::opt::analysis::Type::kInteger: {
+ auto* int_ty = type->AsInteger();
+ TINT_ASSERT_OR_RETURN_VALUE(int_ty->width() == 32, ty_.void_());
+ if (int_ty->IsSigned()) {
+ return ty_.i32();
+ } else {
+ return ty_.u32();
+ }
}
+ case spvtools::opt::analysis::Type::kFloat: {
+ auto* float_ty = type->AsFloat();
+ if (float_ty->width() == 16) {
+ return ty_.f16();
+ } else if (float_ty->width() == 32) {
+ return ty_.f32();
+ } else {
+ TINT_UNREACHABLE()
+ << "unsupported floating point type width: " << float_ty->width();
+ return ty_.void_();
+ }
+ }
+ case spvtools::opt::analysis::Type::kVector: {
+ auto* vec_ty = type->AsVector();
+ TINT_ASSERT_OR_RETURN_VALUE(vec_ty->element_count() <= 4, ty_.void_());
+ return ty_.vec(Type(vec_ty->element_type()), vec_ty->element_count());
+ }
+ case spvtools::opt::analysis::Type::kMatrix: {
+ auto* mat_ty = type->AsMatrix();
+ TINT_ASSERT_OR_RETURN_VALUE(mat_ty->element_count() <= 4, ty_.void_());
+ return ty_.mat(As<core::type::Vector>(Type(mat_ty->element_type())),
+ mat_ty->element_count());
+ }
+ case spvtools::opt::analysis::Type::kArray:
+ return EmitArray(type->AsArray());
+ case spvtools::opt::analysis::Type::kStruct:
+ return EmitStruct(type->AsStruct());
+ case spvtools::opt::analysis::Type::kPointer: {
+ auto* ptr_ty = type->AsPointer();
+ return ty_.ptr(AddressSpace(ptr_ty->storage_class()),
+ Type(ptr_ty->pointee_type()));
+ }
+ default:
+ TINT_UNIMPLEMENTED() << "unhandled SPIR-V type: " << type->str();
+ return ty_.void_();
}
- case spvtools::opt::analysis::Type::kVector: {
- auto* vec_ty = type->AsVector();
- TINT_ASSERT_OR_RETURN_VALUE(vec_ty->element_count() <= 4, ty_.void_());
- return ty_.vec(Type(vec_ty->element_type()), vec_ty->element_count());
- }
- case spvtools::opt::analysis::Type::kMatrix: {
- auto* mat_ty = type->AsMatrix();
- TINT_ASSERT_OR_RETURN_VALUE(mat_ty->element_count() <= 4, ty_.void_());
- return ty_.mat(As<core::type::Vector>(Type(mat_ty->element_type())),
- mat_ty->element_count());
- }
- case spvtools::opt::analysis::Type::kArray:
- return EmitArray(type->AsArray());
- case spvtools::opt::analysis::Type::kPointer: {
- auto* ptr_ty = type->AsPointer();
- return ty_.ptr(AddressSpace(ptr_ty->storage_class()), Type(ptr_ty->pointee_type()));
- }
- default:
- TINT_UNIMPLEMENTED() << "unhandled SPIR-V type: " << type->str();
- return ty_.void_();
- }
+ });
}
/// @param id a SPIR-V result ID for a type declaration instruction
@@ -182,6 +188,48 @@
return ty_.array(Type(arr_ty->element_type()), static_cast<uint32_t>(count_val));
}
+ /// @param struct_ty a SPIR-V struct object
+ /// @returns a Tint struct object
+ const core::type::Type* EmitStruct(const spvtools::opt::analysis::Struct* struct_ty) {
+ if (struct_ty->NumberOfComponents() == 0) {
+ TINT_ICE() << "empty structures are not supported";
+ return ty_.void_();
+ }
+
+ // Build a list of struct members.
+ uint32_t current_size = 0u;
+ Vector<core::type::StructMember*, 4> members;
+ for (uint32_t i = 0; i < struct_ty->NumberOfComponents(); i++) {
+ auto* member_ty = Type(struct_ty->element_types()[i]);
+ uint32_t align = std::max<uint32_t>(member_ty->Align(), 1u);
+ uint32_t offset = tint::RoundUp(align, current_size);
+ core::type::StructMemberAttributes attributes;
+
+ // Handle member decorations that affect layout or attributes.
+ if (struct_ty->element_decorations().count(i)) {
+ for (auto& deco : struct_ty->element_decorations().at(i)) {
+ switch (spv::Decoration(deco[0])) {
+ case spv::Decoration::Offset:
+ offset = deco[1];
+ break;
+ default:
+ TINT_UNIMPLEMENTED() << "unhandled member decoration: " << deco[0];
+ break;
+ }
+ }
+ }
+
+ // TODO(crbug.com/tint/1907): Use OpMemberName to name it.
+ members.Push(ty_.Get<core::type::StructMember>(ir_.symbols.New(), member_ty, i, offset,
+ align, member_ty->Size(),
+ std::move(attributes)));
+
+ current_size = offset + member_ty->Size();
+ }
+ // TODO(crbug.com/tint/1907): Use OpName to name it.
+ return ty_.Struct(ir_.symbols.New(), std::move(members));
+ }
+
/// @param id a SPIR-V result ID for a function declaration instruction
/// @returns a Tint function object
core::ir::Function* Function(uint32_t id) {
@@ -255,6 +303,13 @@
}
return ir_.constant_values.Composite(Type(a->type()), std::move(elements));
}
+ if (auto* s = constant->AsStructConstant()) {
+ Vector<const core::constant::Value*, 16> elements;
+ for (auto& el : s->GetComponents()) {
+ elements.Push(Constant(el));
+ }
+ return ir_.constant_values.Composite(Type(s->type()), std::move(elements));
+ }
TINT_UNIMPLEMENTED() << "unhandled constant type";
return nullptr;
}
@@ -389,6 +444,8 @@
/// The Tint IR function that is currently being emitted.
core::ir::Function* current_function_ = nullptr;
+ /// A map from a SPIR-V type declaration result ID to the corresponding Tint type object.
+ Hashmap<const spvtools::opt::analysis::Type*, const core::type::Type*, 16> types_;
/// A map from a SPIR-V function definition result ID to the corresponding Tint function object.
Hashmap<uint32_t, core::ir::Function*, 8> functions_;
/// A map from a SPIR-V result ID to the corresponding Tint value object.
diff --git a/src/tint/lang/spirv/reader/parser/struct_test.cc b/src/tint/lang/spirv/reader/parser/struct_test.cc
new file mode 100644
index 0000000..afbaa39
--- /dev/null
+++ b/src/tint/lang/spirv/reader/parser/struct_test.cc
@@ -0,0 +1,232 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/spirv/reader/parser/helper_test.h"
+
+#include "gtest/gtest-spi.h"
+
+namespace tint::spirv::reader {
+
+TEST_F(SpirvParserTest, Struct_Empty) {
+ EXPECT_FATAL_FAILURE( //
+ {
+ auto assembly = Assemble(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ %void = OpTypeVoid
+ %str = OpTypeStruct
+ %ep_type = OpTypeFunction %void
+ %fn_type = OpTypeFunction %void %str
+
+ %foo = OpFunction %void None %fn_type
+ %param = OpFunctionParameter %str
+ %foo_start = OpLabel
+ OpReturn
+ OpFunctionEnd
+
+ %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+ OpReturn
+ OpFunctionEnd
+)");
+ auto parsed = Parse(Slice(assembly.Get().data(), assembly.Get().size()));
+ EXPECT_EQ(parsed, Success);
+ },
+ "empty structures are not supported");
+}
+
+TEST_F(SpirvParserTest, Struct_BasicDecl) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ %void = OpTypeVoid
+ %i32 = OpTypeInt 32 1
+ %str = OpTypeStruct %i32 %i32
+ %ep_type = OpTypeFunction %void
+ %fn_type = OpTypeFunction %void %str
+
+ %foo = OpFunction %void None %fn_type
+ %param = OpFunctionParameter %str
+ %foo_start = OpLabel
+ OpReturn
+ OpFunctionEnd
+
+ %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+ OpReturn
+ OpFunctionEnd
+)",
+ R"(
+tint_symbol_2 = struct @align(4) {
+ tint_symbol:i32 @offset(0)
+ tint_symbol_1:i32 @offset(4)
+}
+
+%1 = func(%2:tint_symbol_2):void -> %b1 {
+ %b1 = block {
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, Struct_MultipleUses) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ %void = OpTypeVoid
+ %i32 = OpTypeInt 32 1
+ %str = OpTypeStruct %i32 %i32
+ %ep_type = OpTypeFunction %void
+ %fn_type = OpTypeFunction %str %str %str
+
+ %foo = OpFunction %str None %fn_type
+ %param_1 = OpFunctionParameter %str
+ %param_2 = OpFunctionParameter %str
+ %foo_start = OpLabel
+ OpReturnValue %param_1
+ OpFunctionEnd
+
+ %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+ OpReturn
+ OpFunctionEnd
+)",
+ R"(
+tint_symbol_2 = struct @align(4) {
+ tint_symbol:i32 @offset(0)
+ tint_symbol_1:i32 @offset(4)
+}
+
+%1 = func(%2:tint_symbol_2, %3:tint_symbol_2):tint_symbol_2 -> %b1 {
+ %b1 = block {
+ ret %2
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, Struct_Nested) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ %void = OpTypeVoid
+ %i32 = OpTypeInt 32 1
+ %inner = OpTypeStruct %i32 %i32
+ %middle = OpTypeStruct %inner %inner
+ %outer = OpTypeStruct %inner %middle %inner
+ %ep_type = OpTypeFunction %void
+ %fn_type = OpTypeFunction %void %outer
+
+ %foo = OpFunction %void None %fn_type
+ %param = OpFunctionParameter %outer
+ %foo_start = OpLabel
+ OpReturn
+ OpFunctionEnd
+
+ %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+ OpReturn
+ OpFunctionEnd
+)",
+ R"(
+tint_symbol_2 = struct @align(4) {
+ tint_symbol:i32 @offset(0)
+ tint_symbol_1:i32 @offset(4)
+}
+
+tint_symbol_6 = struct @align(4) {
+ tint_symbol_4:tint_symbol_2 @offset(0)
+ tint_symbol_5:tint_symbol_2 @offset(8)
+}
+
+tint_symbol_9 = struct @align(4) {
+ tint_symbol_3:tint_symbol_2 @offset(0)
+ tint_symbol_7:tint_symbol_6 @offset(8)
+ tint_symbol_8:tint_symbol_2 @offset(24)
+}
+
+%1 = func(%2:tint_symbol_9):void -> %b1 {
+ %b1 = block {
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, Struct_Offset) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpMemberDecorate %str 0 Offset 0
+ OpMemberDecorate %str 1 Offset 4
+ OpMemberDecorate %str 2 Offset 32
+ OpMemberDecorate %str 3 Offset 64
+ %void = OpTypeVoid
+ %i32 = OpTypeInt 32 1
+ %str = OpTypeStruct %i32 %i32 %i32 %i32
+ %ep_type = OpTypeFunction %void
+ %fn_type = OpTypeFunction %void %str
+
+ %foo = OpFunction %void None %fn_type
+ %param = OpFunctionParameter %str
+ %foo_start = OpLabel
+ OpReturn
+ OpFunctionEnd
+
+ %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+ OpReturn
+ OpFunctionEnd
+)",
+ R"(
+tint_symbol_4 = struct @align(4) {
+ tint_symbol:i32 @offset(0)
+ tint_symbol_1:i32 @offset(4)
+ tint_symbol_2:i32 @offset(32)
+ tint_symbol_3:i32 @offset(64)
+}
+
+%1 = func(%2:tint_symbol_4):void -> %b1 {
+ %b1 = block {
+ ret
+ }
+}
+)");
+}
+
+} // namespace tint::spirv::reader