Expected behavior
tests/python/codegen/test_target_codegen_hexagon.py compiles three small modules for
qcom/hexagon-v66 and inspects the generated assembly and LLVM IR. It registers a phony linker
(/bin/true) with the comment "so that we can test codegen without a Hexagon toolchain", so I
expected it to pass on any build whose LLVM has the Hexagon target — no SDK, no device.
Actual behavior
Those three tests never run, and they cannot pass on main.
They are gated on env.has_hexagon(), which requires both HEXAGON_TOOLCHAIN and
ANDROID_SERIAL_NUMBER (an attached device), so they are always skipped — there is no hexagon
mention left under ci/ or .github/. The Hexagon codegen is still compiled into every
LLVM-enabled build though: src/backend/hexagon/codegen/llvm/*.cc is in COMPILER_LLVM_SRCS
next to the CUDA and ROCm LLVM codegen (cmake/modules/LLVM.cmake:62), independent of
USE_HEXAGON, which only controls the device runtime. So it gets recompiled and carried through
refactors on every build, with no test exercising it.
When I narrow the gate so the tests run on a plain USE_LLVM build (USE_HEXAGON=OFF, no
toolchain, no device), all three fail, for two independent reasons.
1. unknown intrinsic tirx.tvm_call_packed
src/target/llvm/codegen_llvm.cc:1545: in CodeGenLLVM::CreateIntrinsic
E tvm.error.InternalError: unknown intrinsic ir.Op(... name="tirx.tvm_call_packed" ...)
split_host_device_mods decides host-ness by target kind (python/tvm/tirx/build.py:104):
def is_host_func(f):
target = f.attrs.get("target", tvm.target.Target("llvm"))
return target.kind.name in ["llvm", "c"]
Its own docstring says something else: "Functions with cpu in the target string are considered
host functions". The hexagon kind registers set_default_keys({"hexagon", "cpu"})
(src/backend/hexagon/codegen/target_kind.cc:42), so with Target(target, target) — target and
host both hexagon, which is what the test does — the host function is classified as a device
function. Device modules only get finalize_device_passes(), which does not include
LowerTVMBuiltin(). So the tvm_call_packed that MakePackedAPI inserts for the set_device
call (src/tirx/transform/make_packed_api.cc:261) is never lowered to tvm_call_packed_lowered,
the form CodeGenCPU knows how to handle.
Only llvm, c and hexagon register the cpu key, so this gap affects exactly one target kind.
2. Cannot open /tmp/tvm-XXXXXX.so
With (1) worked around locally, the failure moves to:
<unknown>:0: in tvm::codegen::BuildHexagon(tvm::IRModule, tvm::Target)
src/runtime/file_utils.cc:83: in LoadBinaryFromFile
E tvm.error.InternalError: Check failed: (!fs.fail()) is false: Cannot open /tmp/tvm-52eea3.so
BuildHexagon emits asm, obj, IR and bitcode into strings, then calls
tvm.contrib.hexagon.link_shared and reads the linked .so back from disk:
std::string so_data;
runtime::LoadBinaryFromFile(so_name, &so_data);
return target::HexagonModuleCreateWithFallback(ffi::Bytes(std::move(so_data)), ...);
Before #19757 (2026-06-13) that tail was return HexagonModuleCreate(so_name, "so", ...): the
filename was passed along and never opened, which is what made the phony-linker approach work.
With the read-back, codegen-only use needs a real Hexagon linker, even though every artifact the
test inspects has already been produced by that point. #19796 (2026-06-16) removed the Hexagon app
and test wrappers three days later, so nothing was left that could have caught it.
Environment
- main at
87b5b27dac, also reproduced at cc0f9f07c1
- Ubuntu 24.04 (WSL2), LLVM 18.1.3 via
llvm-config-18 --ignore-libllvm --link-static,
CMake 3.28.3, Python 3.12
USE_HEXAGON=OFF, no Hexagon SDK, no device, HEXAGON_TOOLCHAIN and ANDROID_SERIAL_NUMBER
unset
Steps to reproduce
Build with USE_LLVM pointing at any LLVM that lists Hexagon in llvm-config --targets-built
(the stock Ubuntu llvm-18-dev does), then:
unset HEXAGON_TOOLCHAIN ANDROID_SERIAL_NUMBER
pytest tests/python/codegen/test_target_codegen_hexagon.py -v
# 3 skipped: need hexagon
Add a narrower probe in python/tvm/testing/env.py and gate the three tests with it instead of
has_hexagon():
@functools.cache
def has_hexagon_codegen() -> bool:
if not has_llvm():
return False
try:
return any(str(t).lower() == "hexagon" for t in tvm.target.codegen.llvm_get_targets())
except Exception: # pylint: disable=broad-except
return False
# 3 failed: unknown intrinsic tirx.tvm_call_packed
Then, for the second failure, widen is_host_func to honour the cpu key:
return target.kind.name in ["llvm", "c"] or "cpu" in target.keys
# 3 failed: Cannot open /tmp/tvm-XXXXXX.so
That one-liner is regression-clean here — tests/python/{codegen,tirx,driver,target,testing} gives
1605 passed, 3195 skipped, nothing newly failing — but I don't think it is the right fix: it would
also send a Hexagon device kernel to the host module in the usual cross-compilation setup
(target=hexagon, host=llvm), and a split on calling_conv looks closer to what the docstring
describes.
Questions before sending a PR
Both fixes look like design calls rather than obvious patches:
- Should
split_host_device_mods key on calling_conv (or on the host target) instead of the
target kind?
- Should
BuildHexagon keep codegen usable without a linker — loading the .so only when one was
actually produced — or should the test stop going through tvm.compile to inspect generated
code?
I'm happy to send a PR for whichever shape you prefer, together with the test-gate change so this
is covered by CI from then on. My repro needs no device and no SDK, just an LLVM with the Hexagon
target.
Triage
Expected behavior
tests/python/codegen/test_target_codegen_hexagon.pycompiles three small modules forqcom/hexagon-v66and inspects the generated assembly and LLVM IR. It registers a phony linker(
/bin/true) with the comment "so that we can test codegen without a Hexagon toolchain", so Iexpected it to pass on any build whose LLVM has the Hexagon target — no SDK, no device.
Actual behavior
Those three tests never run, and they cannot pass on main.
They are gated on
env.has_hexagon(), which requires bothHEXAGON_TOOLCHAINandANDROID_SERIAL_NUMBER(an attached device), so they are always skipped — there is nohexagonmention left under
ci/or.github/. The Hexagon codegen is still compiled into everyLLVM-enabled build though:
src/backend/hexagon/codegen/llvm/*.ccis inCOMPILER_LLVM_SRCSnext to the CUDA and ROCm LLVM codegen (
cmake/modules/LLVM.cmake:62), independent ofUSE_HEXAGON, which only controls the device runtime. So it gets recompiled and carried throughrefactors on every build, with no test exercising it.
When I narrow the gate so the tests run on a plain
USE_LLVMbuild (USE_HEXAGON=OFF, notoolchain, no device), all three fail, for two independent reasons.
1.
unknown intrinsic tirx.tvm_call_packedsplit_host_device_modsdecides host-ness by target kind (python/tvm/tirx/build.py:104):Its own docstring says something else: "Functions with
cpuin the target string are consideredhost functions". The
hexagonkind registersset_default_keys({"hexagon", "cpu"})(
src/backend/hexagon/codegen/target_kind.cc:42), so withTarget(target, target)— target andhost both hexagon, which is what the test does — the host function is classified as a device
function. Device modules only get
finalize_device_passes(), which does not includeLowerTVMBuiltin(). So thetvm_call_packedthatMakePackedAPIinserts for theset_devicecall (
src/tirx/transform/make_packed_api.cc:261) is never lowered totvm_call_packed_lowered,the form
CodeGenCPUknows how to handle.Only
llvm,candhexagonregister thecpukey, so this gap affects exactly one target kind.2.
Cannot open /tmp/tvm-XXXXXX.soWith (1) worked around locally, the failure moves to:
BuildHexagonemits asm, obj, IR and bitcode into strings, then callstvm.contrib.hexagon.link_sharedand reads the linked.soback from disk:Before #19757 (2026-06-13) that tail was
return HexagonModuleCreate(so_name, "so", ...): thefilename was passed along and never opened, which is what made the phony-linker approach work.
With the read-back, codegen-only use needs a real Hexagon linker, even though every artifact the
test inspects has already been produced by that point. #19796 (2026-06-16) removed the Hexagon app
and test wrappers three days later, so nothing was left that could have caught it.
Environment
87b5b27dac, also reproduced atcc0f9f07c1llvm-config-18 --ignore-libllvm --link-static,CMake 3.28.3, Python 3.12
USE_HEXAGON=OFF, no Hexagon SDK, no device,HEXAGON_TOOLCHAINandANDROID_SERIAL_NUMBERunset
Steps to reproduce
Build with
USE_LLVMpointing at any LLVM that lists Hexagon inllvm-config --targets-built(the stock Ubuntu
llvm-18-devdoes), then:Add a narrower probe in
python/tvm/testing/env.pyand gate the three tests with it instead ofhas_hexagon():Then, for the second failure, widen
is_host_functo honour thecpukey:That one-liner is regression-clean here —
tests/python/{codegen,tirx,driver,target,testing}gives1605 passed, 3195 skipped, nothing newly failing — but I don't think it is the right fix: it would
also send a Hexagon device kernel to the host module in the usual cross-compilation setup
(
target=hexagon,host=llvm), and a split oncalling_convlooks closer to what the docstringdescribes.
Questions before sending a PR
Both fixes look like design calls rather than obvious patches:
split_host_device_modskey oncalling_conv(or on the host target) instead of thetarget kind?
BuildHexagonkeep codegen usable without a linker — loading the.soonly when one wasactually produced — or should the test stop going through
tvm.compileto inspect generatedcode?
I'm happy to send a PR for whichever shape you prefer, together with the test-gate change so this
is covered by CI from then on. My repro needs no device and no SDK, just an LLVM with the Hexagon
target.
Triage