aotcompile: thunks for the entry points a pre-relocated image names - #63070
Open
levy wants to merge 4 commits into
Open
aotcompile: thunks for the entry points a pre-relocated image names#63070levy wants to merge 4 commits into
levy wants to merge 4 commits into
Conversation
levy
force-pushed
the
pr-image-entry-thunks
branch
3 times, most recently
from
September 8, 2026 11:57
0263757 to
c1eaa14
Compare
This was referenced Sep 8, 2026
levy
force-pushed
the
pr-image-entry-thunks
branch
from
September 8, 2026 13:04
c1eaa14 to
67d4e37
Compare
xal-0
self-requested a review
September 8, 2026 15:45
A package image references them by tag, since they belong to the loading runtime. The system image is that runtime, so it serializes them as objects, and a field that points at one needs no relocation. `jl_nothing` is already read from the image as a constant global. `julia_init` allocates one before the root task exists, so the root task's fields that hold that one are updated after the relocations, by a walk over the task layout. Boxed integers need no adoption: two boxes of one value are equal by value. A trimmed image keeps the tag encoding, to stay small. Assisted-by: Claude Code (Opus 5)
A package image references a symbol by its index in a name list, and the loader interns the list. The system image did the same, so every start interned tens of thousands of names and wrote one pointer per reference. The system image now serializes its symbols as objects, with their two tree children, and records the root. The loader installs that tree as the symbol table of the process after the relocations; a symbol interned later goes into the same tree. `jl_init_common_symbols` runs after the restore, and `jl_set_root_symbol` refuses a non-empty table. A trimmed image keeps the name list, to stay small. `julia --startup-file=no -e ''`: 66.8 → 61.4 ms, minimum of 15 pinned runs, on top of the lowering-context change. `sys.so` grows 2.3 MB. `test/cmdlineargs.jl` checks that a started process finds `nothing`, the booleans, an image symbol, a freshly interned symbol and the root task's `nothing` fields in the image, and that a new random symbol is not. Assisted-by: Claude Code (Opus 5)
Applying the relocations of a system image writes almost every page of it: 47,566 page faults and 88 ms of a 105 ms `jl_init` on a 368 MB application image. When the image is linked into a non-PIE program it is at the same address at every start, so the relocation can be done once, at build time, and its result written into the program file. `--sysimage-prelink=yes`, when writing an image, reserves room for the residual list: the pointers a file cannot hold. Without it the image is unchanged. `--output-prelinked <file>`, on a program that holds such an image, restores it, writes the program with the restored image to `<file>`, and exits. Linux only, through /proc/self/exe. A start of the written file applies the residual list, fills the gvar slots, registers the native code and runs the fixup list; it reads neither relocation list. The function pointers are resolved before the fixup list, because the fixups write the first pointers a file cannot hold. The runtime refuses an image pre-relocated for another address, one without native code, one with several code variants, and a second pre-relocation. A hello world starts in 7.1 ms instead of 74.9 ms, a network simulator in 7.9 ms instead of 101.8 ms (with the entry thunks; minimum of 15 pinned runs, page cache evicted). Adds the option to `--help` and NEWS, a devdoc section, option tests in `test/cmdlineargs.jl`, and `test/prelink` (`make -C test prelink`, after `test/embedding`): it builds a program with its own image, pre-relocates it, runs both, and checks that the residual list stays under 64 pointers and that the refusals fire. Assisted-by: Claude Code (Opus 5)
A pre-relocated image cannot write the entry points of libjulia-internal into the file (`jl_fptr_args` and its companions, the builtins): the library moves at every start. On a 365 MB program that was 25,831 pointers over 8,500 pages, written at every start. The image object now carries a table of thunks, one per entry point, each a tail call through a slot that the runtime fills at start. A pre-relocation writes the thunk address instead, and a start fills 72 slots in one page. An image that is not pre-relocated keeps its direct pointers. On the same program the residual list falls from 25,831 pointers to 3, the start from 15.8 ms to 7.9 ms, and the page faults from 11,111 to 2,824. Assisted-by: Claude Code (Opus 5)
levy
force-pushed
the
pr-image-entry-thunks
branch
from
September 8, 2026 16:42
67d4e37 to
c376d29
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacks on #63069.
A pre-relocated image cannot write the entry points of
libjulia-internalinto the file —jl_fptr_argsand its companions, and the builtins — because that library moves at every start. On a 365 MB program those were 25,831 pointers over 8,500 pages, written at every start.The image object now carries a table of thunks, one per entry point, each a
musttailcall through a slot that the runtime fills at start. A pre-relocation writes the thunk address instead, and a start fills 72 slots in one page. An image that is not pre-relocated keeps its direct pointers; the thunks are unused.Cost: one indirect jump per call through a runtime entry point, that is, per dynamic dispatch; not measurable on a 6.4 M-event simulation. The table has 128 entries so the image object has one shape; the runtime refuses if it needs more.
Part of #63065.
Disclosure: developed with Claude Code (Opus 5) under my direction. It wrote the code and this text; the measurements were run on my machine. I reviewed the changes. The commits carry an
Assisted-bytrailer.