From 943c89b1c5d2e62f97cda1b761a89ca78278b338 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Sat, 24 Oct 2026 16:13:44 +0900 Subject: [PATCH] Run the precompile workload with PrecompileTools The precompile workload ran directly at the top level of `src/packagedef.jl`, guarded by `jl_generating_output`. Its variables, such as `frame` and `edges`, were left as globals of LoweredCodeUtils and saved in the package image. Code that the workload inferred for methods of other modules through runtime dispatch was not cached, since no backedge chain connects it to LoweredCodeUtils, and the workload could not be skipped. This change moves the workload to `src/precompile.jl` and runs it with PrecompileTools. The workload lowers its input code in `@setup_workload` and calls the LoweredCodeUtils APIs in `@compile_workload`, which also caches the code inferred through runtime dispatch. Both blocks are `let` blocks so that their variables stay local, and users can skip the workload with the `precompile_workload` preference. As in JuliaInterpreter, `src/precompile.jl` is included from `src/LoweredCodeUtils.jl`, so `src/packagedef.jl` still loads without PrecompileTools. The compat bound of PrecompileTools is 1.2.1, since PrecompileTools 1.3 and later require Julia 1.12 while LoweredCodeUtils supports Julia 1.10. Co-Authored-By: Claude Opus 5.5 --- Project.toml | 2 ++ src/LoweredCodeUtils.jl | 6 +++++- src/packagedef.jl | 33 --------------------------------- src/precompile.jl | 30 ++++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 34 deletions(-) create mode 100644 src/precompile.jl diff --git a/Project.toml b/Project.toml index 1208605..4e284c4 100644 --- a/Project.toml +++ b/Project.toml @@ -10,9 +10,11 @@ projects = ["docs", "test"] CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" Compiler = "807dbc54-b67e-4c79-8afb-eafe4df6f2e1" JuliaInterpreter = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" +PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" [compat] CodeTracking = "3" Compiler = "0.1" JuliaInterpreter = "0.12" +PrecompileTools = "1.2.1" julia = "1.10" diff --git a/src/LoweredCodeUtils.jl b/src/LoweredCodeUtils.jl index ee907e1..5213eff 100644 --- a/src/LoweredCodeUtils.jl +++ b/src/LoweredCodeUtils.jl @@ -5,7 +5,9 @@ module LoweredCodeUtils # a Julia base or stdlib package are located in this file here. # Nothing else should appear in this file here, apart from # the `include("packagedef.jl")` statement, which loads what -# we would normally consider the bulk of the package code. +# we would normally consider the bulk of the package code, +# and the precompile workload in precompile.jl, which uses +# PrecompileTools. # This somewhat unusual structure is in place to support # the VS Code extension integration. @@ -19,7 +21,9 @@ using JuliaInterpreter: is_quotenode_egal, is_return, linetable, lookup, lookup_return, moduleof, next_until!, nstatements, pc_expr, step_expr!, whichtt using Compiler: Compiler as CC +using PrecompileTools: PrecompileTools include("packagedef.jl") +include("precompile.jl") end # module diff --git a/src/packagedef.jl b/src/packagedef.jl index dc157e7..55a7e82 100644 --- a/src/packagedef.jl +++ b/src/packagedef.jl @@ -28,36 +28,3 @@ export CodeEdges, SelectiveEvalController, SelectiveInterpreter, include("utils.jl") include("signatures.jl") include("codeedges.jl") - -# precompilation - -if ccall(:jl_generating_output, Cint, ()) == 1 - ex = :(f(x; color::Symbol=:green) = 2x) - lwr = Meta.lower(@__MODULE__, ex) - frame = Frame(@__MODULE__, lwr.args[1]) - rename_framemethods!(frame) - ex = quote - s = 0 - k = 5 - for i = 1:3 - global s, k - s += rand(1:5) - k += i - end - end - lwr = Meta.lower(@__MODULE__, ex) - src = lwr.args[1] - edges = CodeEdges(@__MODULE__, src) - isrequired = lines_required(GlobalRef(@__MODULE__, :s), src, edges) - lines_required(GlobalRef(@__MODULE__, :s), src, edges; norequire=()) - lines_required(GlobalRef(@__MODULE__, :s), src, edges; norequire=exclude_named_typedefs(src)) - for isreq in (isrequired, convert(Vector{Bool}, isrequired)) - lines_required!(isreq, src, edges; norequire=()) - lines_required!(isreq, src, edges; norequire=exclude_named_typedefs(src)) - end - frame = Frame(@__MODULE__, src) - # selective_eval_fromstart!(frame, isrequired, true) - precompile(selective_eval_fromstart!, (typeof(frame), typeof(isrequired), Bool)) # can't @eval during precompilation - print_with_code(Base.inferencebarrier(devnull)::IO, src, edges) - print_with_code(Base.inferencebarrier(devnull)::IO, src, isrequired) -end diff --git a/src/precompile.jl b/src/precompile.jl new file mode 100644 index 0000000..e08f152 --- /dev/null +++ b/src/precompile.jl @@ -0,0 +1,30 @@ +PrecompileTools.@setup_workload let + kwsrc = Meta.lower(@__MODULE__, :(f(x; color::Symbol=:green) = 2x)).args[1] + ex = quote + s = 0 + k = 5 + for i = 1:3 + global s, k + s += rand(1:5) + k += i + end + end + src = Meta.lower(@__MODULE__, ex).args[1] + PrecompileTools.@compile_workload let + frame = Frame(@__MODULE__, kwsrc) + rename_framemethods!(frame) + edges = CodeEdges(@__MODULE__, src) + isrequired = lines_required(GlobalRef(@__MODULE__, :s), src, edges) + lines_required(GlobalRef(@__MODULE__, :s), src, edges; norequire=()) + lines_required(GlobalRef(@__MODULE__, :s), src, edges; norequire=exclude_named_typedefs(src)) + for isreq in (isrequired, convert(Vector{Bool}, isrequired)) + lines_required!(isreq, src, edges; norequire=()) + lines_required!(isreq, src, edges; norequire=exclude_named_typedefs(src)) + end + frame = Frame(@__MODULE__, src) + # selective_eval_fromstart!(frame, isrequired, true) + precompile(selective_eval_fromstart!, (typeof(frame), typeof(isrequired), Bool)) # can't @eval during precompilation + print_with_code(Base.inferencebarrier(devnull)::IO, src, edges) + print_with_code(Base.inferencebarrier(devnull)::IO, src, isrequired) + end +end