Skip to content

Add runtime namespace renaming for import/export/internal scopes#9240

Open
derek-gerstmann wants to merge 10 commits into
mainfrom
dg/runtime_namespace
Open

Add runtime namespace renaming for import/export/internal scopes#9240
derek-gerstmann wants to merge 10 commits into
mainfrom
dg/runtime_namespace

Conversation

@derek-gerstmann

@derek-gerstmann derek-gerstmann commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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:

  • The "import" prefix corresponds to the method names called from within a generated kernel
  • The "export" prefix corresponds to the method names externally visible in the runtime library
  • The "internal" prefix corresponds to the method names called within the runtime library

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_":

    • 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.

Details:

  • 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

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.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

Checklist

  • Tests added or updated (not required for docs, CI config, or typo fixes)
  • Documentation updated (if public API changed)
  • Python bindings updated (if public API changed)
  • Benchmarks are included here if the change is intended to affect performance.
  • Commits include AI attribution where applicable (see Code of Conduct)

Derek Gerstmann and others added 4 commits July 15, 2026 16:58
…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>
@derek-gerstmann
derek-gerstmann requested a review from abadams July 24, 2026 18:18
@abadams

abadams commented Jul 24, 2026

Copy link
Copy Markdown
Member

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).

@derek-gerstmann

Copy link
Copy Markdown
Contributor Author

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

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.60163% with 46 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.05%. Comparing base (4b635d9) to head (cc15c9e).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/Generator.cpp 36.73% 28 Missing and 3 partials ⚠️
src/CodeGen_LLVM.cpp 84.44% 3 Missing and 4 partials ⚠️
src/Pipeline.cpp 70.00% 1 Missing and 2 partials ⚠️
src/AbstractGenerator.cpp 33.33% 1 Missing and 1 partial ⚠️
src/Generator.h 50.00% 2 Missing ⚠️
src/Pipeline.h 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9240      +/-   ##
==========================================
- Coverage   70.17%   70.05%   -0.12%     
==========================================
  Files         255      255              
  Lines       78904    79016     +112     
  Branches    18866    18900      +34     
==========================================
- Hits        55367    55353      -14     
- Misses      17883    17967      +84     
- Partials     5654     5696      +42     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Derek Gerstmann and others added 4 commits July 24, 2026 15:58
…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>
@derek-gerstmann

derek-gerstmann commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author
  • Added global symbol handling
  • Added AOT multi-runtime test case
  • Added C backend equivalents for renaming
  • Added missing python binding updates
  • Added CustomRuntimes.md documentation

@derek-gerstmann

Copy link
Copy Markdown
Contributor Author

@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 key=value settings for generators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants