Skip to content

Resolve MethodInstance-targeted invokes in get_codeinfos - #19

Merged
maleadt merged 1 commit into
mainfrom
tb/resolve_invoke_targets
Jul 17, 2026
Merged

Resolve MethodInstance-targeted invokes in get_codeinfos#19
maleadt merged 1 commit into
mainfrom
tb/resolve_invoke_targets

Conversation

@maleadt

@maleadt maleadt commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Fixes order-dependent InvalidIRError: unsupported dynamic function invocation failures
in GPU stacks on Julia 1.12+ (e.g. JuliaGPU/Metal.jl#876, checked conversions hitting the
throw_inexacterror overlay).

Problem

Inlining's compileable_specialization leaves an :invoke target as a bare
MethodInstance when the compileable specialization is not in the code cache at
optimization time, and that operand gets baked into the cached optimized source. Codegen
(emit_invoke, all 1.12+ versions incl. master) lowers a MethodInstance operand to a
runtime jl_invoke call unconditionally — including the callee's CI in the
jl_emit_native payload is not enough, the operand itself must be rewritten.

get_codeinfos only followed CodeInstance edges, so such callees were silently
dropped from the collection, violating the documented contract that it returns the
transitive callee closure. On CPU that degrades to runtime dispatch; on GPU it's
invalid IR. Whether it triggers depends on cache history, hence the flaky,
order-dependent CI failures.

Fix

get_codeinfos(interp, ci) / get_codeinfos(interp, ci, argtypes) now resolve
MethodInstance-targeted :invoke/:invoke_modify statements through typeinf! and
rewrite the operand to the resulting CodeInstance, in a copy of the containing source
(cached CodeInfo is never mutated; untouched sources are returned as-is). Referenced
CIs that lack stored source (e.g. from an earlier session) are re-inferred instead of
asserting.

Only targets callable through a native ABI are resolved: isdispatchtuple(specTypes)
plus a mirror of nightly's has_valid_abi_sparams (TypeVar/svec/Vararg sparams take
codegen's needsparams path, which emits jl_invoke even for CI operands). Abstract
targets — e.g. @nospecialize-widened compileable signatures — deterministically keep
their runtime-dispatch fallback, so GPUCompiler's dynamic-call diagnostics are
unaffected.

The const-specialized variant now seeds the walk from the const-optimized root source,
so callees reachable only from const-optimized code are included (previously they got
runtime-dispatch stubs).

Breaking: interpreter argument is required

The interpreter-less get_codeinfos(ci) and get_codeinfos(ci, argtypes) are removed:

  • Their precondition — typeinf! ran first, in this session — is unverifiable, and the
    natural cache-hit → codegen pattern violates it (our own examples/native.jl did),
    with an un-closed collection as the silent failure mode.
  • Every caller necessarily has an interpreter (populating the cache required one), so
    passing it costs nothing and lets collection repair gaps instead of asserting.
  • This matches upstream: in Compiler/src/typeinfer.jl the interpreter travels inside
    CompilationQueue, and the compile!/add_codeinsts_to_jit! drain loops fuse
    collection with re-inference. There is no interpreter-less collection in Base either.

Migration: get_codeinfos(ci)get_codeinfos(interp, ci), where interp matches
the cache owner and world that produced ci (typically the one passed to typeinf!).

Version bumped to 0.4.0. GPUCompiler follow-up is a one-line call-site change plus the
compat bump.

Inlining's compileable_specialization leaves an :invoke target as a bare
MethodInstance when the compileable specialization is not cached at
optimization time, and codegen unconditionally lowers such operands to
runtime jl_invoke calls. get_codeinfos only followed CodeInstance edges,
so these callees were dropped from the collection, breaking closed-world
code generation (order-dependent InvalidIRErrors in GPU stacks, e.g.
JuliaGPU/Metal.jl#870).

get_codeinfos now requires the interpreter (breaking; the interp-less
forms' "typeinf! ran first" precondition was unverifiable and violated by
the natural cache-hit path) and uses it to close the collection: bare
MethodInstance targets callable through a native ABI are inferred and the
statements rewritten to their CodeInstance in a copy of the source, and
referenced CIs lacking stored source are re-inferred instead of asserting.
Abstract targets (e.g. @nospecialize-widened compileable signatures) keep
their runtime-dispatch fallback. The const-specialized variant now seeds
the walk from the const-optimized root source.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@maleadt
maleadt merged commit 5b745a6 into main Jul 17, 2026
22 checks passed
@maleadt
maleadt deleted the tb/resolve_invoke_targets branch July 17, 2026 11:18
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.

1 participant