Enforce layering_check in Bazel build rules. (#5032)

* Enforce layering_check in Bazel build rules.

Enforcing layering_check ensures that the Build targets do not rely on
transitive dependencies. See
https://github.com/bazelbuild/bazel/pull/11440 for a detailed
description of the feature.

We also do a style pass on the build files, ensuring that common linters
are happy with it.

* Add .bazelversion file and fix build_defs.bzl.

We fix build_defs.bzl to work on Bazel 5.0.0.
diff --git a/.bazelversion b/.bazelversion
new file mode 100644
index 0000000..28cbf7c
--- /dev/null
+++ b/.bazelversion
@@ -0,0 +1 @@
+5.0.0
\ No newline at end of file
diff --git a/BUILD.bazel b/BUILD.bazel
index 0af7700..255d4e7 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,27 +1,24 @@
 load(
     ":build_defs.bzl",
+    "CLDEBUGINFO100_GRAMMAR_JSON_FILE",
     "COMMON_COPTS",
     "DEBUGINFO_GRAMMAR_JSON_FILE",
-    "CLDEBUGINFO100_GRAMMAR_JSON_FILE",
     "SHDEBUGINFO100_GRAMMAR_JSON_FILE",
     "TEST_COPTS",
-    "base_test",
     "generate_core_tables",
     "generate_enum_string_mapping",
     "generate_extinst_lang_headers",
     "generate_glsl_tables",
     "generate_opencl_tables",
     "generate_vendor_tables",
-    "link_test",
-    "lint_test",
-    "opt_test",
-    "reduce_test",
-    "util_test",
-    "val_test",
+    "incompatible_with",
 )
 
 package(
     default_visibility = ["//visibility:private"],
+    features = [
+        "layering_check",
+    ],
 )
 
 licenses(["notice"])
@@ -41,35 +38,50 @@
     srcs = ["utils/generate_language_headers.py"],
 )
 
-generate_core_tables("unified1")
+generate_core_tables(version = "unified1")
 
-generate_enum_string_mapping("unified1")
+generate_enum_string_mapping(version = "unified1")
 
-generate_opencl_tables("unified1")
+generate_opencl_tables(version = "unified1")
 
-generate_glsl_tables("unified1")
+generate_glsl_tables(version = "unified1")
 
-generate_vendor_tables("spv-amd-shader-explicit-vertex-parameter")
+generate_vendor_tables(extension = "spv-amd-shader-explicit-vertex-parameter")
 
-generate_vendor_tables("spv-amd-shader-trinary-minmax")
+generate_vendor_tables(extension = "spv-amd-shader-trinary-minmax")
 
-generate_vendor_tables("spv-amd-gcn-shader")
+generate_vendor_tables(extension = "spv-amd-gcn-shader")
 
-generate_vendor_tables("spv-amd-shader-ballot")
+generate_vendor_tables(extension = "spv-amd-shader-ballot")
 
-generate_vendor_tables("debuginfo")
+generate_vendor_tables(extension = "debuginfo")
 
-generate_vendor_tables("opencl.debuginfo.100", "CLDEBUG100_")
+generate_vendor_tables(extension = "nonsemantic.clspvreflection")
 
-generate_vendor_tables("nonsemantic.shader.debuginfo.100", "SHDEBUG100_")
+generate_vendor_tables(
+    extension = "opencl.debuginfo.100",
+    operand_kind_prefix = "CLDEBUG100_",
+)
 
-generate_vendor_tables("nonsemantic.clspvreflection")
+generate_vendor_tables(
+    extension = "nonsemantic.shader.debuginfo.100",
+    operand_kind_prefix = "SHDEBUG100_",
+)
 
-generate_extinst_lang_headers("DebugInfo", DEBUGINFO_GRAMMAR_JSON_FILE)
+generate_extinst_lang_headers(
+    name = "DebugInfo",
+    grammar = DEBUGINFO_GRAMMAR_JSON_FILE,
+)
 
-generate_extinst_lang_headers("OpenCLDebugInfo100", CLDEBUGINFO100_GRAMMAR_JSON_FILE)
+generate_extinst_lang_headers(
+    name = "OpenCLDebugInfo100",
+    grammar = CLDEBUGINFO100_GRAMMAR_JSON_FILE,
+)
 
-generate_extinst_lang_headers("NonSemanticShaderDebugInfo100", SHDEBUGINFO100_GRAMMAR_JSON_FILE)
+generate_extinst_lang_headers(
+    name = "NonSemanticShaderDebugInfo100",
+    grammar = SHDEBUGINFO100_GRAMMAR_JSON_FILE,
+)
 
 py_binary(
     name = "generate_registry_tables",
@@ -77,11 +89,11 @@
 )
 
 genrule(
-    name = "gen_registry_tables",
+    name = "generators_inc",
     srcs = ["@spirv_headers//:spirv_xml_registry"],
     outs = ["generators.inc"],
-    cmd = "$(location generate_registry_tables) --xml=$(location @spirv_headers//:spirv_xml_registry) --generator-output=$(location generators.inc)",
-    cmd_bat = "$(location //:generate_registry_tables) --xml=$(location @spirv_headers//:spirv_xml_registry) --generator-output=$(location generators.inc)",
+    cmd = "$(location :generate_registry_tables) --xml=$(location @spirv_headers//:spirv_xml_registry) --generator-output=$(location generators.inc)",
+    cmd_bat = "$(location :generate_registry_tables) --xml=$(location @spirv_headers//:spirv_xml_registry) --generator-output=$(location generators.inc)",
     exec_tools = [":generate_registry_tables"],
 )
 
@@ -91,120 +103,102 @@
 )
 
 genrule(
-    name = "gen_build_version",
+    name = "build_version_inc",
     srcs = ["CHANGES"],
     outs = ["build-version.inc"],
-    cmd = "SOURCE_DATE_EPOCH=0 $(location update_build_version) $(location CHANGES) $(location build-version.inc)",
-    cmd_bat = "set SOURCE_DATE_EPOCH=0  && $(location //:update_build_version) $(location CHANGES) $(location build-version.inc)",
+    cmd = "SOURCE_DATE_EPOCH=0 $(location :update_build_version) $(location CHANGES) $(location build-version.inc)",
+    cmd_bat = "set SOURCE_DATE_EPOCH=0  && $(location :update_build_version) $(location CHANGES) $(location build-version.inc)",
     exec_tools = [":update_build_version"],
 )
 
 # Libraries
 
 cc_library(
-    name = "generated_headers",
+    name = "spirv_tools",
     hdrs = [
-        ":gen_build_version",
+        "include/spirv-tools/libspirv.h",
+        "include/spirv-tools/libspirv.hpp",
+    ],
+    copts = COMMON_COPTS,
+    includes = ["include"],
+    linkstatic = 1,
+    visibility = ["//visibility:public"],
+    deps = [
+        ":spirv_tools_internal",
+    ],
+)
+
+cc_library(
+    name = "spirv_tools_internal",
+    srcs = glob([
+        "source/*.cpp",
+        "source/util/*.cpp",
+        "source/val/*.cpp",
+    ]) + [
+        ":build_version_inc",
         ":gen_core_tables_unified1",
         ":gen_enum_string_mapping",
         ":gen_extinst_lang_headers_DebugInfo",
-        ":gen_extinst_lang_headers_OpenCLDebugInfo100",
         ":gen_extinst_lang_headers_NonSemanticShaderDebugInfo100",
+        ":gen_extinst_lang_headers_OpenCLDebugInfo100",
         ":gen_glsl_tables_unified1",
         ":gen_opencl_tables_unified1",
-        ":gen_registry_tables",
+        ":generators_inc",
         ":gen_vendor_tables_debuginfo",
         ":gen_vendor_tables_nonsemantic_clspvreflection",
-        ":gen_vendor_tables_opencl_debuginfo_100",
         ":gen_vendor_tables_nonsemantic_shader_debuginfo_100",
+        ":gen_vendor_tables_opencl_debuginfo_100",
         ":gen_vendor_tables_spv_amd_gcn_shader",
         ":gen_vendor_tables_spv_amd_shader_ballot",
         ":gen_vendor_tables_spv_amd_shader_explicit_vertex_parameter",
         ":gen_vendor_tables_spv_amd_shader_trinary_minmax",
     ],
-    copts = COMMON_COPTS,
-)
-
-cc_library(
-    name = "spirv_tools_headers",
-    hdrs = glob([
+    hdrs = [
         "include/spirv-tools/libspirv.h",
         "include/spirv-tools/libspirv.hpp",
+        ":gen_extinst_lang_headers_DebugInfo",
+        ":gen_extinst_lang_headers_NonSemanticShaderDebugInfo100",
+        ":gen_extinst_lang_headers_OpenCLDebugInfo100",
+    ] + glob([
         "source/*.h",
         "source/util/*.h",
         "source/val/*.h",
     ]),
     copts = COMMON_COPTS,
-    includes = ["source"],
-    deps = [
-        "@spirv_headers//:spirv_cpp11_headers",
-    ],
-)
-
-cc_library(
-    name = "spirv_tools",
-    srcs = glob([
-        "source/*.cpp",
-        "source/util/*.cpp",
-        "source/val/*.cpp",
-    ]),
-    hdrs = [
-        "include/spirv-tools/libspirv.h",
-        "include/spirv-tools/libspirv.hpp",
-    ],
-    copts = COMMON_COPTS + select({
-        "@bazel_tools//src/conditions:windows": [""],
-        "//conditions:default": ["-Wno-implicit-fallthrough"],
-    }),
     includes = ["include"],
-    linkstatic = 1,
-    visibility = ["//visibility:public"],
     deps = [
-        ":generated_headers",
-        ":spirv_tools_headers",
+        "@spirv_headers//:spirv_common_headers",
         "@spirv_headers//:spirv_cpp11_headers",
-        "@spirv_headers//:spirv_common_headers",
     ],
 )
 
 cc_library(
-    name = "spirv_tools_comp",
-    srcs = glob([
-        "source/comp/*.cpp",
-        "source/comp/*.h",
-    ]),
-    copts = COMMON_COPTS,
-    linkstatic = 1,
-    visibility = ["//visibility:public"],
-    deps = [
-        ":generated_headers",
-        ":spirv_tools",
-        ":spirv_tools_headers",
-        "@spirv_headers//:spirv_common_headers",
-    ],
-)
-
-cc_library(
-    name = "spirv_tools_opt_headers",
-    hdrs = glob(["source/opt/*.h"]),
-    copts = COMMON_COPTS,
-)
-
-cc_library(
     name = "spirv_tools_opt",
-    srcs = glob(["source/opt/*.cpp"]),
     hdrs = [
         "include/spirv-tools/instrument.hpp",
         "include/spirv-tools/optimizer.hpp",
     ],
     copts = COMMON_COPTS,
-    includes = ["include"],
     linkstatic = 1,
     visibility = ["//visibility:public"],
     deps = [
         ":spirv_tools",
-        ":spirv_tools_headers",
-        ":spirv_tools_opt_headers",
+        ":spirv_tools_opt_internal",
+    ],
+)
+
+cc_library(
+    name = "spirv_tools_opt_internal",
+    srcs = glob(["source/opt/*.cpp"]) + [
+        ":gen_vendor_tables_spv_amd_shader_ballot",
+    ],
+    hdrs = glob(["source/opt/*.h"]) + [
+        "include/spirv-tools/instrument.hpp",
+        "include/spirv-tools/optimizer.hpp",
+    ],
+    copts = COMMON_COPTS,
+    deps = [
+        ":spirv_tools_internal",
         "@spirv_headers//:spirv_common_headers",
     ],
 )
@@ -214,11 +208,9 @@
     srcs = glob(["source/reduce/*.cpp"]),
     hdrs = glob(["source/reduce/*.h"]),
     copts = COMMON_COPTS,
-    linkstatic = 1,
-    visibility = ["//visibility:public"],
     deps = [
-        ":spirv_tools",
-        ":spirv_tools_opt",
+        ":spirv_tools_internal",
+        ":spirv_tools_opt_internal",
     ],
 )
 
@@ -230,21 +222,38 @@
     linkstatic = 1,
     visibility = ["//visibility:public"],
     deps = [
-        ":spirv_tools",
-        ":spirv_tools_opt",
+        ":spirv_tools_internal",
+        ":spirv_tools_opt_internal",
+    ],
+)
+
+cc_library(
+    name = "spirv_tools_lint_internal",
+    srcs = glob([
+        "source/lint/*.cpp",
+        "source/lint/*.h",
+    ]),
+    hdrs = ["include/spirv-tools/linter.hpp"] + glob([
+        "source/lint/*.h",
+    ]),
+    copts = COMMON_COPTS,
+    includes = ["include"],
+    deps = [
+        ":spirv_tools_internal",
+        ":spirv_tools_opt_internal",
     ],
 )
 
 cc_library(
     name = "spirv_tools_lint",
-    srcs = glob(["source/lint/*.cpp", "source/lint/*.h"]),
     hdrs = ["include/spirv-tools/linter.hpp"],
     copts = COMMON_COPTS,
+    includes = ["include"],
     linkstatic = 1,
     visibility = ["//visibility:public"],
     deps = [
         ":spirv_tools",
-        ":spirv_tools_opt",
+        ":spirv_tools_lint_internal",
     ],
 )
 
@@ -253,23 +262,27 @@
     srcs = glob(["tools/util/*.cpp"]),
     hdrs = glob(["tools/util/*.h"]),
     copts = COMMON_COPTS,
-    linkstatic = 1,
-    visibility = ["//visibility:public"],
     deps = [":spirv_tools"],
 )
 
+cc_library(
+    name = "tools_io",
+    hdrs = ["tools/io.h"],
+    copts = COMMON_COPTS,
+)
+
 # Tools
 
 cc_binary(
     name = "spirv-as",
     srcs = [
         "tools/as/as.cpp",
-        "tools/io.h",
     ],
     copts = COMMON_COPTS,
     visibility = ["//visibility:public"],
     deps = [
-        ":spirv_tools",
+        ":spirv_tools_internal",
+        ":tools_io",
     ],
 )
 
@@ -277,25 +290,25 @@
     name = "spirv-dis",
     srcs = [
         "tools/dis/dis.cpp",
-        "tools/io.h",
     ],
     copts = COMMON_COPTS,
     visibility = ["//visibility:public"],
     deps = [
         ":spirv_tools",
+        ":tools_io",
     ],
 )
 
 cc_binary(
     name = "spirv-val",
     srcs = [
-        "tools/io.h",
         "tools/val/val.cpp",
     ],
     copts = COMMON_COPTS,
     visibility = ["//visibility:public"],
     deps = [
-        ":spirv_tools",
+        ":spirv_tools_internal",
+        ":tools_io",
         ":tools_util",
     ],
 )
@@ -303,14 +316,14 @@
 cc_binary(
     name = "spirv-opt",
     srcs = [
-        "tools/io.h",
         "tools/opt/opt.cpp",
     ],
     copts = COMMON_COPTS,
     visibility = ["//visibility:public"],
     deps = [
-        ":spirv_tools",
-        ":spirv_tools_opt",
+        ":spirv_tools_internal",
+        ":spirv_tools_opt_internal",
+        ":tools_io",
         ":tools_util",
     ],
 )
@@ -318,15 +331,15 @@
 cc_binary(
     name = "spirv-reduce",
     srcs = [
-        "tools/io.h",
         "tools/reduce/reduce.cpp",
     ],
     copts = COMMON_COPTS,
     visibility = ["//visibility:public"],
     deps = [
-        ":spirv_tools",
-        ":spirv_tools_opt",
+        ":spirv_tools_internal",
+        ":spirv_tools_opt_internal",
         ":spirv_tools_reduce",
+        ":tools_io",
         ":tools_util",
     ],
 )
@@ -334,28 +347,28 @@
 cc_binary(
     name = "spirv-link",
     srcs = [
-        "tools/io.h",
         "tools/link/linker.cpp",
     ],
     copts = COMMON_COPTS,
     visibility = ["//visibility:public"],
     deps = [
-        ":spirv_tools",
+        ":spirv_tools_internal",
         ":spirv_tools_link",
+        ":tools_io",
     ],
 )
 
 cc_binary(
     name = "spirv-lint",
     srcs = [
-        "tools/io.h",
         "tools/lint/lint.cpp",
     ],
     copts = COMMON_COPTS,
     visibility = ["//visibility:public"],
     deps = [
-        ":spirv_tools",
         ":spirv_tools_lint",
+        ":spirv_tools_opt_internal",
+        ":tools_io",
         ":tools_util",
     ],
 )
@@ -366,50 +379,152 @@
         "tools/cfg/bin_to_dot.cpp",
         "tools/cfg/bin_to_dot.h",
         "tools/cfg/cfg.cpp",
-        "tools/io.h",
     ],
     copts = COMMON_COPTS,
     visibility = ["//visibility:public"],
-    deps = [":spirv_tools"],
+    deps = [
+        ":spirv_tools_internal",
+        ":tools_io",
+    ],
 )
 
 # Unit tests
 
 cc_library(
-    name = "test_common",
+    name = "test_lib",
     testonly = 1,
     srcs = [
-        "test/test_fixture.h",
         "test/unit_spirv.cpp",
+    ],
+    hdrs = [
+        "test/test_fixture.h",
         "test/unit_spirv.h",
     ],
-    compatible_with = [],
     copts = TEST_COPTS,
-    includes = ["test"],
-    linkstatic = 1,
     deps = [
-        ":spirv_tools",
+        ":spirv_tools_internal",
         "@com_google_googletest//:gtest",
     ],
 )
 
-cc_library(
-    name = "link_test_common",
-    testonly = 1,
-    srcs = ["test/link/linker_fixture.h"],
-    compatible_with = [],
+# PCH (precompiled header) tests only work when using CMake and MSVC on Windows,
+# so they will be skipped in the Bazel builds.
+
+[cc_test(
+    name = "base_{testcase}_test".format(testcase = f[len("test/"):-len("_test.cpp")]),
+    size = "small",
+    srcs = [f],
+    copts = TEST_COPTS,
+    linkstatic = 1,
+    target_compatible_with = {
+        "test/timer_test.cpp": incompatible_with(["@bazel_tools//src/conditions:windows"]),
+    }.get(f, []),
+    deps = [
+        ":spirv_tools_internal",
+        ":test_lib",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+) for f in glob(
+    ["test/*_test.cpp"],
+    exclude = [
+        "test/cpp_interface_test.cpp",
+        "test/log_test.cpp",
+        "test/pch_test.cpp",
+    ],
+)]
+
+cc_test(
+    name = "base_cpp_interface_test",
+    size = "small",
+    srcs = ["test/cpp_interface_test.cpp"],
+    linkstatic = 1,
+    deps = [
+        ":spirv_tools_opt_internal",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+        "@spirv_headers//:spirv_cpp11_headers",
+    ],
+)
+
+cc_test(
+    name = "base_ilist_test",
+    size = "small",
+    srcs = ["test/util/ilist_test.cpp"],
     copts = TEST_COPTS,
     linkstatic = 1,
     deps = [
-        ":spirv_tools_link",
-        ":test_common",
+        ":spirv_tools_internal",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "base_log_test",
+    size = "small",
+    srcs = ["test/log_test.cpp"],
+    copts = TEST_COPTS,
+    linkstatic = 1,
+    deps = [
+        ":spirv_tools_opt_internal",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
     ],
 )
 
 cc_library(
-    name = "opt_test_common",
+    name = "link_test_lib",
     testonly = 1,
-    srcs = ["test/opt/pass_utils.cpp"],
+    hdrs = ["test/link/linker_fixture.h"],
+    copts = TEST_COPTS,
+    deps = [
+        ":spirv_tools_internal",
+        ":spirv_tools_link",
+        ":test_lib",
+        "@com_google_effcee//:effcee",
+        "@com_googlesource_code_re2//:re2",
+    ],
+)
+
+[cc_test(
+    name = "link_{testcase}_test".format(testcase = f[len("test/link/"):-len("_test.cpp")]),
+    size = "small",
+    srcs = [f],
+    copts = TEST_COPTS,
+    linkstatic = 1,
+    deps = [
+        ":link_test_lib",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+) for f in glob(
+    ["test/link/*_test.cpp"],
+)]
+
+[cc_test(
+    name = "lint_{testcase}_test".format(testcase = f[len("test/lint/"):-len("_test.cpp")]),
+    size = "small",
+    srcs = [f],
+    copts = TEST_COPTS,
+    linkstatic = 1,
+    deps = [
+        ":spirv_tools",
+        ":spirv_tools_lint_internal",
+        ":spirv_tools_opt_internal",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+) for f in glob(
+    ["test/lint/*_test.cpp"],
+)]
+
+cc_library(
+    name = "opt_test_lib",
+    testonly = 1,
+    srcs = [
+        "test/opt/pass_utils.cpp",
+    ],
     hdrs = [
         "test/opt/assembly_builder.h",
         "test/opt/function_utils.h",
@@ -417,143 +532,181 @@
         "test/opt/pass_fixture.h",
         "test/opt/pass_utils.h",
     ],
-    compatible_with = [],
+    copts = TEST_COPTS,
+    deps = [
+        ":spirv_tools_internal",
+        ":spirv_tools_opt_internal",
+        "@com_google_effcee//:effcee",
+        "@com_google_googletest//:gtest",
+    ],
+)
+
+[cc_test(
+    name = "opt_{testcase}_test".format(testcase = f[len("test/opt/"):-len("_test.cpp")]),
+    size = "small",
+    srcs = [f],
     copts = TEST_COPTS,
     linkstatic = 1,
     deps = [
-        ":spirv_tools_opt",
-        ":test_common",
+        ":opt_test_lib",
+        ":spirv_tools_internal",
+        ":spirv_tools_opt_internal",
+        ":test_lib",
+        "@com_google_effcee//:effcee",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
     ],
-)
+) for f in glob(["test/opt/*_test.cpp"])]
 
-cc_library(
-    name = "reduce_test_common",
-    testonly = 1,
-    srcs = [
-        "test/reduce/reduce_test_util.cpp",
-        "tools/io.h",
-    ],
-    hdrs = ["test/reduce/reduce_test_util.h"],
-    compatible_with = [],
+[cc_test(
+    name = "opt_dom_tree_{testcase}_test".format(testcase = f[len("test/opt/dominator_tree/"):-len(".cpp")]),
+    size = "small",
+    srcs = [f],
     copts = TEST_COPTS,
     linkstatic = 1,
     deps = [
-        ":spirv_tools_reduce",
-        ":test_common",
+        ":opt_test_lib",
+        ":spirv_tools_opt_internal",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
     ],
-)
-
-cc_library(
-    name = "val_test_common",
-    testonly = 1,
-    srcs = [
-        "test/val/val_code_generator.cpp",
-        "test/val/val_fixtures.h",
-    ],
-    hdrs = [
-        "test/val/val_code_generator.h",
-    ],
-    compatible_with = [],
-    copts = TEST_COPTS,
-    linkstatic = 1,
-    deps = [":test_common"],
-)
-
-# PCH (precompiled header) tests only work when using CMake and MSVC on Windows,
-# so they will be skipped in the Bazel builds.
-
-[base_test(
-    name = f[5:-4],  # strip test/, .cpp
-    srcs = [f],
-) for f in glob(
-    ["test/*.cpp"],
-    exclude = [
-        "test/cpp_interface_test.cpp", # has its own base_test below.
-        "test/log_test.cpp", # has its own base_test below.
-        "test/pch_test.cpp", # pch tests are skipped.
-        "test/timer_test.cpp", # has its own base_test below.
-    ],
-)]
-
-# This test uses unistd.h and does not run on Windows.
-base_test(
-    name = "timer_test",
-    srcs = select({
-        "@bazel_tools//src/conditions:windows": [],
-        "//conditions:default": ["test/timer_test.cpp"],
-    }),
-)
-
-base_test(
-    name = "cpp_interface_test",
-    srcs = ["test/cpp_interface_test.cpp"],
-    deps = [":spirv_tools_opt"],
-)
-
-base_test(
-    name = "log_test",
-    srcs = ["test/log_test.cpp"],
-    deps = [":spirv_tools_opt"],
-)
-
-[link_test(
-    name = f[10:-4],  # strip test/link/, .cpp
-    srcs = [f],
-) for f in glob(
-    ["test/link/*.cpp"],
-)]
-
-[lint_test(
-    name = f[10:-4],  # strip test/lint/, .cpp
-    srcs = [f],
-) for f in glob(
-    ["test/lint/*.cpp"],
-)]
-
-[opt_test(
-    name = f[9:-4],  # strip test/opt/, .cpp
-    srcs = [f],
-) for f in glob(
-    ["test/opt/*.cpp"],
-    # pch tests are skipped.
-    exclude = ["test/opt/pch_test_opt.cpp"],
-)]
-
-[opt_test(
-    name = "dom_tree_" + f[24:-4],  # strip test/opt/dominator_tree/, .cpp
-    srcs = [f],
 ) for f in glob(
     ["test/opt/dominator_tree/*.cpp"],
-    # pch tests are skipped.
     exclude = ["test/opt/dominator_tree/pch_test_opt_dom.cpp"],
 )]
 
-[opt_test(
-    name = "loop_" + f[28:-4],  # strip test/opt/loop_optimizations/, .cpp
+[cc_test(
+    name = "opt_loop_{testcase}_test".format(testcase = f[len("test/opt/loop_optimizations/"):-len(".cpp")]),
+    size = "small",
     srcs = [f],
+    copts = TEST_COPTS,
+    linkstatic = 1,
+    deps = [
+        ":opt_test_lib",
+        ":spirv_tools",
+        ":spirv_tools_opt_internal",
+        "@com_google_effcee//:effcee",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
 ) for f in glob(
     ["test/opt/loop_optimizations/*.cpp"],
-    # pch tests are skipped.
     exclude = ["test/opt/loop_optimizations/pch_test_opt_loop.cpp"],
 )]
 
-[reduce_test(
-    name = f[12:-4],  # strip test/reduce/, .cpp
-    srcs = [f],
-) for f in glob(["test/reduce/*.cpp"])]
+cc_library(
+    name = "reduce_test_lib",
+    testonly = 1,
+    srcs = [
+        "test/reduce/reduce_test_util.cpp",
+    ],
+    hdrs = ["test/reduce/reduce_test_util.h"],
+    copts = TEST_COPTS,
+    deps = [
+        ":spirv_tools",
+        ":spirv_tools_opt_internal",
+        ":spirv_tools_reduce",
+        ":test_lib",
+        ":tools_io",
+        "@com_google_googletest//:gtest",
+    ],
+)
 
-[util_test(
-    name = f[10:-4],  # strip test/util/, .cpp
+[cc_test(
+    name = "reduce_{testcase}_test".format(testcase = f[len("test/reduce/"):-len("_test.cpp")]),
+    size = "small",
     srcs = [f],
-) for f in glob(["test/util/*.cpp"])]
+    copts = TEST_COPTS,
+    linkstatic = 1,
+    deps = [
+        ":reduce_test_lib",
+        ":spirv_tools_internal",
+        ":spirv_tools_opt_internal",
+        ":spirv_tools_reduce",
+        "@com_google_googletest//:gtest_main",
+    ],
+) for f in glob(["test/reduce/*_test.cpp"])]
 
-[val_test(
-    name = f[9:-4],  # strip test/val/, .cpp
+[cc_test(
+    name = "util_{testcase}_test".format(testcase = f[len("test/util/"):-len("_test.cpp")]),
+    size = "small",
     srcs = [f],
+    copts = TEST_COPTS,
+    linkstatic = 1,
+    deps = [
+        ":spirv_tools_internal",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+) for f in glob(["test/util/*_test.cpp"])]
+
+cc_library(
+    name = "val_test_lib",
+    testonly = 1,
+    srcs = [
+        "test/val/val_code_generator.cpp",
+    ],
+    hdrs = [
+        "test/val/val_code_generator.h",
+        "test/val/val_fixtures.h",
+    ],
+    copts = TEST_COPTS,
+    deps = [
+        ":spirv_tools_internal",
+        ":test_lib",
+    ],
+)
+
+[cc_test(
+    name = "val_{testcase}_test".format(testcase = f[len("test/val/val_"):-len("_test.cpp")]),
+    size = "small",
+    srcs = [f],
+    copts = TEST_COPTS,
+    linkstatic = 1,
+    deps = [
+        ":spirv_tools_internal",
+        ":test_lib",
+        ":val_test_lib",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
 ) for f in glob(
-    ["test/val/*.cpp"],
+    ["test/val/val_*_test.cpp"],
     exclude = [
-        "test/val/pch_test_val.cpp", # pch tests are skipped.
+        "test/val/val_capability_test.cpp",
+        "test/val/val_limits_test.cpp",
     ],
 )]
 
+cc_test(
+    name = "val_capability_test",
+    size = "large",
+    timeout = "long",
+    srcs = ["test/val/val_capability_test.cpp"],
+    copts = TEST_COPTS + ["-O3"],
+    linkstatic = 1,
+    deps = [
+        ":spirv_tools_internal",
+        ":test_lib",
+        ":val_test_lib",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "val_limits_test",
+    size = "large",
+    timeout = "long",
+    srcs = ["test/val/val_limits_test.cpp"],
+    copts = TEST_COPTS + [
+        "-O3",
+    ],
+    linkstatic = 1,
+    deps = [
+        ":test_lib",
+        ":val_test_lib",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/build_defs.bzl b/build_defs.bzl
index ed726ae..a94a425 100644
--- a/build_defs.bzl
+++ b/build_defs.bzl
@@ -1,20 +1,22 @@
+"""Constants and macros for spirv-tools BUILD."""
+
 COMMON_COPTS = [
-        "-DSPIRV_CHECK_CONTEXT",
-        "-DSPIRV_COLOR_TERMINAL",
-    ] + select({
-    "@bazel_tools//src/conditions:windows": [""],
+    "-DSPIRV_CHECK_CONTEXT",
+    "-DSPIRV_COLOR_TERMINAL",
+] + select({
+    "@bazel_tools//src/conditions:windows": [],
     "//conditions:default": [
         "-DSPIRV_LINUX",
         "-DSPIRV_TIMER_ENABLED",
+        "-std=c++11",
+        "-fvisibility=hidden",
+        "-fno-exceptions",
+        "-fno-rtti",
         "-Wall",
         "-Wextra",
         "-Wnon-virtual-dtor",
         "-Wno-missing-field-initializers",
         "-Werror",
-        "-std=c++11",
-        "-fvisibility=hidden",
-        "-fno-exceptions",
-        "-fno-rtti",
         "-Wno-long-long",
         "-Wshadow",
         "-Wundef",
@@ -23,154 +25,161 @@
     ],
 })
 
-TEST_COPTS = COMMON_COPTS + select({
+TEST_COPTS = COMMON_COPTS + [
+] + select({
     "@bazel_tools//src/conditions:windows": [
         # Disable C4503 "decorated name length exceeded" warning,
         # triggered by some heavily templated types.
         # We don't care much about that in test code.
         # Important to do since we have warnings-as-errors.
-        "/wd4503"
+        "/wd4503",
     ],
     "//conditions:default": [
         "-Wno-undef",
         "-Wno-self-assign",
         "-Wno-shadow",
-        "-Wno-unused-parameter"
+        "-Wno-unused-parameter",
     ],
 })
 
+def incompatible_with(incompatible_constraints):
+    return select(_merge_dicts([{"//conditions:default": []}, {
+        constraint: ["@platforms//:incompatible"]
+        for constraint in incompatible_constraints
+    }]))
+
 DEBUGINFO_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_debuginfo_grammar_unified1"
 CLDEBUGINFO100_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_opencl_debuginfo_100_grammar_unified1"
 SHDEBUGINFO100_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_nonsemantic_shader_debuginfo_100_grammar_unified1"
 
-def generate_core_tables(version = None):
+def _merge_dicts(dicts):
+    merged = {}
+    for d in dicts:
+        merged.update(d)
+    return merged
+
+def generate_core_tables(version):
     if not version:
         fail("Must specify version", "version")
-    grammars = [
-        "@spirv_headers//:spirv_core_grammar_" + version,
-        DEBUGINFO_GRAMMAR_JSON_FILE,
-        CLDEBUGINFO100_GRAMMAR_JSON_FILE,
-    ]
-    outs = [
-        "core.insts-{}.inc".format(version),
-        "operand.kinds-{}.inc".format(version),
-    ]
-    fmtargs = grammars + outs
+
+    grammars = dict(
+        core_grammar = "@spirv_headers//:spirv_core_grammar_{}".format(version),
+        debuginfo_grammar = DEBUGINFO_GRAMMAR_JSON_FILE,
+        cldebuginfo_grammar = CLDEBUGINFO100_GRAMMAR_JSON_FILE,
+    )
+
+    outs = dict(
+        core_insts_output = "core.insts-{}.inc".format(version),
+        operand_kinds_output = "operand.kinds-{}.inc".format(version),
+    )
+
+    cmd = (
+        "$(location :generate_grammar_tables)" +
+        " --spirv-core-grammar=$(location {core_grammar})" +
+        " --extinst-debuginfo-grammar=$(location {debuginfo_grammar})" +
+        " --extinst-cldebuginfo100-grammar=$(location {cldebuginfo_grammar})" +
+        " --core-insts-output=$(location {core_insts_output})" +
+        " --operand-kinds-output=$(location {operand_kinds_output})" +
+        " --output-language=c++"
+    ).format(**_merge_dicts([grammars, outs]))
+
     native.genrule(
         name = "gen_core_tables_" + version,
-        srcs = grammars,
-        outs = outs,
-        cmd = (
-            "$(location :generate_grammar_tables) " +
-            "--spirv-core-grammar=$(location {0}) " +
-            "--extinst-debuginfo-grammar=$(location {1}) " +
-            "--extinst-cldebuginfo100-grammar=$(location {2}) " +
-            "--core-insts-output=$(location {3}) " +
-            "--operand-kinds-output=$(location {4}) " +
-            "--output-language=c++"
-        ).format(*fmtargs),
-        cmd_bat = (
-            "$(location :generate_grammar_tables) " +
-            "--spirv-core-grammar=$(location {0}) " +
-            "--extinst-debuginfo-grammar=$(location {1}) " +
-            "--extinst-cldebuginfo100-grammar=$(location {2}) " +
-            "--core-insts-output=$(location {3}) " +
-            "--operand-kinds-output=$(location {4}) " +
-            "--output-language=c++"
-        ).format(*fmtargs),
+        srcs = grammars.values(),
+        outs = outs.values(),
+        cmd = cmd,
+        cmd_bat = cmd,
         exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
-def generate_enum_string_mapping(version = None):
+def generate_enum_string_mapping(version):
     if not version:
         fail("Must specify version", "version")
-    grammars = [
-        "@spirv_headers//:spirv_core_grammar_" + version,
-        DEBUGINFO_GRAMMAR_JSON_FILE,
-        CLDEBUGINFO100_GRAMMAR_JSON_FILE,
-    ]
-    outs = [
-        "extension_enum.inc",
-        "enum_string_mapping.inc",
-    ]
-    fmtargs = grammars + outs
+
+    grammars = dict(
+        core_grammar = "@spirv_headers//:spirv_core_grammar_{}".format(version),
+        debuginfo_grammar = DEBUGINFO_GRAMMAR_JSON_FILE,
+        cldebuginfo_grammar = CLDEBUGINFO100_GRAMMAR_JSON_FILE,
+    )
+
+    outs = dict(
+        extension_enum_ouput = "extension_enum.inc",
+        enum_string_mapping_output = "enum_string_mapping.inc",
+    )
+
+    cmd = (
+        "$(location :generate_grammar_tables)" +
+        " --spirv-core-grammar=$(location {core_grammar})" +
+        " --extinst-debuginfo-grammar=$(location {debuginfo_grammar})" +
+        " --extinst-cldebuginfo100-grammar=$(location {cldebuginfo_grammar})" +
+        " --extension-enum-output=$(location {extension_enum_ouput})" +
+        " --enum-string-mapping-output=$(location {enum_string_mapping_output})" +
+        " --output-language=c++"
+    ).format(**_merge_dicts([grammars, outs]))
+
     native.genrule(
         name = "gen_enum_string_mapping",
-        srcs = grammars,
-        outs = outs,
-        cmd = (
-            "$(location :generate_grammar_tables) " +
-            "--spirv-core-grammar=$(location {0}) " +
-            "--extinst-debuginfo-grammar=$(location {1}) " +
-            "--extinst-cldebuginfo100-grammar=$(location {2}) " +
-            "--extension-enum-output=$(location {3}) " +
-            "--enum-string-mapping-output=$(location {4}) " +
-            "--output-language=c++"
-        ).format(*fmtargs),
-        cmd_bat = (
-            "$(location :generate_grammar_tables) " +
-            "--spirv-core-grammar=$(location {0}) " +
-            "--extinst-debuginfo-grammar=$(location {1}) " +
-            "--extinst-cldebuginfo100-grammar=$(location {2}) " +
-            "--extension-enum-output=$(location {3}) " +
-            "--enum-string-mapping-output=$(location {4}) " +
-            "--output-language=c++"
-        ).format(*fmtargs),
+        srcs = grammars.values(),
+        outs = outs.values(),
+        cmd = cmd,
+        cmd_bat = cmd,
         exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
-def generate_opencl_tables(version = None):
+def generate_opencl_tables(version):
     if not version:
         fail("Must specify version", "version")
-    grammars = [
-        "@spirv_headers//:spirv_opencl_grammar_" + version,
-    ]
-    outs = ["opencl.std.insts.inc"]
-    fmtargs = grammars + outs
+
+    grammars = dict(
+        opencl_grammar = "@spirv_headers//:spirv_opencl_grammar_{}".format(version),
+    )
+
+    outs = dict(
+        opencl_insts_output = "opencl.std.insts.inc",
+    )
+
+    cmd = (
+        "$(location :generate_grammar_tables)" +
+        " --extinst-opencl-grammar=$(location {opencl_grammar})" +
+        " --opencl-insts-output=$(location {opencl_insts_output})"
+    ).format(**_merge_dicts([grammars, outs]))
+
     native.genrule(
         name = "gen_opencl_tables_" + version,
-        srcs = grammars,
-        outs = outs,
-        cmd = (
-            "$(location :generate_grammar_tables) " +
-            "--extinst-opencl-grammar=$(location {0}) " +
-            "--opencl-insts-output=$(location {1})"
-        ).format(*fmtargs),
-        cmd_bat = (
-            "$(location :generate_grammar_tables) " +
-            "--extinst-opencl-grammar=$(location {0}) " +
-            "--opencl-insts-output=$(location {1})"
-        ).format(*fmtargs),
+        srcs = grammars.values(),
+        outs = outs.values(),
+        cmd = cmd,
+        cmd_bat = cmd,
         exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
-def generate_glsl_tables(version = None):
+def generate_glsl_tables(version):
     if not version:
         fail("Must specify version", "version")
-    grammars = [
-        "@spirv_headers//:spirv_glsl_grammar_" + version,
-    ]
-    outs = ["glsl.std.450.insts.inc"]
-    fmtargs = grammars + outs
+
+    grammars = dict(
+        gsls_grammar = "@spirv_headers//:spirv_glsl_grammar_{}".format(version),
+    )
+    outs = dict(
+        gsls_insts_outs = "glsl.std.450.insts.inc",
+    )
+
+    cmd = (
+        "$(location :generate_grammar_tables)" +
+        " --extinst-glsl-grammar=$(location {gsls_grammar})" +
+        " --glsl-insts-output=$(location {gsls_insts_outs})" +
+        " --output-language=c++"
+    ).format(**_merge_dicts([grammars, outs]))
+
     native.genrule(
         name = "gen_glsl_tables_" + version,
-        srcs = grammars,
-        outs = outs,
-        cmd = (
-            "$(location :generate_grammar_tables) " +
-            "--extinst-glsl-grammar=$(location {0}) " +
-            "--glsl-insts-output=$(location {1}) " +
-            "--output-language=c++"
-        ).format(*fmtargs),
-        cmd_bat = (
-            "$(location :generate_grammar_tables) " +
-            "--extinst-glsl-grammar=$(location {0}) " +
-            "--glsl-insts-output=$(location {1}) " +
-            "--output-language=c++"
-        ).format(*fmtargs),
+        srcs = grammars.values(),
+        outs = outs.values(),
+        cmd = cmd,
+        cmd_bat = cmd,
         exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
@@ -178,27 +187,27 @@
 def generate_vendor_tables(extension, operand_kind_prefix = ""):
     if not extension:
         fail("Must specify extension", "extension")
+
     extension_rule = extension.replace("-", "_").replace(".", "_")
-    grammars = ["@spirv_headers//:spirv_ext_inst_{}_grammar_unified1".format(extension_rule)]
-    outs = ["{}.insts.inc".format(extension)]
-    prefices = [operand_kind_prefix]
-    fmtargs = grammars + outs + prefices
+    grammars = dict(
+        vendor_grammar = "@spirv_headers//:spirv_ext_inst_{}_grammar_unified1".format(extension_rule),
+    )
+    outs = dict(
+        vendor_insts_output = "{}.insts.inc".format(extension),
+    )
+    cmd = (
+        "$(location :generate_grammar_tables)" +
+        " --extinst-vendor-grammar=$(location {vendor_grammar})" +
+        " --vendor-insts-output=$(location {vendor_insts_output})" +
+        " --vendor-operand-kind-prefix={operand_kind_prefix}"
+    ).format(operand_kind_prefix = operand_kind_prefix, **_merge_dicts([grammars, outs]))
+
     native.genrule(
         name = "gen_vendor_tables_" + extension_rule,
-        srcs = grammars,
-        outs = outs,
-        cmd = (
-            "$(location :generate_grammar_tables) " +
-            "--extinst-vendor-grammar=$(location {0}) " +
-            "--vendor-insts-output=$(location {1}) " +
-            "--vendor-operand-kind-prefix={2}"
-        ).format(*fmtargs),
-        cmd_bat = (
-            "$(location :generate_grammar_tables) " +
-            "--extinst-vendor-grammar=$(location {0}) " +
-            "--vendor-insts-output=$(location {1}) " +
-            "--vendor-operand-kind-prefix={2}"
-        ).format(*fmtargs),
+        srcs = grammars.values(),
+        outs = outs.values(),
+        cmd = cmd,
+        cmd_bat = cmd,
         exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
@@ -206,147 +215,21 @@
 def generate_extinst_lang_headers(name, grammar = None):
     if not grammar:
         fail("Must specify grammar", "grammar")
-    outs = [name + ".h"]
-    fmtargs = outs
+    outs = dict(
+        extinst_output_path = name + ".h",
+    )
+    cmd = (
+        "$(location :generate_language_headers)" +
+        " --extinst-grammar=$<" +
+        " --extinst-output-path=$(location {extinst_output_path})"
+    ).format(**outs)
+
     native.genrule(
-        name = "gen_extinst_lang_headers_" + name,
+        name = "gen_extinst_lang_headers_{}".format(name),
         srcs = [grammar],
-        outs = outs,
-        cmd = (
-            "$(location :generate_language_headers) " +
-            "--extinst-grammar=$< " +
-            "--extinst-output-path=$(location {0})"
-        ).format(*fmtargs),
-        cmd_bat = (
-            "$(location :generate_language_headers) " +
-            "--extinst-grammar=$< " +
-            "--extinst-output-path=$(location {0})"
-        ).format(*fmtargs),
+        outs = outs.values(),
+        cmd = cmd,
+        cmd_bat = cmd,
         exec_tools = [":generate_language_headers"],
         visibility = ["//visibility:private"],
     )
-
-def base_test(name, srcs, deps = []):
-    if srcs == []:
-        return
-    if name[-5:] != "_test":
-        name = name + "_test"
-    native.cc_test(
-        name = "base_" + name,
-        srcs = srcs,
-        compatible_with = [],
-        copts = TEST_COPTS,
-        size = "large",
-        deps = [
-            ":test_common",
-            "@com_google_googletest//:gtest_main",
-            "@com_google_googletest//:gtest",
-            "@com_google_effcee//:effcee",
-        ] + deps,
-    )
-
-def lint_test(name, srcs, deps = []):
-    if name[-5:] != "_test":
-        name = name + "_test"
-    native.cc_test(
-        name = "lint_" + name,
-        srcs = srcs,
-        compatible_with = [],
-        copts = TEST_COPTS,
-        size = "large",
-        deps = [
-            ":spirv_tools_lint",
-            "@com_google_googletest//:gtest_main",
-            "@com_google_googletest//:gtest",
-            "@com_google_effcee//:effcee",
-        ] + deps,
-    )
-
-def link_test(name, srcs, deps = []):
-    if name[-5:] != "_test":
-        name = name + "_test"
-    native.cc_test(
-        name = "link_" + name,
-        srcs = srcs,
-        compatible_with = [],
-        copts = TEST_COPTS,
-        size = "large",
-        deps = [
-            ":link_test_common",
-            "@com_google_googletest//:gtest_main",
-            "@com_google_googletest//:gtest",
-            "@com_google_effcee//:effcee",
-        ] + deps,
-    )
-
-def opt_test(name, srcs, deps = []):
-    if name[-5:] != "_test":
-        name = name + "_test"
-    native.cc_test(
-        name = "opt_" + name,
-        srcs = srcs,
-        compatible_with = [],
-        copts = TEST_COPTS,
-        size = "large",
-        deps = [
-            ":opt_test_common",
-            "@com_google_googletest//:gtest_main",
-            "@com_google_googletest//:gtest",
-            "@com_google_effcee//:effcee",
-        ] + deps,
-    )
-
-def reduce_test(name, srcs, deps = []):
-    if name[-5:] != "_test":
-        name = name + "_test"
-    native.cc_test(
-        name = "reduce_" + name,
-        srcs = srcs,
-        compatible_with = [],
-        copts = TEST_COPTS,
-        size = "large",
-        deps = [
-            ":reduce_test_common",
-            ":spirv_tools_reduce",
-            "@com_google_googletest//:gtest_main",
-            "@com_google_googletest//:gtest",
-            "@com_google_effcee//:effcee",
-        ] + deps,
-    )
-
-def util_test(name, srcs, deps = []):
-    if name[-5:] != "_test":
-        name = name + "_test"
-    native.cc_test(
-        name = "util_" + name,
-        srcs = srcs,
-        compatible_with = [],
-        copts = TEST_COPTS,
-        size = "large",
-        deps = [
-            ":opt_test_common",
-            "@com_google_googletest//:gtest_main",
-            "@com_google_googletest//:gtest",
-            "@com_google_effcee//:effcee",
-        ] + deps,
-    )
-
-def val_test(name, srcs = [], copts = [], deps = [], **kwargs):
-    if name[-5:] != "_test":
-        name = name + "_test"
-    if name[:4] != "val_":
-        name = "val_" + name
-    native.cc_test(
-        name = name,
-        srcs = srcs,
-        compatible_with = [],
-        copts = TEST_COPTS + copts,
-        size = "large",
-        deps = [
-            ":val_test_common",
-            "@com_google_googletest//:gtest_main",
-            "@com_google_googletest//:gtest",
-            "@com_google_effcee//:effcee",
-        ] + deps,
-        **kwargs
-    )
diff --git a/test/opt/module_utils.h b/test/opt/module_utils.h
index 007f132..6859188 100644
--- a/test/opt/module_utils.h
+++ b/test/opt/module_utils.h
@@ -17,6 +17,7 @@
 
 #include <vector>
 #include "source/opt/module.h"
+#include "gtest/gtest.h"
 
 namespace spvtest {