blob: 834bfd3e20677a1fdac367358aabfab8b2a3f3cb [file] [log] [blame] [edit]
# 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
)