| # Copyright 2026 The IREE Authors | |
| # | |
| # Licensed under the Apache License v2.0 with LLVM Exceptions. | |
| # See https://llvm.org/LICENSE.txt for license information. | |
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| """Rules for compiling IREE HAL executables from MLIR sources. | |
| HAL executables are backend-specific binaries produced by iree-compile in | |
| hal-executable mode. Unlike full VM bytecode modules (iree_bytecode_module), | |
| these contain only the device-side code for a single target backend. | |
| Two compilation rules handle the actual iree-compile invocations: | |
| _iree_hal_executable - Starlark rule: one MLIR source -> one .bin file. | |
| _iree_hal_executables - Starlark rule: multiple MLIR sources -> multiple | |
| .bin files. Accepts filegroups (label_list | |
| expansion happens at analysis time, not loading | |
| time), enabling cross-package file discovery. | |
| Two macro wrappers compose the compilation rules with iree_c_embed_data: | |
| iree_hal_executable() - compile + optional embed into a cc_library. | |
| iree_hal_executables() - batch compile + bundle into a single | |
| iree_c_embed_data cc_library. | |
| Typical usage (single file): | |
| load("//build_tools/bazel:iree_hal_executable.bzl", "iree_hal_executable") | |
| iree_hal_executable( | |
| name = "dispatch_test_vmvx", | |
| src = "testdata/dispatch_test.mlir", | |
| target_device = "local", | |
| flags = ["--iree-hal-local-target-device-backends=vmvx"], | |
| ) | |
| Typical usage (batch compile + embed): | |
| load("//build_tools/bazel:iree_hal_executable.bzl", "iree_hal_executables") | |
| # Each call compiles all sources for one format and bundles them into | |
| # an iree_c_embed_data cc_library. Multiple calls in the same package | |
| # (for different formats) are safe — outputs are namespaced by target | |
| # name to avoid collisions. | |
| iree_hal_executables( | |
| name = "testdata_vmvx", | |
| srcs = ["//path/to/testdata:executable_srcs"], # filegroup! | |
| target_device = "local", | |
| flags = ["--iree-hal-local-target-device-backends=vmvx"], | |
| identifier = "iree_cts_testdata_vmvx", | |
| ) | |
| Flags can contain {PLACEHOLDER} template variables resolved via the | |
| flag_values dict. Each entry maps a placeholder name to a label. | |
| Resolution is determined by the target type: | |
| Build settings (string_flag) — replaced with the setting's value. | |
| File targets — the flag is repeated for each output file, with | |
| the placeholder replaced by the file's path. For directory | |
| targets (TreeArtifacts via iree_directory), this produces | |
| a single flag with the directory path. | |
| Example: | |
| iree_hal_executables( | |
| name = "testdata_amdgpu", | |
| srcs = ["//path/to/testdata:executable_srcs"], | |
| target_device = "amdgpu", | |
| flags = [ | |
| "--iree-rocm-target={ROCM_TARGET}", | |
| "--iree-rocm-bc-dir={ROCM_BC_DIR}", | |
| ], | |
| flag_values = { | |
| "ROCM_TARGET": "//build_tools/bazel:rocm_test_target", | |
| "ROCM_BC_DIR": "@amdgpu_device_libs//:bitcode", | |
| }, | |
| ) | |
| Override build settings at build time: | |
| --//build_tools/bazel:rocm_test_target=gfx942 | |
| """ | |
| load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") | |
| load("//build_tools/embed_data:build_defs.bzl", "iree_c_embed_data") | |
| def _resolve_flags(ctx): | |
| """Resolves {PLACEHOLDER} template variables in flags via flag_values. | |
| Each flag_values entry maps a placeholder name to a target label. | |
| Resolution depends on the target type: | |
| Build settings (BuildSettingInfo) — the setting's string value | |
| replaces {PLACEHOLDER} in each flag. | |
| File targets — each flag containing {PLACEHOLDER} is repeated | |
| for every output file, with the placeholder replaced by | |
| the file's path. For TreeArtifact targets (iree_directory), | |
| this produces a single flag with the directory path. | |
| """ | |
| flags = list(ctx.attr.flags) | |
| for target, placeholder in ctx.attr.flag_values.items(): | |
| template = "{%s}" % placeholder | |
| if BuildSettingInfo in target: | |
| value = target[BuildSettingInfo].value | |
| flags = [f.replace(template, value) for f in flags] | |
| else: | |
| files = target.files.to_list() | |
| if not files: | |
| fail("{%s} references a label that produces no files" % | |
| placeholder) | |
| expanded = [] | |
| for flag in flags: | |
| if template in flag: | |
| for f in files: | |
| expanded.append(flag.replace(template, f.path)) | |
| else: | |
| expanded.append(flag) | |
| flags = expanded | |
| return flags | |
| def _compile_hal_executable(ctx, src, output, flags): | |
| """Runs iree-compile in hal-executable mode for a single source file. | |
| Shared action logic used by both _iree_hal_executable and | |
| _iree_hal_executables rules. | |
| Args: | |
| ctx: Rule context (for tools and action factory). | |
| src: Source File to compile. | |
| output: Output File to produce. | |
| flags: Resolved compiler flags (template variables already expanded). | |
| """ | |
| args = ctx.actions.args() | |
| args.add("--compile-mode=hal-executable") | |
| args.add("--iree-hal-target-device=" + ctx.attr.target_device) | |
| args.add("--mlir-print-op-on-diagnostic=false") | |
| args.add( | |
| ctx.executable._linker_tool, | |
| format = "--iree-llvmcpu-embedded-linker-path=%s", | |
| ) | |
| args.add( | |
| ctx.executable._linker_tool, | |
| format = "--iree-llvmcpu-wasm-linker-path=%s", | |
| ) | |
| for flag in flags: | |
| args.add(flag) | |
| args.add("-o", output) | |
| args.add(src) | |
| transitive_inputs = [depset(ctx.files._linker_tool)] | |
| if ctx.files.data: | |
| transitive_inputs.append(depset(ctx.files.data)) | |
| for target in ctx.attr.flag_values: | |
| if BuildSettingInfo not in target: | |
| transitive_inputs.append(target.files) | |
| # Set PATH so that backends using findTool() (e.g., ROCM's search for | |
| # iree-lld/lld) can locate the linker in the sandbox. | |
| env = {"PATH": ctx.executable._linker_tool.dirname} | |
| ctx.actions.run( | |
| inputs = depset([src], transitive = transitive_inputs), | |
| outputs = [output], | |
| executable = ctx.executable._compile_tool, | |
| arguments = [args], | |
| env = env, | |
| mnemonic = "IreeCompileHalExecutable", | |
| progress_message = "Compiling HAL executable %%{label} (%s)" % src.basename, | |
| ) | |
| def _iree_hal_executable_impl(ctx): | |
| """Compiles a single MLIR source to a HAL executable binary.""" | |
| src = ctx.file.src | |
| output = ctx.actions.declare_file(ctx.attr.name + ".bin") | |
| _compile_hal_executable(ctx, src, output, _resolve_flags(ctx)) | |
| return [DefaultInfo(files = depset([output]))] | |
| _COMMON_ATTRS = { | |
| "target_device": attr.string(mandatory = True), | |
| "flags": attr.string_list(), | |
| "flag_values": attr.label_keyed_string_dict( | |
| allow_files = True, | |
| ), | |
| "data": attr.label_list(allow_files = True), | |
| "_compile_tool": attr.label( | |
| default = "//tools:iree-compile", | |
| executable = True, | |
| cfg = "exec", | |
| ), | |
| "_linker_tool": attr.label( | |
| default = "@llvm-project//lld:lld", | |
| executable = True, | |
| cfg = "exec", | |
| ), | |
| } | |
| _iree_hal_executable = rule( | |
| implementation = _iree_hal_executable_impl, | |
| attrs = dict( | |
| _COMMON_ATTRS, | |
| src = attr.label(mandatory = True, allow_single_file = [".mlir"]), | |
| ), | |
| ) | |
| def _iree_hal_executables_impl(ctx): | |
| """Compiles multiple MLIR sources to HAL executable binaries. | |
| Iterates ctx.files.srcs (which expands filegroups at analysis time) | |
| and creates one compile action per source. Outputs are placed under | |
| a {name}/ subdirectory to avoid collisions when multiple | |
| _iree_hal_executables targets coexist in the same package. | |
| """ | |
| flags = _resolve_flags(ctx) | |
| outputs = [] | |
| for src in ctx.files.srcs: | |
| if src.basename.endswith(".mlir"): | |
| stem = src.basename[:-5] | |
| else: | |
| stem = src.basename | |
| output = ctx.actions.declare_file(ctx.attr.name + "/" + stem + ".bin") | |
| _compile_hal_executable(ctx, src, output, flags) | |
| outputs.append(output) | |
| return [DefaultInfo(files = depset(outputs))] | |
| _iree_hal_executables = rule( | |
| implementation = _iree_hal_executables_impl, | |
| attrs = dict( | |
| _COMMON_ATTRS, | |
| srcs = attr.label_list(allow_files = [".mlir"]), | |
| ), | |
| ) | |
| def iree_hal_executable( | |
| name, | |
| src, | |
| target_device, | |
| flags = [], | |
| flag_values = {}, | |
| data = [], | |
| c_identifier = "", | |
| deps = [], | |
| testonly = True, | |
| **kwargs): | |
| """Compiles an MLIR source to a HAL executable binary. | |
| Args: | |
| name: Name of the target. Output is {name}.bin. | |
| src: MLIR source file to compile. | |
| target_device: Target device for compilation (e.g., "local", "vulkan", | |
| "hip", "cuda", "metal"). Generates --iree-hal-target-device=<value>. | |
| For devices with sub-backends (like "local"), pass the backend | |
| selection flag via the flags parameter (e.g., | |
| "--iree-hal-local-target-device-backends=vmvx"). | |
| flags: Additional compiler flags beyond target device selection. | |
| May contain {PLACEHOLDER} template variables resolved from | |
| flag_values. See module docstring for resolution semantics. | |
| flag_values: Dict mapping placeholder names to target labels. | |
| Build settings (string_flag) resolve to their string value. | |
| File targets resolve to their output paths, repeating the | |
| flag for each file. iree_directory targets (TreeArtifacts) | |
| resolve to the directory path. Example: | |
| flag_values = { | |
| "ROCM_TARGET": "//build_tools/bazel:rocm_test_target", | |
| "ROCM_BC_DIR": "@amdgpu_device_libs//:bitcode", | |
| } | |
| data: Additional files to include in the compile action's inputs. | |
| c_identifier: If set, generates embedded C data via iree_c_embed_data. | |
| deps: Dependencies for the generated C library (when c_identifier is set). | |
| testonly: If True, only testonly targets can depend on this target. | |
| Defaults to True since HAL executables are primarily used for testing. | |
| **kwargs: Additional attributes (e.g., target_compatible_with) | |
| passed to the underlying rules. | |
| """ | |
| # Invert flag_values: user writes {"PLACEHOLDER": "//label"} (readable), | |
| # rule attr is label_keyed_string_dict so needs {"//label": "PLACEHOLDER"}. | |
| rule_flag_values = {v: k for k, v in flag_values.items()} | |
| _iree_hal_executable( | |
| name = name, | |
| src = src, | |
| target_device = target_device, | |
| flags = flags, | |
| flag_values = rule_flag_values, | |
| data = data, | |
| testonly = testonly, | |
| **kwargs | |
| ) | |
| if c_identifier: | |
| iree_c_embed_data( | |
| name = "%s_c" % (name), | |
| identifier = c_identifier, | |
| srcs = [":%s" % name], | |
| c_file_output = "%s_c.c" % (name), | |
| h_file_output = "%s_c.h" % (name), | |
| flatten = True, | |
| testonly = testonly, | |
| deps = deps, | |
| **kwargs | |
| ) | |
| def iree_hal_executables( | |
| name, | |
| srcs, | |
| target_device, | |
| flags = [], | |
| flag_values = {}, | |
| data = [], | |
| identifier = None, | |
| testonly = True, | |
| **kwargs): | |
| """Compiles multiple MLIR sources to HAL executables and bundles them. | |
| Batch form of iree_hal_executable(). Compiles each source to a HAL | |
| executable binary, then bundles all outputs into a single | |
| iree_c_embed_data() cc_library target. | |
| srcs can include filegroup labels — the underlying rule expands them | |
| at analysis time, enabling cross-package file discovery without | |
| requiring the caller to enumerate individual files. | |
| TOC entry names are the source stems with a .bin extension (e.g., | |
| "dispatch.mlir" produces TOC entry "dispatch.bin"). This is independent | |
| of the format being compiled, so the same test code can look up | |
| executables by stem regardless of which backend compiled them. | |
| Multiple calls in the same package (for different formats) are safe: | |
| each call's compiled outputs are placed in a {name}/ subdirectory to | |
| avoid filename collisions, and flatten=True on the embedded data strips | |
| the directory prefix from TOC entries. | |
| Args: | |
| name: Name of the output iree_c_embed_data cc_library target. | |
| srcs: MLIR source file labels or filegroup labels to compile. | |
| Each source produces one executable binary in the embedded data. | |
| target_device: Target device for iree-compile (e.g., "local", | |
| "vulkan", "hip", "cuda"). | |
| flags: Backend-specific compiler flags. May contain {PLACEHOLDER} | |
| template variables resolved from flag_values. | |
| flag_values: Dict mapping placeholder names to target labels. | |
| See iree_hal_executable() for details. | |
| data: Additional files to include in compile action inputs. | |
| identifier: C identifier for the generated embed data functions. | |
| Defaults to name. The generated header exposes | |
| {identifier}_create() and {identifier}_size(). | |
| testonly: Defaults to True. | |
| **kwargs: Additional attributes (e.g., target_compatible_with) | |
| passed to the underlying rules. | |
| """ | |
| if identifier == None: | |
| identifier = name | |
| # Invert flag_values: user writes {"PLACEHOLDER": "//label"} (readable), | |
| # rule attr is label_keyed_string_dict so needs {"//label": "PLACEHOLDER"}. | |
| rule_flag_values = {v: k for k, v in flag_values.items()} | |
| compile_name = "%s_bin" % name | |
| _iree_hal_executables( | |
| name = compile_name, | |
| srcs = srcs, | |
| target_device = target_device, | |
| flags = flags, | |
| flag_values = rule_flag_values, | |
| data = data, | |
| testonly = testonly, | |
| **kwargs | |
| ) | |
| iree_c_embed_data( | |
| name = name, | |
| srcs = [":%s" % compile_name], | |
| c_file_output = "%s.c" % name, | |
| h_file_output = "%s.h" % name, | |
| identifier = identifier, | |
| flatten = True, | |
| testonly = testonly, | |
| **kwargs | |
| ) |