Skip to content

Commit 17a5754

Browse files
KristofferCStefanKarpinski
authored andcommitted
Revenge of the precompile statement generator (JuliaLang#28371)
Third times the charm?
1 parent b0bf91e commit 17a5754

12 files changed

+193
-98
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ define sysimg_builder
206206
$$(build_private_libdir)/sys$1-o.a $$(build_private_libdir)/sys$1-bc.a : $$(build_private_libdir)/sys$1-%.a : $$(build_private_libdir)/sys.ji
207207
@$$(call PRINT_JULIA, cd $$(JULIAHOME)/base && \
208208
if ! $$(call spawn,$3) $2 -C "$$(JULIA_CPU_TARGET)" --output-$$* $$(call cygpath_w,$$@).tmp $$(JULIA_SYSIMG_BUILD_FLAGS) \
209-
--startup-file=no --warn-overwrite=yes --sysimage $$(call cygpath_w,$$<) -e nothing; then \
209+
--startup-file=no --warn-overwrite=yes --sysimage $$(call cygpath_w,$$<) $$(call cygpath_w,$$(JULIAHOME)/contrib/generate_precompile.jl) $$(call cygpath_w,$$<); then \
210210
echo '*** This error is usually fixed by running `make clean`. If the error persists$$(COMMA) try `make cleanall`. ***'; \
211211
false; \
212212
fi )

base/options.jl

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct JLOptions
2626
warn_overwrite::Int8
2727
can_inline::Int8
2828
polly::Int8
29+
trace_compile::Int8
2930
fast_math::Int8
3031
worker::Int8
3132
cookie::Ptr{UInt8}

base/sysimg.jl

+1-3
Original file line numberDiff line numberDiff line change
@@ -922,11 +922,10 @@ Base.init_load_path() # want to be able to find external packages in userimg.jl
922922

923923
let
924924
tot_time_userimg = @elapsed (Base.isfile("userimg.jl") && Base.include(Main, "userimg.jl"))
925-
tot_time_precompile = Base.is_primary_base_module ? (@elapsed Base.include(Base, "precompile.jl")) : 0.0
926925

927926

928927
tot_time_base = (Base.end_base_include - Base.start_base_include) * 10.0^(-9)
929-
tot_time = tot_time_base + Base.tot_time_stdlib[] + tot_time_userimg + tot_time_precompile
928+
tot_time = tot_time_base + Base.tot_time_stdlib[] + tot_time_userimg
930929

931930
println("Sysimage built. Summary:")
932931
print("Total ─────── "); Base.time_print(tot_time * 10^9); print(" \n");
@@ -935,7 +934,6 @@ print("Stdlibs: ──── "); Base.time_print(Base.tot_time_stdlib[] * 10^9);
935934
if isfile("userimg.jl")
936935
print("Userimg: ──── "); Base.time_print(tot_time_userimg * 10^9); print(" "); show(IOContext(stdout, :compact=>true), (tot_time_userimg / tot_time) * 100); println("%")
937936
end
938-
print("Precompile: ─ "); Base.time_print(tot_time_precompile * 10^9); print(" "); show(IOContext(stdout, :compact=>true), (tot_time_precompile / tot_time) * 100); println("%")
939937
end
940938

941939
empty!(LOAD_PATH)

contrib/add_license_to_files.jl

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ const excludedirs = [
3030

3131
const skipfiles = [
3232
"../contrib/add_license_to_files.jl",
33-
"../contrib/fixup_precompile.jl",
3433
# files to check - already copyright
3534
# see: https://github.com/JuliaLang/julia/pull/11073#issuecomment-98099389
3635
"../base/special/trig.jl",

contrib/fixup_precompile.jl

-70
This file was deleted.

contrib/generate_precompile.jl

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
# Prevent this from being put into the Main namespace
4+
let
5+
M = Module()
6+
@eval M begin
7+
if !isdefined(Base, :uv_eventloop)
8+
Base.reinit_stdio()
9+
end
10+
Base.include(@__MODULE__, joinpath(Sys.BINDIR, "..", "share", "julia", "test", "testhelpers", "FakePTYs.jl"))
11+
import .FakePTYs: with_fake_pty
12+
13+
CTRL_C = '\x03'
14+
UP_ARROW = "\e[A"
15+
DOWN_ARROW = "\e[B"
16+
17+
precompile_script = """
18+
2+2
19+
print("")
20+
@time 1+1
21+
; pwd
22+
? reinterpret
23+
using Ra\t$CTRL_C
24+
\\alpha\t$CTRL_C
25+
\e[200~paste here ;)\e[201~"$CTRL_C
26+
$UP_ARROW$DOWN_ARROW$CTRL_C
27+
123\b\b\b$CTRL_C
28+
\b\b$CTRL_C
29+
f(x) = x03
30+
f(1,2)
31+
[][1]
32+
cd("complet_path\t\t$CTRL_C
33+
"""
34+
35+
julia_cmd() = (julia = joinpath(Sys.BINDIR, Base.julia_exename()); `$julia`)
36+
have_repl = haskey(Base.loaded_modules,
37+
Base.PkgId(Base.UUID("3fa0cd96-eef1-5676-8a61-b3b8758bbffb"), "REPL"))
38+
have_pkg = haskey(Base.loaded_modules,
39+
Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg"))
40+
41+
if have_pkg
42+
precompile_script *= """
43+
tmp = mktempdir()
44+
cd(tmp)
45+
touch("Project.toml")
46+
] activate .
47+
st
48+
$CTRL_C
49+
rm(tmp; recursive=true)
50+
"""
51+
end
52+
53+
function generate_precompile_statements()
54+
start_time = time()
55+
56+
# Precompile a package
57+
mktempdir() do prec_path
58+
push!(DEPOT_PATH, prec_path)
59+
push!(LOAD_PATH, prec_path)
60+
pkgname = "__PackagePrecompilationStatementModule"
61+
mkpath(joinpath(prec_path, pkgname, "src"))
62+
write(joinpath(prec_path, pkgname, "src", "$pkgname.jl"),
63+
"""
64+
module $pkgname
65+
end
66+
""")
67+
@eval using __PackagePrecompilationStatementModule
68+
empty!(LOAD_PATH)
69+
empty!(DEPOT_PATH)
70+
end
71+
72+
# Create a staging area where all the loaded packages are available
73+
PrecompileStagingArea = Module()
74+
for (_pkgid, _mod) in Base.loaded_modules
75+
if !(_pkgid.name in ("Main", "Core", "Base"))
76+
eval(PrecompileStagingArea, :($(Symbol(_mod)) = $_mod))
77+
end
78+
end
79+
80+
# TODO: Implement REPL replayer for Windows
81+
@static if !Sys.iswindows()
82+
print("Generating precompile statements...")
83+
sysimg = isempty(ARGS) ? joinpath(dirname(Sys.BINDIR), "lib", "julia", "sys.ji") : ARGS[1]
84+
85+
# Run a repl process and replay our script
86+
stdout_accumulator, stderr_accumulator = IOBuffer(), IOBuffer()
87+
with_fake_pty() do slave, master
88+
with_fake_pty() do slave_err, master_err
89+
done = false
90+
withenv("JULIA_HISTORY" => tempname(), "JULIA_PROJECT" => nothing,
91+
"TERM" => "") do
92+
p = run(`$(julia_cmd()) -O0 --trace-compile=yes --sysimage $sysimg
93+
--startup-file=no --color=yes`,
94+
slave, slave, slave_err; wait=false)
95+
readuntil(master, "julia>", keep=true)
96+
for (tty, accumulator) in (master => stdout_accumulator,
97+
master_err => stderr_accumulator)
98+
@async begin
99+
while true
100+
done && break
101+
write(accumulator, readavailable(tty))
102+
end
103+
end
104+
end
105+
if have_repl
106+
for l in split(precompile_script, '\n'; keepempty=false)
107+
write(master, l, '\n')
108+
end
109+
end
110+
write(master, "exit()\n")
111+
wait(p)
112+
done = true
113+
end
114+
end
115+
end
116+
117+
stderr_output = String(take!(stderr_accumulator))
118+
# println(stderr_output)
119+
# stdout_output = String(take!(stdout_accumulator))
120+
# println(stdout_output)
121+
122+
# Extract the precompile statements from stderr
123+
statements = Set{String}()
124+
for statement in split(stderr_output, '\n')
125+
m = match(r"(precompile\(Tuple{.*)", statement)
126+
m === nothing && continue
127+
statement = m.captures[1]
128+
occursin(r"Main.", statement) && continue
129+
push!(statements, statement)
130+
end
131+
132+
# Load the precompile statements
133+
statements_ordered = join(sort(collect(statements)), '\n')
134+
# println(statements_ordered)
135+
if have_repl
136+
# Seems like a reasonable number right now, adjust as needed
137+
@assert length(statements) > 700
138+
end
139+
140+
Base.include_string(PrecompileStagingArea, statements_ordered)
141+
print(" $(length(statements)) generated in ")
142+
Base.time_print((time() - start_time) * 10^9)
143+
println()
144+
end
145+
146+
# Fall back to explicit list on Windows, might as well include them
147+
# for everyone though
148+
Base.include(PrecompileStagingArea, "precompile_explicit.jl")
149+
150+
return
151+
end
152+
153+
generate_precompile_statements()
154+
155+
end # @eval
156+
end # let

base/precompile.jl contrib/precompile_explicit.jl

+4-15
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,11 @@
33
# Steps to regenerate this file:
44
# 1. Remove all `precompile` calls
55
# 2. Rebuild system image
6-
# 3. Enable TRACE_COMPILE in options.h and rebuild
7-
# 4. Run `./julia 2> precompiles.txt` and do various things.
8-
# 5. Run `./julia contrib/fixup_precompile.jl precompiles.txt to overwrite `precompile.jl`
9-
# or ./julia contrib/fixup_precompile.jl --merge precompiles.txt to merge into existing
10-
# `precompile.jl`
6+
# 3. Start julia with `--trace-compile=yes and do some stuff
7+
# 5. Run `grep -v '#[0-9]' precompiles.txt >> contrib/precompile_explicit.jl`
8+
# (filters out closures, which might have different generated names in different environments)
9+
# This list is only used on Windows, otherwise precompile statements are generated dynamically.
1110

12-
let
13-
PrecompileStagingArea = Module()
14-
for (_pkgid, _mod) in Base.loaded_modules
15-
if !(_pkgid.name in ("Main", "Core", "Base"))
16-
@eval PrecompileStagingArea $(Symbol(_mod)) = $_mod
17-
end
18-
end
19-
@eval PrecompileStagingArea begin
2011
precompile(Tuple{Type{Array{Base.StackTraces.StackFrame, 1}}, UndefInitializer, Int64})
2112
precompile(Tuple{Type{Array{Union{Nothing, String}, 1}}, UndefInitializer, Int64})
2213
precompile(Tuple{Type{Base.CoreLogging.LogState}, Logging.ConsoleLogger})
@@ -742,5 +733,3 @@ precompile(Tuple{typeof(REPL.setup_interface), REPL.LineEditREPL})
742733
precompile(Tuple{typeof(REPL.start_repl_backend), Base.Channel{Any}, Base.Channel{Any}})
743734
precompile(Tuple{typeof(Random.__init__)})
744735
precompile(Tuple{typeof(eval), Module, Expr})
745-
end
746-
end

src/gf.c

+6-6
Original file line numberDiff line numberDiff line change
@@ -1088,13 +1088,13 @@ static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype
10881088
if (entry != NULL) {
10891089
jl_method_t *m = entry->func.method;
10901090
if (!jl_has_call_ambiguities((jl_value_t*)tt, m)) {
1091-
#ifdef TRACE_COMPILE
1092-
if (!jl_has_free_typevars((jl_value_t*)tt)) {
1093-
jl_printf(JL_STDERR, "precompile(");
1094-
jl_static_show(JL_STDERR, (jl_value_t*)tt);
1095-
jl_printf(JL_STDERR, ")\n");
1091+
if (jl_options.trace_compile) {
1092+
if (!jl_has_free_typevars((jl_value_t*)tt)) {
1093+
jl_printf(JL_STDERR, "precompile(");
1094+
jl_static_show(JL_STDERR, (jl_value_t*)tt);
1095+
jl_printf(JL_STDERR, ")\n");
1096+
}
10961097
}
1097-
#endif
10981098
if (!mt_cache) {
10991099
intptr_t nspec = (mt == jl_type_type_mt ? m->nargs + 1 : mt->max_args + 2);
11001100
jl_compilation_sig(tt, env, m, nspec, &newparams);

src/jloptions.c

+16
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ jl_options_t jl_options = { 0, // quiet
5555
0, // method overwrite warning
5656
1, // can_inline
5757
JL_OPTIONS_POLLY_ON, // polly
58+
#ifdef TRACE_COMPILE
59+
1, // trace_compile
60+
#else
61+
0, // trace_compile
62+
#endif
5863
JL_OPTIONS_FAST_MATH_DEFAULT,
5964
0, // worker
6065
NULL, // cookie
@@ -159,6 +164,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
159164
opt_warn_overwrite,
160165
opt_inline,
161166
opt_polly,
167+
opt_trace_compile,
162168
opt_math_mode,
163169
opt_worker,
164170
opt_bind_to,
@@ -215,6 +221,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
215221
{ "warn-overwrite", required_argument, 0, opt_warn_overwrite },
216222
{ "inline", required_argument, 0, opt_inline },
217223
{ "polly", required_argument, 0, opt_polly },
224+
{ "trace-compile", required_argument, 0, opt_trace_compile },
218225
{ "math-mode", required_argument, 0, opt_math_mode },
219226
{ "handle-signals", required_argument, 0, opt_handle_signals },
220227
// hidden command line options
@@ -567,6 +574,15 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
567574
jl_errorf("julia: invalid argument to --polly (%s)", optarg);
568575
}
569576
break;
577+
case opt_trace_compile:
578+
if (!strcmp(optarg,"yes"))
579+
jl_options.trace_compile = 1;
580+
else if (!strcmp(optarg,"no"))
581+
jl_options.trace_compile = 0;
582+
else {
583+
jl_errorf("julia: invalid argument to --trace-compile (%s)", optarg);
584+
}
585+
break;
570586
case opt_math_mode:
571587
if (!strcmp(optarg,"ieee"))
572588
jl_options.fast_math = JL_OPTIONS_FAST_MATH_OFF;

src/julia.h

+1
Original file line numberDiff line numberDiff line change
@@ -1799,6 +1799,7 @@ typedef struct {
17991799
int8_t warn_overwrite;
18001800
int8_t can_inline;
18011801
int8_t polly;
1802+
int8_t trace_compile;
18021803
int8_t fast_math;
18031804
int8_t worker;
18041805
const char *cookie;

stdlib/REPL/src/REPL.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ struct REPLBackendRef
183183
response_channel::Channel
184184
end
185185

186-
function run_repl(repl::AbstractREPL, consumer::Function = x->nothing)
186+
function run_repl(repl::AbstractREPL, @nospecialize(consumer = x -> nothing))
187187
repl_channel = Channel(1)
188188
response_channel = Channel(1)
189189
backend = start_repl_backend(repl_channel, response_channel)

test/precompile.jl

+6-1
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,12 @@ try
235235
:Future, :Libdl, :LinearAlgebra, :Logging, :Mmap, :Printf,
236236
:Profile, :Random, :Serialization, :SharedArrays, :SparseArrays, :SuiteSparse, :Test,
237237
:Unicode, :REPL, :InteractiveUtils, :OldPkg, :Pkg, :LibGit2, :SHA, :UUIDs, :Sockets,
238-
:Statistics, ]))
238+
:Statistics, ]),
239+
# Plus precompilation module generated at build time
240+
let id = Base.PkgId("__PackagePrecompilationStatementModule")
241+
Dict(id => Base.module_build_id(Base.root_module(id)))
242+
end
243+
)
239244
@test discard_module.(deps) == deps1
240245

241246
@test current_task()(0x01, 0x4000, 0x30031234) == 2

0 commit comments

Comments
 (0)