Import Tint changes from Dawn
Changes:
- 48360eaf7a0aa3c275654ba88858c72003208eda [tint][ir] Use CheckOperandsMatchTarget() for exit instru... by Ben Clayton <bclayton@google.com>
- 6654d1f4064770aec50ba2ff8de1bde4b295e237 [tint][ir] Validate next_iteration value types match body... by Ben Clayton <bclayton@google.com>
- fac459af919a4e6c2add45724ade2b83ccb0c854 [tint][ir] Validate continue value types match continuing... by Ben Clayton <bclayton@google.com>
- c951b86b055943d20ea686bb16dbf05aae691db1 [tint][ir] Validate break_if value types match body block... by Ben Clayton <bclayton@google.com>
- c7d4205172c8ae635e86d328873f2f67c9b6e8d4 Add generating Tint IR fuzzer corpus by Ryan Harrison <rharrison@chromium.org>
- 47366120df82fe6d7c3874afe21e4a956ac8bc9d Tint/Inspector: Add blend_src to entry points by Jiawei Shao <jiawei.shao@intel.com>
- f46acc5b7e8d6a899952e636be4c778b3f1b7675 [tint][ir] Move terminator validation to CheckTerminator() by Ben Clayton <bclayton@google.com>
- 94ef15d00f29e8a241fb88648dca3121ee74d5fe [tint][ast][fuzz] Skip MultiplanarExternalTexture fuzzer ... by Ben Clayton <bclayton@google.com>
- b6bff7f647a1615ebb73a6a435029465205d24d9 [tint][ast][fuzz] Replace Options DI with Context by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: 48360eaf7a0aa3c275654ba88858c72003208eda
Change-Id: Ic74746d2708a0f776bad3479818acf0f01183c59
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/189300
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: David Neto <dneto@google.com>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index af3d7a5..2544db7 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -275,7 +275,7 @@
if (tint_has_fuzzers) {
action("tint_generate_wgsl_corpus") {
script = "${tint_src_dir}/cmd/fuzz/wgsl/generate_wgsl_corpus.py"
- sources = [ "${tint_src_dir}/cmd/fuzz/wgsl/generate_wgsl_corpus.py" ]
+ sources = [ "${script}" ]
args = [
"--stamp=" + rebase_path(fuzzer_corpus_wgsl_stamp, root_build_dir),
rebase_path("${tint_root_dir}/test", root_build_dir),
@@ -283,6 +283,23 @@
]
outputs = [ fuzzer_corpus_wgsl_stamp ]
}
+
+ if (tint_build_cmds) {
+ if (tint_build_wgsl_reader && tint_build_ir_binary) {
+ action("tint_generate_ir_corpus") {
+ script = "${tint_src_dir}/cmd/fuzz/wgsl/generate_ir_corpus.py"
+ sources = [ "${script}" ]
+ deps = [ "${tint_src_dir}/cmd/tint" ]
+ args = [
+ "--stamp=" + rebase_path(fuzzer_corpus_ir_stamp, root_build_dir),
+ rebase_path("${root_build_dir}/tint", root_build_dir),
+ rebase_path("${tint_root_dir}/test", root_build_dir),
+ rebase_path(fuzzer_corpus_ir_dir, root_build_dir),
+ ]
+ outputs = [ fuzzer_corpus_ir_stamp ]
+ }
+ }
+ }
}
###############################################################################
diff --git a/src/tint/cmd/fuzz/ir/fuzz.cc b/src/tint/cmd/fuzz/ir/fuzz.cc
index 771a37a..08f9568 100644
--- a/src/tint/cmd/fuzz/ir/fuzz.cc
+++ b/src/tint/cmd/fuzz/ir/fuzz.cc
@@ -41,32 +41,13 @@
#if TINT_BUILD_WGSL_READER
namespace tint::fuzz::ir {
-namespace {
-
-bool IsUnsupported(const ast::Enable* enable) {
- for (auto ext : enable->extensions) {
- switch (ext->name) {
- case tint::wgsl::Extension::kChromiumExperimentalFramebufferFetch:
- case tint::wgsl::Extension::kChromiumExperimentalPixelLocal:
- case tint::wgsl::Extension::kChromiumExperimentalPushConstant:
- case tint::wgsl::Extension::kChromiumInternalDualSourceBlending:
- case tint::wgsl::Extension::kChromiumInternalRelaxedUniformLayout:
- return true;
- default:
- break;
- }
- }
- return false;
-}
-
-} // namespace
void Register(const IRFuzzer& fuzzer) {
wgsl::Register({
fuzzer.name,
- [fn = fuzzer.fn](const Program& program, const fuzz::wgsl::Options& options,
+ [fn = fuzzer.fn](const Program& program, const fuzz::wgsl::Context& context,
Slice<const std::byte> data) {
- if (program.AST().Enables().Any(IsUnsupported)) {
+ if (program.AST().Enables().Any(tint::wgsl::reader::IsUnsupportedByIR)) {
return;
}
diff --git a/src/tint/cmd/fuzz/wgsl/fuzz.cc b/src/tint/cmd/fuzz/wgsl/fuzz.cc
index 17c2e1c..20de4f5 100644
--- a/src/tint/cmd/fuzz/wgsl/fuzz.cc
+++ b/src/tint/cmd/fuzz/wgsl/fuzz.cc
@@ -28,14 +28,24 @@
#include "src/tint/cmd/fuzz/wgsl/fuzz.h"
#include <iostream>
+#include <string>
+#include <string_view>
#include <thread>
+#include "src/tint/lang/core/builtin_type.h"
+#include "src/tint/lang/wgsl/ast/alias.h"
+#include "src/tint/lang/wgsl/ast/function.h"
+#include "src/tint/lang/wgsl/ast/identifier.h"
+#include "src/tint/lang/wgsl/ast/struct.h"
+#include "src/tint/lang/wgsl/ast/variable.h"
+#include "src/tint/lang/wgsl/builtin_fn.h"
#include "src/tint/lang/wgsl/common/allowed_features.h"
#include "src/tint/lang/wgsl/reader/options.h"
#include "src/tint/lang/wgsl/reader/reader.h"
#include "src/tint/utils/containers/vector.h"
#include "src/tint/utils/macros/defer.h"
#include "src/tint/utils/macros/static_init.h"
+#include "src/tint/utils/rtti/switch.h"
#if TINT_BUILD_WGSL_WRITER
#include "src/tint/lang/wgsl/program/program.h"
@@ -61,6 +71,42 @@
__builtin_trap();
}
+bool IsBuiltinFn(std::string_view name) {
+ return tint::wgsl::ParseBuiltinFn(name) != tint::wgsl::BuiltinFn::kNone;
+}
+
+bool IsBuiltinType(std::string_view name) {
+ return tint::core::ParseBuiltinType(name) != tint::core::BuiltinType::kUndefined;
+}
+
+/// Scans @p program for patterns, returning a set of ProgramProperties.
+EnumSet<ProgramProperties> ScanProgramProperties(const Program& program) {
+ EnumSet<ProgramProperties> out;
+ auto check = [&](std::string_view name) {
+ if (IsBuiltinFn(name)) {
+ out.Add(ProgramProperties::kBuiltinFnsShadowed);
+ }
+ if (IsBuiltinType(name)) {
+ out.Add(ProgramProperties::kBuiltinTypesShadowed);
+ }
+ };
+
+ for (auto* node : program.ASTNodes().Objects()) {
+ tint::Switch(
+ node, //
+ [&](const ast::Variable* variable) { check(variable->name->symbol.NameView()); },
+ [&](const ast::Function* fn) { check(fn->name->symbol.NameView()); },
+ [&](const ast::Struct* str) { check(str->name->symbol.NameView()); },
+ [&](const ast::Alias* alias) { check(alias->name->symbol.NameView()); });
+
+ if (out.Contains(ProgramProperties::kBuiltinFnsShadowed) &&
+ out.Contains(ProgramProperties::kBuiltinTypesShadowed)) {
+ break; // Early exit - nothing more to find.
+ }
+ }
+ return out;
+}
+
} // namespace
void Register(const ProgramFuzzer& fuzzer) {
@@ -100,6 +146,10 @@
return;
}
+ Context context;
+ context.options = options;
+ context.program_properties = ScanProgramProperties(program);
+
// Run each of the program fuzzer functions
if (options.run_concurrently) {
size_t n = Fuzzers().Length();
@@ -110,13 +160,13 @@
Fuzzers()[i].name.find(options.filter) == std::string::npos) {
continue;
}
- threads.Push(std::thread([i, &program, &data, &options] {
+ threads.Push(std::thread([i, &program, &data, &context] {
auto& fuzzer = Fuzzers()[i];
currently_running = fuzzer.name;
- if (options.verbose) {
+ if (context.options.verbose) {
std::cout << " • [" << i << "] Running: " << currently_running << std::endl;
}
- fuzzer.fn(program, options, data);
+ fuzzer.fn(program, context, data);
}));
}
for (auto& thread : threads) {
@@ -133,7 +183,7 @@
if (options.verbose) {
std::cout << " • Running: " << currently_running << std::endl;
}
- fuzzer.fn(program, options, data);
+ fuzzer.fn(program, context, data);
}
}
}
diff --git a/src/tint/cmd/fuzz/wgsl/fuzz.h b/src/tint/cmd/fuzz/wgsl/fuzz.h
index d128695..a24514b 100644
--- a/src/tint/cmd/fuzz/wgsl/fuzz.h
+++ b/src/tint/cmd/fuzz/wgsl/fuzz.h
@@ -35,6 +35,7 @@
#include "src/tint/lang/wgsl/program/program.h"
#include "src/tint/utils/bytes/buffer_reader.h"
#include "src/tint/utils/bytes/decoder.h"
+#include "src/tint/utils/containers/enum_set.h"
#include "src/tint/utils/containers/slice.h"
#include "src/tint/utils/macros/static_init.h"
@@ -53,17 +54,34 @@
std::string dxc;
};
+/// ProgramProperties is an enumerator of flags used to describe characteristics of the input
+/// program.
+enum class ProgramProperties {
+ /// The program has builtin functions which have been shadowed
+ kBuiltinFnsShadowed,
+ /// The program has builtin types which have been shadowed
+ kBuiltinTypesShadowed,
+};
+
+/// Context holds information about the fuzzer options and the input program.
+struct Context {
+ /// The options used for Run()
+ Options options;
+ /// The properties of the input program
+ EnumSet<ProgramProperties> program_properties;
+};
+
/// ProgramFuzzer describes a fuzzer function that takes a WGSL program as input
struct ProgramFuzzer {
/// @param name the name of the fuzzer
- /// @param fn the fuzzer function with the signature `void(const Program&, const Options&, ...)`
- /// @returns a ProgramFuzzer that invokes the function @p fn with the Program, Options, along
+ /// @param fn the fuzzer function with the signature `void(const Program&, const Context&, ...)`
+ /// @returns a ProgramFuzzer that invokes the function @p fn with the Program, Context, along
/// with any additional arguments which are deserialized from the fuzzer input.
template <typename... ARGS>
static ProgramFuzzer Create(std::string_view name,
- void (*fn)(const Program&, const Options&, ARGS...)) {
+ void (*fn)(const Program&, const Context&, ARGS...)) {
if constexpr (sizeof...(ARGS) > 0) {
- auto fn_with_decode = [fn](const Program& program, const Options& options,
+ auto fn_with_decode = [fn](const Program& program, const Context& context,
Slice<const std::byte> data) {
if (!data.data) {
return;
@@ -72,7 +90,7 @@
auto data_args = bytes::Decode<std::tuple<std::decay_t<ARGS>...>>(reader);
if (data_args == Success) {
auto all_args =
- std::tuple_cat(std::tuple<const Program&, const Options&>{program, options},
+ std::tuple_cat(std::tuple<const Program&, const Context&>{program, context},
data_args.Get());
std::apply(*fn, all_args);
}
@@ -81,7 +99,7 @@
} else {
return ProgramFuzzer{
name,
- [fn](const Program& program, const Options& options, Slice<const std::byte>) {
+ [fn](const Program& program, const Context& options, Slice<const std::byte>) {
fn(program, options);
},
};
@@ -95,7 +113,7 @@
template <typename... ARGS>
static ProgramFuzzer Create(std::string_view name, void (*fn)(const Program&, ARGS...)) {
if constexpr (sizeof...(ARGS) > 0) {
- auto fn_with_decode = [fn](const Program& program, const Options&,
+ auto fn_with_decode = [fn](const Program& program, const Context&,
Slice<const std::byte> data) {
if (!data.data) {
return;
@@ -112,7 +130,7 @@
} else {
return ProgramFuzzer{
name,
- [fn](const Program& program, const Options&, Slice<const std::byte>) {
+ [fn](const Program& program, const Context&, Slice<const std::byte>) {
fn(program);
},
};
@@ -122,7 +140,7 @@
/// Name of the fuzzer function
std::string_view name;
/// The fuzzer function
- std::function<void(const Program&, const Options&, Slice<const std::byte> data)> fn;
+ std::function<void(const Program&, const Context&, Slice<const std::byte> data)> fn;
};
/// Runs all the registered WGSL fuzzers with the supplied WGSL
@@ -142,9 +160,9 @@
/// Where `...` is any number of deserializable parameters which are decoded from the base64
/// content of the WGSL comments.
/// @see bytes::Decode()
-#define TINT_WGSL_PROGRAM_FUZZER(FUNCTION) \
+#define TINT_WGSL_PROGRAM_FUZZER(FUNCTION, ...) \
TINT_STATIC_INIT(::tint::fuzz::wgsl::Register( \
- ::tint::fuzz::wgsl::ProgramFuzzer::Create(#FUNCTION, FUNCTION)))
+ ::tint::fuzz::wgsl::ProgramFuzzer::Create(#FUNCTION, FUNCTION, ##__VA_ARGS__)))
} // namespace tint::fuzz::wgsl
diff --git a/src/tint/cmd/fuzz/wgsl/generate_ir_corpus.py b/src/tint/cmd/fuzz/wgsl/generate_ir_corpus.py
new file mode 100644
index 0000000..c332259
--- /dev/null
+++ b/src/tint/cmd/fuzz/wgsl/generate_ir_corpus.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+# 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.
+
+# Collect all .wgsl files under a given directory and convert them to IR
+# protobuf in a given corpus directory, flattening their file names by replacing
+# path separators with underscores. If the output directory already exists, it
+# will be deleted and re-created. Files ending with ".expected.wgsl" are
+# skipped.
+#
+# The intended use of this script is to generate a corpus of IR protobufs
+# for fuzzing.
+#
+# Based off of generate_wgsl_corpus.py
+#
+# Usage:
+# generate_ir_corpus.py <path to tint cmd> <input_dir> <corpus_dir>
+
+import optparse
+import subprocess
+
+import os
+import pathlib
+import shutil
+import sys
+
+
+def list_wgsl_files(root_search_dir):
+ for root, folders, files in os.walk(root_search_dir):
+ for filename in folders + files:
+ if pathlib.Path(filename).suffix == '.wgsl':
+ yield os.path.join(root, filename)
+
+
+def main():
+ parser = optparse.OptionParser(
+ usage="usage: %prog [option] tint-cmd input-dir output-dir")
+ parser.add_option('--stamp', dest='stamp', help='stamp file')
+ options, args = parser.parse_args(sys.argv[1:])
+
+ if len(args) != 3:
+ parser.error("incorrect number of arguments")
+
+ tint_cmd: str = os.path.abspath(args[0])
+ if not os.path.isfile(tint_cmd) or not os.access(tint_cmd, os.X_OK):
+ parser.error("Unable to run tint-cmd '" + os.path.abspath(args[0]) +
+ "' (" + tint_cmd + ")")
+
+ input_dir: str = os.path.abspath(args[1].rstrip(os.sep))
+ corpus_dir: str = os.path.abspath(args[2])
+
+ if os.path.exists(corpus_dir):
+ shutil.rmtree(corpus_dir)
+ os.makedirs(corpus_dir)
+
+ for in_file in list_wgsl_files(input_dir):
+ if in_file.endswith(".expected.wgsl"):
+ continue
+
+ out_file = os.path.splitext(in_file[len(input_dir) + 1:].replace(
+ os.sep, '_'))[0] + '.tirb'
+ subprocess.run([
+ tint_cmd, '--format=ir_bin',
+ '--output-name=' + corpus_dir + os.sep + out_file, in_file
+ ],
+ stderr=subprocess.STDOUT)
+
+ if options.stamp:
+ pathlib.Path(options.stamp).touch(mode=0o644, exist_ok=True)
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index ccfdcf1..5203fec 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -71,6 +71,8 @@
#if TINT_BUILD_IR_BINARY
#include "src/tint/lang/core/ir/binary/encode.h"
+#include "src/tint/lang/core/ir/validator.h"
+#include "src/tint/lang/wgsl/helpers/apply_substitute_overrides.h"
#endif // TINT_BUILD_IR_BINARY
#endif // TINT_BUILD_WGSL_READER
@@ -1259,6 +1261,37 @@
#endif
}
+/// Generate an IR module for a program, performs checking for unsupported
+/// enables, needed transforms, and validation.
+/// @param program the program to generate
+/// @param options the options that Tint was invoked with
+/// @returns generated module on success, tint::failure on failure
+#if WGSL_READER_AND_IR_BINARY
+tint::Result<tint::core::ir::Module> GenerateIrModule([[maybe_unused]] const tint::Program& program,
+ [[maybe_unused]] const Options& options) {
+ if (program.AST().Enables().Any(tint::wgsl::reader::IsUnsupportedByIR)) {
+ return tint::Failure{"Unsupported enable used in shader"};
+ }
+
+ auto transformed = tint::wgsl::ApplySubstituteOverrides(program);
+ auto& src = transformed ? transformed.value() : program;
+ if (!src.IsValid()) {
+ return tint::Failure{src.Diagnostics()};
+ }
+
+ auto ir = tint::wgsl::reader::ProgramToLoweredIR(src);
+ if (ir != tint::Success) {
+ return ir.Failure();
+ }
+
+ if (auto val = tint::core::ir::Validate(ir.Get()); val != tint::Success) {
+ return val.Failure();
+ }
+
+ return ir;
+}
+#endif // WGSL_READER_AND_IR_BINARY
+
/// Generate IR binary protobuf for a program.
/// @param program the program to generate
/// @param options the options that Tint was invoked with
@@ -1272,11 +1305,12 @@
std::cerr << "IR binary not enabled in tint build" << std::endl;
return false;
#else
- auto module = tint::wgsl::reader::ProgramToLoweredIR(program);
+ auto module = GenerateIrModule(program, options);
if (module != tint::Success) {
- std::cerr << "Failed to build IR from program: " << module.Failure() << "\n";
+ std::cerr << "Failed to generate lowered IR from program: " << module.Failure() << "\n";
return false;
}
+
auto pb = tint::core::ir::binary::Encode(module.Get());
if (pb != tint::Success) {
std::cerr << "Failed to encode IR module to protobuf: " << pb.Failure() << "\n";
@@ -1300,11 +1334,12 @@
#if WGSL_READER_AND_IR_BINARY
bool GenerateIrProtoDebug([[maybe_unused]] const tint::Program& program,
[[maybe_unused]] const Options& options) {
- auto module = tint::wgsl::reader::ProgramToLoweredIR(program);
+ auto module = GenerateIrModule(program, options);
if (module != tint::Success) {
- std::cerr << "Failed to build IR from program: " << module.Failure() << "\n";
+ std::cerr << "Failed to generate lowered IR from program: " << module.Failure() << "\n";
return false;
}
+
auto pb = tint::core::ir::binary::EncodeDebug(module.Get());
if (pb != tint::Success) {
std::cerr << "Failed to encode IR module to protobuf: " << pb.Failure() << "\n";
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 1d69625..b72475c 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";
@@ -716,11 +824,6 @@
if (inst->Block() != blk) {
AddError(inst) << "block instruction does not have same block as parent";
AddNote(blk) << "in block";
- continue;
- }
- if (inst->Is<ir::Terminator>() && inst != blk->Terminator()) {
- AddError(inst) << "block terminator which isn't the final instruction";
- continue;
}
}
@@ -1165,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); }, //
@@ -1173,6 +1276,32 @@
[&](const ir::TerminateInvocation*) {}, //
[&](const ir::Unreachable*) {}, //
[&](Default) { AddError(b) << "missing validation"; });
+
+ if (b->next) {
+ AddError(b) << "must be the last instruction in the block";
+ }
+}
+
+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) {
@@ -1189,6 +1318,11 @@
}
}
+ if (auto* cont = loop->Continuing()) {
+ CheckOperandsMatchTarget(c, Continue::kArgsOperandOffset, c->Args().Length(), cont,
+ cont->Params());
+ }
+
first_continues_.Add(loop, c);
}
@@ -1203,24 +1337,9 @@
return;
}
- auto results = e->ControlInstruction()->Results();
auto args = e->Args();
- if (results.Length() != args.Length()) {
- AddError(e) << ("args count (") << args.Length()
- << ") does not match control instruction result count (" << results.Length()
- << ")";
- AddNote(e->ControlInstruction()) << "control instruction";
- return;
- }
-
- for (size_t i = 0; i < results.Length(); ++i) {
- if (results[i] && args[i] && results[i]->Type() != args[i]->Type()) {
- AddError(e, i) << "argument type " << style::Type(results[i]->Type()->FriendlyName())
- << " does not match control instruction type "
- << style::Type(args[i]->Type()->FriendlyName());
- AddNote(e->ControlInstruction()) << "control instruction";
- }
- }
+ CheckOperandsMatchTarget(e, e->ArgsOperandOffset(), args.Length(), e->ControlInstruction(),
+ e->ControlInstruction()->Results());
tint::Switch(
e, //
@@ -1243,6 +1362,11 @@
AddError(n) << "called outside of associated loop";
}
}
+
+ if (auto* body = loop->Body()) {
+ CheckOperandsMatchTarget(n, NextIteration::kArgsOperandOffset, n->Args().Length(), body,
+ body->Params());
+ }
}
void Validator::CheckExitIf(const ExitIf* e) {
@@ -1396,6 +1520,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 bc6b744..820182a 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -1144,7 +1144,7 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_EQ(res.Failure().reason.Str(),
- R"(:3:5 error: return: block terminator which isn't the final instruction
+ R"(:3:5 error: return: must be the last instruction in the block
ret
^^^
@@ -1914,9 +1914,8 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
- EXPECT_EQ(
- res.Failure().reason.Str(),
- R"(:5:9 error: exit_if: args count (1) does not match control instruction result count (2)
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:5:9 error: exit_if: provides 1 value but 'if' expects 2 values
exit_if 1i # if_1
^^^^^^^^^^
@@ -1924,7 +1923,7 @@
$B2: { # true
^^^
-:3:5 note: control instruction
+:3:5 note: 'if' declared here
%2:i32, %3:f32 = if true [t: $B2] { # if_1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1958,9 +1957,8 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
- EXPECT_EQ(
- res.Failure().reason.Str(),
- R"(:5:9 error: exit_if: args count (3) does not match control instruction result count (2)
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:5:9 error: exit_if: provides 3 values but 'if' expects 2 values
exit_if 1i, 2.0f, 3i # if_1
^^^^^^^^^^^^^^^^^^^^
@@ -1968,7 +1966,7 @@
$B2: { # true
^^^
-:3:5 note: control instruction
+:3:5 note: 'if' declared here
%2:i32, %3:f32 = if true [t: $B2] { # if_1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -2019,9 +2017,8 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
- EXPECT_EQ(
- res.Failure().reason.Str(),
- R"(:5:21 error: exit_if: argument type 'f32' does not match control instruction type 'i32'
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:5:21 error: exit_if: operand with type 'i32' does not match 'if' target type 'f32'
exit_if 1i, 2i # if_1
^^
@@ -2029,9 +2026,9 @@
$B2: { # true
^^^
-:3:5 note: control instruction
+:3:13 note: %3 declared here
%2:i32, %3:f32 = if true [t: $B2] { # if_1
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ ^^^^^^
note: # Disassembly
%my_func = func():void {
@@ -2307,9 +2304,8 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
- EXPECT_EQ(
- res.Failure().reason.Str(),
- R"(:5:9 error: exit_switch: args count (1) does not match control instruction result count (2)
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:5:9 error: exit_switch: provides 1 value but 'switch' expects 2 values
exit_switch 1i # switch_1
^^^^^^^^^^^^^^
@@ -2317,7 +2313,7 @@
$B2: { # case
^^^
-:3:5 note: control instruction
+:3:5 note: 'switch' declared here
%2:i32, %3:f32 = switch true [c: (default, $B2)] { # switch_1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -2351,9 +2347,8 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
- EXPECT_EQ(
- res.Failure().reason.Str(),
- R"(:5:9 error: exit_switch: args count (3) does not match control instruction result count (2)
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:5:9 error: exit_switch: provides 3 values but 'switch' expects 2 values
exit_switch 1i, 2.0f, 3i # switch_1
^^^^^^^^^^^^^^^^^^^^^^^^
@@ -2361,7 +2356,7 @@
$B2: { # case
^^^
-:3:5 note: control instruction
+:3:5 note: 'switch' declared here
%2:i32, %3:f32 = switch true [c: (default, $B2)] { # switch_1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -2415,7 +2410,7 @@
ASSERT_NE(res, Success);
EXPECT_EQ(
res.Failure().reason.Str(),
- R"(:5:25 error: exit_switch: argument type 'f32' does not match control instruction type 'i32'
+ R"(:5:25 error: exit_switch: operand with type 'i32' does not match 'switch' target type 'f32'
exit_switch 1i, 2i # switch_1
^^
@@ -2423,9 +2418,9 @@
$B2: { # case
^^^
-:3:5 note: control instruction
+:3:13 note: %3 declared here
%2:i32, %3:f32 = switch true [c: (default, $B2)] { # switch_1
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ ^^^^^^
note: # Disassembly
%my_func = func():void {
@@ -2622,7 +2617,7 @@
)");
}
-TEST_F(IR_ValidatorTest, ContinueOutsideOfLoop) {
+TEST_F(IR_ValidatorTest, Continue_OutsideOfLoop) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* loop = b.Loop();
@@ -2655,7 +2650,7 @@
)");
}
-TEST_F(IR_ValidatorTest, ContinueInLoopInit) {
+TEST_F(IR_ValidatorTest, Continue_InLoopInit) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* loop = b.Loop();
@@ -2692,7 +2687,7 @@
)");
}
-TEST_F(IR_ValidatorTest, ContinueInLoopBody) {
+TEST_F(IR_ValidatorTest, Continue_InLoopBody) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* loop = b.Loop();
@@ -2704,7 +2699,7 @@
ASSERT_EQ(res, Success);
}
-TEST_F(IR_ValidatorTest, ContinueInLoopContinuing) {
+TEST_F(IR_ValidatorTest, Continue_InLoopContinuing) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* loop = b.Loop();
@@ -2741,7 +2736,161 @@
)");
}
-TEST_F(IR_ValidatorTest, NextIterationOutsideOfLoop) {
+TEST_F(IR_ValidatorTest, Continue_UnexpectedValues) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] { b.Continue(loop, 1_i, 2_f); });
+ b.Append(loop->Continuing(), [&] { b.BreakIf(loop, true); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:5:9 error: continue: provides 2 values but 'loop' block $B3 expects 0 values
+ continue 1i, 2.0f # -> $B3
+ ^^^^^^^^^^^^^^^^^
+
+:4:7 note: in block
+ $B2: { # body
+ ^^^
+
+:7:7 note: 'loop' block $B3 declared here
+ $B3: { # continuing
+ ^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ continue 1i, 2.0f # -> $B3
+ }
+ $B3: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Continue_MissingValues) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ loop->Continuing()->SetParams({b.BlockParam<i32>(), b.BlockParam<i32>()});
+ b.Append(loop->Body(), [&] { b.Continue(loop); });
+ b.Append(loop->Continuing(), [&] { b.BreakIf(loop, true); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:5:9 error: continue: provides 0 values but 'loop' block $B3 expects 2 values
+ continue # -> $B3
+ ^^^^^^^^
+
+:4:7 note: in block
+ $B2: { # body
+ ^^^
+
+:7:7 note: 'loop' block $B3 declared here
+ $B3 (%2:i32, %3:i32): { # continuing
+ ^^^^^^^^^^^^^^^^^^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ continue # -> $B3
+ }
+ $B3 (%2:i32, %3:i32): { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Continue_MismatchedTypes) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ loop->Continuing()->SetParams(
+ {b.BlockParam<i32>(), b.BlockParam<f32>(), b.BlockParam<u32>(), b.BlockParam<bool>()});
+ b.Append(loop->Body(), [&] { b.Continue(loop, 1_i, 2_i, 3_f, false); });
+ b.Append(loop->Continuing(), [&] { b.BreakIf(loop, true); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:5:22 error: continue: operand with type 'i32' does not match 'loop' block $B3 target type 'f32'
+ continue 1i, 2i, 3.0f, false # -> $B3
+ ^^
+
+:4:7 note: in block
+ $B2: { # body
+ ^^^
+
+:7:20 note: %3 declared here
+ $B3 (%2:i32, %3:f32, %4:u32, %5:bool): { # continuing
+ ^^
+
+:5:26 error: continue: operand with type 'f32' does not match 'loop' block $B3 target type 'u32'
+ continue 1i, 2i, 3.0f, false # -> $B3
+ ^^^^
+
+:4:7 note: in block
+ $B2: { # body
+ ^^^
+
+:7:28 note: %4 declared here
+ $B3 (%2:i32, %3:f32, %4:u32, %5:bool): { # continuing
+ ^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ loop [b: $B2, c: $B3] { # loop_1
+ $B2: { # body
+ continue 1i, 2i, 3.0f, false # -> $B3
+ }
+ $B3 (%2:i32, %3:f32, %4:u32, %5:bool): { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B2]
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Continue_MatchedTypes) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ loop->Continuing()->SetParams(
+ {b.BlockParam<i32>(), b.BlockParam<f32>(), b.BlockParam<u32>(), b.BlockParam<bool>()});
+ b.Append(loop->Body(), [&] { b.Continue(loop, 1_i, 2_f, 3_u, false); });
+ b.Append(loop->Continuing(), [&] { b.BreakIf(loop, true); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_EQ(res, Success);
+}
+
+TEST_F(IR_ValidatorTest, NextIteration_OutsideOfLoop) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* loop = b.Loop();
@@ -2774,7 +2923,7 @@
)");
}
-TEST_F(IR_ValidatorTest, NextIterationInLoopInit) {
+TEST_F(IR_ValidatorTest, NextIteration_InLoopInit) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* loop = b.Loop();
@@ -2787,7 +2936,7 @@
ASSERT_EQ(res, Success);
}
-TEST_F(IR_ValidatorTest, NextIterationInLoopBody) {
+TEST_F(IR_ValidatorTest, NextIteration_InLoopBody) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* loop = b.Loop();
@@ -2820,7 +2969,7 @@
)");
}
-TEST_F(IR_ValidatorTest, NextIterationInLoopContinuing) {
+TEST_F(IR_ValidatorTest, NextIteration_InLoopContinuing) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* loop = b.Loop();
@@ -2833,6 +2982,160 @@
ASSERT_EQ(res, Success);
}
+TEST_F(IR_ValidatorTest, NextIteration_UnexpectedValues) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] { b.NextIteration(loop, 1_i, 2_f); });
+ b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:5:9 error: next_iteration: provides 2 values but 'loop' block $B3 expects 0 values
+ next_iteration 1i, 2.0f # -> $B3
+ ^^^^^^^^^^^^^^^^^^^^^^^
+
+:4:7 note: in block
+ $B2: { # initializer
+ ^^^
+
+:7:7 note: 'loop' block $B3 declared here
+ $B3: { # body
+ ^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3] { # loop_1
+ $B2: { # initializer
+ next_iteration 1i, 2.0f # -> $B3
+ }
+ $B3: { # body
+ exit_loop # loop_1
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, NextIteration_MissingValues) {
+ 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->Initializer(), [&] { b.NextIteration(loop); });
+ b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:5:9 error: next_iteration: provides 0 values but 'loop' block $B3 expects 2 values
+ next_iteration # -> $B3
+ ^^^^^^^^^^^^^^
+
+:4:7 note: in block
+ $B2: { # initializer
+ ^^^
+
+:7:7 note: 'loop' block $B3 declared here
+ $B3 (%2:i32, %3:i32): { # body
+ ^^^^^^^^^^^^^^^^^^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3] { # loop_1
+ $B2: { # initializer
+ next_iteration # -> $B3
+ }
+ $B3 (%2:i32, %3:i32): { # body
+ exit_loop # loop_1
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, NextIteration_MismatchedTypes) {
+ 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->Initializer(), [&] { b.NextIteration(loop, 1_i, 2_i, 3_f, false); });
+ b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:5:28 error: next_iteration: operand with type 'i32' does not match 'loop' block $B3 target type 'f32'
+ next_iteration 1i, 2i, 3.0f, false # -> $B3
+ ^^
+
+:4:7 note: in block
+ $B2: { # initializer
+ ^^^
+
+:7:20 note: %3 declared here
+ $B3 (%2:i32, %3:f32, %4:u32, %5:bool): { # body
+ ^^
+
+:5:32 error: next_iteration: operand with type 'f32' does not match 'loop' block $B3 target type 'u32'
+ next_iteration 1i, 2i, 3.0f, false # -> $B3
+ ^^^^
+
+:4:7 note: in block
+ $B2: { # initializer
+ ^^^
+
+:7:28 note: %4 declared here
+ $B3 (%2:i32, %3:f32, %4:u32, %5:bool): { # body
+ ^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3] { # loop_1
+ $B2: { # initializer
+ next_iteration 1i, 2i, 3.0f, false # -> $B3
+ }
+ $B3 (%2:i32, %3:f32, %4:u32, %5:bool): { # body
+ exit_loop # loop_1
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, NextIteration_MatchedTypes) {
+ 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->Initializer(), [&] { b.NextIteration(loop, 1_i, 2_f, 3_u, false); });
+ b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_EQ(res, Success);
+}
+
TEST_F(IR_ValidatorTest, ContinuingUseValueBeforeContinue) {
auto* f = b.Function("my_func", ty.void_());
auto* value = b.Let("value", 1_i);
@@ -2914,6 +3217,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));
@@ -2981,9 +3596,8 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
- EXPECT_EQ(
- res.Failure().reason.Str(),
- R"(:5:9 error: exit_loop: args count (1) does not match control instruction result count (2)
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:5:9 error: exit_loop: provides 1 value but 'loop' expects 2 values
exit_loop 1i # loop_1
^^^^^^^^^^^^
@@ -2991,7 +3605,7 @@
$B2: { # body
^^^
-:3:5 note: control instruction
+:3:5 note: 'loop' declared here
%2:i32, %3:f32 = loop [b: $B2, c: $B3] { # loop_1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -3028,9 +3642,8 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
- EXPECT_EQ(
- res.Failure().reason.Str(),
- R"(:5:9 error: exit_loop: args count (3) does not match control instruction result count (2)
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:5:9 error: exit_loop: provides 3 values but 'loop' expects 2 values
exit_loop 1i, 2.0f, 3i # loop_1
^^^^^^^^^^^^^^^^^^^^^^
@@ -3038,7 +3651,7 @@
$B2: { # body
^^^
-:3:5 note: control instruction
+:3:5 note: 'loop' declared here
%2:i32, %3:f32 = loop [b: $B2, c: $B3] { # loop_1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -3095,7 +3708,7 @@
ASSERT_NE(res, Success);
EXPECT_EQ(
res.Failure().reason.Str(),
- R"(:5:23 error: exit_loop: argument type 'f32' does not match control instruction type 'i32'
+ R"(:5:23 error: exit_loop: operand with type 'i32' does not match 'loop' target type 'f32'
exit_loop 1i, 2i # loop_1
^^
@@ -3103,9 +3716,9 @@
$B2: { # body
^^^
-:3:5 note: control instruction
+:3:13 note: %3 declared here
%2:i32, %3:f32 = loop [b: $B2, c: $B3] { # loop_1
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ ^^^^^^
note: # Disassembly
%my_func = func():void {
diff --git a/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc b/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc
index 12497e6..568a636 100644
--- a/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc
+++ b/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc
@@ -39,9 +39,7 @@
namespace tint::hlsl::writer {
namespace {
-void ASTFuzzer(const tint::Program& program,
- const fuzz::wgsl::Options& fuzz_options,
- Options options) {
+void ASTFuzzer(const tint::Program& program, const fuzz::wgsl::Context& context, Options options) {
if (program.AST().HasOverrides()) {
return;
}
@@ -50,9 +48,9 @@
if (res == Success) {
const char* dxc_path = validate::kDxcDLLName;
bool must_validate = false;
- if (!fuzz_options.dxc.empty()) {
+ if (!context.options.dxc.empty()) {
must_validate = true;
- dxc_path = fuzz_options.dxc.c_str();
+ dxc_path = context.options.dxc.c_str();
}
auto dxc = tint::Command::LookPath(dxc_path);
diff --git a/src/tint/lang/spirv/writer/loop_test.cc b/src/tint/lang/spirv/writer/loop_test.cc
index 1fb4f9e..f8ccb38 100644
--- a/src/tint/lang/spirv/writer/loop_test.cc
+++ b/src/tint/lang/spirv/writer/loop_test.cc
@@ -212,7 +212,7 @@
auto* loop = b.Loop();
b.Append(loop->Body(), [&] {
auto* result = b.Equal(ty.bool_(), 1_i, 2_i);
- b.Continue(loop, result);
+ b.Continue(loop);
b.Append(loop->Continuing(), [&] { //
b.BreakIf(loop, result);
@@ -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);
diff --git a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture_fuzz.cc b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture_fuzz.cc
index 4c6efeb..78d6d9a 100644
--- a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture_fuzz.cc
+++ b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture_fuzz.cc
@@ -36,7 +36,13 @@
namespace {
bool CanRun(const Program& program,
+ const fuzz::wgsl::Context& context,
const MultiplanarExternalTexture::NewBindingPoints& remappings) {
+ if (context.program_properties.Contains(fuzz::wgsl::ProgramProperties::kBuiltinFnsShadowed) ||
+ context.program_properties.Contains(fuzz::wgsl::ProgramProperties::kBuiltinTypesShadowed)) {
+ return false; // MultiplanarExternalTexture assumes the Renamer transform has been run
+ }
+
Hashset<BindingPoint, 8> all_binding_points;
for (auto* global : program.AST().GlobalVariables()) {
if (auto* sem = program.Sem().Get<sem::GlobalVariable>(global)) {
@@ -83,8 +89,9 @@
void MultiplanarExternalTextureFuzzer(
const Program& program,
+ const fuzz::wgsl::Context& context,
const MultiplanarExternalTexture::NewBindingPoints& binding_points) {
- if (!CanRun(program, binding_points)) {
+ if (!CanRun(program, context, binding_points)) {
return;
}
diff --git a/src/tint/lang/wgsl/inspector/entry_point.h b/src/tint/lang/wgsl/inspector/entry_point.h
index 3709ade..3946e63 100644
--- a/src/tint/lang/wgsl/inspector/entry_point.h
+++ b/src/tint/lang/wgsl/inspector/entry_point.h
@@ -92,6 +92,8 @@
std::optional<uint32_t> location;
/// Value of the color attribute, if set.
std::optional<uint32_t> color;
+ /// Value of the blend_src attribute, if set.
+ std::optional<uint32_t> blend_src;
} attributes;
/// Scalar type that the variable is composed of.
ComponentType component_type = ComponentType::kUnknown;
diff --git a/src/tint/lang/wgsl/inspector/inspector.cc b/src/tint/lang/wgsl/inspector/inspector.cc
index d98ada7..9857271 100644
--- a/src/tint/lang/wgsl/inspector/inspector.cc
+++ b/src/tint/lang/wgsl/inspector/inspector.cc
@@ -49,6 +49,7 @@
#include "src/tint/lang/core/type/u32.h"
#include "src/tint/lang/core/type/vector.h"
#include "src/tint/lang/core/type/void.h"
+#include "src/tint/lang/wgsl/ast/blend_src_attribute.h"
#include "src/tint/lang/wgsl/ast/bool_literal_expression.h"
#include "src/tint/lang/wgsl/ast/call_expression.h"
#include "src/tint/lang/wgsl/ast/float_literal_expression.h"
@@ -613,6 +614,12 @@
std::tie(stage_variable.component_type, stage_variable.composition_type) =
CalculateComponentAndComposition(type);
+ if (auto* blend_src_attribute = ast::GetAttribute<ast::BlendSrcAttribute>(attributes)) {
+ TINT_ASSERT(blend_src_attribute->expr->Is<ast::IntLiteralExpression>());
+ stage_variable.attributes.blend_src = static_cast<uint32_t>(
+ blend_src_attribute->expr->As<ast::IntLiteralExpression>()->value);
+ }
+
stage_variable.attributes.location = location;
stage_variable.attributes.color = color;
diff --git a/src/tint/lang/wgsl/inspector/inspector_test.cc b/src/tint/lang/wgsl/inspector/inspector_test.cc
index 7468ccc..1a7c557 100644
--- a/src/tint/lang/wgsl/inspector/inspector_test.cc
+++ b/src/tint/lang/wgsl/inspector/inspector_test.cc
@@ -143,6 +143,8 @@
class InspectorGetEnableDirectivesTest : public InspectorRunner, public testing::Test {};
+class InspectorGetBlendSrcTest : public InspectorBuilder, public testing::Test {};
+
// This is a catch all for shaders that have demonstrated regressions/crashes in
// the wild.
class InspectorRegressionTest : public InspectorRunner, public testing::Test {};
@@ -4046,6 +4048,34 @@
}
}
+TEST_F(InspectorGetBlendSrcTest, Basic) {
+ Enable(wgsl::Extension::kChromiumInternalDualSourceBlending);
+
+ Structure("out_struct",
+ Vector{
+ Member("output_color", ty.vec4<f32>(), Vector{Location(0_u), BlendSrc(0_u)}),
+ Member("output_blend", ty.vec4<f32>(), Vector{Location(0_u), BlendSrc(1_u)}),
+ });
+
+ Func("ep_func", tint::Empty, ty("out_struct"),
+ Vector{
+ Decl(Var("out_var", ty("out_struct"))),
+ Return("out_var"),
+ },
+ Vector{
+ Stage(ast::PipelineStage::kFragment),
+ });
+
+ Inspector& inspector = Build();
+
+ auto result = inspector.GetEntryPoints();
+
+ ASSERT_EQ(1u, result.size());
+ ASSERT_EQ(2u, result[0].output_variables.size());
+ EXPECT_EQ(0u, result[0].output_variables[0].attributes.blend_src);
+ EXPECT_EQ(1u, result[0].output_variables[1].attributes.blend_src);
+}
+
} // namespace
static std::ostream& operator<<(std::ostream& out, const Inspector::TextureQueryType& ty) {
diff --git a/src/tint/lang/wgsl/reader/reader.cc b/src/tint/lang/wgsl/reader/reader.cc
index ed8009a..d446964 100644
--- a/src/tint/lang/wgsl/reader/reader.cc
+++ b/src/tint/lang/wgsl/reader/reader.cc
@@ -77,4 +77,20 @@
return ir;
}
+bool IsUnsupportedByIR(const ast::Enable* enable) {
+ for (auto ext : enable->extensions) {
+ switch (ext->name) {
+ case tint::wgsl::Extension::kChromiumExperimentalFramebufferFetch:
+ case tint::wgsl::Extension::kChromiumExperimentalPixelLocal:
+ case tint::wgsl::Extension::kChromiumExperimentalPushConstant:
+ case tint::wgsl::Extension::kChromiumInternalDualSourceBlending:
+ case tint::wgsl::Extension::kChromiumInternalRelaxedUniformLayout:
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
} // namespace tint::wgsl::reader
diff --git a/src/tint/lang/wgsl/reader/reader.h b/src/tint/lang/wgsl/reader/reader.h
index 45edcd8..991e3e0 100644
--- a/src/tint/lang/wgsl/reader/reader.h
+++ b/src/tint/lang/wgsl/reader/reader.h
@@ -32,6 +32,10 @@
#include "src/tint/lang/wgsl/program/program.h"
#include "src/tint/lang/wgsl/reader/options.h"
+namespace tint::ast {
+class Enable;
+} // namespace tint::ast
+
namespace tint::wgsl::reader {
/// Parses the WGSL source, returning the parsed program.
@@ -58,6 +62,10 @@
/// concrete types.
tint::Result<core::ir::Module> ProgramToLoweredIR(const Program& program);
+/// Allows for checking if an extension is currently supported/unsupported by IR
+/// before trying to convert to it.
+bool IsUnsupportedByIR(const ast::Enable* enable);
+
} // namespace tint::wgsl::reader
#endif // SRC_TINT_LANG_WGSL_READER_READER_H_
diff --git a/src/tint/tint.gni b/src/tint/tint.gni
index bf48aae..8f3d528 100644
--- a/src/tint/tint.gni
+++ b/src/tint/tint.gni
@@ -133,6 +133,8 @@
import("//testing/libfuzzer/fuzzer_test.gni")
fuzzer_corpus_wgsl_dir = "${root_gen_dir}/fuzzers/wgsl_corpus"
fuzzer_corpus_wgsl_stamp = "${fuzzer_corpus_wgsl_dir}.stamp"
+ fuzzer_corpus_ir_dir = "${root_gen_dir}/fuzzers/ir_corpus"
+ fuzzer_corpus_ir_stamp = "${fuzzer_corpus_ir_dir}.stamp"
template("tint_fuzz_source_set") {
source_set(target_name) {