Skip to content

Commit c120bca

Browse files
committed
wip: implement --trim failure analyzer
1 parent caed8ec commit c120bca

4 files changed

Lines changed: 562 additions & 0 deletions

File tree

TrimAnalyzer/Project.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name = "TrimAnalyzer"
2+
uuid = "db0f0d6f-36c4-4e19-a1b7-72446e3087f7"
3+
version = "0.1.0"
4+
authors = ["Shuhei Kadowaki <aviatesk@gmail.com>"]
5+
6+
[deps]
7+
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
8+
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
9+
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
10+
LSP = "880dcf91-6fde-4251-87fc-bfd84012291a"
11+
12+
[sources]
13+
JET = {rev = "master", url = "https://github.com/aviatesk/JET.jl"}
14+
LSP = {path = "/Users/aviatesk/julia/packages/JETLS/LSP"}
15+
16+
[compat]
17+
InteractiveUtils = "1.11.0"
18+
JET = "0.10.6"
19+
JSON3 = "1.14.3"
20+
LSP = "0.1"
21+
22+
[apps.report-trim]

TrimAnalyzer/src/TrimAnalyzer.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module TrimAnalyzer
2+
3+
export report_trim, @report_trim
4+
5+
include("TrimAnalyzerImpl.jl")
6+
using .TrimAnalyzerImpl: TrimAnalyzerImpl
7+
8+
# Entry points
9+
# ============
10+
11+
using InteractiveUtils: InteractiveUtils
12+
using JET: JET
13+
14+
function report_trim(args...; jetconfigs...)
15+
analyzer = TrimAnalyzerImpl.TrimAnalyzer(; jetconfigs...)
16+
return JET.analyze_and_report_call!(analyzer, args...; jetconfigs...)
17+
end
18+
macro report_trim(ex0...)
19+
return InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :report_trim, ex0)
20+
end
21+
22+
include("app.jl")
23+
using .TrimAnalyzerApp: main
24+
25+
end # module TrimAnalyzer
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
module TrimAnalyzerImpl
2+
3+
using Core.IR
4+
using JET.JETInterface
5+
using JET: JET, CC
6+
7+
struct TrimAnalyzer <: ToplevelAbstractAnalyzer
8+
state::AnalyzerState
9+
analysis_token::AnalysisToken
10+
method_table::CC.CachedMethodTable{CC.OverlayMethodTable}
11+
function TrimAnalyzer(state::AnalyzerState, analysis_token::AnalysisToken)
12+
method_table = CC.CachedMethodTable(CC.OverlayMethodTable(state.world, TRIM_METHOD_TABLE))
13+
return new(state, analysis_token, method_table)
14+
end
15+
end
16+
function TrimAnalyzer(state::AnalyzerState)
17+
analysis_cache_key = JET.compute_hash(state.inf_params)
18+
analysis_token = get!(AnalysisToken, TRIM_ANALYZER_CACHE, analysis_cache_key)
19+
return TrimAnalyzer(state, analysis_token)
20+
end
21+
22+
# AbstractInterpreter API
23+
# =======================
24+
25+
# TrimAnalyzer does not need any sources, so discard them always
26+
CC.method_table(analyzer::TrimAnalyzer) = analyzer.method_table
27+
28+
# AbstractAnalyzer API
29+
# ====================
30+
31+
JETInterface.AnalyzerState(analyzer::TrimAnalyzer) = analyzer.state
32+
function JETInterface.AbstractAnalyzer(analyzer::TrimAnalyzer, state::AnalyzerState)
33+
return TrimAnalyzer(state, analyzer.analysis_token)
34+
end
35+
JETInterface.AnalysisToken(analyzer::TrimAnalyzer) = analyzer.analysis_token
36+
37+
const TRIM_ANALYZER_CACHE = Dict{UInt, AnalysisToken}()
38+
39+
# TRIM_METHOD_TABLE
40+
# ===============
41+
42+
using Base.Experimental: @overlay
43+
Base.Experimental.@MethodTable TRIM_METHOD_TABLE
44+
45+
@eval @overlay TRIM_METHOD_TABLE Core.DomainError(@nospecialize(val), @nospecialize(msg::AbstractString)) = (@noinline; $(Expr(:new, :DomainError, :val, :msg)))
46+
47+
@overlay TRIM_METHOD_TABLE (f::Base.RedirectStdStream)(io::Core.CoreSTDOUT) = Base._redirect_io_global(io, f.unix_fd)
48+
49+
@overlay TRIM_METHOD_TABLE Base.depwarn(msg, funcsym; force::Bool=false) = nothing
50+
@overlay TRIM_METHOD_TABLE Base._assert_tostring(msg) = ""
51+
@overlay TRIM_METHOD_TABLE Base.reinit_stdio() = nothing
52+
@overlay TRIM_METHOD_TABLE Base.JuliaSyntax.enable_in_core!() = nothing
53+
@overlay TRIM_METHOD_TABLE Base.init_active_project() = Base.ACTIVE_PROJECT[] = nothing
54+
@overlay TRIM_METHOD_TABLE Base.set_active_project(projfile::Union{AbstractString,Nothing}) = Base.ACTIVE_PROJECT[] = projfile
55+
@overlay TRIM_METHOD_TABLE Base.disable_library_threading() = nothing
56+
@overlay TRIM_METHOD_TABLE Base.start_profile_listener() = nothing
57+
@overlay TRIM_METHOD_TABLE Base.invokelatest(f, args...; kwargs...) = f(args...; kwargs...)
58+
@overlay TRIM_METHOD_TABLE function Base.sprint(f::F, args::Vararg{Any,N}; context=nothing, sizehint::Integer=0) where {F<:Function,N}
59+
s = IOBuffer(sizehint=sizehint)
60+
if context isa Tuple
61+
f(IOContext(s, context...), args...)
62+
elseif context !== nothing
63+
f(IOContext(s, context), args...)
64+
else
65+
f(s, args...)
66+
end
67+
String(Base._unsafe_take!(s))
68+
end
69+
function show_typeish(io::IO, @nospecialize(T))
70+
if T isa Type
71+
show(io, T)
72+
elseif T isa TypeVar
73+
print(io, (T::TypeVar).name)
74+
else
75+
print(io, "?")
76+
end
77+
end
78+
@overlay TRIM_METHOD_TABLE function Base.show(io::IO, T::Type)
79+
if T isa DataType
80+
print(io, T.name.name)
81+
if T !== T.name.wrapper && length(T.parameters) > 0
82+
print(io, "{")
83+
first = true
84+
for p in T.parameters
85+
if !first
86+
print(io, ", ")
87+
end
88+
first = false
89+
if p isa Int
90+
show(io, p)
91+
elseif p isa Type
92+
show(io, p)
93+
elseif p isa Symbol
94+
print(io, ":")
95+
print(io, p)
96+
elseif p isa TypeVar
97+
print(io, p.name)
98+
else
99+
print(io, "?")
100+
end
101+
end
102+
print(io, "}")
103+
end
104+
elseif T isa Union
105+
print(io, "Union{")
106+
show_typeish(io, T.a)
107+
print(io, ", ")
108+
show_typeish(io, T.b)
109+
print(io, "}")
110+
elseif T isa UnionAll
111+
print(io, T.body::Type)
112+
print(io, " where ")
113+
print(io, T.var.name)
114+
end
115+
end
116+
@overlay TRIM_METHOD_TABLE Base.show_type_name(io::IO, tn::Core.TypeName) = print(io, tn.name)
117+
118+
@overlay TRIM_METHOD_TABLE Base.mapreduce(f::F, op::F2, A::Base.AbstractArrayOrBroadcasted; dims=:, init=Base._InitialValue()) where {F, F2} =
119+
Base._mapreduce_dim(f, op, init, A, dims)
120+
@overlay TRIM_METHOD_TABLE Base.mapreduce(f::F, op::F2, A::Base.AbstractArrayOrBroadcasted...; kw...) where {F, F2} =
121+
reduce(op, map(f, A...); kw...)
122+
123+
@overlay TRIM_METHOD_TABLE Base._mapreduce_dim(f::F, op::F2, nt, A::Base.AbstractArrayOrBroadcasted, ::Colon) where {F, F2} =
124+
Base.mapfoldl_impl(f, op, nt, A)
125+
126+
@overlay TRIM_METHOD_TABLE Base._mapreduce_dim(f::F, op::F2, ::Base._InitialValue, A::Base.AbstractArrayOrBroadcasted, ::Colon) where {F, F2} =
127+
Base._mapreduce(f, op, IndexStyle(A), A)
128+
129+
@overlay TRIM_METHOD_TABLE Base._mapreduce_dim(f::F, op::F2, nt, A::Base.AbstractArrayOrBroadcasted, dims) where {F, F2} =
130+
Base.mapreducedim!(f, op, Base.reducedim_initarray(A, dims, nt), A)
131+
132+
@overlay TRIM_METHOD_TABLE Base._mapreduce_dim(f::F, op::F2, ::Base._InitialValue, A::Base.AbstractArrayOrBroadcasted, dims) where {F,F2} =
133+
Base.mapreducedim!(f, op, Base.reducedim_init(f, op, A, dims), A)
134+
135+
@overlay TRIM_METHOD_TABLE Base.mapreduce_empty_iter(f::F, op::F2, itr, ItrEltype) where {F, F2} =
136+
Base.reduce_empty_iter(Base.MappingRF(f, op), itr, ItrEltype)
137+
@overlay TRIM_METHOD_TABLE Base.mapreduce_first(f::F, op::F2, x) where {F,F2} = Base.reduce_first(op, f(x))
138+
139+
@overlay TRIM_METHOD_TABLE Base._mapreduce(f::F, op::F2, A::Base.AbstractArrayOrBroadcasted) where {F,F2} = Base._mapreduce(f, op, Base.IndexStyle(A), A)
140+
@overlay TRIM_METHOD_TABLE Base.mapreduce_empty(::typeof(identity), op::F, T) where {F} = Base.reduce_empty(op, T)
141+
@overlay TRIM_METHOD_TABLE Base.mapreduce_empty(::typeof(abs), op::F, T) where {F} = abs(Base.reduce_empty(op, T))
142+
@overlay TRIM_METHOD_TABLE Base.mapreduce_empty(::typeof(abs2), op::F, T) where {F} = abs2(Base.reduce_empty(op, T))
143+
144+
@overlay TRIM_METHOD_TABLE Base.Sys.__init_build() = nothing
145+
146+
# function __init__()
147+
# try
148+
# ccall((:__gmp_set_memory_functions, libgmp), Cvoid,
149+
# (Ptr{Cvoid},Ptr{Cvoid},Ptr{Cvoid}),
150+
# cglobal(:jl_gc_counted_malloc),
151+
# cglobal(:jl_gc_counted_realloc_with_old_size),
152+
# cglobal(:jl_gc_counted_free_with_size))
153+
# ZERO.alloc, ZERO.size, ZERO.d = 0, 0, C_NULL
154+
# ONE.alloc, ONE.size, ONE.d = 1, 1, pointer(_ONE)
155+
# catch ex
156+
# Base.showerror_nostdio(ex, "WARNING: Error during initialization of module GMP")
157+
# end
158+
# # This only works with a patched version of GMP, ignore otherwise
159+
# try
160+
# ccall((:__gmp_set_alloc_overflow_function, libgmp), Cvoid,
161+
# (Ptr{Cvoid},),
162+
# cglobal(:jl_throw_out_of_memory_error))
163+
# ALLOC_OVERFLOW_FUNCTION[] = true
164+
# catch ex
165+
# # ErrorException("ccall: could not find function...")
166+
# if typeof(ex) != ErrorException
167+
# rethrow()
168+
# end
169+
# end
170+
# end
171+
172+
@overlay TRIM_METHOD_TABLE Base.Sort.issorted(itr;
173+
lt::T=isless, by::F=identity, rev::Union{Bool,Nothing}=nothing, order::Base.Sort.Ordering=Forward) where {T,F} =
174+
Base.Sort.issorted(itr, Base.Sort.ord(lt,by,rev,order))
175+
176+
@overlay TRIM_METHOD_TABLE function Base.TOML.try_return_datetime(p, year, month, day, h, m, s, ms)
177+
return Base.TOML.DateTime(year, month, day, h, m, s, ms)
178+
end
179+
@overlay TRIM_METHOD_TABLE function Base.TOML.try_return_date(p, year, month, day)
180+
return Base.TOML.Date(year, month, day)
181+
end
182+
@overlay TRIM_METHOD_TABLE function Base.TOML.parse_local_time(l::Base.TOML.Parser)
183+
h = Base.TOML.@try Base.TOML.parse_int(l, false)
184+
h in 0:23 || return Base.TOML.ParserError(Base.TOML.ErrParsingDateTime)
185+
_, m, s, ms = Base.TOML.@try Base.TOML._parse_local_time(l, true)
186+
# TODO: Could potentially parse greater accuracy for the
187+
# fractional seconds here.
188+
return Base.TOML.try_return_time(l, h, m, s, ms)
189+
end
190+
@overlay TRIM_METHOD_TABLE function Base.TOML.try_return_time(p, h, m, s, ms)
191+
return Base.TOML.Time(h, m, s, ms)
192+
end
193+
194+
# analysis injections
195+
# ===================
196+
197+
function CC.abstract_call_gf_by_type(analyzer::TrimAnalyzer,
198+
@nospecialize(func), arginfo::CC.ArgInfo, si::CC.StmtInfo, @nospecialize(atype), sv::CC.InferenceState,
199+
max_methods::Int)
200+
ret = @invoke CC.abstract_call_gf_by_type(analyzer::ToplevelAbstractAnalyzer,
201+
func::Any, arginfo::CC.ArgInfo, si::CC.StmtInfo, atype::Any, sv::CC.InferenceState, max_methods::Int)
202+
atype′ = Ref{Any}(atype)
203+
function after_abstract_call_gf_by_type(analyzer′::TrimAnalyzer, sv′::CC.InferenceState)
204+
ret′ = ret[]
205+
report_dispatch_error!(analyzer′, sv′, ret′, atype′[])
206+
return true
207+
end
208+
if isready(ret)
209+
after_abstract_call_gf_by_type(analyzer, sv)
210+
else
211+
push!(sv.tasks, after_abstract_call_gf_by_type)
212+
end
213+
return ret
214+
end
215+
216+
# analysis
217+
# ========
218+
219+
# DispatchErrorReport
220+
# -------------------
221+
222+
@jetreport struct DispatchErrorReport <: InferenceErrorReport
223+
@nospecialize t # ::Union{Type, Vector{Type}}
224+
end
225+
JETInterface.print_report_message(io::IO, report::DispatchErrorReport) = print(io, "Unresolved call found")
226+
227+
function is_inlineable(analyzer::TrimAnalyzer, match, info)
228+
mi = CC.specialize_method(match; preexisting=true)
229+
isnothing(mi) && return false
230+
ci = get(CC.code_cache(analyzer), mi, nothing)
231+
isnothing(ci) && return false
232+
src = @atomic :monotonic ci.inferred
233+
return CC.src_inlining_policy(analyzer, src, info, zero(UInt32))
234+
end
235+
236+
function report_dispatch_error!(analyzer::TrimAnalyzer, sv::CC.InferenceState, call::CC.CallMeta, @nospecialize(atype))
237+
info = call.info
238+
if info === CC.NoCallInfo()
239+
report = DispatchErrorReport(sv, atype)
240+
add_new_report!(analyzer, sv.result, report)
241+
else
242+
if info isa CC.ConstCallInfo
243+
info = info.call
244+
end
245+
if info isa CC.MethodMatchInfo
246+
for match in info.results
247+
if (isnothing(CC.get_compileable_sig(match.method, match.spec_types, match.sparams)) &&
248+
!is_inlineable(analyzer, match, info))
249+
report = DispatchErrorReport(sv, atype)
250+
add_new_report!(analyzer, sv.result, report)
251+
end
252+
end
253+
else
254+
@assert info isa CC.UnionSplitInfo
255+
for info in info.split
256+
for match in info.results
257+
if (isnothing(CC.get_compileable_sig(match.method, match.spec_types, match.sparams)) &&
258+
!is_inlineable(analyzer, match, info))
259+
report = DispatchErrorReport(sv, atype)
260+
add_new_report!(analyzer, sv.result, report)
261+
end
262+
end
263+
end
264+
end
265+
end
266+
return false
267+
end
268+
269+
# Constructor
270+
# ===========
271+
272+
# the entry constructor
273+
function TrimAnalyzer(world::UInt = Base.get_world_counter(); jetconfigs...)
274+
jetconfigs = JET.kwargs_dict(jetconfigs)
275+
jetconfigs[:max_methods] = 3
276+
# jetconfigs[:assume_bindings_static] = true # TODO
277+
state = AnalyzerState(world; jetconfigs...)
278+
return TrimAnalyzer(state)
279+
end
280+
281+
JETInterface.valid_configurations(::TrimAnalyzer) = JET.GENERAL_CONFIGURATIONS
282+
283+
end # module TrimAnalyzerImpl

0 commit comments

Comments
 (0)