Skip to content

Commit e91e27b

Browse files
authored
Merge pull request #4 from aviatesk/avi/newcompilerinterface
use AbstractInterpreter interface
2 parents ee7b794 + 4197131 commit e91e27b

18 files changed

+847
-275
lines changed

README.md

Lines changed: 110 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,84 +2,134 @@
22

33
TypeProfiler.jl employs Julia's type inference for bug reports.
44

5+
!!! note
6+
TypeProfiler.jl needs Julia v1.6, especially [this commit](https://github.com/JuliaLang/julia/commit/d5cf73ffffbab40ae06cc1ec99cac9d8e3d2b6a2);
7+
as such I recommend you give a try on this package with Julia built from source after the commit.
8+
59
```julia
10+
julia> using TypeProfiler
11+
612
julia> fib(n) = n 2 ? n : fib(n - 1) + fib(n - 2)
713
fib (generic function with 1 method)
814

915
julia> @profile_call fib(100000) # computation explodes otherwise
1016
No errors !
11-
Int64
17+
Int64
1218

1319
julia> @profile_call fib(100000.)
1420
No errors !
15-
Float64
21+
Float64
1622

1723
julia> @profile_call fib("100000") # report errors
18-
1 errors found
19-
┌ @ none:1 within `fib`
20-
│┌ @ operators.jl:317 <=(x, y) = (x < y) | (x == y)
21-
││┌ @ operators.jl:268 <(x, y) = isless(x, y)
22-
│││ no method matching signature: isless(::String, ::Int64)
24+
1 error found
25+
┌ @ none:1 within `fib(n) in Main at none:1`
26+
│┌ @ operators.jl:326 <=(x, y) = (x < y) | (x == y)
27+
││┌ @ operators.jl:277 <(x, y) = isless(x, y)
28+
│││ no matching method found for signature: isless(::String, ::Int64)
2329
││└
24-
TypeProfiler.Unknown
30+
Union{}
31+
32+
julia> @profile_call sum("julia")
33+
2 errors found
34+
┌ @ reduce.jl:528 sum(a; kw...) = sum(identity, a; kw...)
35+
│┌ @ reduce.jl:528 sum(a; kw...) = sum(identity, a; kw...)
36+
││┌ @ reduce.jl:501 sum(f, a; kw...) = mapreduce(f, add_sum, a; kw...)
37+
│││┌ @ reduce.jl:501 sum(f, a; kw...) = mapreduce(f, add_sum, a; kw...)
38+
││││┌ @ reduce.jl:287 mapreduce(f, op, itr; kw...) = mapfoldl(f, op, itr; kw...)
39+
│││││┌ @ reduce.jl:287 mapreduce(f, op, itr; kw...) = mapfoldl(f, op, itr; kw...)
40+
││││││┌ @ reduce.jl:160 mapfoldl(f, op, itr; init=_InitialValue()) = mapfoldl_impl(f, op, init, itr)
41+
│││││││┌ @ reduce.jl:160 mapfoldl(f, op, itr; init=_InitialValue()) = mapfoldl_impl(f, op, init, itr)
42+
││││││││┌ @ reduce.jl:42 function mapfoldl_impl(f::F, op::OP, nt, itr) where {F,OP}
43+
│││││││││┌ @ reduce.jl:47 function foldl_impl(op::OP, nt, itr) where {OP}
44+
││││││││││┌ @ reduce.jl:53 function _foldl_impl(op::OP, init, itr) where {OP}
45+
│││││││││││┌ @ reduce.jl:81 @inline (op::BottomRF)(acc, x) = op.rf(acc, x)
46+
││││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
47+
│││││││││││││ no matching method found for signature: +(::Char, ::Char)
48+
││││││││││││└
49+
││││││││││┌ @ reduce.jl:354 @inline reduce_empty_iter(op, itr) = reduce_empty_iter(op, itr, IteratorEltype(itr))
50+
│││││││││││┌ @ reduce.jl:355 @inline reduce_empty_iter(op, itr, ::HasEltype) = reduce_empty(op, eltype(itr))
51+
││││││││││││┌ @ reduce.jl:328 reduce_empty(op::BottomRF, ::Type{T}) where {T} = reduce_empty(op.rf, T)
52+
│││││││││││││┌ @ reduce.jl:320 reduce_empty(::typeof(add_sum), ::Type{T}) where {T} = reduce_empty(+, T)
53+
││││││││││││││┌ @ reduce.jl:311 reduce_empty(::typeof(+), ::Type{T}) where {T} = zero(T)
54+
│││││││││││││││ no matching method found for signature: zero(::Type{Char})
55+
││││││││││││││└
56+
Char
57+
58+
julia> ary = Union{Symbol,Int}[:one, 1]
2559

26-
julia> @profile_call sum("julia") # can find errors that would happen in "deeper" calls
27-
3 errors found
28-
┌ @ reduce.jl:500 sum(a) = sum(identity, a)
29-
│┌ @ reduce.jl:483 sum(f, a) = mapreduce(f, add_sum, a)
30-
││┌ @ reduce.jl:280 mapreduce(f, op, itr; kw...) = mapfoldl(f, op, itr; kw...)
31-
│││┌ @ reduce.jl:280 mapreduce(f, op, itr; kw...) = mapfoldl(f, op, itr; kw...)
32-
││││┌ @ reduce.jl:157 mapfoldl(f, op, itr; kw...) = mapfoldl_impl(f, op, kw.data, itr)
33-
│││││┌ @ reduce.jl:157 mapfoldl(f, op, itr; kw...) = mapfoldl_impl(f, op, kw.data, itr)
34-
││││││┌ @ reduce.jl:41 return foldl_impl(op′, nt, itr′)
35-
│││││││┌ @ reduce.jl:45 v = _foldl_impl(op, get(nt, :init, _InitialValue()), itr)
36-
││││││││┌ @ namedtuple.jl:271 get(nt::NamedTuple, key::Union{Integer, Symbol}, default) = haskey(nt, key) ? getfield(nt, key) : default
37-
│││││││││ invalid builtin function call: getfield(::NamedTuple{(),Tuple{}}, ::Symbol)
38-
││││││││└
39-
││││││││┌ @ reduce.jl:59 v = op(v, y[1])
40-
│││││││││┌ @ reduce.jl:78 @inline (op::BottomRF)(acc, x) = op.rf(acc, x)
41-
││││││││││┌ @ reduce.jl:21 add_sum(x, y) = x + y
42-
│││││││││││ no method matching signature: +(::Char, ::Char)
60+
julia> @profile_call sum(ary) # can profile on abstract-typed inputs
61+
14 errors found
62+
┌ @ reducedim.jl:867 @inline ($fname)(a::AbstractArray; dims=:, kw...) = ($_fname)(a, dims; kw...)
63+
│┌ @ reducedim.jl:867 @inline ($fname)(a::AbstractArray; dims=:, kw...) = ($_fname)(a, dims; kw...)
64+
││┌ @ reducedim.jl:871 ($_fname)(a, ::Colon; kw...) = ($_fname)(identity, a, :; kw...)
65+
│││┌ @ reducedim.jl:871 ($_fname)(a, ::Colon; kw...) = ($_fname)(identity, a, :; kw...)
66+
││││┌ @ reducedim.jl:872 ($_fname)(f, a, ::Colon; kw...) = mapreduce(f, $op, a; kw...)
67+
│││││┌ @ reducedim.jl:872 ($_fname)(f, a, ::Colon; kw...) = mapreduce(f, $op, a; kw...)
68+
││││││┌ @ reducedim.jl:310 mapreduce(f, op, A::AbstractArrayOrBroadcasted; dims=:, init=_InitialValue()) =
69+
│││││││┌ @ reducedim.jl:310 mapreduce(f, op, A::AbstractArrayOrBroadcasted; dims=:, init=_InitialValue()) =
70+
││││││││┌ @ reducedim.jl:318 _mapreduce_dim(f, op, ::_InitialValue, A::AbstractArrayOrBroadcasted, ::Colon) =
71+
│││││││││┌ @ reduce.jl:396 function _mapreduce(f, op, ::IndexLinear, A::AbstractArrayOrBroadcasted)
72+
││││││││││┌ @ reduce.jl:351 mapreduce_empty_iter(f, op, itr, ItrEltype) =
73+
│││││││││││┌ @ reduce.jl:355 @inline reduce_empty_iter(op, itr, ::HasEltype) = reduce_empty(op, eltype(itr))
74+
││││││││││││┌ @ reduce.jl:329 reduce_empty(op::MappingRF, ::Type{T}) where {T} = mapreduce_empty(op.f, op.rf, T)
75+
│││││││││││││┌ @ reduce.jl:343 mapreduce_empty(::typeof(identity), op, T) = reduce_empty(op, T)
76+
││││││││││││││┌ @ reduce.jl:320 reduce_empty(::typeof(add_sum), ::Type{T}) where {T} = reduce_empty(+, T)
77+
│││││││││││││││┌ @ reduce.jl:311 reduce_empty(::typeof(+), ::Type{T}) where {T} = zero(T)
78+
││││││││││││││││ no matching method found for signature: zero(::Type{Union{Int64, Symbol}})
79+
│││││││││││││││└
80+
││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
81+
│││││││││││ no matching method found for signature: +(::Symbol, ::Int64)
4382
││││││││││└
44-
│││││││┌ @ reduce.jl:46 v isa _InitialValue && return reduce_empty_iter(op, itr)
45-
││││││││┌ @ reduce.jl:343 @inline reduce_empty_iter(op, itr) = reduce_empty_iter(op, itr, IteratorEltype(itr))
46-
│││││││││┌ @ reduce.jl:344 @inline reduce_empty_iter(op, itr, ::HasEltype) = reduce_empty(op, eltype(itr))
47-
││││││││││┌ @ reduce.jl:317 reduce_empty(op::BottomRF, T) = reduce_empty(op.rf, T)
48-
│││││││││││┌ @ reduce.jl:310 reduce_empty(::typeof(add_sum), T) = reduce_empty(+, T)
49-
││││││││││││┌ @ reduce.jl:303 reduce_empty(::typeof(+), T) = zero(T)
50-
│││││││││││││ no method matching signature: zero(::Type{Char})
83+
││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
84+
│││││││││││ no matching method found for signature: +(::Int64, ::Symbol)
85+
││││││││││└
86+
││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
87+
│││││││││││ no matching method found for signature: +(::Symbol, ::Symbol)
88+
││││││││││└
89+
││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
90+
│││││││││││ no matching method found for signature: +(::Int64, ::Symbol)
91+
││││││││││└
92+
││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
93+
│││││││││││ no matching method found for signature: +(::Int64, ::Symbol)
94+
││││││││││└
95+
││││││││││┌ @ reduce.jl:257 mapreduce_impl(f, op, A::AbstractArrayOrBroadcasted, ifirst::Integer, ilast::Integer) =
96+
│││││││││││┌ @ reduce.jl:233 @noinline function mapreduce_impl(f, op, A::AbstractArrayOrBroadcasted,
97+
││││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
98+
│││││││││││││ no matching method found for signature: +(::Symbol, ::Int64)
99+
││││││││││││└
100+
││││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
101+
│││││││││││││ no matching method found for signature: +(::Int64, ::Symbol)
51102
││││││││││││└
52-
Char
103+
││││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
104+
│││││││││││││ no matching method found for signature: +(::Symbol, ::Symbol)
105+
││││││││││││└
106+
││││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
107+
│││││││││││││ no matching method found for signature: +(::Int64, ::Symbol)
108+
││││││││││││└
109+
││││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
110+
│││││││││││││ no matching method found for signature: +(::Int64, ::Symbol)
111+
││││││││││││└
112+
││││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
113+
│││││││││││││ no matching method found for signature: +(::Symbol, ::Int64)
114+
││││││││││││└
115+
││││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
116+
│││││││││││││ no matching method found for signature: +(::Int64, ::Symbol)
117+
││││││││││││└
118+
││││││││││││┌ @ reduce.jl:24 add_sum(x, y) = x + y
119+
│││││││││││││ no matching method found for signature: +(::Symbol, ::Symbol)
120+
││││││││││││└
121+
Union{Int64, Symbol}
53122
```
54123
55124
### TODOs
56125
57126
in order of priority:
58-
59-
- [x] show profiled results
60-
- [x] escape recursive calls
61-
- [ ] bug fixes: `TypeVar` etc. can serious errors within the current implementation
62-
- [ ] toplevel executions
63-
- handle untyped IRs while splitting expressions as JuliaIntepreter.jl does
64-
- then, TP will be able to profile a file directly like an usual static linter
65-
- [ ] more reports
66-
- [ ] `UndefVarError`
67-
- [x] report `GlobalRef` for really undefined variable
68-
- report `:throw_undef_if_not` ? (includes lots of false positives as is)
69-
- [ ] method ambiguity error
70-
- more and more ...
71-
- [ ] don't trace into "primitive" functions in `Core` and `Base`: with the similar approach to https://github.com/FluxML/Mjolnir.jl/tree/9435d98673752cec4e222e31a6b9f38edcd7d5e0/src/lib
72-
- [ ] balance between Julia's inference approach and error profiling
127+
- setup a type-level virtual machine and enable profiling on toplevel code
128+
- more reports
129+
* invalid built-in function calls
130+
* report some cases of `throw`, e.g. `rand('1')::ArgumentError("Sampler for this object is not defined")`
131+
- balance between Julia's inference approach and error profiling ?
73132
- Julia's type inference allows abstract type (like `Any`) to slip into the inference process by various heuristics, in order to ensure its termination and obtain the performance
74-
- but this is obviously unideal for TP, since our basic stance is _"better safe than sorry"_, meaning ideally we want to find all the possible errors while revealing some uncertainty Julia's inference accepts
75-
- nevertheless, as far as TP relies on the Julia's inference, we need to achieve the conservative error profiling in the existence of abstract types, _somehow_
76-
- as a consequence, TP will be able to profile, e.g.:
77-
- [ ] `print`
78-
- [ ] `sort`
79-
- [ ] support generated functions
80-
- [ ] replace `Core` types: enables profiling things in `Core.Compiler` module
81-
82-
### Ideas
83-
84-
- report performance pitfalls
85-
- somehow profiles possible exceptions ?
133+
- but this is somewhat unideal in the context of bug reports, since the stance would be _"better safe than sorry"_, meaning we ideally want to find all the possible errors while revealing some uncertainty Julia's inference accepts
134+
- maybe we need some additional setup for supporting generated functions
135+
- report performance pitfalls ?

src/TypeProfiler.jl

Lines changed: 16 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,24 @@
1+
@doc read(normpath(dirname(@__DIR__), "README.md"), String)
12
module TypeProfiler
23

3-
export
4-
# profile_file, profile_text,
5-
@profile_call
6-
7-
# TODO: using once gets stable
8-
import Core:
9-
SimpleVector, svec,
10-
MethodInstance, CodeInfo, LineInfoNode,
11-
GotoNode, PiNode, PhiNode, PhiCNode, UpsilonNode, SlotNumber
12-
import Core.Compiler:
13-
specialize_method, typeinf_ext,
14-
tmerge,
15-
SSAValue
16-
import Base:
17-
unwrap_unionall, rewrap_unionall
18-
import Base.Meta: isexpr
19-
20-
const to_tt = Base.to_tuple_type
21-
22-
include("types.jl")
23-
include("utils.jl")
24-
include("construct.jl")
25-
include("interpret.jl")
26-
include("builtin.jl")
27-
include("profile.jl")
28-
include("print.jl")
4+
include("profiler/profiler.jl")
295

30-
# for debugging
31-
# -------------
32-
33-
# this method is basically only for testing
34-
# TODO: keyword args
35-
function prepare_frame(f, args...)
36-
slottyps = Type[typeof′(f), typeof′.(args)...]
37-
tt = to_tt(slottyps)
38-
mms = matching_methods(tt)
39-
@assert (n = length(mms)) === 1 "$(n === 0 ? "no" : "multiple") methods found: $tt"
40-
tt, sparams::SimpleVector, m::Method = mms[1]
41-
mi = specialize_method(m, tt, sparams)
42-
return prepare_frame(mi, slottyps)
43-
end
6+
using Base:
7+
Meta.isexpr
448

459
macro profile_call(ex, kwargs...)
46-
@assert isexpr(ex, :call) "function call expression should be given"
47-
f = ex.args[1]
48-
args = ex.args[2:end]
49-
return quote let
50-
maybe_newframe = prepare_frame($(esc(f)), $(map(esc, args)...))
51-
!isa(maybe_newframe, Frame) && return maybe_newframe
52-
frame = maybe_newframe::Frame
53-
try
54-
evaluate_or_profile!(frame)
55-
print_report(frame; $(map(esc, kwargs)...))
56-
catch err
57-
rethrow(err)
58-
finally
59-
global $(esc(:frame)) = frame # HACK: assign the erroneous frame into global `frame` variable for debugging
60-
end
61-
end end
10+
@assert isexpr(ex, :call) "function call expression should be given"
11+
f = ex.args[1]
12+
args = ex.args[2:end]
13+
14+
quote let
15+
interp, frame = Profiler.profile_call($(esc(f)), $(map(esc, args)...))
16+
Profiler.print_reports(interp; $(map(esc, kwargs)...))
17+
frame.result.result
18+
end end
6219
end
6320

21+
export
22+
@profile_call
23+
6424
end

src/abandoned/TypeProfiler.jl

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
module TypeProfiler
2+
3+
export
4+
# profile_file, profile_text,
5+
@profile_call
6+
7+
# TODO: using once gets stable
8+
import Core:
9+
SimpleVector, svec,
10+
MethodInstance, CodeInfo, LineInfoNode,
11+
GotoNode, PiNode, PhiNode, PhiCNode, UpsilonNode, SlotNumber
12+
import Core.Compiler:
13+
specialize_method, typeinf_ext,
14+
tmerge,
15+
SSAValue
16+
import Base:
17+
unwrap_unionall, rewrap_unionall
18+
import Base.Meta: isexpr
19+
20+
const to_tt = Base.to_tuple_type
21+
22+
include("types.jl")
23+
include("utils.jl")
24+
include("construct.jl")
25+
include("interpret.jl")
26+
include("builtin.jl")
27+
include("profile.jl")
28+
include("print.jl")
29+
30+
# for debugging
31+
# -------------
32+
33+
# this method is basically only for testing
34+
# TODO: keyword args
35+
function prepare_frame(f, args...)
36+
slottyps = Type[typeof′(f), typeof′.(args)...]
37+
tt = to_tt(slottyps)
38+
mms = matching_methods(tt)
39+
@assert (n = length(mms)) === 1 "$(n === 0 ? "no" : "multiple") methods found: $tt"
40+
tt, sparams::SimpleVector, m::Method = mms[1]
41+
mi = specialize_method(m, tt, sparams)
42+
return prepare_frame(mi, slottyps)
43+
end
44+
45+
macro profile_call(ex, kwargs...)
46+
@assert isexpr(ex, :call) "function call expression should be given"
47+
f = ex.args[1]
48+
args = ex.args[2:end]
49+
return quote let
50+
maybe_newframe = prepare_frame($(esc(f)), $(map(esc, args)...))
51+
!isa(maybe_newframe, Frame) && return maybe_newframe
52+
frame = maybe_newframe::Frame
53+
try
54+
evaluate_or_profile!(frame)
55+
print_report(frame; $(map(esc, kwargs)...))
56+
catch err
57+
rethrow(err)
58+
finally
59+
global $(esc(:frame)) = frame # HACK: assign the erroneous frame into global `frame` variable for debugging
60+
end
61+
end end
62+
end
63+
64+
end
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)