Snap for 10399941 from 0b2acca7e55a2c242137868ed2eaf876e903aefd to sdk-release

Change-Id: Ie85177666276d96407c35f4d304b95e0f0fa3094
diff --git a/README.md b/README.md
index 0eea7f2..26b18cc 100644
--- a/README.md
+++ b/README.md
@@ -52,9 +52,9 @@
 * [**Android Emulator**](https://developer.android.com/studio/run/emulator.html)
   * Currently clang-r487747c
   * Linux emulator is temporarily hardcoded to clang-r487747c, see b/268674933
-  * Look for "clang-" in [external/qemu/android/build/cmake/toolchain.cmake](https://android.googlesource.com/platform/external/qemu/+/emu-master-dev/android/build/cmake/toolchain.cmake#25).
+  * Look for "clang-" in [external/qemu/android/build/toolchains.json](https://android.googlesource.com/platform/external/qemu/+/emu-master-dev/android/build/toolchains.json#2).
     * Note that they work out of the emu-master-dev branch.
-    * [Android Code Search link](https://cs.android.com/android/platform/superproject/+/emu-master-dev:external/qemu/android/build/cmake/toolchain.cmake?q=clang-)
+    * [Android Code Search link](https://cs.android.com/android/platform/superproject/+/emu-master-dev:external/qemu/android/build/toolchains.json?q=clang)
 
 * [**Context Hub Runtime Environment (CHRE)**](https://android.googlesource.com/platform/system/chre/)
   * Currently clang-r487747c
diff --git a/cc_toolchain_config.bzl b/cc_toolchain_config.bzl
index 5347087..8f4eacd 100644
--- a/cc_toolchain_config.bzl
+++ b/cc_toolchain_config.bzl
@@ -26,6 +26,7 @@
     "get_features",
     "int_overflow_ignorelist_filename",
     "int_overflow_ignorelist_path",
+    "sanitizer_blocklist_dict",
 )
 load("//build/bazel/platforms/arch/variants:constants.bzl", _arch_constants = "constants")
 
@@ -516,6 +517,9 @@
             "%s_compiler_binaries" % name,
             "%s_compiler_clang_includes" % name,
             "@//%s:%s" % (int_overflow_ignorelist_path, int_overflow_ignorelist_filename),
+        ] + [
+            "@//%s:%s" % (blocklist[0], blocklist[1])
+            for blocklist in sanitizer_blocklist_dict.items()
         ],
     )
 
diff --git a/cc_toolchain_features.bzl b/cc_toolchain_features.bzl
index 902992e..c46f8e0 100644
--- a/cc_toolchain_features.bzl
+++ b/cc_toolchain_features.bzl
@@ -1766,7 +1766,7 @@
         ),
     ]
 
-def _ubsan_flag_feature(name, actions, flags):
+def _sanitizer_flag_feature(name, actions, flags):
     return feature(
         name = name,
         enabled = True,
@@ -1778,24 +1778,29 @@
                         flags = flags,
                     ),
                 ],
+                # Any new sanitizers that are enabled need to have a
+                # with_feature_set added here.
                 with_features = [
                     with_feature_set(
                         features = ["ubsan_enabled"],
                     ),
+                    with_feature_set(
+                        features = ["android_cfi"],
+                    ),
                 ],
             ),
         ],
     )
 
-def _host_or_device_specific_ubsan_feature(target_os):
+def _host_or_device_specific_sanitizer_feature(target_os):
     if is_os_device(target_os):
-        return _ubsan_flag_feature(
-            "ubsan_device_only_flags",
+        return _sanitizer_flag_feature(
+            "sanitizer_device_only_flags",
             _actions.compile,
             _generated_sanitizer_constants.DeviceOnlySanitizeFlags,
         )
-    return _ubsan_flag_feature(
-        "ubsan_host_only_flags",
+    return _sanitizer_flag_feature(
+        "sanitizer_host_only_flags",
         _actions.compile,
         _generated_sanitizer_constants.HostOnlySanitizeFlags,
     )
@@ -1821,6 +1826,80 @@
 int_overflow_ignorelist_path = "build/soong/cc/config"
 int_overflow_ignorelist_filename = "integer_overflow_blocklist.txt"
 
+def _get_unsupported_integer_check_with_feature_sets(feature_check):
+    integer_sanitizers = [
+        "implicit-unsigned-integer-truncation",
+        "implicit-signed-integer-truncation",
+        "implicit-integer-sign-change",
+        "integer-divide-by-zero",
+        "signed-integer-overflow",
+        "unsigned-integer-overflow",
+        "implicit-integer-truncation",
+        "implicit-integer-arithmetic-value-change",
+        "integer",
+        "integer_overflow",
+    ]
+    if feature_check in integer_sanitizers:
+        integer_sanitizers.remove(feature_check)
+
+    return [
+        with_feature_set(
+            features = ["ubsan_" + integer_sanitizer],
+            not_features = ["ubsan_" + feature_check],
+        )
+        for integer_sanitizer in integer_sanitizers
+    ]
+
+# The key of this dict is the package path to the blocklist, while the value is
+# its filename.
+# TODO: b/286872909 - When the blocking bug is complete, put these in their
+#                     respective packages
+sanitizer_blocklist_dict = {
+    "external/libavc": "libavc_blocklist.txt",
+    "external/libaom": "libaom_blocklist.txt",
+    "external/libvpx": "libvpx_blocklist.txt",
+    "frameworks/base/libs/androidfw": "libandroidfw_blocklist.txt",
+    "external/libhevc": "libhevc_blocklist.txt",
+    "external/libexif": "libexif_blocklist.txt",
+    "external/libopus": "libopus_blocklist.txt",
+    "external/libmpeg2": "libmpeg2dec_blocklist.txt",
+    "external/expat": "libexpat_blocklist.txt",
+    "external/flac/src/libFLAC": "libFLAC_blocklist.txt",
+    "system/extras/toolchain-extras": "libprofile_clang_extras_blocklist.txt",
+}
+
+def _get_ubsan_blocklist_features():
+    features = []
+    for blocklist in sanitizer_blocklist_dict.items():
+        # Format the blocklist name to be used in a feature name
+        blocklist_feature_name_suffix = blocklist[1].lower().replace(".", "_")
+        features.append(
+            feature(
+                name = "ubsan_blocklist_" + blocklist_feature_name_suffix,
+                enabled = False,
+                requires = [
+                    feature_set(features = ["ubsan_enabled"]),
+                ],
+                flag_sets = [
+                    flag_set(
+                        actions = _actions.c_and_cpp_compile,
+                        flag_groups = [
+                            flag_group(
+                                flags = [
+                                    "%s%s/%s" % (
+                                        _generated_sanitizer_constants.SanitizeIgnorelistPrefix,
+                                        blocklist[0],
+                                        blocklist[1],
+                                    ),
+                                ],
+                            ),
+                        ],
+                    ),
+                ],
+            ),
+        )
+    return features
+
 def _get_ubsan_features(target_os, libclang_rt_ubsan_minimal):
     if target_os in [_oses.Windows, _oses.Darwin]:
         return []
@@ -1990,7 +2069,7 @@
     )
 
     ubsan_features += [
-        _host_or_device_specific_ubsan_feature(target_os),
+        _host_or_device_specific_sanitizer_feature(target_os),
         _exclude_ubsan_rt_feature(libclang_rt_ubsan_minimal),
     ]
 
@@ -2031,14 +2110,9 @@
                             ],
                         ),
                     ],
-                    with_features = [
-                        with_feature_set(
-                            features = ["ubsan_integer"],
-                            not_features = [
-                                "ubsan_implicit-integer-sign-change",
-                            ],
-                        ),
-                    ],
+                    with_features = _get_unsupported_integer_check_with_feature_sets(
+                        "implicit-integer-sign-change",
+                    ),
                 ),
                 # TODO(b/171275751): If this bug is fixed, this shouldn't be
                 #                    needed anymore
@@ -2051,19 +2125,16 @@
                             ],
                         ),
                     ],
-                    with_features = [
-                        with_feature_set(
-                            features = ["ubsan_integer"],
-                            not_features = [
-                                "ubsan_unsigned-shift-base",
-                            ],
-                        ),
-                    ],
+                    with_features = _get_unsupported_integer_check_with_feature_sets(
+                        "unsigned-shift-base",
+                    ),
                 ),
             ],
         ),
     )
 
+    ubsan_features.extend(_get_ubsan_blocklist_features())
+
     return ubsan_features
 
 def _manual_binder_interface_feature():
diff --git a/cc_toolchain_features_cfi_test.bzl b/cc_toolchain_features_cfi_test.bzl
index ebaeafe..fb4b99e 100644
--- a/cc_toolchain_features_cfi_test.bzl
+++ b/cc_toolchain_features_cfi_test.bzl
@@ -21,14 +21,27 @@
 )
 load(
     "//build/bazel/rules/test_common:flags.bzl",
+    "action_flags_absent_for_mnemonic_aosp_arm64_host_test",
+    "action_flags_absent_for_mnemonic_aosp_arm64_test",
     "action_flags_absent_for_mnemonic_test",
     "action_flags_present_for_mnemonic_nonexclusive_test",
+    "action_flags_present_only_for_mnemonic_aosp_arm64_host_test",
+    "action_flags_present_only_for_mnemonic_aosp_arm64_test",
     "action_flags_present_only_for_mnemonic_test",
 )
 
 compile_action_mnemonic = "CppCompile"
 link_action_mnemonic = "CppLink"
 
+# Include these different file types to make sure that all actions types are
+# triggered
+test_srcs = [
+    "foo.cpp",
+    "bar.c",
+    "baz.s",
+    "blah.S",
+]
+
 def test_cfi_c_and_cpp_has_correct_flags():
     name = "cfi_c_and_cpp_has_correct_flags"
     native.cc_binary(
@@ -370,6 +383,58 @@
 #
 #    return test_name
 
+def _test_host_only_and_device_only_features():
+    name = "cfi_host_only_and_device_only_features"
+    test_names = []
+
+    native.cc_binary(
+        name = name,
+        srcs = ["foo.c", "bar.cpp"],
+        features = ["android_cfi"],
+        tags = ["manual"],
+    )
+
+    host_with_host_flags_test_name = name + "_host_flags_present_when_host_test"
+    test_names += [host_with_host_flags_test_name]
+    action_flags_present_only_for_mnemonic_aosp_arm64_host_test(
+        name = host_with_host_flags_test_name,
+        target_under_test = name,
+        mnemonics = [compile_action_mnemonic],
+        expected_flags = generated_sanitizer_constants.HostOnlySanitizeFlags,
+    )
+
+    device_with_host_flags_test_name = name + "_host_flags_absent_when_device_test"
+    test_names += [device_with_host_flags_test_name]
+    action_flags_absent_for_mnemonic_aosp_arm64_test(
+        name = device_with_host_flags_test_name,
+        target_under_test = name,
+        mnemonics = [compile_action_mnemonic],
+        expected_absent_flags = generated_sanitizer_constants.HostOnlySanitizeFlags,
+    )
+
+    device_with_device_flags_test_name = name + "_device_flags_present_when_device_test"
+    test_names += [device_with_device_flags_test_name]
+    action_flags_present_only_for_mnemonic_aosp_arm64_test(
+        name = device_with_device_flags_test_name,
+        target_under_test = name,
+        mnemonics = [compile_action_mnemonic],
+        expected_flags = generated_sanitizer_constants.DeviceOnlySanitizeFlags,
+    )
+
+    host_with_device_flags_test_name = name + "_device_flags_absent_when_host_test"
+    test_names += [host_with_device_flags_test_name]
+    action_flags_absent_for_mnemonic_aosp_arm64_host_test(
+        name = host_with_device_flags_test_name,
+        target_under_test = name,
+        mnemonics = [compile_action_mnemonic],
+        expected_absent_flags = generated_sanitizer_constants.DeviceOnlySanitizeFlags,
+    )
+
+    return test_names
+
+def _test_device_only_and_host_only_features_absent_when_cfi_disabled():
+    pass
+
 def cc_toolchain_features_cfi_test_suite(name):
     individual_tests = [
         test_cross_dso_not_added_when_cfi_disabled(),
@@ -390,5 +455,6 @@
         tests = individual_tests +
                 test_cfi_c_and_cpp_has_correct_flags() +
                 test_cfi_s_has_correct_flags() +
-                test_cross_dso_not_added_when_static_binary(),
+                test_cross_dso_not_added_when_static_binary() +
+                _test_host_only_and_device_only_features(),
     )
diff --git a/clang-r487747c/bin/llvm-dlltool b/clang-r487747c/bin/llvm-dlltool
new file mode 120000
index 0000000..3b94d1a
--- /dev/null
+++ b/clang-r487747c/bin/llvm-dlltool
@@ -0,0 +1 @@
+llvm-ar
\ No newline at end of file
diff --git a/kleaf/BUILD.bazel b/kleaf/BUILD.bazel
index 4454530..b17c850 100644
--- a/kleaf/BUILD.bazel
+++ b/kleaf/BUILD.bazel
@@ -17,16 +17,23 @@
     "@kernel_toolchain_info//:dict.bzl",
     "VARS",
 )
-load(
-    ":clang_toolchain.bzl",
-    "android_arm64_clang_toolchain",
-    "android_riscv64_clang_toolchain",
-    "android_x86_64_clang_toolchain",
-    "linux_x86_64_clang_toolchain",
-)
+load(":architecture_constants.bzl", "SUPPORTED_ARCHITECTURES")
+load(":clang_toolchain.bzl", "clang_toolchain")
 load(":versions.bzl", "VERSIONS")
 
 bzl_library(
+    name = "architecture_constants",
+    srcs = ["architecture_constants.bzl"],
+    visibility = ["//visibility:private"],
+)
+
+bzl_library(
+    name = "user_clang_toolchain_repository",
+    srcs = ["user_clang_toolchain_repository.bzl"],
+    visibility = ["//visibility:private"],
+)
+
+bzl_library(
     name = "versions",
     srcs = ["versions.bzl"],
     visibility = ["//visibility:public"],
@@ -36,32 +43,24 @@
     name = "register",
     srcs = ["register.bzl"],
     visibility = ["//visibility:public"],
-    deps = [":versions"],
+    deps = [
+        ":architecture_constants",
+        ":user_clang_toolchain_repository",
+        ":versions",
+    ],
 )
 
-# Default toolchain for cc_* rules.
+# Default toolchains.
 
-linux_x86_64_clang_toolchain(
-    name = "linux_x86_64_clang_toolchain",
+[clang_toolchain(
+    name = "{}_{}_clang_toolchain".format(target_os, target_cpu),
+    clang_pkg = "//prebuilts/clang/host/linux-x86/clang-{}".format(VARS["CLANG_VERSION"]),
     clang_version = VARS["CLANG_VERSION"],
-)
+    target_cpu = target_cpu,
+    target_os = target_os,
+) for target_os, target_cpu in SUPPORTED_ARCHITECTURES]
 
-android_arm64_clang_toolchain(
-    name = "android_arm64_clang_toolchain",
-    clang_version = VARS["CLANG_VERSION"],
-)
-
-android_x86_64_clang_toolchain(
-    name = "android_x86_64_clang_toolchain",
-    clang_version = VARS["CLANG_VERSION"],
-)
-
-android_riscv64_clang_toolchain(
-    name = "android_riscv64_clang_toolchain",
-    clang_version = VARS["CLANG_VERSION"],
-)
-
-# Extra toolchain for kernel_* rules
+# Versioned toolchains: extra toolchains when a certain version is requested.
 
 constraint_setting(name = "clang_version")
 
@@ -71,26 +70,11 @@
     visibility = ["//visibility:public"],
 ) for version in VERSIONS]
 
-[linux_x86_64_clang_toolchain(
-    name = version + "_linux_x86_64_clang_toolchain",
+[clang_toolchain(
+    name = "{}_{}_{}_clang_toolchain".format(version, target_os, target_cpu),
+    clang_pkg = "//prebuilts/clang/host/linux-x86/clang-{}".format(version),
     clang_version = version,
     extra_compatible_with = [version],
-) for version in VERSIONS]
-
-[android_arm64_clang_toolchain(
-    name = version + "_android_arm64_clang_toolchain",
-    clang_version = version,
-    extra_compatible_with = [version],
-) for version in VERSIONS]
-
-[android_x86_64_clang_toolchain(
-    name = version + "_android_x86_64_clang_toolchain",
-    clang_version = version,
-    extra_compatible_with = [version],
-) for version in VERSIONS]
-
-[android_riscv64_clang_toolchain(
-    name = version + "_android_riscv64_clang_toolchain",
-    clang_version = version,
-    extra_compatible_with = [version],
-) for version in VERSIONS]
+    target_cpu = target_cpu,
+    target_os = target_os,
+) for version in VERSIONS for target_os, target_cpu in SUPPORTED_ARCHITECTURES]
diff --git a/kleaf/README.md b/kleaf/README.md
new file mode 100644
index 0000000..62d3413
--- /dev/null
+++ b/kleaf/README.md
@@ -0,0 +1,244 @@
+# C Toolchain in Kleaf
+
+## Toolchain declaration
+
+Source: [BUILD.bazel](BUILD.bazel)
+
+The following toolchains are declared:
+
+*   Default toolchains
+*   Versioned toolchains
+*   User toolchains, provided via command line flags
+
+### Default toolchains
+
+[Source: `BUILD.bazel`](BUILD.bazel)
+
+Default toolchains, named `{target_os}_{target_cpu}_clang_toolchain`,
+are the fallback toolchains when a C toolchain is requested without
+any version information.
+
+These toolchains are marked "target_compatible_with":
+*   the corresponding `@platforms//os:{target_os}`
+*   the corresponding `@platforms//cpu:{target_cpu}`
+
+These toolchains are marked "exec_compatible_with":
+*   `@platforms//os:linux`
+*   `@platforms//cpu:x86_64`
+
+These toolchains have `cc_toolchain.compiler` set to `CLANG_VERSION` from
+`@kernel_toolchain_info//:dict.bzl`.
+
+### Versioned toolchains
+
+[Source: `BUILD.bazel`](BUILD.bazel)
+
+Versioned toolchains, named
+`{version}_{target_os}_{target_cpu}_clang_toolchain`,
+are the toolchains bearing extra version information.
+
+These toolchains are marked "target_compatible_with":
+*   the corresponding `@platforms//os:{target_os}`
+*   the corresponding `@platforms//cpu:{target_cpu}`
+*   the corresponding `//prebuilts/clang/host/linux-x86/kleaf:{version}`
+
+These toolchains are marked "exec_compatible_with":
+*   `@platforms//os:linux`
+*   `@platforms//cpu:x86_64`
+*   the corresponding `//prebuilts/clang/host/linux-x86/kleaf:{version}`
+
+These toolchains have `cc_toolchain.compiler` set to the corresponding
+`{version}`.
+
+### User toolchains
+
+[Source: `user_clang_toolchain_repository.bzl`](user_clang_toolchain_repository.bzl)
+
+User toolchains,
+`@kleaf_user_clang_toolchain//:user_{target_os}_{target_cpu}_clang_toolchain`,
+are the toolchains provided by the user from the command line.
+
+These toolchains are marked "target_compatible_with":
+*   the corresponding `@platforms//os:{target_os}`
+*   the corresponding `@platforms//cpu:{target_cpu}`
+
+These toolchains are marked "exec_compatible_with":
+*   `@platforms//os:linux`
+*   `@platforms//cpu:x86_64`
+
+These toolchains have `cc_toolchain.compiler` set to
+`"kleaf_user_clang_toolchain_skip_version_check"` (an implementation detail).
+
+## Toolchain registration
+
+Toolchains are registered in the following order:
+
+1.  If `--user_clang_toolchain` is set, the user toolchains are registered.
+    Otherwise, fake user toolchains are registered.
+    ([source](register.bzl))
+2.  The versioned toolchains ([source](register.bzl))
+3.  The default toolchains ([source](register.bzl))
+
+## Toolchain resolution
+
+The following assumes that the reader is familar with
+[Bazel's toolchain resolution process](https://bazel.build/extending/toolchains#toolchain-resolution).
+
+Each toolchain registered through Bazel can specify a list of compatible
+constraint values with `exec_compatible_with` and `target_compatible_with`. A
+toolchain matches a platform when the toolchain’s constraint values are a
+**SUBSET** of the platform's constraint values.
+
+To determine the toolchain for a
+[build target](https://bazel.build/extending/rules#target_instantiation),
+Bazel does the following.
+
+1.  The platform of the build target is determined. By default, the platform of
+    of the build target is the
+    [target platform](https://bazel.build/extending/platforms), subject to
+    transitions. If the build target is built in an `exec` configuration (e.g.
+    when building as one of the
+    [`tools` of a `genrule`](https://bazel.build/reference/be/general#genrule.tools),
+    or as
+    [a dependency with `cfg="exec"`](https://bazel.build/extending/rules#configurations), the platform of the build target is the
+    [execution platform](https://bazel.build/extending/platforms), subject to
+    transitions.
+2.  Bazel looks for a toolchain such that the toolchain's constraint values
+    are a **SUBSET** of the constraint values of the platform of the build
+    target.
+
+For a build without any flags or transitions, the execution platform is
+`@local_config_platform//:host`. For Kleaf, this usually contains two
+constraint values: (`linux`, `x86_64`).
+
+For a build without any flags or transitions, Bazel uses
+["single-platform builds"](https://bazel.build/extending/platforms)
+by default, so the target platform is the same as the execution platform with
+two constraint values: (`linux`, `x86_64`).
+
+In Kleaf, if a target is built with `--config=android_{cpu}`, or is wrapped in
+an `android_filegroup` with a given `cpu`, the target platform has two
+constraint values (`android`, `{cpu}`).
+
+### Example: cc\_* rules
+
+For a build without any flags or transitions, the platform of the build target
+has two constrants: (`linux`, `x86_64`). The toolchains are looked up in
+the following order:
+
+1.  If `--user_clang_toolchain` is set, `user_linux_x86_64_clang_toolchain`
+    is returned because its constraint values (`linux`, `x86_64`) are a subset
+    of the platform's constraint values (`linux`, `x86_64`). Otherwise, if
+    `--user_clang_toolchain` is not set, Bazel continues checking the following
+    toolchains.
+2.  The versioned toolchains are skipped because their constraint values
+    (`{version}`, `{target_os}`, `{target_cpu}`) are not a subset of
+    the platform's constraint values (`linux`, `x86_64`).
+3.  The default toolchain `linux_x86_64_clang_toolchain` is returned because
+    its constraint values (`linux`, `x86_64`) are a subset
+    of the platform's constraint values (`linux`, `x86_64`)
+
+If a `cc_*` target is built with `--config=android_arm64`, the platform of the
+build target has two constrants: (`android`, `arm64`). The toolchains are looked
+up in the following order:
+
+1.  If `--user_clang_toolchain` is set, `user_android_arm64_clang_toolchain`
+    is returned because its constraint values (`android`, `arm64`) are a subset
+    of the platform's constraint values (`android`, `arm64`). Otherwise, if
+    `--user_clang_toolchain` is not set, Bazel continues checking the following
+    toolchains.
+2.  The versioned toolchains are skipped because their constraint values
+    (`{version}`, `{target_os}`, `{target_cpu}`) are not a subset of
+    the platform's constraint values (`android`, `arm64`).
+3.  The default toolchain `android_arm64_clang_toolchain` is returned because
+    its constraint values (`android`, `arm64`) are a subset
+    of the platform's constraint values (`android`, `arm64`)
+
+If a `cc_*` target is wrapped in an `android_filegroup` target with
+`cpu="arm64"`, **when the `android_filegroup` target is built**, a transition is
+applied on the `cc_*` target so its platform has constraint values
+(`android`, `arm64`). The toolchain resolution process is the same as with
+`--config=android_arm64.`
+
+### Example: kernel\_* rules without toolchain\_version
+
+If a `kernel_build` does not specify `toolchain_version`:
+
+*   Its execution platform has constraint values (`linux`, `x86_64`)
+*   Its target platform has constraint values (`android`, `{target_cpu}`) where
+    `{target_cpu}` is specified in `kernel_build.arch`.
+
+When `kernel_toolchains` looks up the toolchains for the execution platform
+and the target platform, respectively, the process is similar to the one
+for [`cc_*` rules](#example-cc_-rules). That is:
+
+*   If `--user_clang_toolchain` is set, the user toolchains are returned:
+    *   `user_linux_x86_64_clang_toolchain` is resolved for the
+        execution platform
+    *   `user_android_{target_cpu}_clang_toolchain` is resolved for the
+        target platform
+*   Otherwise, the default toolchains are returned:
+    *   `linux_x86_64_clang_toolchain` is resolved for the
+        execution platform
+    *   `android_{target_cpu}_clang_toolchain` is resolved for the
+        target platform
+
+### Example: kernel\_* rules with toolchain\_version
+
+This is unusual. You are recommended to not set `kernel_build.toolchain_version`
+to use the default toolchains.
+
+If a `kernel_build` does specify `toolchain_version`:
+
+*   Its execution platform has constraint values
+    (`linux`, `x86_64`, `{toolchain_version}`)
+*   Its target platform has constraint values
+    (`android`, `{target_cpu}`, `{toolchain_version}`) where
+    `{target_cpu}` is specified in `kernel_build.arch`.
+
+When `kernel_toolchains` looks up the toolchains for the execution platform:
+
+*   If `--user_clang_toolchain` is set, the user toolchain
+    `user_linux_x86_64_clang_toolchain` is returned because its constraint
+    values (`linux`, `x86_64`) are a subset of the execution platform's
+    constraint values (`linux`, `x86_64`, `{toolchain_version}`). However,
+    `kernel_toolchains` rejects the user toolchain because the version
+    `"unknown"` does not match the declared value,
+    `kernel_build.toolchain_version`, resulting in a build error. You should
+    delete `kernel_build.toolchain_version`, and try again.
+*   If `--user_clang_toolchain` is not set, the matching versioned toolchain,
+    `{version}_linux_x86_64_clang_toolchain` is returned because
+    its constraint values (`linux`, `x86_64` `{toolchain_version}`) are a subset
+    of the execution platform's constraint values
+    (`linux`, `x86_64`, `{toolchain_version}`).
+*   If no matching versioned toolchain is found, the default toolchain is
+    returned. However, `kernel_toolchains` rejects the default toolchain
+    because the version does not match the declared value,
+    `kernel_build.toolchain_version`, resulting in a build error as expected.
+
+The same goes for the target platform:
+
+*   If `--user_clang_toolchain` is set, the user toolchain
+    `user_android_{target_cpu}_clang_toolchain` is returned because its
+    constraint values (`android`, `{target_cpu}`) are a subset of the target
+    platform's constraint values
+    (`android`, `{target_cpu}`, `{toolchain_version}`).
+    `kernel_toolchains` accepts the user toolchain with a warning because
+    the version of the user toolchain
+    `"kleaf_user_clang_toolchain_skip_version_check"` does not match the declared value, `kernel_build.toolchain_version`.
+*   If `--user_clang_toolchain` is not set, the matching versioned toolchain,
+    `{version}_android_{target_cpu}_clang_toolchain` is returned because
+    its constraint values (`android`, `{target_cpu}` `{toolchain_version}`) are a subset of the target platform's constraint values
+    (`android`, `{target_cpu}`, `{toolchain_version}`).
+*   If no matching versioned toolchain is found, the default toolchain is
+    returned. However, `kernel_toolchains` rejects the default toolchain
+    because the version does not match the declared value,
+    `kernel_build.toolchain_version`, resulting in a build error as expected.
+
+## Caveats
+
+To use the user toolchains, the following is expected from the workspace:
+
+*   An environment variable, `KLEAF_USER_CLANG_TOOLCHAIN_PATH`, must
+    be set to the path to the user clang toolchain. This is set by
+    Kleaf's Bazel wrapper, `bazel.py`, when `--user_clang_toolchain` is set.
diff --git a/kleaf/architecture_constants.bzl b/kleaf/architecture_constants.bzl
new file mode 100644
index 0000000..400be31
--- /dev/null
+++ b/kleaf/architecture_constants.bzl
@@ -0,0 +1,24 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""List of supported architectures by Kleaf."""
+
+SUPPORTED_ARCHITECTURES = [
+    ("linux", "x86_64"),
+    ("android", "arm64"),
+    ("android", "arm"),
+    ("android", "x86_64"),
+    ("android", "i386"),
+    ("android", "riscv64"),
+]
diff --git a/kleaf/clang_config.bzl b/kleaf/clang_config.bzl
index 1fc3bb3..f30254f 100644
--- a/kleaf/clang_config.bzl
+++ b/kleaf/clang_config.bzl
@@ -50,7 +50,9 @@
     implementation = _impl,
     attrs = {
         "target_cpu": attr.string(mandatory = True, values = [
+            "arm",
             "arm64",
+            "i386",
             "riscv64",
             "x86_64",
         ]),
diff --git a/kleaf/clang_toolchain.bzl b/kleaf/clang_toolchain.bzl
index 56e3922..a77dace 100644
--- a/kleaf/clang_toolchain.bzl
+++ b/kleaf/clang_toolchain.bzl
@@ -19,13 +19,15 @@
     "@kernel_toolchain_info//:dict.bzl",
     "VARS",
 )
+load(":architecture_constants.bzl", "SUPPORTED_ARCHITECTURES")
 load(":clang_config.bzl", "clang_config")
 
-def clang_toolchain(
+def _clang_toolchain_internal(
         name,
         clang_version,
         target_cpu,
         target_os,
+        clang_pkg,
         linker_files = None,
         sysroot_label = None,
         sysroot_path = None,
@@ -38,6 +40,10 @@
         clang_version: value of `CLANG_VERSION`, e.g. `r475365b`.
         target_cpu: CPU that the toolchain cross-compiles to
         target_os: OS that the toolchain cross-compiles to
+        clang_pkg: Label to any target in the clang toolchain package.
+
+            This is used as an anchor to locate other targets in the package.
+            Name of the label is ignored.
         linker_files: Additional dependencies to the linker
         sysroot_label: Label to a list of files from sysroot
         sysroot_path: Path to sysroot
@@ -58,13 +64,13 @@
     if extra_compatible_with == None:
         extra_compatible_with = []
 
-    clang_pkg = "//prebuilts/clang/host/linux-x86/clang-{}".format(clang_version)
-    clang_includes = Label("{}:includes".format(clang_pkg))
+    clang_pkg = native.package_relative_label(clang_pkg)
+    clang_includes = clang_pkg.relative(":includes")
 
     # Technically we can split the binaries into those for compiler, linker
     # etc., but since these binaries are usually updated together, it is okay
     # to use a superset here.
-    clang_all_binaries = Label("{}:binaries".format(clang_pkg))
+    clang_all_binaries = clang_pkg.relative(":binaries")
 
     # Individual binaries
     # From _setup_env.sh
@@ -82,12 +88,12 @@
     #
     # Note: ld.lld does not recognize --target etc. from android.bzl,
     # so just use clang directly
-    clang = Label("{}:bin/clang".format(clang_pkg))
-    clang_plus_plus = Label("{}:bin/clang++".format(clang_pkg))
+    clang = clang_pkg.relative(":bin/clang")
+    clang_plus_plus = clang_pkg.relative(":bin/clang++")
     ld = clang
-    strip = Label("{}:bin/llvm-strip".format(clang_pkg))
-    ar = Label("{}:bin/llvm-ar".format(clang_pkg))
-    objcopy = Label("{}:bin/llvm-objcopy".format(clang_pkg))
+    strip = clang_pkg.relative(":bin/llvm-strip")
+    ar = clang_pkg.relative(":bin/llvm-ar")
+    objcopy = clang_pkg.relative(":bin/llvm-objcopy")
     # cc_* rules doesn't seem to need nm, obj-dump, size, and readelf
 
     native.filegroup(
@@ -156,97 +162,87 @@
         toolchain_type = CPP_TOOLCHAIN_TYPE,
     )
 
-def linux_x86_64_clang_toolchain(
+def clang_toolchain(
         name,
         clang_version,
+        clang_pkg,
+        target_cpu,
+        target_os,
         extra_compatible_with = None):
-    """Declare an linux_x86_64 toolchain.
+    """Declare a clang toolchain for the given OS-architecture.
+
+    The toolchain should be under `prebuilts/clang/host/linux-x86`.
 
     Args:
-        name: name prefix
-        clang_version: `CLANG_VERSION`
-        extra_compatible_with: extra `exec_compatible_with` and `target_compatible_with`
+        name: name of the toolchain
+        clang_version: nonconfigurable. version of the toolchain
+        clang_pkg: Label to any target in the clang toolchain package.
+
+            This is used as an anchor to locate other targets in the package.
+            Name of the label is ignored.
+        target_cpu: nonconfigurable. CPU of the toolchain
+        target_os: nonconfigurable. OS of the toolchain
+        extra_compatible_with: nonconfigurable. extra `exec_compatible_with` and `target_compatible_with`
     """
-    clang_toolchain(
+
+    if sorted(ARCH_CONFIG.keys()) != sorted(SUPPORTED_ARCHITECTURES):
+        fail("FATAL: ARCH_CONFIG is not up-to-date with SUPPORTED_ARCHITECTURES!")
+
+    extra_kwargs = ARCH_CONFIG[(target_os, target_cpu)]
+
+    _clang_toolchain_internal(
         name = name,
         clang_version = clang_version,
+        target_os = target_os,
+        target_cpu = target_cpu,
+        clang_pkg = clang_pkg,
+        extra_compatible_with = extra_compatible_with,
+        **extra_kwargs
+    )
+
+# Keys: (target_os, target_cpu)
+# Values: arguments to clang_toolchain()
+ARCH_CONFIG = {
+    ("linux", "x86_64"): dict(
         linker_files = [
             # From _setup_env.sh, HOSTLDFLAGS
-            "//prebuilts/kernel-build-tools:linux-x86-libs",
+            Label("//prebuilts/kernel-build-tools:linux-x86-libs"),
         ],
         # From _setup_env.sh
         # sysroot_flags+="--sysroot=${ROOT_DIR}/build/kernel/build-tools/sysroot "
-        sysroot_label = "//build/kernel:sysroot",
+        sysroot_label = Label("//build/kernel:sysroot"),
         sysroot_path = "build/kernel/build-tools/sysroot",
-        target_cpu = "x86_64",
-        target_os = "linux",
-        extra_compatible_with = extra_compatible_with,
-    )
-
-def android_arm64_clang_toolchain(
-        name,
-        clang_version,
-        extra_compatible_with = None):
-    """Declare an android_arm64 toolchain.
-
-    Args:
-        name: name prefix
-        clang_version: `CLANG_VERSION`
-        extra_compatible_with: extra `exec_compatible_with` and `target_compatible_with`
-    """
-    clang_toolchain(
-        name = name,
-        clang_version = clang_version,
+    ),
+    ("android", "arm64"): dict(
         ndk_triple = VARS.get("AARCH64_NDK_TRIPLE"),
         # From _setup_env.sh: when NDK triple is set,
         # --sysroot=${NDK_DIR}/toolchains/llvm/prebuilt/linux-x86_64/sysroot
         sysroot_label = "@prebuilt_ndk//:sysroot" if "AARCH64_NDK_TRIPLE" in VARS else None,
         sysroot_path = "external/prebuilt_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot" if "AARCH64_NDK_TRIPLE" in VARS else None,
-        target_cpu = "arm64",
-        target_os = "android",
-        extra_compatible_with = extra_compatible_with,
-    )
-
-def android_x86_64_clang_toolchain(
-        name,
-        clang_version,
-        extra_compatible_with = None):
-    """Declare an android_x86_64 toolchain.
-
-    Args:
-        name: name prefix
-        clang_version: `CLANG_VERSION`
-        extra_compatible_with: extra `exec_compatible_with` and `target_compatible_with`
-    """
-    clang_toolchain(
-        name = name,
-        clang_version = clang_version,
+    ),
+    ("android", "arm"): dict(
+        ndk_triple = VARS.get("ARM_NDK_TRIPLE"),
+        # From _setup_env.sh: when NDK triple is set,
+        # --sysroot=${NDK_DIR}/toolchains/llvm/prebuilt/linux-x86_64/sysroot
+        sysroot_label = "@prebuilt_ndk//:sysroot" if "ARM_NDK_TRIPLE" in VARS else None,
+        sysroot_path = "external/prebuilt_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot" if "AARCH64_NDK_TRIPLE" in VARS else None,
+    ),
+    ("android", "x86_64"): dict(
         ndk_triple = VARS.get("X86_64_NDK_TRIPLE"),
         # From _setup_env.sh: when NDK triple is set,
         # --sysroot=${NDK_DIR}/toolchains/llvm/prebuilt/linux-x86_64/sysroot
         sysroot_label = "@prebuilt_ndk//:sysroot" if "X86_64_NDK_TRIPLE" in VARS else None,
         sysroot_path = "external/prebuilt_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot" if "X86_64_NDK_TRIPLE" in VARS else None,
-        target_cpu = "x86_64",
-        target_os = "android",
-        extra_compatible_with = extra_compatible_with,
-    )
-
-def android_riscv64_clang_toolchain(
-        name,
-        clang_version,
-        extra_compatible_with = None):
-    """Declare an android_riscv toolchain.
-
-    Args:
-        name: name prefix
-        clang_version: `CLANG_VERSION`
-        extra_compatible_with: extra `exec_compatible_with` and `target_compatible_with`
-    """
-    clang_toolchain(
-        name = name,
-        clang_version = clang_version,
-        target_cpu = "riscv64",
-        target_os = "android",
-        extra_compatible_with = extra_compatible_with,
+    ),
+    ("android", "i386"): dict(
+        # i386 uses the same NDK_TRIPLE as x86_64
+        ndk_triple = VARS.get("X86_64_NDK_TRIPLE"),
+        # From _setup_env.sh: when NDK triple is set,
+        # --sysroot=${NDK_DIR}/toolchains/llvm/prebuilt/linux-x86_64/sysroot
+        sysroot_label = "@prebuilt_ndk//:sysroot" if "X86_64_NDK_TRIPLE" in VARS else None,
+        sysroot_path = "external/prebuilt_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot" if "X86_64_NDK_TRIPLE" in VARS else None,
+    ),
+    ("android", "riscv64"): dict(
         # TODO(b/271919464): We need NDK_TRIPLE for riscv
-    )
+    ),
+}
diff --git a/kleaf/empty_toolchain.bzl b/kleaf/empty_toolchain.bzl
new file mode 100644
index 0000000..49b01a6
--- /dev/null
+++ b/kleaf/empty_toolchain.bzl
@@ -0,0 +1,50 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""An empty toolchain that does nothing."""
+
+def _empty_toolchain_impl(_ctx):
+    return platform_common.ToolchainInfo()
+
+_empty_toolchain = rule(
+    implementation = _empty_toolchain_impl,
+)
+
+def empty_toolchain(
+        name,
+        toolchain_type,
+        **kwargs):
+    """Defines an empty toolchain.
+
+    Args:
+        name: name of the toolchain
+        toolchain_type: type of the toolchain
+        **kwargs: additional kwargs passed to the toolchain target
+    """
+
+    private_kwargs = kwargs | dict(
+        visibility = ["//visibility:private"],
+    )
+
+    _empty_toolchain(
+        name = name + "_internal",
+        **private_kwargs
+    )
+
+    native.toolchain(
+        name = name,
+        toolchain = name + "_internal",
+        toolchain_type = toolchain_type,
+        **kwargs
+    )
diff --git a/kleaf/register.bzl b/kleaf/register.bzl
index a1d01fc..a886838 100644
--- a/kleaf/register.bzl
+++ b/kleaf/register.bzl
@@ -14,22 +14,37 @@
 
 """Registers all clang toolchains defined in this package."""
 
+load(":architecture_constants.bzl", "SUPPORTED_ARCHITECTURES")
+load(":user_clang_toolchain_repository.bzl", "user_clang_toolchain_repository")
 load(":versions.bzl", "VERSIONS")
 
 # buildifier: disable=unnamed-macro
 def register_clang_toolchains():
-    """Registers all clang toolchains defined in this package."""
-    for version in VERSIONS:
+    """Registers all clang toolchains defined in this package.
+
+    The user clang toolchain is expected from the path defined in the
+    `KLEAF_USER_CLANG_TOOLCHAIN_PATH` environment variable, if set.
+    """
+
+    user_clang_toolchain_repository(
+        name = "kleaf_user_clang_toolchain",
+    )
+
+    for target_os, target_cpu in SUPPORTED_ARCHITECTURES:
         native.register_toolchains(
-            "//prebuilts/clang/host/linux-x86/kleaf:{}_android_arm64_clang_toolchain".format(version),
-            "//prebuilts/clang/host/linux-x86/kleaf:{}_android_x86_64_clang_toolchain".format(version),
-            "//prebuilts/clang/host/linux-x86/kleaf:{}_android_riscv64_clang_toolchain".format(version),
-            "//prebuilts/clang/host/linux-x86/kleaf:{}_linux_x86_64_clang_toolchain".format(version),
+            "@kleaf_user_clang_toolchain//:user_{}_{}_clang_toolchain".format(
+                target_os,
+                target_cpu,
+            ),
         )
 
-    native.register_toolchains(
-        "//prebuilts/clang/host/linux-x86/kleaf:android_arm64_clang_toolchain",
-        "//prebuilts/clang/host/linux-x86/kleaf:android_x86_64_clang_toolchain",
-        "//prebuilts/clang/host/linux-x86/kleaf:android_riscv64_clang_toolchain",
-        "//prebuilts/clang/host/linux-x86/kleaf:linux_x86_64_clang_toolchain",
-    )
+    for version in VERSIONS:
+        for target_os, target_cpu in SUPPORTED_ARCHITECTURES:
+            native.register_toolchains(
+                "//prebuilts/clang/host/linux-x86/kleaf:{}_{}_{}_clang_toolchain".format(version, target_os, target_cpu),
+            )
+
+    for target_os, target_cpu in SUPPORTED_ARCHITECTURES:
+        native.register_toolchains(
+            "//prebuilts/clang/host/linux-x86/kleaf:{}_{}_clang_toolchain".format(target_os, target_cpu),
+        )
diff --git a/kleaf/user_clang_toolchain_repository.bzl b/kleaf/user_clang_toolchain_repository.bzl
new file mode 100644
index 0000000..6922d8f
--- /dev/null
+++ b/kleaf/user_clang_toolchain_repository.bzl
@@ -0,0 +1,123 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Defines a repository that provides a clang version at a user defined path."""
+
+def _user_clang_toolchain_repository_impl(repository_ctx):
+    repository_ctx.file("WORKSPACE.bazel", """\
+workspace(name = "{}")
+""".format(repository_ctx.attr.name))
+
+    if "KLEAF_USER_CLANG_TOOLCHAIN_PATH" not in repository_ctx.os.environ:
+        _empty_user_clang_toolchain_repository_impl(repository_ctx)
+    else:
+        _real_user_clang_toolchain_repository_impl(repository_ctx)
+
+def _empty_user_clang_toolchain_repository_impl(repository_ctx):
+    build_file_content = '''\
+"""Fake user C toolchains.
+
+These toolchains are not registered with the C toolchain type.
+"""
+
+load("{architecture_constants}", "SUPPORTED_ARCHITECTURES")
+load("{empty_toolchain}", "empty_toolchain")
+
+toolchain_type(
+    name = "empty_toolchain_type",
+    visibility = ["//visibility:private"],
+)
+
+[empty_toolchain(
+    name = "user_{{}}_{{}}_clang_toolchain".format(target_os, target_cpu),
+    toolchain_type = ":empty_toolchain_type",
+    visibility = ["//visibility:private"],
+) for target_os, target_cpu in SUPPORTED_ARCHITECTURES]
+'''.format(
+        architecture_constants = Label(":architecture_constants.bzl"),
+        empty_toolchain = Label(":empty_toolchain.bzl"),
+    )
+    repository_ctx.file("BUILD.bazel", build_file_content)
+
+def _real_user_clang_toolchain_repository_impl(repository_ctx):
+    user_clang_toolchain_path = repository_ctx.os.environ["KLEAF_USER_CLANG_TOOLCHAIN_PATH"]
+    user_clang_toolchain_path = repository_ctx.path(user_clang_toolchain_path)
+
+    # Symlink contents of user_clang_toolchain_path to the top of the repository
+    for subpath in user_clang_toolchain_path.readdir():
+        if subpath.basename in ("BUILD.bazel", "BUILD", "WORKSPACE.bazel", "WORKSPACE"):
+            continue
+
+        subpath_s = str(subpath)
+        user_clang_toolchain_path_s = str(user_clang_toolchain_path)
+        if not subpath_s.startswith(user_clang_toolchain_path_s + "/"):
+            fail("FATAL: {} does not start with {}/".format(
+                subpath_s,
+                user_clang_toolchain_path_s,
+            ))
+
+        repository_ctx.symlink(
+            subpath,
+            subpath_s.removeprefix(user_clang_toolchain_path_s + "/"),
+        )
+
+    build_file_content = '''\
+"""User C toolchains specified via command line flags."""
+
+load("{architecture_constants}", "SUPPORTED_ARCHITECTURES")
+load("{clang_toolchain}", "clang_toolchain")
+
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+    name = "binaries",
+    srcs = glob([
+        "bin/*",
+        "lib/*",
+    ]),
+)
+
+filegroup(
+    name = "includes",
+    srcs = glob([
+        "lib/clang/*/include/**",
+    ]),
+)
+
+[clang_toolchain(
+    name = "user_{{}}_{{}}_clang_toolchain".format(target_os, target_cpu),
+    target_cpu = target_cpu,
+    target_os = target_os,
+    clang_pkg = ":fake_anchor_target",
+    clang_version = "kleaf_user_clang_toolchain_skip_version_check",
+) for target_os, target_cpu in SUPPORTED_ARCHITECTURES]
+'''.format(
+        architecture_constants = Label(":architecture_constants.bzl"),
+        clang_toolchain = Label(":clang_toolchain.bzl"),
+    )
+
+    repository_ctx.file("BUILD.bazel", build_file_content)
+
+user_clang_toolchain_repository = repository_rule(
+    doc = """Defines a repository that provides a clang version at a user defined path.
+
+The user clang toolchain is expected from the path defined in the
+`KLEAF_USER_CLANG_TOOLCHAIN_PATH` environment variable, if set.
+""",
+    implementation = _user_clang_toolchain_repository_impl,
+    environ = [
+        "KLEAF_USER_CLANG_TOOLCHAIN_PATH",
+    ],
+    local = True,
+)
diff --git a/profiles/bolt-r487747.tar.bz2 b/profiles/bolt-r487747.tar.bz2
deleted file mode 100644
index bc277cb..0000000
--- a/profiles/bolt-r487747.tar.bz2
+++ /dev/null
Binary files differ
diff --git a/profiles/bolt-r498229.tar.bz2 b/profiles/bolt-r498229.tar.bz2
new file mode 100644
index 0000000..ac0749f
--- /dev/null
+++ b/profiles/bolt-r498229.tar.bz2
Binary files differ
diff --git a/profiles/pgo-r487747.tar.bz2 b/profiles/pgo-r487747.tar.bz2
deleted file mode 100644
index a119827..0000000
--- a/profiles/pgo-r487747.tar.bz2
+++ /dev/null
Binary files differ
diff --git a/profiles/pgo-r498229.tar.bz2 b/profiles/pgo-r498229.tar.bz2
new file mode 100644
index 0000000..5eec1c6
--- /dev/null
+++ b/profiles/pgo-r498229.tar.bz2
Binary files differ