[tint][ir] Validate break_if value types match body block parameters / loop results
Change-Id: I6108d84cfe043f8bf7170a26495523e959a9f8a7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/188981
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/core/ir/builder.h b/src/tint/lang/core/ir/builder.h
index 5fbbb14..240afc1 100644
--- a/src/tint/lang/core/ir/builder.h
+++ b/src/tint/lang/core/ir/builder.h
@@ -1406,6 +1406,15 @@
return BlockParam(name, type);
}
+ /// Creates a new `BlockParam`
+ /// @tparam TYPE the parameter type
+ /// @returns the value
+ template <typename TYPE>
+ ir::BlockParam* BlockParam() {
+ auto* type = ir.Types().Get<TYPE>();
+ return BlockParam(type);
+ }
+
/// Creates a new `FunctionParam`
/// @param type the parameter type
/// @returns the value
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 504d50e..0f3010f 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -27,9 +27,11 @@
#include "src/tint/lang/core/ir/validator.h"
+#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
+#include <string_view>
#include <utility>
#include "src/tint/lang/core/intrinsic/table.h"
@@ -81,8 +83,11 @@
#include "src/tint/utils/containers/predicates.h"
#include "src/tint/utils/containers/reverse.h"
#include "src/tint/utils/containers/transform.h"
+#include "src/tint/utils/diagnostic/diagnostic.h"
#include "src/tint/utils/ice/ice.h"
#include "src/tint/utils/macros/defer.h"
+#include "src/tint/utils/result/result.h"
+#include "src/tint/utils/rtti/castable.h"
#include "src/tint/utils/rtti/switch.h"
#include "src/tint/utils/text/styled_text.h"
#include "src/tint/utils/text/text_style.h"
@@ -240,15 +245,57 @@
/// @param src the source lines to highlight
diag::Diagnostic& AddNote(Source src = {});
- /// Adds a note to the diagnostics highlighting where the value was declared, if it has a source
+ /// Adds a note to the diagnostics highlighting where the value instruction or block is
+ /// declared, if it has a source location.
+ /// @param decl the value instruction or block
+ void AddDeclarationNote(const CastableBase* decl);
+
+ /// Adds a note to the diagnostics highlighting where the block is declared, if it has a source
/// location.
- /// @param value the value
- void AddDeclarationNote(const Value* value);
+ /// @param block the block
+ void AddDeclarationNote(const Block* block);
+
+ /// Adds a note to the diagnostics highlighting where the block parameter is declared, if it
+ /// has a source location.
+ /// @param param the block parameter
+ void AddDeclarationNote(const BlockParam* param);
+
+ /// Adds a note to the diagnostics highlighting where the function is declared, if it has a
+ /// source location.
+ /// @param fn the function
+ void AddDeclarationNote(const Function* fn);
+
+ /// Adds a note to the diagnostics highlighting where the function parameter is declared, if it
+ /// has a source location.
+ /// @param param the function parameter
+ void AddDeclarationNote(const FunctionParam* param);
+
+ /// Adds a note to the diagnostics highlighting where the instruction is declared, if it has a
+ /// source location.
+ /// @param inst the inst
+ void AddDeclarationNote(const Instruction* inst);
+
+ /// Adds a note to the diagnostics highlighting where instruction result was declared, if it has
+ /// a source location.
+ /// @param res the res
+ void AddDeclarationNote(const InstructionResult* res);
+
+ /// @param decl the value, instruction or block to get the name for
+ /// @returns the styled name for the given value, instruction or block
+ StyledText NameOf(const CastableBase* decl);
/// @param v the value to get the name for
- /// @returns the name for the given value
+ /// @returns the styled name for the given value
StyledText NameOf(const Value* v);
+ /// @param inst the instruction to get the name for
+ /// @returns the styled name for the given instruction
+ StyledText NameOf(const Instruction* inst);
+
+ /// @param block the block to get the name for
+ /// @returns the styled name for the given block
+ StyledText NameOf(const Block* block);
+
/// Checks the given operand is not null
/// @param inst the instruction
/// @param operand the operand
@@ -327,6 +374,10 @@
/// @param b the terminator to validate
void CheckTerminator(const Terminator* b);
+ /// Validates the break if instruction
+ /// @param b the break if to validate
+ void CheckBreakIf(const BreakIf* b);
+
/// Validates the continue instruction
/// @param c the continue to validate
void CheckContinue(const Continue* c);
@@ -377,6 +428,19 @@
/// @param s the store vector element to validate
void CheckStoreVectorElement(const StoreVectorElement* s);
+ /// Validates that the number and types of the source instruction operands match the target's
+ /// values.
+ /// @param source_inst the source instruction
+ /// @param source_operand_offset the index of the first operand of the source instruction
+ /// @param source_operand_count the number of operands of the source instruction
+ /// @param target the receiver of the operand values
+ /// @param target_values the receiver of the operand values
+ void CheckOperandsMatchTarget(const Instruction* source_inst,
+ size_t source_operand_offset,
+ size_t source_operand_count,
+ const CastableBase* target,
+ VectorRef<const Value*> target_values);
+
/// @param inst the instruction
/// @param idx the operand index
/// @returns the vector pointer type for the given instruction operand
@@ -574,39 +638,83 @@
return diag;
}
-void Validator::AddDeclarationNote(const Value* value) {
+void Validator::AddDeclarationNote(const CastableBase* decl) {
tint::Switch(
- value, //
- [&](const InstructionResult* res) {
- if (auto* inst = res->Instruction()) {
- auto results = inst->Results();
- for (size_t i = 0; i < results.Length(); i++) {
- if (results[i] == value) {
- AddResultNote(res->Instruction(), i) << NameOf(value) << " declared here";
- return;
- }
- }
+ decl, //
+ [&](const Block* block) { AddDeclarationNote(block); },
+ [&](const BlockParam* param) { AddDeclarationNote(param); },
+ [&](const Function* fn) { AddDeclarationNote(fn); },
+ [&](const FunctionParam* param) { AddDeclarationNote(param); },
+ [&](const Instruction* inst) { AddDeclarationNote(inst); },
+ [&](const InstructionResult* res) { AddDeclarationNote(res); });
+}
+
+void Validator::AddDeclarationNote(const Block* block) {
+ auto src = Disassembly().BlockSource(block);
+ if (src.file) {
+ AddNote(src) << NameOf(block) << " declared here";
+ }
+}
+
+void Validator::AddDeclarationNote(const BlockParam* param) {
+ auto src = Disassembly().BlockParamSource(param);
+ if (src.file) {
+ AddNote(src) << NameOf(param) << " declared here";
+ }
+}
+
+void Validator::AddDeclarationNote(const Function* fn) {
+ AddNote(fn) << NameOf(fn) << " declared here";
+}
+
+void Validator::AddDeclarationNote(const FunctionParam* param) {
+ auto src = Disassembly().FunctionParamSource(param);
+ if (src.file) {
+ AddNote(src) << NameOf(param) << " declared here";
+ }
+}
+
+void Validator::AddDeclarationNote(const Instruction* inst) {
+ auto src = Disassembly().InstructionSource(inst);
+ if (src.file) {
+ AddNote(src) << NameOf(inst) << " declared here";
+ }
+}
+
+void Validator::AddDeclarationNote(const InstructionResult* res) {
+ if (auto* inst = res->Instruction()) {
+ auto results = inst->Results();
+ for (size_t i = 0; i < results.Length(); i++) {
+ if (results[i] == res) {
+ AddResultNote(res->Instruction(), i) << NameOf(res) << " declared here";
+ return;
}
- },
- [&](const FunctionParam* param) {
- auto src = Disassembly().FunctionParamSource(param);
- if (src.file) {
- AddNote(src) << NameOf(value) << " declared here";
- }
- },
- [&](const BlockParam* param) {
- auto src = Disassembly().BlockParamSource(param);
- if (src.file) {
- AddNote(src) << NameOf(value) << " declared here";
- }
- },
- [&](const Function* fn) { AddNote(fn) << NameOf(value) << " declared here"; });
+ }
+ }
+}
+
+StyledText Validator::NameOf(const CastableBase* decl) {
+ return tint::Switch(
+ decl, //
+ [&](const Value* value) { return NameOf(value); },
+ [&](const Instruction* inst) { return NameOf(inst); },
+ [&](const Block* block) { return NameOf(block); }, //
+ TINT_ICE_ON_NO_MATCH);
}
StyledText Validator::NameOf(const Value* value) {
return Disassembly().NameOf(value);
}
+StyledText Validator::NameOf(const Instruction* inst) {
+ return StyledText{} << style::Instruction(inst->FriendlyName());
+}
+
+StyledText Validator::NameOf(const Block* block) {
+ return StyledText{} << style::Instruction(block->Parent()->FriendlyName()) << " block "
+ << Disassembly().NameOf(block);
+}
+
void Validator::CheckOperandNotNull(const Instruction* inst, const ir::Value* operand, size_t idx) {
if (operand == nullptr) {
AddError(inst, idx) << "operand is undefined";
@@ -1160,7 +1268,7 @@
tint::Switch(
b, //
- [&](const ir::BreakIf*) {}, //
+ [&](const ir::BreakIf* i) { CheckBreakIf(i); }, //
[&](const ir::Continue* c) { CheckContinue(c); }, //
[&](const ir::Exit* e) { CheckExit(e); }, //
[&](const ir::NextIteration* n) { CheckNextIteration(n); }, //
@@ -1174,6 +1282,28 @@
}
}
+void Validator::CheckBreakIf(const BreakIf* b) {
+ auto* loop = b->Loop();
+ if (loop == nullptr) {
+ AddError(b) << "has no associated loop";
+ return;
+ }
+
+ if (loop->Continuing() != b->Block()) {
+ AddError(b) << "must only be called directly from loop continuing";
+ }
+
+ auto next_iter_values = b->NextIterValues();
+ if (auto* body = loop->Body()) {
+ CheckOperandsMatchTarget(b, b->ArgsOperandOffset(), next_iter_values.Length(), body,
+ body->Params());
+ }
+
+ auto exit_values = b->ExitValues();
+ CheckOperandsMatchTarget(b, b->ArgsOperandOffset() + next_iter_values.Length(),
+ exit_values.Length(), loop, loop->Results());
+}
+
void Validator::CheckContinue(const Continue* c) {
auto* loop = c->Loop();
if (loop == nullptr) {
@@ -1395,6 +1525,37 @@
}
}
+void Validator::CheckOperandsMatchTarget(const Instruction* source_inst,
+ size_t source_operand_offset,
+ size_t source_operand_count,
+ const CastableBase* target,
+ VectorRef<const Value*> target_values) {
+ if (source_operand_count != target_values.Length()) {
+ auto values = [&](size_t n) { return n == 1 ? " value" : " values"; };
+ AddError(source_inst) << "provides " << source_operand_count << values(source_operand_count)
+ << " but " << NameOf(target) << " expects " << target_values.Length()
+ << values(target_values.Length());
+ AddDeclarationNote(target);
+ }
+ size_t count = std::min(source_operand_count, target_values.Length());
+ for (size_t i = 0; i < count; i++) {
+ auto* source_value = source_inst->Operand(source_operand_offset + i);
+ auto* target_value = target_values[i];
+ if (!source_value || !target_value) {
+ continue; // Caller should be checking operands are not null
+ }
+ auto* source_type = source_value->Type();
+ auto* target_type = target_value->Type();
+ if (source_type != target_type) {
+ AddError(source_inst, source_operand_offset + i)
+ << "operand with type " << style::Type(source_type->FriendlyName())
+ << " does not match " << NameOf(target) << " target type "
+ << style::Type(target_type->FriendlyName());
+ AddDeclarationNote(target_value);
+ }
+ }
+}
+
const core::type::Type* Validator::GetVectorPtrElementType(const Instruction* inst, size_t idx) {
auto* operand = inst->Operands()[idx];
if (TINT_UNLIKELY(!operand)) {
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 3fedeef..c3e5c25 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -2914,6 +2914,318 @@
)");
}
+TEST_F(IR_ValidatorTest, BreakIf_NextIterUnexpectedValues) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(), [&] { b.BreakIf(loop, true, b.Values(1_i, 2_i), Empty); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:8:9 error: break_if: provides 2 values but 'loop' block $B2 expects 0 values
+ break_if true next_iteration: [ 1i ] # -> [t: exit_loop loop_1, f: $B2]
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+:7:7 note: in block
+ $B3: { # continuing
+ ^^^
+
+:4:7 note: 'loop' block $B2 declared here
+ $B2: { # body
+ ^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true next_iteration: [ 1i ] # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, BreakIf_NextIterMissingValues) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ loop->Body()->SetParams({b.BlockParam<i32>(), b.BlockParam<i32>()});
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(), [&] { b.BreakIf(loop, true, Empty, Empty); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:8:9 error: break_if: provides 0 values but 'loop' block $B2 expects 2 values
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ ^^^^^^^^^^^^^
+
+:7:7 note: in block
+ $B3: { # continuing
+ ^^^
+
+:4:7 note: 'loop' block $B2 declared here
+ $B2 (%2:i32, %3:i32): { # body
+ ^^^^^^^^^^^^^^^^^^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2 (%2:i32, %3:i32): { # body
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, BreakIf_NextIterMismatchedTypes) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ loop->Body()->SetParams(
+ {b.BlockParam<i32>(), b.BlockParam<f32>(), b.BlockParam<u32>(), b.BlockParam<bool>()});
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(),
+ [&] { b.BreakIf(loop, true, b.Values(1_i, 2_i, 3_f, false), Empty); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:8:45 error: break_if: operand with type 'i32' does not match 'loop' block $B2 target type 'f32'
+ break_if true next_iteration: [ 1i, 2i, 3.0f ] # -> [t: exit_loop loop_1, f: $B2]
+ ^^
+
+:7:7 note: in block
+ $B3: { # continuing
+ ^^^
+
+:4:20 note: %3 declared here
+ $B2 (%2:i32, %3:f32, %4:u32, %5:bool): { # body
+ ^^
+
+:8:49 error: break_if: operand with type 'f32' does not match 'loop' block $B2 target type 'u32'
+ break_if true next_iteration: [ 1i, 2i, 3.0f ] # -> [t: exit_loop loop_1, f: $B2]
+ ^^^^
+
+:7:7 note: in block
+ $B3: { # continuing
+ ^^^
+
+:4:28 note: %4 declared here
+ $B2 (%2:i32, %3:f32, %4:u32, %5:bool): { # body
+ ^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2 (%2:i32, %3:f32, %4:u32, %5:bool): { # body
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true next_iteration: [ 1i, 2i, 3.0f ] # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, BreakIf_NextIterMatchedTypes) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ loop->Body()->SetParams(
+ {b.BlockParam<i32>(), b.BlockParam<f32>(), b.BlockParam<u32>(), b.BlockParam<bool>()});
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(),
+ [&] { b.BreakIf(loop, true, b.Values(1_i, 2_f, 3_u, false), Empty); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_EQ(res, Success);
+}
+
+TEST_F(IR_ValidatorTest, BreakIf_ExitUnexpectedValues) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(), [&] { b.BreakIf(loop, true, Empty, b.Values(1_i, 2_i)); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:8:9 error: break_if: provides 2 values but 'loop' expects 0 values
+ break_if true exit_loop: [ 1i, 2i ] # -> [t: exit_loop loop_1, f: $B2]
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+:7:7 note: in block
+ $B3: { # continuing
+ ^^^
+
+:3:5 note: 'loop' declared here
+ loop [b: $B2, c: $B3] { # loop_1
+ ^^^^^^^^^^^^^^^^^^^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true exit_loop: [ 1i, 2i ] # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, BreakIf_ExitMissingValues) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ loop->SetResults(b.InstructionResult<i32>(), b.InstructionResult<i32>());
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(), [&] { b.BreakIf(loop, true, Empty, Empty); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:8:9 error: break_if: provides 0 values but 'loop' expects 2 values
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ ^^^^^^^^^^^^^
+
+:7:7 note: in block
+ $B3: { # continuing
+ ^^^
+
+:3:5 note: 'loop' declared here
+ %2:i32, %3:i32 = loop [b: $B2, c: $B3] { # loop_1
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ %2:i32, %3:i32 = loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, BreakIf_ExitMismatchedTypes) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ loop->SetResults(b.InstructionResult<i32>(), b.InstructionResult<f32>(),
+ b.InstructionResult<u32>(), b.InstructionResult<bool>());
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(),
+ [&] { b.BreakIf(loop, true, Empty, b.Values(1_i, 2_i, 3_f, false)); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:8:40 error: break_if: operand with type 'i32' does not match 'loop' target type 'f32'
+ break_if true exit_loop: [ 1i, 2i, 3.0f, false ] # -> [t: exit_loop loop_1, f: $B2]
+ ^^
+
+:7:7 note: in block
+ $B3: { # continuing
+ ^^^
+
+:3:13 note: %3 declared here
+ %2:i32, %3:f32, %4:u32, %5:bool = loop [b: $B2, c: $B3] { # loop_1
+ ^^^^^^
+
+:8:44 error: break_if: operand with type 'f32' does not match 'loop' target type 'u32'
+ break_if true exit_loop: [ 1i, 2i, 3.0f, false ] # -> [t: exit_loop loop_1, f: $B2]
+ ^^^^
+
+:7:7 note: in block
+ $B3: { # continuing
+ ^^^
+
+:3:21 note: %4 declared here
+ %2:i32, %3:f32, %4:u32, %5:bool = loop [b: $B2, c: $B3] { # loop_1
+ ^^^^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ %2:i32, %3:f32, %4:u32, %5:bool = loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ continue # -> $B3
+ }
+ $B3: { # continuing
+ break_if true exit_loop: [ 1i, 2i, 3.0f, false ] # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, BreakIf_ExitMatchedTypes) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ loop->SetResults(b.InstructionResult<i32>(), b.InstructionResult<f32>(),
+ b.InstructionResult<u32>(), b.InstructionResult<bool>());
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(),
+ [&] { b.BreakIf(loop, true, Empty, b.Values(1_i, 2_f, 3_u, false)); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_EQ(res, Success);
+}
+
TEST_F(IR_ValidatorTest, ExitLoop) {
auto* loop = b.Loop();
loop->Continuing()->Append(b.NextIteration(loop));
diff --git a/src/tint/lang/spirv/writer/loop_test.cc b/src/tint/lang/spirv/writer/loop_test.cc
index 1fb4f9e..ffa15ae 100644
--- a/src/tint/lang/spirv/writer/loop_test.cc
+++ b/src/tint/lang/spirv/writer/loop_test.cc
@@ -410,7 +410,7 @@
b.Append(loop->Continuing(), [&] {
auto* cmp = b.GreaterThan(ty.bool_(), cont_param_a, 5_i);
auto* not_b = b.Not(ty.bool_(), cont_param_b);
- b.BreakIf(loop, cmp, cont_param_a, not_b);
+ b.BreakIf(loop, cmp, b.Values(cont_param_a, not_b), Empty);
});
b.Return(func);