precompile: don't re-infer image-compiled callees when generating a pkgimage - #62934
precompile: don't re-infer image-compiled callees when generating a pkgimage#62934IanButterworth wants to merge 2 commits into
Conversation
…kgimage Since JuliaLang#59361 the pkgimage worklist is compiled through `compile_and_emit_native` -> `typeinf_ext_toplevel` -> `compile!`, which enqueues every `:invoke` target of each compiled body and demands its source. For callees that already live in the sysimage that source is no longer stored (JuliaLang#58172, JuliaLang#58662), so `typeinf_ext` runs inference on them from scratch, and `jl_emit_native` then discards the result: under external linkage it already skips anything carrying `JL_CI_FLAGS_FROM_IMAGE`. On 1.12 the same skip existed, but the source demand was satisfied cheaply because the sysimage still kept the IR. The cost scales with how much Base code a package's compiled methods call. `Base.compilecache` of Preferences goes from 0.31s on 1.12.7 to 0.65s on 1.13.0-rc3; Static from 7.0s to 11.0s. In-process, `typeinf_ext_toplevel` on the single `Preferences.load_preference(::UUID, ::String, ::Nothing)` entry point returns 707 CodeInstances in 0.33s, all of which already have native code (487 in Base, 92 in Base.TOML, ...), against 483 in 0.02s on 1.12 where 481 of them still carried IR. A native sample of the Static precompile child puts the whole 1.12 -> 1.13 difference in `typeinf_ext_toplevel` under `jl_create_native_impl`, with LLVM time unchanged. Skip callees that already have native code from an image when the output is a package image (external linkage, not trimming), which is exactly the set `jl_emit_native` was going to discard. Codegen calls them through the image's fptr as before. Trimmed and system images are unaffected. With the skip, the entry point above compiles to 1 CodeInstance in negligible time. Assisted-by: Claude Code (Fable 5) Claude-Session: https://claude.ai/code/session_01VzpF8n8QdxxtrbpjgEgW6J
For an entry point whose callees are compiled into the sysimage, `typeinf_ext_toplevel` with `external_linkage=true` must return only the entry point's CodeInstance, while without it the image callees are still collected. Assisted-by: Claude Code (Fable 5) Claude-Session: https://claude.ai/code/session_01VzpF8n8QdxxtrbpjgEgW6J
|
Looks like a real issue. Excellent find @IanButterworth
This suggests that the extra re-inference is being thrown away, but that seems to be contradicted by the impact to the image size. Might be a difference between IIUC #60031 added the I think these are inference-only + discarded on |
Same five-arm sweep with JuliaLang/julia#62934 applied to both dev arms (local builds of master and of the release-1.13 backports branch, linked as juliaup channels). Against the 2026-08-30 stock run: precompile geomean -20% on the 1.13 arm (-10% after excluding the stock run's inflated cells) and -13.5% on nightly; load and script execution move with the 1.12 control arm, i.e. unchanged. Relative to 1.10, the 1.13 arm's precompile geomean flips from +11% to -12%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VzpF8n8QdxxtrbpjgEgW6J
The investigation behind JuliaLang/julia#62934: 1.13 re-infers sysimage callees while generating every pkgimage because the image driver demands source that the sysimage no longer retains. Includes the MWEs, the evidence trail, and the before/after numbers from the patched builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VzpF8n8QdxxtrbpjgEgW6J
|
@topolarity good point. Claude agrees. I've replaced
with
|
|
Closing as #61479 was already prepared and is more complete. |
First sweep with per-repeat load/run times and the JIT objcache cleared per task. Arms were local builds of master and the release-1.13 backports branch with JuliaLang/julia#62934 applied, plus stock 1.12/1.11/1.10. Headline: TTSX/TTFX run ratio is 0.30x on nightly (the objcache) against 0.95-0.97x on 1.10-1.13, and nightly's true cold TTFX run (0.112s geomean) is slower than patched 1.13 (0.097s); the earlier "-64% script execution" on nightly was a warm-cache TTSX with cross-snippet bleed. See ttfx-summary.txt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VzpF8n8QdxxtrbpjgEgW6J


Seems when generating pkgimages we're re-interring stuff that's already in loaded images. Developed with Fable, passed review by Sol.
Claude:
Since #59361, pkgimage worklists pass through
typeinf_ext_toplevel, wherecompile!recursively enqueues each:invoketarget and requests its source. Sysimage source IR is no longer retained (#58172, #58662), so this needlessly re-infers image callees; master then discards the result under external linkage (JL_CI_FLAGS_FROM_IMAGE, #60031), while 1.13's older emit path additionally emits some of it as redundant copies in the image. This change skips those callees when building untrimmed package images.Codegen continues to call skipped callees through their image fptr. Trimmed and system images are unaffected because they must contain everything they call.
Measured on
backports-release-1.13(1.13.0-rc3.32), macOS aarch64, with a coldcompiled/directory for each run:Base.compilecache(Preferences)Pkg.precompile(), StaticPkg.precompile(), ComponentArrays treeThe Preferences image returns to the 1.12 shape: non-specsig image callees use
tojlinvokeinstead of receiving a localjapi1_copy. Package load and first-call times for the ComponentArrays and ShareAdd tasks are unchanged.This should be backported to 1.13, where the regression first appears.