[tint][ir][val] Improve checks for shader IO annotations
Fixes: 379652398
Change-Id: I110c31169fd8f116b4e658309a505e57341f44fb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/215814
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/transform/demote_to_helper_test.cc b/src/tint/lang/core/ir/transform/demote_to_helper_test.cc
index 861de81..4e0d1c6 100644
--- a/src/tint/lang/core/ir/transform/demote_to_helper_test.cc
+++ b/src/tint/lang/core/ir/transform/demote_to_helper_test.cc
@@ -538,7 +538,11 @@
auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
front_facing->SetBuiltin(BuiltinValue::kFrontFacing);
+
auto* coord = b.FunctionParam("coord", ty.vec2<i32>());
+ IOAttributes coord_attr;
+ coord_attr.location = 0;
+ coord->SetAttributes(coord_attr);
auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
ep->SetParams({front_facing, coord});
ep->SetReturnLocation(0_u);
@@ -559,7 +563,7 @@
%texture:ptr<handle, texture_storage_2d<r32float, write>, read> = var @binding_point(0, 0)
}
-%ep = @fragment func(%front_facing:bool [@front_facing], %coord:vec2<i32>):f32 [@location(0)] {
+%ep = @fragment func(%front_facing:bool [@front_facing], %coord:vec2<i32> [@location(0)]):f32 [@location(0)] {
$B2: {
if %front_facing [t: $B3] { # if_1
$B3: { # true
@@ -581,7 +585,7 @@
%continue_execution:ptr<private, bool, read_write> = var, true
}
-%ep = @fragment func(%front_facing:bool [@front_facing], %coord:vec2<i32>):f32 [@location(0)] {
+%ep = @fragment func(%front_facing:bool [@front_facing], %coord:vec2<i32> [@location(0)]):f32 [@location(0)] {
$B2: {
if %front_facing [t: $B3] { # if_1
$B3: { # true
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 5bafb6e..0744f3f 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -140,16 +140,6 @@
return false;
}
-/// @returns true if @p attr contains both a location and builtin decoration
-bool HasLocationAndBuiltin(const tint::core::IOAttributes& attr) {
- return attr.builtin.has_value() && attr.location.has_value();
-}
-
-/// @returns true if @p attr contains one of location or builtin decoration
-bool HasLocationOrBuiltin(const tint::core::IOAttributes& attr) {
- return attr.builtin.has_value() || attr.location.has_value();
-}
-
/// @return true if @param attr does not have invariant decoration or if it also has position
/// decoration
bool InvariantOnlyIfAlsoPosition(const tint::core::IOAttributes& attr) {
@@ -187,7 +177,7 @@
/// If the type that the attributes are attached to is a struct, the check is run over the members,
/// otherwise it run on the attributes directly.
///
-/// @param msg_anchor what to associate errors with, i.e. the 'foo' of AddError(foo)
+/// @param msg_anchor what to associate errors with, e.g. the 'foo' of AddError(foo)
/// @param ty_attr the directly attached attributes
/// @param ty the type of the thing that the attributes are attached to
/// @param is_not_struct_impl has the signature 'void(const MSG_ANCHOR*, const IOAttributes&)' and
@@ -237,7 +227,7 @@
/// If the type that the attributes are attached to is a struct, the check is run over the members,
/// otherwise it run on the attributes directly.
///
-/// @param msg_anchor what to associate errors with, i.e. the 'foo' of AddError(foo)
+/// @param msg_anchor what to associate errors with, e.g. the 'foo' of AddError(foo)
/// @param ty_attr the directly attached attributes
/// @param ty the type of the thing that the attributes are attached to
/// @param is_not_struct_impl has the signature 'void(const MSG_ANCHOR*, const IOAttributes&, const
@@ -650,6 +640,86 @@
return Success;
}
+// Annotations that can be associated with a value that are used for shader IO, e.g. binding_points,
+// @location, being in workgroup address space, etc.
+enum class IOAnnotation : uint8_t {
+ /// @group + @binding
+ kBindingPoint,
+ /// @location
+ kLocation,
+ /// @builtin(...)
+ kBuiltin,
+ /// Pointer to Workgroup address space
+ kWorkgroup,
+ /// @color
+ kColor,
+};
+
+/// @returns text describing the annotation for error logging
+std::string ToString(IOAnnotation value) {
+ switch (value) {
+ case IOAnnotation::kBindingPoint:
+ return "@group + @binding";
+ case IOAnnotation::kLocation:
+ return "@location";
+ case IOAnnotation::kBuiltin:
+ return "built-in";
+ case IOAnnotation::kWorkgroup:
+ return "<workgroup>";
+ case IOAnnotation::kColor:
+ return "@color";
+ }
+ TINT_ICE() << "Unknown enum passed to ToString(IOAnnotation)";
+}
+
+/// @returns a human-readable string of all the entries in a set of IOAnnotations
+std::string ToString(const EnumSet<IOAnnotation>& values) {
+ std::stringstream result;
+ result << "[ ";
+ bool first = true;
+ for (auto v : values) {
+ if (!first) {
+ result << ", ";
+ }
+ first = false;
+ result << ToString(v);
+ }
+ result << " ]";
+ return result.str();
+}
+
+/// Adds appropriate entries to annotations, based on what values are present in attributes
+/// @param annotations the set to updated
+/// @param attr the attributes to be examined
+/// @returns Success if none of the values being added where already present, otherwise returns the
+/// first non-unique value as a Failure
+Result<SuccessType, IOAnnotation> AddIOAnnotationsFromIOAttributes(
+ EnumSet<IOAnnotation>& annotations,
+ const IOAttributes& attr) {
+ if (attr.location.has_value()) {
+ if (annotations.Contains(IOAnnotation::kLocation)) {
+ return IOAnnotation::kLocation;
+ }
+ annotations.Add(IOAnnotation::kLocation);
+ }
+
+ if (attr.builtin.has_value()) {
+ if (annotations.Contains(IOAnnotation::kBuiltin)) {
+ return IOAnnotation::kBuiltin;
+ }
+ annotations.Add(IOAnnotation::kBuiltin);
+ }
+
+ if (attr.color.has_value()) {
+ if (annotations.Contains(IOAnnotation::kColor)) {
+ return IOAnnotation::kColor;
+ }
+ annotations.Add(IOAnnotation::kColor);
+ }
+
+ return Success;
+}
+
/// The core IR validator.
class Validator {
public:
@@ -935,29 +1005,6 @@
};
}
- /// @returns a function that validates that location and builtin attributes are not present at
- /// the same time
- /// @param err error message to log when check fails
- template <typename MSG_ANCHOR>
- auto CheckDoesNotHaveBothLocationAndBuiltinFunc(const std::string& err) {
- return [this, err](const MSG_ANCHOR* msg_anchor, const IOAttributes& attr) {
- if (HasLocationAndBuiltin(attr)) {
- AddError(msg_anchor) << err;
- }
- };
- }
-
- /// @returns a function that validates that either a location or builtin attribute are present
- /// @param err error message to log when check fails
- template <typename MSG_ANCHOR>
- auto CheckHasLocationOrBuiltinFunc(const std::string& err) {
- return [this, err](const MSG_ANCHOR* msg_anchor, const IOAttributes& attr) {
- if (!HasLocationOrBuiltin(attr)) {
- AddError(msg_anchor) << err;
- }
- };
- }
-
/// @returns a function that validates that type is bool iff decorated with
/// @builtin(front_facing)
/// @param err error message to log when check fails
@@ -1006,6 +1053,22 @@
AddressSpace address_space,
const std::string& target_str = "variable");
+ /// Validates shader IO annotations for entry point input/output
+ /// Note: Call is required to ensure that the value being validated is associated with an entry
+ /// point function
+ /// @param ty type of the value under test
+ /// @param binding_point the binding information associated with the value
+ /// @param attr IO attributes associated with the values
+ /// @param target_str string to insert in error message describing what has a binding_point,
+ /// something like 'input param' or 'return value'
+ /// @returns Success if one, and only one, shader IO is present, otherwise a Failure with the
+ /// error reason is returned
+ Result<SuccessType, std::string> ValidateShaderIOAnnotations(
+ const core::type::Type* ty,
+ const std::optional<struct BindingPoint>& binding_point,
+ const core::IOAttributes& attr,
+ const std::string& target_str);
+
/// Validates the given let
/// @param l the let to validate
void CheckLet(const Let* l);
@@ -1874,15 +1937,6 @@
"invariant can only decorate a param member iff it is also "
"decorated with position"));
- if (func->Stage() != Function::PipelineStage::kUndefined) {
- CheckFunctionParamAttributes(
- param,
- CheckDoesNotHaveBothLocationAndBuiltinFunc<FunctionParam>(
- "a builtin and location cannot be both declared for a param"),
- CheckDoesNotHaveBothLocationAndBuiltinFunc<FunctionParam>(
- "a builtin and location cannot be both declared for a struct member"));
- }
-
if (func->Stage() == Function::PipelineStage::kFragment) {
CheckFunctionParamAttributesAndType(
param,
@@ -1910,9 +1964,19 @@
}
if (func->Stage() != Function::PipelineStage::kUndefined) {
- auto result = ValidateBindingPoint(param->BindingPoint(), address_space, "input param");
- if (result != Success) {
- AddError(param) << result.Failure();
+ {
+ auto result = ValidateShaderIOAnnotations(param->Type(), param->BindingPoint(),
+ param->Attributes(), "input param");
+ if (result != Success) {
+ AddError(param) << result.Failure();
+ }
+ }
+ {
+ auto result =
+ ValidateBindingPoint(param->BindingPoint(), address_space, "input param");
+ if (result != Success) {
+ AddError(param) << result.Failure();
+ }
}
} else {
if (param->BindingPoint().has_value()) {
@@ -1937,16 +2001,6 @@
"invariant can only decorate outputs iff they are also position builtins"),
CheckInvariantFunc<Function>(
"invariant can only decorate output members iff they are also position builtins"));
-
- if (func->Stage() != Function::PipelineStage::kUndefined) {
- CheckFunctionReturnAttributes(
- func,
- CheckDoesNotHaveBothLocationAndBuiltinFunc<Function>(
- "a builtin and location cannot be both declared for a function return"),
- CheckDoesNotHaveBothLocationAndBuiltinFunc<Function>(
- "a builtin and location cannot be both declared for a struct member"));
- }
-
// void needs to be filtered out, since it isn't constructible, but used in the IR when no
// return is specified.
if (DAWN_UNLIKELY(!func->ReturnType()->Is<core::type::Void>() &&
@@ -1968,20 +2022,13 @@
}
}
- if (func->Stage() == Function::PipelineStage::kFragment) {
- if (!func->ReturnType()->Is<core::type::Void>()) {
- CheckFunctionReturnAttributes(
- func,
- CheckHasLocationOrBuiltinFunc<Function>(
- "a non-void return for an entry point must have a "
- "builtin or location decoration"),
- CheckHasLocationOrBuiltinFunc<Function>(
- "members of struct used for returns of entry points must have a builtin or "
- "location decoration"));
- }
- }
-
if (func->Stage() != Function::PipelineStage::kUndefined) {
+ auto result = ValidateShaderIOAnnotations(func->ReturnType(), std::nullopt,
+ func->ReturnAttributes(), "return values");
+ if (result != Success) {
+ AddError(func) << result.Failure();
+ }
+
CheckFunctionReturnAttributesAndType(
func, CheckFrontFacingIfBoolFunc<Function>("entry point returns can not be bool"),
CheckFrontFacingIfBoolFunc<Function>("entry point return members can not be bool"));
@@ -2338,20 +2385,10 @@
if (var->Block() == mod_.root_block) {
if (mv->AddressSpace() == AddressSpace::kIn || mv->AddressSpace() == AddressSpace::kOut) {
- if (HasLocationAndBuiltin(var->Attributes())) {
- AddError(var)
- << "a builtin and location cannot be both declared for a module scope var";
- return;
- }
-
- if (auto* s = var->Result(0)->Type()->UnwrapPtrOrRef()->As<core::type::Struct>()) {
- for (auto* mem : s->Members()) {
- if (HasLocationAndBuiltin(mem->Attributes())) {
- AddError(var) << "a builtin and location cannot be both declared for a "
- "module scope var struct member";
- return;
- }
- }
+ auto result = ValidateShaderIOAnnotations(var->Result(0)->Type(), var->BindingPoint(),
+ var->Attributes(), "module scope variable");
+ if (result != Success) {
+ AddError(var) << result.Failure();
}
}
}
@@ -2384,6 +2421,70 @@
return Success;
}
+Result<SuccessType, std::string> Validator::ValidateShaderIOAnnotations(
+ const core::type::Type* ty,
+ const std::optional<struct BindingPoint>& binding_point,
+ const core::IOAttributes& attr,
+ const std::string& target_str) {
+ EnumSet<IOAnnotation> annotations;
+ // Since there is no entries in the set at this point, this should never fail.
+ TINT_ASSERT(AddIOAnnotationsFromIOAttributes(annotations, attr) == Success);
+ if (binding_point.has_value()) {
+ annotations.Add(IOAnnotation::kBindingPoint);
+ }
+ if (auto* mv = ty->As<core::type::MemoryView>()) {
+ if (mv->AddressSpace() == AddressSpace::kWorkgroup) {
+ annotations.Add(IOAnnotation::kWorkgroup);
+ }
+ }
+
+ // void being annotated should never occur
+ TINT_ASSERT(!ty->Is<core::type::Void>() || annotations.Empty());
+ if (ty->Is<core::type::Void>()) {
+ return Success;
+ }
+
+ if (auto* ty_struct = ty->UnwrapPtrOrRef()->As<core::type::Struct>()) {
+ for (const auto* mem : ty_struct->Members()) {
+ EnumSet<IOAnnotation> mem_annotations = annotations;
+ auto add_result = AddIOAnnotationsFromIOAttributes(mem_annotations, mem->Attributes());
+ if (add_result != Success) {
+ return target_str +
+ " struct member has same IO annotation, as top-level struct, '" +
+ ToString(add_result.Failure()) + "'";
+ }
+
+ if (capabilities_.Contains(Capability::kAllowPointersInStructures)) {
+ if (auto* mv = mem->Type()->As<core::type::MemoryView>()) {
+ if (mv->AddressSpace() == AddressSpace::kWorkgroup) {
+ mem_annotations.Add(IOAnnotation::kWorkgroup);
+ }
+ }
+ }
+
+ if (mem_annotations.Empty()) {
+ return target_str +
+ " struct members must have at least one IO annotation, e.g. a binding "
+ "point, a location, etc";
+ }
+
+ if (mem_annotations.Size() > 1) {
+ return target_str + " struct member has more than one IO annotation, " +
+ ToString(mem_annotations);
+ }
+ }
+ } else {
+ if (annotations.Empty()) {
+ return target_str +
+ " must have at least one IO annotation, e.g. a binding point, a location, etc";
+ }
+ if (annotations.Size() > 1) {
+ return target_str + " has more than one IO annotation, " + ToString(annotations);
+ }
+ }
+ return Success;
+}
+
void Validator::CheckLet(const Let* l) {
if (!CheckResultsAndOperands(l, Let::kNumResults, Let::kNumOperands)) {
return;
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 774ad8c..f1187cd 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -66,9 +66,7 @@
/// Builds and returns a basic 'vertex' entry point function, named @p name
Function* VertexEntryPoint(const std::string& name = "f") {
auto* f = b.Function(name, ty.vec4<f32>(), Function::PipelineStage::kVertex);
- IOAttributes attr;
- attr.builtin = BuiltinValue::kPosition;
- f->SetReturnAttributes(attr);
+ f->SetReturnBuiltin(BuiltinValue::kPosition);
return f;
}
@@ -78,10 +76,8 @@
const std::string& name,
BuiltinValue builtin,
const core::type::Type* type) {
- IOAttributes attr;
- attr.builtin = builtin;
auto* p = b.FunctionParam(name, type);
- p->SetAttributes(attr);
+ p->SetBuiltin(builtin);
func->AppendParam(p);
}
@@ -438,14 +434,12 @@
)");
}
-TEST_F(IR_ValidatorTest, Function_Param_BothLocationAndBuiltin) {
+TEST_F(IR_ValidatorTest, Function_Param_MultipleIOAnnotations) {
auto* f = FragmentEntryPoint("my_func");
auto* p = b.FunctionParam("my_param", ty.vec4<f32>());
- IOAttributes attr;
- attr.builtin = BuiltinValue::kPosition;
- attr.location = 0;
- p->SetAttributes(attr);
+ p->SetBuiltin(BuiltinValue::kPosition);
+ p->SetLocation(0);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
@@ -453,7 +447,7 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_EQ(res.Failure().reason.Str(),
- R"(:1:27 error: a builtin and location cannot be both declared for a param
+ R"(:1:27 error: input param has more than one IO annotation, [ @location, built-in ]
%my_func = @fragment func(%my_param:vec4<f32> [@location(0), @position]):void {
^^^^^^^^^^^^^^^^^^^
@@ -466,12 +460,12 @@
)");
}
-TEST_F(IR_ValidatorTest, Function_Param_Struct_BothLocationAndBuiltin) {
+TEST_F(IR_ValidatorTest, Function_Param_Struct_MultipleIOAnnotations) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr;
attr.builtin = BuiltinValue::kPosition;
- attr.location = 0;
+ attr.color = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.vec4<f32>(), attr},
@@ -483,14 +477,15 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
- EXPECT_EQ(res.Failure().reason.Str(),
- R"(:5:27 error: a builtin and location cannot be both declared for a struct member
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:5:27 error: input param struct member has more than one IO annotation, [ built-in, @color ]
%my_func = @fragment func(%my_param:MyStruct):void {
^^^^^^^^^^^^^^^^^^
note: # Disassembly
MyStruct = struct @align(16) {
- a:vec4<f32> @offset(0), @location(0), @builtin(position)
+ a:vec4<f32> @offset(0), @color(0), @builtin(position)
}
%my_func = @fragment func(%my_param:MyStruct):void {
@@ -501,6 +496,158 @@
)");
}
+TEST_F(IR_ValidatorTest, Function_Param_MissingIOAnnotations) {
+ auto* f = FragmentEntryPoint("my_func");
+
+ auto* p = b.FunctionParam("my_param", ty.vec4<f32>());
+ f->SetParams({p});
+
+ b.Append(f->Block(), [&] { b.Return(f); });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:1:27 error: input param must have at least one IO annotation, e.g. a binding point, a location, etc
+%my_func = @fragment func(%my_param:vec4<f32>):void {
+ ^^^^^^^^^^^^^^^^^^^
+
+note: # Disassembly
+%my_func = @fragment func(%my_param:vec4<f32>):void {
+ $B1: {
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Function_Param_Struct_MissingIOAnnotations) {
+ auto* f = ComputeEntryPoint("my_func");
+
+ auto* str_ty =
+ ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), ty.vec4<f32>(), {}},
+ });
+ auto* p = b.FunctionParam("my_param", str_ty);
+ f->SetParams({p});
+
+ b.Append(f->Block(), [&] { b.Return(f); });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:5:54 error: input param struct members must have at least one IO annotation, e.g. a binding point, a location, etc
+%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct):void {
+ ^^^^^^^^^^^^^^^^^^
+
+note: # Disassembly
+MyStruct = struct @align(16) {
+ a:vec4<f32> @offset(0)
+}
+
+%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct):void {
+ $B1: {
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Function_Param_Struct_DuplicateAnnotations) {
+ auto* f = ComputeEntryPoint("my_func");
+ IOAttributes attr;
+ attr.location = 0;
+ auto* str_ty =
+ ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), ty.vec4<f32>(), attr},
+ });
+ auto* p = b.FunctionParam("my_param", str_ty);
+ p->SetLocation(0);
+ f->SetParams({p});
+
+ b.Append(f->Block(), [&] { b.Return(f); });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:5:54 error: input param struct member has same IO annotation, as top-level struct, '@location'
+%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct [@location(0)]):void {
+ ^^^^^^^^^^^^^^^^^^
+
+note: # Disassembly
+MyStruct = struct @align(16) {
+ a:vec4<f32> @offset(0), @location(0)
+}
+
+%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct [@location(0)]):void {
+ $B1: {
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Function_Param_WorkgroupPlusOtherIOAnnotation) {
+ auto* f = ComputeEntryPoint("my_func");
+ auto* p = b.FunctionParam("my_param", ty.ptr<workgroup, i32>());
+ p->SetLocation(0);
+ f->SetParams({p});
+
+ b.Append(f->Block(), [&] { b.Return(f); });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:1:54 error: input param has more than one IO annotation, [ @location, <workgroup> ]
+%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:ptr<workgroup, i32, read_write> [@location(0)]):void {
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+note: # Disassembly
+%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:ptr<workgroup, i32, read_write> [@location(0)]):void {
+ $B1: {
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Function_Param_Struct_WorkgroupPlusOtherIOAnnotations) {
+ auto* f = ComputeEntryPoint("my_func");
+ IOAttributes attr;
+ attr.location = 0;
+ auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"),
+ {
+ {mod.symbols.New("a"), ty.ptr<workgroup, i32>(), attr},
+ });
+ auto* p = b.FunctionParam("my_param", str_ty);
+ f->SetParams({p});
+
+ b.Append(f->Block(), [&] { b.Return(f); });
+
+ auto res = ir::Validate(mod, Capabilities{Capability::kAllowPointersInStructures});
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:5:54 error: input param struct member has more than one IO annotation, [ @location, <workgroup> ]
+%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct):void {
+ ^^^^^^^^^^^^^^^^^^
+
+note: # Disassembly
+MyStruct = struct @align(1) {
+ a:ptr<workgroup, i32, read_write> @offset(0), @location(0)
+}
+
+%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct):void {
+ $B1: {
+ ret
+ }
+}
+)");
+}
+
TEST_F(IR_ValidatorTest, Function_ParameterWithConstructibleType) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", ty.u32());
@@ -568,10 +715,8 @@
auto* f = b.Function("my_func", ty.void_(), Function::PipelineStage::kFragment);
auto* p = b.FunctionParam("my_param", ty.vec4<f32>());
- IOAttributes attr;
- attr.builtin = BuiltinValue::kPosition;
- attr.invariant = true;
- p->SetAttributes(attr);
+ p->SetInvariant(true);
+ p->SetBuiltin(BuiltinValue::kPosition);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
@@ -583,9 +728,7 @@
TEST_F(IR_ValidatorTest, Function_Param_InvariantWithoutPosition) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", ty.vec4<f32>());
- IOAttributes attr;
- attr.invariant = true;
- p->SetAttributes(attr);
+ p->SetInvariant(true);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
@@ -686,19 +829,16 @@
)");
}
-TEST_F(IR_ValidatorTest, Function_Return_BothLocationAndBuiltin) {
+TEST_F(IR_ValidatorTest, Function_Return_MultipleIOAnnotations) {
auto* f = VertexEntryPoint("my_func");
- IOAttributes attr;
- attr.builtin = BuiltinValue::kPosition;
- attr.location = 0;
- f->SetReturnAttributes(attr);
+ f->SetReturnLocation(0);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_EQ(res.Failure().reason.Str(),
- R"(:1:1 error: a builtin and location cannot be both declared for a function return
+ R"(:1:1 error: return values has more than one IO annotation, [ @location, built-in ]
%my_func = @vertex func():vec4<f32> [@location(0), @position] {
^^^^^^^^
@@ -711,7 +851,7 @@
)");
}
-TEST_F(IR_ValidatorTest, Function_Return_Struct_BothLocationAndBuiltin) {
+TEST_F(IR_ValidatorTest, Function_Return_Struct_MultipleIOAnnotations) {
IOAttributes attr;
attr.builtin = BuiltinValue::kPosition;
attr.location = 0;
@@ -724,8 +864,9 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
- EXPECT_EQ(res.Failure().reason.Str(),
- R"(:5:1 error: a builtin and location cannot be both declared for a struct member
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:5:1 error: return values struct member has more than one IO annotation, [ @location, built-in ]
%my_func = @vertex func():MyStruct {
^^^^^^^^
@@ -742,7 +883,7 @@
)");
}
-TEST_F(IR_ValidatorTest, Function_Return_NonVoid_MissingLocationAndBuiltin) {
+TEST_F(IR_ValidatorTest, Function_Return_NonVoid_MissingIOAnnotations) {
auto* f = b.Function("my_func", ty.f32(), Function::PipelineStage::kFragment);
b.Append(f->Block(), [&] { b.Unreachable(); });
@@ -751,7 +892,7 @@
ASSERT_NE(res, Success);
EXPECT_EQ(
res.Failure().reason.Str(),
- R"(:1:1 error: a non-void return for an entry point must have a builtin or location decoration
+ R"(:1:1 error: return values must have at least one IO annotation, e.g. a binding point, a location, etc
%my_func = @fragment func():f32 {
^^^^^^^^
@@ -764,7 +905,7 @@
)");
}
-TEST_F(IR_ValidatorTest, Function_Return_NonVoid_Struct_MissingLocationAndBuiltin) {
+TEST_F(IR_ValidatorTest, Function_Return_NonVoid_Struct_MissingIOAnnotations) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), {}},
});
@@ -776,7 +917,7 @@
ASSERT_NE(res, Success);
EXPECT_EQ(
res.Failure().reason.Str(),
- R"(:5:1 error: members of struct used for returns of entry points must have a builtin or location decoration
+ R"(:5:1 error: return values struct members must have at least one IO annotation, e.g. a binding point, a location, etc
%my_func = @fragment func():MyStruct {
^^^^^^^^
@@ -794,12 +935,9 @@
}
TEST_F(IR_ValidatorTest, Function_Return_InvariantWithPosition) {
- IOAttributes attr;
- attr.builtin = BuiltinValue::kPosition;
- attr.invariant = true;
-
auto* f = b.Function("my_func", ty.vec4<f32>(), Function::PipelineStage::kVertex);
- f->SetReturnAttributes(attr);
+ f->SetReturnBuiltin(BuiltinValue::kPosition);
+ f->SetReturnInvariant(true);
b.Append(f->Block(), [&] { b.Unreachable(); });
@@ -808,11 +946,8 @@
}
TEST_F(IR_ValidatorTest, Function_Return_InvariantWithoutPosition) {
- IOAttributes attr;
- attr.invariant = true;
-
auto* f = b.Function("my_func", ty.vec4<f32>());
- f->SetReturnAttributes(attr);
+ f->SetReturnInvariant(true);
b.Append(f->Block(), [&] { b.Unreachable(); });
@@ -973,6 +1108,7 @@
TEST_F(IR_ValidatorTest, Function_Compute_NonVoidReturn) {
auto* f = b.Function("my_func", ty.f32(), core::ir::Function::PipelineStage::kCompute);
f->SetWorkgroupSize(b.Constant(1_u), b.Constant(1_u), b.Constant(1_u));
+ f->SetReturnLocation(0);
b.Append(f->Block(), [&] { b.Unreachable(); });
@@ -980,11 +1116,11 @@
ASSERT_NE(res, Success);
EXPECT_EQ(res.Failure().reason.Str(),
R"(:1:1 error: compute entry point must not have a return type
-%my_func = @compute @workgroup_size(1u, 1u, 1u) func():f32 {
+%my_func = @compute @workgroup_size(1u, 1u, 1u) func():f32 [@location(0)] {
^^^^^^^^
note: # Disassembly
-%my_func = @compute @workgroup_size(1u, 1u, 1u) func():f32 {
+%my_func = @compute @workgroup_size(1u, 1u, 1u) func():f32 [@location(0)] {
$B1: {
unreachable
}
@@ -1229,17 +1365,19 @@
TEST_F(IR_ValidatorTest, Function_Vertex_MissingPosition) {
auto* f = b.Function("my_func", ty.vec4<f32>(), Function::PipelineStage::kVertex);
+ f->SetReturnLocation(0);
+
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_EQ(res.Failure().reason.Str(),
R"(:1:1 error: position must be declared for vertex entry point output
-%my_func = @vertex func():vec4<f32> {
+%my_func = @vertex func():vec4<f32> [@location(0)] {
^^^^^^^^
note: # Disassembly
-%my_func = @vertex func():vec4<f32> {
+%my_func = @vertex func():vec4<f32> [@location(0)] {
$B1: {
unreachable
}
@@ -1249,18 +1387,20 @@
TEST_F(IR_ValidatorTest, Function_NonFragment_BoolInput) {
auto* f = VertexEntryPoint();
- f->AppendParam(b.FunctionParam("invalid", ty.bool_()));
+ auto* p = b.FunctionParam("invalid", ty.bool_());
+ p->SetLocation(0);
+ f->AppendParam(p);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_EQ(res.Failure().reason.Str(),
R"(:1:19 error: entry point params can only be a bool for fragment shaders
-%f = @vertex func(%invalid:bool):vec4<f32> [@position] {
+%f = @vertex func(%invalid:bool [@location(0)]):vec4<f32> [@position] {
^^^^^^^^^^^^^
note: # Disassembly
-%f = @vertex func(%invalid:bool):vec4<f32> [@position] {
+%f = @vertex func(%invalid:bool [@location(0)]):vec4<f32> [@position] {
$B1: {
unreachable
}
@@ -1270,7 +1410,9 @@
TEST_F(IR_ValidatorTest, Function_NonFragment_BoolOutput) {
auto* f = VertexEntryPoint();
- AddReturn(f, "invalid", ty.bool_());
+ IOAttributes attr;
+ attr.location = 0;
+ AddReturn(f, "invalid", ty.bool_(), attr);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
@@ -1283,7 +1425,7 @@
note: # Disassembly
OutputStruct = struct @align(16) {
pos:vec4<f32> @offset(0), @builtin(position)
- invalid:bool @offset(16)
+ invalid:bool @offset(16), @location(0)
}
%f = @vertex func():OutputStruct {
@@ -1296,7 +1438,9 @@
TEST_F(IR_ValidatorTest, Function_Fragment_BoolInputWithoutFrontFacing) {
auto* f = FragmentEntryPoint();
- f->AppendParam(b.FunctionParam("invalid", ty.bool_()));
+ auto* p = b.FunctionParam("invalid", ty.bool_());
+ p->SetLocation(0);
+ f->AppendParam(p);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
@@ -1304,11 +1448,11 @@
EXPECT_EQ(
res.Failure().reason.Str(),
R"(:1:21 error: fragment entry point params can only be a bool if decorated with @builtin(front_facing)
-%f = @fragment func(%invalid:bool):void {
+%f = @fragment func(%invalid:bool [@location(0)]):void {
^^^^^^^^^^^^^
note: # Disassembly
-%f = @fragment func(%invalid:bool):void {
+%f = @fragment func(%invalid:bool [@location(0)]):void {
$B1: {
unreachable
}
@@ -1343,6 +1487,9 @@
auto* f = ComputeEntryPoint();
auto* v = b.Var(ty.ptr(AddressSpace::kOut, ty.bool_(), core::Access::kReadWrite));
+ IOAttributes attr;
+ attr.location = 0;
+ v->SetAttributes(attr);
mod.root_block->Append(v);
b.Append(f->Block(), [&] {
@@ -1361,7 +1508,7 @@
note: # Disassembly
$B1: { # root
- %1:ptr<__out, bool, read_write> = var
+ %1:ptr<__out, bool, read_write> = var @location(0)
}
%f = @compute @workgroup_size(1u, 1u, 1u) func():void {
@@ -1377,6 +1524,9 @@
auto* f = FragmentEntryPoint();
auto* invalid = b.Var("invalid", AddressSpace::kIn, ty.bool_());
+ IOAttributes attr;
+ attr.location = 0;
+ invalid->SetAttributes(attr);
mod.root_block->Append(invalid);
b.Append(f->Block(), [&] {
@@ -1396,7 +1546,7 @@
note: # Disassembly
$B1: { # root
- %invalid:ptr<__in, bool, read> = var
+ %invalid:ptr<__in, bool, read> = var @location(0)
}
%f = @fragment func():void {
@@ -5200,7 +5350,7 @@
)");
}
-TEST_F(IR_ValidatorTest, Var_IOBothLocationAndBuiltin) {
+TEST_F(IR_ValidatorTest, Var_MultipleIOAnnotations) {
auto* v = b.Var<AddressSpace::kIn, vec4<f32>>();
IOAttributes attr;
attr.builtin = BuiltinValue::kPosition;
@@ -5212,7 +5362,7 @@
ASSERT_NE(res, Success);
EXPECT_EQ(
res.Failure().reason.Str(),
- R"(:2:35 error: var: a builtin and location cannot be both declared for a module scope var
+ R"(:2:35 error: var: module scope variable has more than one IO annotation, [ @location, built-in ]
%1:ptr<__in, vec4<f32>, read> = var @location(0) @builtin(position)
^^^
@@ -5228,10 +5378,10 @@
)");
}
-TEST_F(IR_ValidatorTest, Var_Struct_IOBothLocationAndBuiltin) {
+TEST_F(IR_ValidatorTest, Var_Struct_MultipleIOAnnotations) {
IOAttributes attr;
attr.builtin = BuiltinValue::kPosition;
- attr.location = 0;
+ attr.color = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
@@ -5244,7 +5394,7 @@
ASSERT_NE(res, Success);
EXPECT_EQ(
res.Failure().reason.Str(),
- R"(:6:41 error: var: a builtin and location cannot be both declared for a module scope var struct member
+ R"(:6:41 error: var: module scope variable struct member has more than one IO annotation, [ built-in, @color ]
%1:ptr<__out, MyStruct, read_write> = var
^^^
@@ -5254,7 +5404,62 @@
note: # Disassembly
MyStruct = struct @align(4) {
- a:f32 @offset(0), @location(0), @builtin(position)
+ a:f32 @offset(0), @color(0), @builtin(position)
+}
+
+$B1: { # root
+ %1:ptr<__out, MyStruct, read_write> = var
+}
+
+)");
+}
+
+TEST_F(IR_ValidatorTest, Var_MissingIOAnnotations) {
+ auto* v = b.Var<AddressSpace::kIn, vec4<f32>>();
+ mod.root_block->Append(v);
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:2:35 error: var: module scope variable must have at least one IO annotation, e.g. a binding point, a location, etc
+ %1:ptr<__in, vec4<f32>, read> = var
+ ^^^
+
+:1:1 note: in block
+$B1: { # root
+^^^
+
+note: # Disassembly
+$B1: { # root
+ %1:ptr<__in, vec4<f32>, read> = var
+}
+
+)");
+}
+
+TEST_F(IR_ValidatorTest, Var_Struct_MissingIOAnnotations) {
+ auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), ty.f32(), {}},
+ });
+ auto* v = b.Var(ty.ptr(AddressSpace::kOut, str_ty, read_write));
+ mod.root_block->Append(v);
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:6:41 error: var: module scope variable struct members must have at least one IO annotation, e.g. a binding point, a location, etc
+ %1:ptr<__out, MyStruct, read_write> = var
+ ^^^
+
+:5:1 note: in block
+$B1: { # root
+^^^
+
+note: # Disassembly
+MyStruct = struct @align(4) {
+ a:f32 @offset(0)
}
$B1: { # root
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc b/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc
index 1d2db45..b4f1e84 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc
+++ b/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc
@@ -1386,7 +1386,10 @@
b.ir.root_block->Append(var);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ core::IOAttributes index_attr;
+ index_attr.location = 0;
auto index = b.FunctionParam(ty.u32());
+ index->SetAttributes(index_attr);
func->SetParams({index});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<storage>(ty.atomic<i32>()), var, 0_u, index, 1_u, index);
@@ -1408,7 +1411,7 @@
%v:ptr<storage, S2, read_write> = var @binding_point(0, 0)
}
-%foo = @fragment func(%3:u32):void {
+%foo = @fragment func(%3:u32 [@location(0)]):void {
$B2: {
%4:ptr<storage, atomic<i32>, read_write> = access %v, 0u, %3, 1u, %3
%5:void = atomicStore %4, 123i
@@ -1432,7 +1435,7 @@
%v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
}
-%foo = @fragment func(%3:u32):void {
+%foo = @fragment func(%3:u32 [@location(0)]):void {
$B2: {
%4:u32 = mul %3, 32u
%5:u32 = mul %3, 4u
@@ -1576,7 +1579,10 @@
b.ir.root_block->Append(var);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ core::IOAttributes index_attr;
+ index_attr.location = 0;
auto index = b.FunctionParam(ty.u32());
+ index->SetAttributes(index_attr);
func->SetParams({index});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<storage>(ty.atomic<i32>()), var, 0_u, index, 1_u, index);
@@ -1598,7 +1604,7 @@
%v:ptr<storage, S2, read_write> = var @binding_point(0, 0)
}
-%foo = @fragment func(%3:u32):void {
+%foo = @fragment func(%3:u32 [@location(0)]):void {
$B2: {
%4:ptr<storage, atomic<i32>, read_write> = access %v, 0u, %3, 1u, %3
%5:i32 = atomicLoad %4
@@ -1623,7 +1629,7 @@
%v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
}
-%foo = @fragment func(%3:u32):void {
+%foo = @fragment func(%3:u32 [@location(0)]):void {
$B2: {
%4:u32 = mul %3, 32u
%5:u32 = mul %3, 4u
@@ -1773,7 +1779,10 @@
b.ir.root_block->Append(var);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ core::IOAttributes index_attr;
+ index_attr.location = 0;
auto index = b.FunctionParam(ty.u32());
+ index->SetAttributes(index_attr);
func->SetParams({index});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<storage>(ty.atomic<i32>()), var, 0_u, index, 1_u, index);
@@ -1795,7 +1804,7 @@
%v:ptr<storage, S2, read_write> = var @binding_point(0, 0)
}
-%foo = @fragment func(%3:u32):void {
+%foo = @fragment func(%3:u32 [@location(0)]):void {
$B2: {
%4:ptr<storage, atomic<i32>, read_write> = access %v, 0u, %3, 1u, %3
%5:i32 = atomicSub %4, 123i
@@ -1820,7 +1829,7 @@
%v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
}
-%foo = @fragment func(%3:u32):void {
+%foo = @fragment func(%3:u32 [@location(0)]):void {
$B2: {
%4:u32 = mul %3, 32u
%5:u32 = mul %3, 4u
@@ -1985,7 +1994,10 @@
b.ir.root_block->Append(var);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ core::IOAttributes index_attr;
+ index_attr.location = 0;
auto index = b.FunctionParam(ty.u32());
+ index->SetAttributes(index_attr);
func->SetParams({index});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<storage>(ty.atomic<i32>()), var, 0_u, index, 1_u, index);
@@ -2013,7 +2025,7 @@
%v:ptr<storage, S2, read_write> = var @binding_point(0, 0)
}
-%foo = @fragment func(%3:u32):void {
+%foo = @fragment func(%3:u32 [@location(0)]):void {
$B2: {
%4:ptr<storage, atomic<i32>, read_write> = access %v, 0u, %3, 1u, %3
%5:__atomic_compare_exchange_result_i32 = atomicCompareExchangeWeak %4, 123i, 345i
@@ -2043,7 +2055,7 @@
%v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
}
-%foo = @fragment func(%3:u32):void {
+%foo = @fragment func(%3:u32 [@location(0)]):void {
$B2: {
%4:u32 = mul %3, 32u
%5:u32 = mul %3, 4u
@@ -2274,6 +2286,7 @@
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
auto index = b.FunctionParam(ty.u32());
+ index->SetLocation(0);
func->SetParams({index});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<storage>(ty.atomic<u32>()), var, 0_u, index, 0_u, index);
@@ -2294,7 +2307,7 @@
%v:ptr<storage, S2, read_write> = var @binding_point(0, 0)
}
-%foo = @fragment func(%3:u32):void {
+%foo = @fragment func(%3:u32 [@location(0)]):void {
$B2: {
%4:ptr<storage, atomic<u32>, read_write> = access %v, 0u, %3, 0u, %3
%5:u32 = )" +
@@ -2319,7 +2332,7 @@
%v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
}
-%foo = @fragment func(%3:u32):void {
+%foo = @fragment func(%3:u32 [@location(0)]):void {
$B2: {
%4:u32 = mul %3, 12u
%5:u32 = mul %3, 4u
diff --git a/src/tint/lang/msl/writer/raise/simd_ballot_test.cc b/src/tint/lang/msl/writer/raise/simd_ballot_test.cc
index 3e8e1f7..c026411 100644
--- a/src/tint/lang/msl/writer/raise/simd_ballot_test.cc
+++ b/src/tint/lang/msl/writer/raise/simd_ballot_test.cc
@@ -44,6 +44,9 @@
TEST_F(MslWriter_SimdBallotTest, SimdBallot_WithUserDeclaredSubgroupSize) {
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
auto* subgroup_size = b.FunctionParam("user_subgroup_size", ty.u32());
+ core::IOAttributes attr;
+ attr.location = 0;
+ subgroup_size->SetAttributes(attr);
func->SetParams({subgroup_size});
b.Append(func->Block(), [&] { //
b.Call<vec4<u32>>(core::BuiltinFn::kSubgroupBallot, true);
@@ -51,7 +54,7 @@
});
auto* src = R"(
-%foo = @fragment func(%user_subgroup_size:u32):void {
+%foo = @fragment func(%user_subgroup_size:u32 [@location(0)]):void {
$B1: {
%3:vec4<u32> = subgroupBallot true
ret
@@ -65,7 +68,7 @@
%tint_subgroup_size_mask:ptr<private, vec2<u32>, read_write> = var
}
-%foo = @fragment func(%user_subgroup_size:u32, %tint_subgroup_size:u32 [@subgroup_size]):void {
+%foo = @fragment func(%user_subgroup_size:u32 [@location(0)], %tint_subgroup_size:u32 [@subgroup_size]):void {
$B2: {
%5:bool = gt %tint_subgroup_size, 32u
%6:u32 = sub 32u, %tint_subgroup_size
@@ -160,6 +163,9 @@
auto* ep1 = b.Function("ep1", ty.void_(), core::ir::Function::PipelineStage::kFragment);
auto* subgroup_size = b.FunctionParam("user_subgroup_size", ty.u32());
+ core::IOAttributes attr;
+ attr.location = 0;
+ subgroup_size->SetAttributes(attr);
ep1->SetParams({subgroup_size});
b.Append(ep1->Block(), [&] { //
b.Call<vec4<u32>>(foo, true);
@@ -179,7 +185,7 @@
ret %3
}
}
-%ep1 = @fragment func(%user_subgroup_size:u32):void {
+%ep1 = @fragment func(%user_subgroup_size:u32 [@location(0)]):void {
$B2: {
%6:vec4<u32> = call %foo, true
ret
@@ -205,7 +211,7 @@
ret %4
}
}
-%ep1 = @fragment func(%user_subgroup_size:u32, %tint_subgroup_size:u32 [@subgroup_size]):void {
+%ep1 = @fragment func(%user_subgroup_size:u32 [@location(0)], %tint_subgroup_size:u32 [@subgroup_size]):void {
$B3: {
%9:bool = gt %tint_subgroup_size, 32u
%10:u32 = sub 32u, %tint_subgroup_size
diff --git a/src/tint/lang/spirv/writer/raise/merge_return_test.cc b/src/tint/lang/spirv/writer/raise/merge_return_test.cc
index 860f1b9..3c6446c 100644
--- a/src/tint/lang/spirv/writer/raise/merge_return_test.cc
+++ b/src/tint/lang/spirv/writer/raise/merge_return_test.cc
@@ -213,6 +213,9 @@
TEST_F(SpirvWriter_MergeReturnTest, NoModify_EntryPoint_IfElse_OneSideReturns) {
auto* cond = b.FunctionParam(ty.u32());
+ core::IOAttributes attr;
+ attr.location = 0;
+ cond->SetAttributes(attr);
auto* func = b.ComputeFunction("entrypointfunction", 2_u, 3_u, 4_u);
func->SetParams({cond});
b.Append(func->Block(), [&] {
@@ -224,7 +227,7 @@
});
auto* src = R"(
-%entrypointfunction = @compute @workgroup_size(2u, 3u, 4u) func(%2:u32):void {
+%entrypointfunction = @compute @workgroup_size(2u, 3u, 4u) func(%2:u32 [@location(0)]):void {
$B1: {
%3:bool = eq %2, 0u
if %3 [t: $B2, f: $B3] { # if_1
diff --git a/src/tint/lang/spirv/writer/raise/shader_io_test.cc b/src/tint/lang/spirv/writer/raise/shader_io_test.cc
index 4d09417..417b406 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io_test.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io_test.cc
@@ -1528,7 +1528,7 @@
auto* in1 = b.FunctionParam("in1", ty.f16());
auto* in2 = b.FunctionParam("in2", ty.vec4<f16>());
in1->SetLocation(1);
- in1->SetLocation(2);
+ in2->SetLocation(2);
auto* func = b.Function("main", outputs, core::ir::Function::PipelineStage::kFragment);
func->SetParams({in1, in2});
b.Append(func->Block(), [&] { //
@@ -1541,7 +1541,7 @@
out2:vec4<f16> @offset(8), @location(2)
}
-%main = @fragment func(%in1:f16 [@location(2)], %in2:vec4<f16>):Outputs {
+%main = @fragment func(%in1:f16 [@location(1)], %in2:vec4<f16> [@location(2)]):Outputs {
$B1: {
%4:Outputs = construct %in1, %in2
ret %4
@@ -1557,8 +1557,8 @@
}
$B1: { # root
- %main_loc2_Input:ptr<__in, f16, read> = var @location(2)
- %main_Input:ptr<__in, vec4<f16>, read> = var
+ %main_loc1_Input:ptr<__in, f16, read> = var @location(1)
+ %main_loc2_Input:ptr<__in, vec4<f16>, read> = var @location(2)
%main_loc1_Output:ptr<__out, f16, write> = var @location(1)
%main_loc2_Output:ptr<__out, vec4<f16>, write> = var @location(2)
}
@@ -1571,8 +1571,8 @@
}
%main = @fragment func():void {
$B3: {
- %10:f16 = load %main_loc2_Input
- %11:vec4<f16> = load %main_Input
+ %10:f16 = load %main_loc1_Input
+ %11:vec4<f16> = load %main_loc2_Input
%12:Outputs = call %main_inner, %10, %11
%13:f16 = access %12, 0u
store %main_loc1_Output, %13
@@ -1622,7 +1622,7 @@
auto* in1 = b.FunctionParam("in1", ty.f16());
auto* in2 = b.FunctionParam("in2", ty.vec4<f16>());
in1->SetLocation(1);
- in1->SetLocation(2);
+ in2->SetLocation(2);
auto* func = b.Function("main", outputs, core::ir::Function::PipelineStage::kFragment);
func->SetParams({in1, in2});
b.Append(func->Block(), [&] { //
@@ -1635,7 +1635,7 @@
out2:vec4<f16> @offset(8), @location(2)
}
-%main = @fragment func(%in1:f16 [@location(2)], %in2:vec4<f16>):Outputs {
+%main = @fragment func(%in1:f16 [@location(1)], %in2:vec4<f16> [@location(2)]):Outputs {
$B1: {
%4:Outputs = construct %in1, %in2
ret %4
@@ -1651,8 +1651,8 @@
}
$B1: { # root
- %main_loc2_Input:ptr<__in, f32, read> = var @location(2)
- %main_Input:ptr<__in, vec4<f32>, read> = var
+ %main_loc1_Input:ptr<__in, f32, read> = var @location(1)
+ %main_loc2_Input:ptr<__in, vec4<f32>, read> = var @location(2)
%main_loc1_Output:ptr<__out, f32, write> = var @location(1)
%main_loc2_Output:ptr<__out, vec4<f32>, write> = var @location(2)
}
@@ -1665,9 +1665,9 @@
}
%main = @fragment func():void {
$B3: {
- %10:f32 = load %main_loc2_Input
+ %10:f32 = load %main_loc1_Input
%11:f16 = convert %10
- %12:vec4<f32> = load %main_Input
+ %12:vec4<f32> = load %main_loc2_Input
%13:vec4<f16> = convert %12
%14:Outputs = call %main_inner, %11, %13
%15:f16 = access %14, 0u