|
2 | 2 |
|
3 | 3 | TypeProfiler.jl employs Julia's type inference for bug reports. |
4 | 4 |
|
| 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 | + |
5 | 9 | ```julia |
| 10 | +julia> using TypeProfiler |
| 11 | + |
6 | 12 | julia> fib(n) = n ≤ 2 ? n : fib(n - 1) + fib(n - 2) |
7 | 13 | fib (generic function with 1 method) |
8 | 14 |
|
9 | 15 | julia> @profile_call fib(100000) # computation explodes otherwise |
10 | 16 | No errors ! |
11 | | -Int64 |
| 17 | + Int64 |
12 | 18 |
|
13 | 19 | julia> @profile_call fib(100000.) |
14 | 20 | No errors ! |
15 | | -Float64 |
| 21 | + Float64 |
16 | 22 |
|
17 | 23 | 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) |
23 | 29 | ││└ |
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] |
25 | 59 |
|
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) |
43 | 82 | ││││││││││└ |
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) |
51 | 102 | ││││││││││││└ |
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} |
53 | 122 | ``` |
54 | 123 |
|
55 | 124 | ### TODOs |
56 | 125 |
|
57 | 126 | 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 ? |
73 | 132 | - 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 ? |
0 commit comments