-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
Expand file tree
/
Copy pathprecompile.jl
More file actions
232 lines (217 loc) · 8.2 KB
/
precompile.jl
File metadata and controls
232 lines (217 loc) · 8.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# This file is a part of Julia. License is MIT: https://julialang.org/license
module Precompile
import ..REPL
# Ugly hack for our cache file to not have a dependency edge on the FakePTYs file.
Base._track_dependencies[] = false
try
Base.include(@__MODULE__, joinpath(Sys.BINDIR, "..", "share", "julia", "test", "testhelpers", "FakePTYs.jl"))
@Core.latestworld
import .FakePTYs: open_fake_pty
finally
Base._track_dependencies[] = true
end
function repl_workload()
# these are intentionally triggered
allowed_errors = [
"BoundsError: attempt to access 0-element Vector{Any} at index [1]",
"MethodError: no method matching f(::$Int, ::$Int)",
"Padding of type", # reinterpret docstring has ERROR examples
]
function check_errors(out)
str = String(out)
if occursin("ERROR:", str) && !any(occursin(e, str) for e in allowed_errors)
@error "Unexpected error (Review REPL precompilation with debug_output on):\n$str" exception=(
Base.PrecompilableError(), Base.backtrace())
exit(1)
end
end
## Debugging options
# View the code sent to the repl by setting this to `stdout`
debug_output = devnull # or stdout
CTRL_C = '\x03'
CTRL_D = '\x04'
CTRL_R = '\x12'
UP_ARROW = "\e[A"
DOWN_ARROW = "\e[B"
# This is notified as soon as the first prompt appears
repl_init_event = Base.Event()
repl_init_done_event = Base.Event()
atreplinit() do repl
# Main is closed so we can't evaluate in it, but atreplinit runs at
# a time that repl.mistate === nothing so REPL.activate fails. So do
# it async and wait for the first prompt to know its ready.
t = @async begin
wait(repl_init_event)
REPL.activate(REPL.Precompile; interactive_utils=false)
notify(repl_init_done_event)
end
Base.errormonitor(t)
end
repl_script = """
2+2
print("")
printstyled("a", "b")
display([1])
display([1 2; 3 4])
display("a string")
foo(x) = 1
@time @eval foo(1)
; pwd
$CTRL_C
$CTRL_R$CTRL_C#
? reinterpret
using Ra\t$CTRL_C
\\alpha\t$CTRL_C
\e[200~paste here ;)\e[201~"$CTRL_C
$UP_ARROW$DOWN_ARROW$CTRL_C
123\b\b\b$CTRL_C
\b\b$CTRL_C
f(x) = x03
f(1,2)
[][1]
Base.Iterators.minimum
cd("complete_path\t\t$CTRL_C
\x12?\x7f\e[A\e[B\t history\r
println("done")
"""
JULIA_PROMPT = "julia> "
# The help text for `reinterpret` has example `julia>` prompts in it,
# so use the longer prompt to avoid desychronization.
ACTIVATED_JULIA_PROMPT = "(REPL.Precompile) julia> "
PKG_PROMPT = "pkg> "
SHELL_PROMPT = "shell> "
HELP_PROMPT = "help?> "
tmphistfile = tempname()
write(tmphistfile, """
# time: 2020-10-31 13:16:39 AWST
# mode: julia
\tcos
# time: 2020-10-31 13:16:40 AWST
# mode: julia
\tsin
# time: 2020-11-01 02:19:36 AWST
# mode: help
\t?
""")
withenv("JULIA_HISTORY" => tmphistfile,
"JULIA_PROJECT" => nothing, # remove from environment
"JULIA_LOAD_PATH" => "@stdlib",
"JULIA_DEPOT_PATH" => Sys.iswindows() ? ";" : ":",
"TERM" => "",
"JULIA_FALLBACK_REPL" => "0" # Make sure REPL.jl is turned on
) do
rawpts, ptm = open_fake_pty()
pts = open(rawpts)::Base.TTY
if Sys.iswindows()
pts.ispty = false
else
# workaround libuv bug where it leaks pts
Base._fd(pts) == rawpts || Base.close_stdio(rawpts)
end
# Prepare a background process to copy output from `ptm` until `pts` is closed
output_copy = Base.BufferStream()
tee = @async try
while !eof(ptm)
l = readavailable(ptm)
write(debug_output, l)
write(output_copy, l)
end
write(debug_output, "\n#### EOF ####\n")
catch ex
if !(ex isa Base.IOError && ex.code == Base.UV_EIO)
rethrow() # ignore EIO on ptm after pts dies
end
finally
close(output_copy)
close(ptm)
end
Base.errormonitor(tee)
orig_stdin = stdin
orig_stdout = stdout
orig_stderr = stderr
repltask = @task try
Base.run_std_repl(REPL, false, :yes, true)
finally
redirect_stdin(isopen(orig_stdin) ? orig_stdin : devnull)
redirect_stdout(isopen(orig_stdout) ? orig_stdout : devnull)
close(pts)
end
Base.errormonitor(repltask)
try
Base.REPL_MODULE_REF[] = REPL
redirect_stdin(pts)
redirect_stdout(pts)
redirect_stderr(pts)
try
REPL.print_qualified_access_warning(Base.Iterators, Base, :minimum) # trigger the warning while stderr is suppressed
finally
redirect_stderr(isopen(orig_stderr) ? orig_stderr : devnull)
end
schedule(repltask)
# wait for the definitive prompt before start writing to the TTY
check_errors(readuntil(output_copy, JULIA_PROMPT, keep=true))
# Switch to the activated prompt
notify(repl_init_event)
wait(repl_init_done_event)
write(ptm, "\n")
# The prompt prints twice - once for the restatement of the input, once
# to indicate ready for the new prompt.
check_errors(readuntil(output_copy, ACTIVATED_JULIA_PROMPT, keep=true))
check_errors(readuntil(output_copy, ACTIVATED_JULIA_PROMPT, keep=true))
write(debug_output, "\n#### REPL STARTED ####\n")
# Input our script
precompile_lines = split(repl_script::String, '\n'; keepempty=false)
curr = 0
for l in precompile_lines
sleep(0.01) # try to let a bit of output accumulate before reading again
curr += 1
# push our input
write(debug_output, "\n#### inputting statement: ####\n$(repr(l))\n####\n")
# If the line ends with a CTRL_C, don't write an extra newline, which would
# cause a second empty prompt. Our code below expects one new prompt per
# input line and can race out of sync with the unexpected second line.
endswith(l, CTRL_C) ? write(ptm, l) : write(ptm, l, "\n")
check_errors(readuntil(output_copy, "\n"))
# wait for the next prompt-like to appear
check_errors(readuntil(output_copy, "\n"))
strbuf = ""
while !eof(output_copy)
strbuf *= String(readavailable(output_copy))
occursin(ACTIVATED_JULIA_PROMPT, strbuf) && break
occursin(PKG_PROMPT, strbuf) && break
occursin(SHELL_PROMPT, strbuf) && break
occursin(HELP_PROMPT, strbuf) && break
sleep(0.01) # try to let a bit of output accumulate before reading again
end
check_errors(strbuf)
end
write(debug_output, "\n#### COMPLETED - Closing REPL ####\n")
write(ptm, "$CTRL_D")
wait(repltask)
finally
redirect_stdin(isopen(orig_stdin) ? orig_stdin : devnull)
redirect_stdout(isopen(orig_stdout) ? orig_stdout : devnull)
close(pts)
end
wait(tee)
end
write(debug_output, "\n#### FINISHED ####\n")
nothing
end
let
if Base.generating_output() && Base.JLOptions().use_pkgimages != 0
# Bare-bones PrecompileTools.jl
# Do we need latestworld-if-toplevel here
ccall(:jl_tag_newly_inferred_enable, Cvoid, ())
try
repl_workload()
precompile(Tuple{typeof(Base.setindex!), Base.Dict{Any, Any}, Any, Char})
precompile(Tuple{typeof(Base.setindex!), Base.Dict{Any, Any}, Any, Int})
precompile(Tuple{typeof(Base.delete!), Base.Set{Any}, String})
precompile(Tuple{typeof(Base.:(==)), Char, String})
finally
ccall(:jl_tag_newly_inferred_disable, Cvoid, ())
end
end
end
end # Precompile