Skip to content

Commit be9e6e0

Browse files
committed
add --trim analyzer
This --trim specialized analyzer was previously managed by the JETLS repository, but since it can be used without JETLS, it should be managed as an independent analyzer. While it's still uncertain whether this will be integrated into JET proper, at least in the current experimental stage, it's more convenient to make it usable by simply installing JET from this branch. Therefore, this is being submitted as a draft PR for now.
1 parent 7e74b1f commit be9e6e0

File tree

3 files changed

+300
-0
lines changed

3 files changed

+300
-0
lines changed

src/JET.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const exports = Set{Symbol}((
2020
:watch_file,
2121
# optanalyzer
2222
Symbol("@report_opt"), :report_opt, Symbol("@test_opt"), :test_opt,
23+
# trimanalyzer
24+
Symbol("@report_trim"), :report_trim, Symbol("@test_trim"), :test_trim,
2325
# configurations
2426
:LastFrameModule, :AnyFrameModule
2527
))

src/JETBase.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,7 @@ reexport_as_api!(JETInterface,
12421242

12431243
include("analyzers/jetanalyzer.jl")
12441244
include("analyzers/optanalyzer.jl")
1245+
include("analyzers/trimanalyzer.jl")
12451246

12461247
# NOTE Use the fixed world here to make `JETAnalyzer`/`OptAnalyzer` robust against potential invalidations
12471248
const JET_TYPEINF_WORLD = Ref{UInt}(typemax(UInt))

src/analyzers/trimanalyzer.jl

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

0 commit comments

Comments
 (0)