Add runtime namespace renaming for import/export/internal scopes - #9240
Add runtime namespace renaming for import/export/internal scopes#9240derek-gerstmann wants to merge 16 commits into
Conversation
…l scopes
Rename halide_-prefixed runtime symbols in generated modules according to
user-supplied prefixes, so runtimes and kernels can be built with a custom
ABI prefix that doesn't collide with other Halide runtimes.
- Add apply_runtime_namespace_prefixes in CodeGen_LLVM: a single pass over the
final llvm::Module (call sites auto-follow renamed functions via use-lists).
Classifies each halide_ symbol by role:
* definition -> export prefix
* declaration called by the kernel -> import prefix
* declaration called only by other runtime methods -> internal prefix
Only halide_-prefixed symbols are touched; pipeline entry points and libc
symbols are left alone.
- Plumb RuntimeNamespaceParams from Pipeline/Generator onto the Module:
Module::set_runtime_namespace_map, Pipeline::apply_runtime_namespace stores
the params, compile_to_module attaches the map, and AbstractGenerator wires
the GeneratorContext prefixes through.
- Reject runtime-namespace prefixes on JIT targets: the JIT resolves runtime
calls against a process-global, non-namespaced shared runtime.
- Fix GeneratorParam_RuntimeNamespaceParams::try_set, whose
(key == n) && starts_with(key, n + ".") guard could never match, so
runtime_namespace.import/export/internal were never parsed.
- Add test/correctness/runtime_namespace.cpp covering baseline, export, import,
the import/internal discriminator, all optional-prefix combinations for AOT
and NoRuntime, and the JIT error case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
There are also internal globals not prefixed with halide_ in the runtime - i.e. stuff in Halide::Runtime::Internal. I suggest an aot generator test (or demo app, if that's easier) that links two differently-prefixed runtimes in the same process, and has a pipeline that uses each. It could check that they don't interfere with each other by tweaking internal state of each (e.g. number of threads in the thread pool, the existence of a custom malloc override, etc). |
|
Ah, yes ... good idea. I'll add an AOT generator and binary that links in multiple namespaced runtimes to validate we have no collisions. |
…l scopes
Rename halide runtime symbols in generated modules according to user-supplied
prefixes, so runtimes and kernels can be built with a custom ABI namespace that
does not collide with other Halide runtimes -- including keeping each runtime's
mutable state independent when several differently-namespaced runtimes are
linked into the same process.
LLVM backend (apply_runtime_namespace_prefixes in CodeGen_LLVM, a single pass
over the final llvm::Module; call sites and initializers auto-follow via
use-lists):
- The halide_-prefixed extern "C" ABI, replacing the leading "halide_":
* definition -> export prefix
* declaration called by the kernel -> import prefix
* declaration called only by other runtime methods -> internal prefix
Import vs. internal is decided by the caller (kernel entry points vs. other
runtime methods); kernel-import wins for a declaration with mixed callers.
- The runtime's internal C++ symbols in the Halide::Runtime::Internal namespace
(functions and, crucially, the linkonce state globals such as custom_malloc,
the thread-pool work queue, the memoization cache, ...). These carry no
"halide_" to replace, so the internal prefix is prepended. Without this, two
namespaced runtimes' linkonce state globals would be merged into one shared
copy, defeating isolation of the renamed halide_set_*/halide_get_* methods.
C backend (CodeGen_C): a kernel that calls an external runtime, so only the
import prefix applies. It emits a block of `#define halide_x <prefix>x` for the
runtime C ABI functions (discovered by scanning the embedded runtime header) at
the top of the generated *source* (never the header, so namespaced headers can
be co-included). The preprocessor rewrites the runtime declarations and all call
sites consistently while leaving types, typedefs, and enum values untouched.
Pipeline entry points and libc symbols are left untouched. JIT is rejected: it
resolves runtime calls against a process-global, non-namespaced shared runtime.
Plumbing:
- Module::set_runtime_namespace_map; Pipeline::apply_runtime_namespace stores
the params (and rejects JIT); compile_to_module attaches the map to the Module
and guards JIT; AbstractGenerator wires the GeneratorContext prefixes through;
Module.cpp forwards the import prefix to CodeGen_C for c_source output.
- The generator command-line -r (standalone runtime) path forwards
runtime_namespace.{import,export,internal} params to compile_standalone_runtime.
- add_halide_runtime() (CMake) gains a PARAMS argument, forwarded to GenRT, so
namespaced runtimes can be produced from the build system.
- Fixes GeneratorParam_RuntimeNamespaceParams::try_set, whose
(key == n) && starts_with(key, n + ".") guard could never match.
Docs: doc/CustomRuntimes.md describes the use case, scopes, backends, and usage
from C++, the GenGen command line, and CMake.
Tests:
- test/correctness/runtime_namespace.cpp: baseline, export, import, internal
(namespace-state renaming + the import/internal discriminator), all
optional-prefix combinations for AOT and NoRuntime, and the JIT error case.
- test/generator/runtime_namespace_iso: builds three variants of one pipeline
with both the LLVM and the C backend, each linked against a runtime with a
distinct namespace (none/a/b), links them all into one program, and checks
that per-runtime state stays independent across the custom allocator,
halide_set/get_num_threads, and the error handler, while each pipeline still
computes correctly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #9240 +/- ##
==========================================
+ Coverage 70.13% 70.31% +0.17%
==========================================
Files 255 255
Lines 78895 79079 +184
Branches 18865 18926 +61
==========================================
+ Hits 55332 55602 +270
+ Misses 17866 17852 -14
+ Partials 5697 5625 -72 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…e implementation Resolve conflicts in src/CodeGen_LLVM.cpp and test/correctness/runtime_namespace.cpp in favor of the local branch, which supersedes the remote's earlier snapshot (adds the Halide::Runtime::Internal state-global renaming, the C backend, and the my_ns_ example prefixes).
The prior binding of compile_standalone_runtime() took a std::map<RuntimeVisibility, std::string> but RuntimeVisibility was never bound, so the map could not be constructed from Python, and the map argument had no default (a regression for existing callers). - Bind the RuntimeVisibility enum (Import/Export/Internal). - Give compile_standalone_runtime()'s namespace_map argument an empty default. - Bind Pipeline.apply_runtime_namespace(target, namespace_map), accepting a dict keyed by RuntimeVisibility (rejects JIT targets, matching the C++ API). - Add python_bindings/test/correctness/runtime_namespace.py covering the enum, a namespaced standalone runtime, the optional/default map, a namespaced AOT pipeline, and the JIT error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Correctness tests (runtime_namespace.cpp) and the Python test are picked up automatically (the former via the correctness glob; the Makefile does not build the Python bindings tests). The generator isolation test needs custom rules because it links three separately namespaced standalone runtimes plus both LLVM- and C-backend kernels for each into a single binary, which the generic single-runtime generator rules cannot express. - Add rules for the three namespaced runtimes (stock, runtime_a_, runtime_b_) and the six kernels (LLVM + C backend, distinct function names, matching import/internal prefixes), forwarding runtime_namespace.* to the generator. - Add a custom generator_aot_runtime_namespace_iso link rule that combines them. - Exclude generator_aotcpp_runtime_namespace_iso: the single test already links both backends, so there is no separate C++-backend-only variant. Note: the Makefile build could not be exercised in this environment (missing libpng/pkg-config); the rules follow the existing alias/cxx_mangling/pyramid precedents and the -r -e static_library + runtime_namespace.* flags were verified against a built generator binary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
@alexreinking Have a look at the added PARAMS in the CMake helper. Let me know if you feel this is an appropriate way of handling additional |
|
Currently investigating failures ... it appears there's still some duplicate symbol collisions from global vars. Working on a fix. |
|
Some of the failures may be because we have bad hygiene about how we name things inside the runtime, which should be fixed by renaming them (e.g. adding halide_), rather than making the namespacing pass more complex. |
…(GPU crash) The internal-scope rename only renamed symbols in the Halide::Runtime::Internal mangled namespace. GPU device runtimes also define externally-linked helpers that are neither halide_-prefixed nor in that namespace (e.g. is_compiled_metallib, copy_to_device_already_locked, metal_device_crop_from_offset). These escaped the rename, so linking several differently-namespaced runtimes into one process merged them into a single shared copy -> shared device state -> segfaults on GPU targets. Identify runtime-internal symbols precisely instead of by name: snapshot the symbols present in the initial runtime-only module (CodeGen_LLVM::init_module, before any pipeline functions are emitted), and for the Internal scope rename every *defined* symbol from that set except the halide_ C ABI (handled by the export/import scopes) and LLVM's reserved globals. Declarations (the runtime's libc imports such as strstr/write/usleep) are left untouched so they still bind to libc. Verified by linking three separately-namespaced Metal runtimes plus their kernels into one process: previously a duplicate-symbol/shared-state failure, now links cleanly and keeps per-runtime state independent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The most general solution I could come up with was to grab a snapshot of the runtime module before pipeline methods get injected, and then check their declaration and usage during the renaming pass to handle all globals regardless of the declaration scope. |
On Windows/COFF, link_modules() places each weak runtime function in a COMDAT
named after the function, and the COFF backend emits associative sections (e.g.
exception-handling data) that reference that COMDAT's leader symbol by name.
The runtime-namespace rename used llvm::GlobalValue::setName() directly, which
renamed the symbol but left its COMDAT keyed by the old name, producing:
Associative COMDAT symbol 'halide_internal_aligned_alloc' does not exist.
when emitting the namespaced runtime objects.
Add rename_runtime_symbol(), used for every runtime-symbol rename: if the symbol
has a COMDAT keyed by its (old) name, re-key that COMDAT to the new name (same
selection kind) so the leader stays consistent. On targets where COMDATs are
stripped (e.g. macOS) getComdat() is null and this is just a setName().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dows) Two follow-ups to the runtime-namespace renaming, both surfacing as Windows link failures when the ISO test links several namespaced runtimes: 1. halide_-prefixed *global variables* were renamed by neither pass: pass (a) only iterated functions, and pass (b) skips halide_-prefixed names. Some runtime state is exposed with C linkage and a halide_ name (e.g. halide_reference_clock_inited in windows_clock.cpp, which is inside `extern "C"`, unlike the namespaced posix_clock.cpp used on Mac/Linux). Left un-renamed, it stayed identical across runtimes and produced "conflicting weak extern definition for 'halide_reference_clock_inited'". Pass (a) now also renames halide_-prefixed globals (definition -> export, declaration -> import). 2. rename_runtime_symbol now gives every renamed symbol that had a COMDAT its own fresh COMDAT named after the new symbol, rather than only re-keying when the COMDAT name matched the old symbol name. This dissolves stale shared / associative COMDAT groups (e.g. a global grouped with aligned_alloc), keeping every COMDAT leader well-defined. Verified by cross-compiling the runtime for x86-64-windows and inspecting the COFF object: halide_reference_clock_inited becomes runtime_a_reference_clock_inited with a matching .weak....default.runtime_a_internal_aligned_alloc, and no bare .weak.halide_ symbols remain. Mac/Metal isolation still links and runs with independent per-runtime state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The custom rules for the ISO test's six kernels passed -g/-f but not -n, so the
generator named its outputs after the function (strip_namespaces(-f), e.g.
pipe_none.a) instead of the rule's target (rniso_none.a). The generator ran but
produced differently-named files, so the link step failed with
"no such file or directory: bin/host/build/rniso_none.a".
Pass -n <base> (rniso_none, rniso_a, ... and the _c variants) so the emitted
files match the Make targets. The runtimes already name their output via -r, so
only the kernel rules needed this.
Verified by replaying the exact rule commands against a built generator: the
files are now named rniso_*.{a,h} / rniso_*_c.{halide_generated.cpp,h}, and the
Makefile-style link of all six kernels plus the three namespaced runtimes builds
and runs the isolation test successfully.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implement runtime namespace symbol renaming for import/export/internal scopes.
Use Case:
Enable custom runtimes to be built that partially override existing behavior,
by encapsulating the Halide runtime methods into namespaced functions
with user given prefixes for three scopes:
Implementation:
Rename halide runtime symbols in generated modules according to user-supplied
prefixes, so runtimes and kernels can be built with a custom ABI namespace that
does not collide with other Halide runtimes -- including keeping each runtime's
mutable state independent when several differently-namespaced runtimes are
linked into the same process.
LLVM backend (apply_runtime_namespace_prefixes in CodeGen_LLVM, a single pass
over the final llvm::Module; call sites and initializers auto-follow via
use-lists):
The halide_-prefixed extern "C" ABI, replacing the leading "halide_":
Import vs. internal is decided by the caller (kernel entry points vs. other
runtime methods); kernel-import wins for a declaration with mixed callers.
The runtime's internal C++ symbols in the Halide::Runtime::Internal namespace
(functions and, crucially, the linkonce state globals such as custom_malloc,
the thread-pool work queue, the memoization cache, ...). These carry no
"halide_" to replace, so the internal prefix is prepended. Without this, two
namespaced runtimes' linkonce state globals would be merged into one shared
copy, defeating isolation of the renamed halide_set_/halide_get_ methods.
C backend (CodeGen_C): a kernel that calls an external runtime, so only the
import prefix applies. It emits a block of
#define halide_x <prefix>xfor theruntime C ABI functions (discovered by scanning the embedded runtime header) at
the top of the generated source (never the header, so namespaced headers can
be co-included). The preprocessor rewrites the runtime declarations and all call
sites consistently while leaving types, typedefs, and enum values untouched.
Pipeline entry points and libc symbols are left untouched. JIT is rejected: it
resolves runtime calls against a process-global, non-namespaced shared runtime.
Details:
the params (and rejects JIT); compile_to_module attaches the map to the Module
and guards JIT; AbstractGenerator wires the GeneratorContext prefixes through;
Module.cpp forwards the import prefix to CodeGen_C for c_source output.
runtime_namespace.{import,export,internal} params to compile_standalone_runtime.
namespaced runtimes can be produced from the build system.
Docs: doc/CustomRuntimes.md describes the use case, scopes, backends, and usage
from C++, the GenGen command line, and CMake.
Tests:
(namespace-state renaming + the import/internal discriminator), all
optional-prefix combinations for AOT and NoRuntime, and the JIT error case.
with both the LLVM and the C backend, each linked against a runtime with a
distinct namespace (none/a/b), links them all into one program, and checks
that per-runtime state stays independent across the custom allocator,
halide_set/get_num_threads, and the error handler, while each pipeline still
computes correctly.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Issues:
This only covers AOT. For JIT, I'm not sure how to handle this reliably, so we currently
assert if a namespace prefix is specified.
Checklist