Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions base/sysimg_stdlibs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

# Second-stage sysimage builder that loads all stdlibs from precompiled cache files
# This is used when building a "fat" sysimage with all stdlibs included
# This file follows the same structure as sysimg.jl but loads all available stdlibs

Base.reinit_stdio()
@eval Sys BINDIR = ccall(:jl_get_julia_bindir, Any, ())::String
@eval Sys STDLIB = abspath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "stdlib", string('v', VERSION.major, '.', VERSION.minor))
@eval Base _atexit_hooks_finished = false

# Set up depot & load paths to be able to find stdlib packages
Base.init_depot_path()
Base.init_load_path()

if Base.is_primary_base_module
# Load all stdlib packages (similar to sysimg.jl but loading all stdlibs)
let
# Collect all stdlibs from the stdlib directory
stdlibs = Symbol[]
for entry in readdir(Sys.STDLIB)
stdlib_path = joinpath(Sys.STDLIB, entry)
# Check if it's a directory with a Project.toml (indicates a stdlib)
if isdir(stdlib_path) && isfile(joinpath(stdlib_path, "Project.toml"))
push!(stdlibs, Symbol(entry))
end
end


maxlen = maximum(textwidth.(string.(stdlibs)); init=0)

m = Core.Module()
GC.@preserve m begin
print_time = @eval m (mod, t) -> (print(rpad(string(mod) * " ", $maxlen + 3, "─"));
Base.time_print(stdout, t * 10^9); println())

tot_time_stdlib = @elapsed for stdlib in stdlibs
tt = @elapsed Base.require(Base, stdlib)
print_time(stdlib, tt)
end

print_time("Stdlibs total", tot_time_stdlib)
end

# Clear global state
empty!(Core.ARGS)
empty!(Base.ARGS)
empty!(LOAD_PATH)
empty!(DEPOT_PATH)
end

empty!(Base.TOML_CACHE.d)
Base.TOML.reinit!(Base.TOML_CACHE.p, "")
@eval Base BUILDROOT = ""
@eval Sys begin
BINDIR = ""
STDLIB = ""
end
end
17 changes: 17 additions & 0 deletions pkgimage.mk
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ JULIAHOME := $(SRCDIR)
include $(JULIAHOME)/Make.inc
include $(JULIAHOME)/stdlib/stdlib.mk

# Import the fat sysimage setting from sysimage.mk
JULIA_BUILD_FAT_SYSIMAGE ?= 0

DEPOTDIR := $(build_prefix)/share/julia

# set some influential environment variables
Expand All @@ -25,13 +28,27 @@ $(DEPOTDIR)/compiled:
print-depot-path:
@$(call PRINT_JULIA, $(call spawn,$(JULIA_EXECUTABLE)) --startup-file=no -e '@show Base.DEPOT_PATH')

ifeq ($(JULIA_BUILD_FAT_SYSIMAGE),1)
# In fat mode, use sysbase and add stdlib to LOAD_PATH
$(BUILDDIR)/stdlib/release.image: $(build_private_libdir)/sysbase.$(SHLIB_EXT) $(JULIAHOME)/stdlib/Project.toml $(JULIAHOME)/stdlib/Manifest.toml $(INDEPENDENT_STDLIBS_SRCS) $(DEPOTDIR)/compiled
@$(call PRINT_JULIA, JULIA_CPU_TARGET="sysimage" $(call spawn,$(JULIA_EXECUTABLE)) --sysimage=$(build_private_libdir)/sysbase.$(SHLIB_EXT) --startup-file=no -e \
'Base.Precompilation.precompilepkgs(configs=[``=>Base.CacheFlags(debug_level=2, opt_level=3), ``=>Base.CacheFlags(check_bounds=1, debug_level=2, opt_level=3)])')
touch $@

$(BUILDDIR)/stdlib/debug.image: $(build_private_libdir)/sysbase-debug.$(SHLIB_EXT) $(JULIAHOME)/stdlib/Project.toml $(JULIAHOME)/stdlib/Manifest.toml $(INDEPENDENT_STDLIBS_SRCS) $(DEPOTDIR)/compiled
@$(call PRINT_JULIA, JULIA_CPU_TARGET="sysimage" $(call spawn,$(JULIA_EXECUTABLE)) --sysimage=$(build_private_libdir)/sysbase-debug.$(SHLIB_EXT) --startup-file=no -e \
'Base.Precompilation.precompilepkgs(configs=[``=>Base.CacheFlags(debug_level=2, opt_level=3), ``=>Base.CacheFlags(check_bounds=1, debug_level=2, opt_level=3)])')
touch $@
else
# In normal mode, use default sysimage
$(BUILDDIR)/stdlib/%.image: $(JULIAHOME)/stdlib/Project.toml $(JULIAHOME)/stdlib/Manifest.toml $(INDEPENDENT_STDLIBS_SRCS) $(DEPOTDIR)/compiled
@$(call PRINT_JULIA, JULIA_CPU_TARGET="sysimage" $(call spawn,$(JULIA_EXECUTABLE)) --startup-file=no -e \
'Base.Precompilation.precompilepkgs(configs=[``=>Base.CacheFlags(debug_level=2, opt_level=3), ``=>Base.CacheFlags(check_bounds=1, debug_level=2, opt_level=3)])')
touch $@

$(BUILDDIR)/stdlib/release.image: $(build_private_libdir)/sys.$(SHLIB_EXT)
$(BUILDDIR)/stdlib/debug.image: $(build_private_libdir)/sys-debug.$(SHLIB_EXT)
endif

clean:
rm -rf $(DEPOTDIR)/compiled
Expand Down
58 changes: 58 additions & 0 deletions sysimage.mk
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ JULIAHOME := $(SRCDIR)
include $(JULIAHOME)/Make.inc
include $(JULIAHOME)/stdlib/stdlib.mk

# Allow building a "fat" sysimage with all stdlibs included
# When enabled, this creates a two-stage build:
# 1. Build the normal sysimage (with default stdlibs)
# 2. Precompile all remaining stdlibs using the normal sysimage
# 3. Build the final fat sysimage by loading all precompiled stdlibs on top
# This allows stdlibs to run their __init__() during precompilation,
# which is necessary for stdlibs with complex precompile workloads.
# Enable by setting JULIA_BUILD_FAT_SYSIMAGE=1 in Make.user or environment
# TODO: Default to false
JULIA_BUILD_FAT_SYSIMAGE ?= 1

default: sysimg-$(JULIA_BUILD_MODE) # contains either "debug" or "release"
all: sysimg-release sysimg-debug
basecompiler-ji: $(build_private_libdir)/basecompiler.ji
Expand Down Expand Up @@ -147,7 +158,54 @@ $$(build_private_libdir)/sys$1-o.a $$(build_private_libdir)/sys$1-bc.a : $$(buil
.SECONDARY: $$(build_private_libdir)/sys$1-o.a $(build_private_libdir)/sys$1-bc.a # request Make to keep these files around
.SECONDARY: $$(build_private_libdir)/sysbase$1-o.a $(build_private_libdir)/sysbase$1-bc.a # request Make to keep these files around
endef

# Fat sysimage builder - builds on top of normal sysimage with all stdlibs
# Parameters: $1=suffix (e.g., -debug), $2=image name (release/debug), $3=opt flags (e.g., -O3), $4=executable
define sysimg_builder_fat
build_sysbase_$1 := $$(or $$(CROSS_BOOTSTRAP_SYSBASE),$$(build_private_libdir)/sysbase$1.$$(SHLIB_EXT))
$$(build_private_libdir)/sys$1-o.a : $$(build_sysbase_$1) $$(BUILDROOT)/stdlib/$2.image $$(JULIAHOME)/base/sysimg_stdlibs.jl
@$$(call PRINT_JULIA, cd $$(JULIAHOME)/base && \
if ! JULIA_BINDIR=$$(call cygpath_w,$$(build_bindir)) \
WINEPATH="$$(call cygpath_w,$$(build_bindir));$$$$WINEPATH" \
JULIA_LOAD_PATH='@stdlib' \
JULIA_PROJECT= \
JULIA_DEPOT_PATH=$$(call cygpath_w,$$(build_prefix)/share/julia) \
JULIA_NUM_THREADS=1 \
$$(call spawn, $4) $3 -C "$$(JULIA_CPU_TARGET)" $$(HEAPLIM) --output-o $$(call cygpath_w,$$@).tmp $$(JULIA_SYSIMG_BUILD_FLAGS) \
--startup-file=no --warn-overwrite=yes --depwarn=error --sysimage $$(call cygpath_w,$$<) sysimg_stdlibs.jl; then \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this missing generate_precompile.jl?

Seems that it never runs on this code path

Copy link
Member Author

@KristofferC KristofferC Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's already run on the base image, and we build on top of that so why would we want to run that again?

Copy link
Member

@topolarity topolarity Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this using build_sysbase_$1, which is not the final base sysimage? I thought that would be $$(build_private_libdir)/sys$1.$(SHLIB_EXT)

Copy link
Member Author

@KristofferC KristofferC Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, hm, I actually wasn't aware there were two such sysimages being created. So yes, I think you are right.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know the reason for not running generate_precompile.jl directly in the "base" sysimage?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit finicky, e.g.

Stdlibs total  ────────────────  5.223498 seconds
Collecting and executing precompile statements
└ Collect (Basic: Assertion failed: ((stream->type == UV_TCP || stream->type == UV_NAMED_PIPE || stream->type == UV_TTY) && "uv_write (unix) does not yet support other types of streams"), function uv__check_before_write, file src/unix/stream.c, line 1297.

[4079] signal 6: Abort trap: 6
in expression starting at /Users/kc/julia/contrib/generate_precompile.jl:10
__pthread_kill at /usr/lib/system/libsystem_kernel.dylib (unknown line)
Allocations: 5369068 (Pool: 5368927; Big: 141); GC: 8
/bin/sh: line 1:  4079 Abort trap: 6     

echo '*** This error is usually fixed by running `make clean`. If the error persists$$(COMMA) try `make cleanall`. ***'; \
false; \
fi )
@mv [email protected] $$@
.SECONDARY: $$(build_private_libdir)/sys$1-o.a # request Make to keep these files around
endef

ifeq ($(JULIA_BUILD_FAT_SYSIMAGE),1)
# For fat sysimage, build normal sysbase first, then precompile all stdlibs, then build fat sys
# Stage 1: Build normal sysbase (with default stdlibs)
# Stage 2: Precompile all stdlibs (handled by pkgimage.mk)
# Stage 3: Build final fat sys with all precompiled stdlibs

# Delegate to pkgimage.mk for building the precompilation stamps
$(BUILDROOT)/stdlib/release.image: $(build_private_libdir)/sysbase.$(SHLIB_EXT)
@$(MAKE) -C $(BUILDROOT) -f $(JULIAHOME)/pkgimage.mk release JULIA_BUILD_FAT_SYSIMAGE=$(JULIA_BUILD_FAT_SYSIMAGE)

$(BUILDROOT)/stdlib/debug.image: $(build_private_libdir)/sysbase-debug.$(SHLIB_EXT)
@$(MAKE) -C $(BUILDROOT) -f $(JULIAHOME)/pkgimage.mk debug JULIA_BUILD_FAT_SYSIMAGE=$(JULIA_BUILD_FAT_SYSIMAGE)

$(eval $(call base_builder,,-O1,$(JULIA_EXECUTABLE_release)))
$(eval $(call base_builder,-debug,-O0,$(JULIA_EXECUTABLE_debug)))
$(eval $(call sysimg_builder,,-O3,$(JULIA_EXECUTABLE_release)))
$(eval $(call sysimg_builder,-debug,-O0,$(JULIA_EXECUTABLE_debug)))
$(eval $(call sysimg_builder_fat,,release,-O3,$(JULIA_EXECUTABLE_release)))
$(eval $(call sysimg_builder_fat,-debug,debug,-O0,$(JULIA_EXECUTABLE_debug)))

# In fat mode, the normal sysimg targets build the fat sysimage
# The dependency on stdlib/.image is implicit through sys-o.a dependencies

else
$(eval $(call base_builder,,-O1,$(JULIA_EXECUTABLE_release)))
$(eval $(call base_builder,-debug,-O0,$(JULIA_EXECUTABLE_debug)))
$(eval $(call sysimg_builder,,-O3,$(JULIA_EXECUTABLE_release)))
$(eval $(call sysimg_builder,-debug,-O0,$(JULIA_EXECUTABLE_debug)))
endif
Loading