├── .travis.yml ├── Project.toml ├── LICENSE.md ├── README.md ├── src ├── codeview.jl ├── ui.jl ├── callsite.jl ├── Cthulhu.jl └── reflection.jl └── test └── runtests.jl /.travis.yml: -------------------------------------------------------------------------------- 1 | ## Documentation: http://docs.travis-ci.com/user/languages/julia/ 2 | language: julia 3 | os: 4 | - linux 5 | - osx 6 | julia: 7 | - 1.0 8 | - nightly 9 | notifications: 10 | email: false 11 | git: 12 | depth: 99999999 13 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "Cthulhu" 2 | uuid = "f68482b8-f384-11e8-15f7-abe071a5a75f" 3 | authors = ["Valentin Churavy "] 4 | version = "0.1.0" 5 | 6 | [deps] 7 | InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" 8 | TerminalMenus = "dc548174-15c3-5faf-af27-7997cfbde655" 9 | 10 | [extras] 11 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 12 | 13 | [targets] 14 | test = ["Test"] 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2018-2019 Valentin Churavy, and other contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cthulhu.jl 2 | *The slow descent into madness* 3 | 4 | Cthulhu can help you debug type inference issues by recursively showing the 5 | `code_typed` output until you find the exact point where inference gave up, 6 | messed up, or did something unexpected. Using the Cthulhu interface you can 7 | debug type inference problems faster. 8 | 9 | ```julia 10 | descend(f, tt) 11 | @descend f() 12 | ``` 13 | 14 | Given a function and a tuple-type, interactively explore the output of 15 | `code_typed` by descending into `invoke` statements. Type enter to select an 16 | `invoke` to descend into, select ↩ to ascend, and press q or control-c to 17 | quit. 18 | 19 | ## Usage 20 | 21 | ```julia 22 | function foo() 23 | T = rand() > 0.5 ? Int64 : Float64 24 | sum(rand(T, 100)) 25 | end 26 | 27 | descend(foo, Tuple{}) 28 | @descend foo() 29 | ``` 30 | 31 | ### Examples 32 | 33 | #### `@descend optimize=true foo()` 34 | [![asciicast1](https://asciinema.org/a/y3a7kR38nbDGdm98kL9yZcUJA.svg)](https://asciinema.org/a/y3a7kR38nbDGdm98kL9yZcUJA) 35 | 36 | ## Methods 37 | 38 | - `@descend_code_typed` 39 | - `descend_code_typed` 40 | - `@descend_code_warntype` 41 | - `descend_code_warntype` 42 | - `@descend`: Shortcut for `@descend_code_typed` 43 | - `descend`: Shortcut for `descend_code_typed` 44 | -------------------------------------------------------------------------------- /src/codeview.jl: -------------------------------------------------------------------------------- 1 | const asm_syntax = Ref(:att) 2 | 3 | function cthulhu_llvm(io::IO, mi, optimize, debuginfo, params) 4 | dump = InteractiveUtils._dump_function_linfo( 5 | mi, params.world, #=native=# false, 6 | #=wrapper=# false, #=strip_ir_metadata=# true, 7 | #=dump_module=# false, #=syntax=# asm_syntax[], 8 | optimize, debuginfo ? :source : :none) 9 | print(io, dump) 10 | end 11 | 12 | function cthulhu_native(io::IO, mi, optimize, debuginfo, params) 13 | dump = InteractiveUtils._dump_function_linfo( 14 | mi, params.world, #=native=# true, 15 | #=wrapper=# false, #=strip_ir_metadata=# true, 16 | #=dump_module=# false, #=syntax=# asm_syntax[], 17 | optimize, debuginfo ? :source : :none) 18 | print(io, dump) 19 | end 20 | 21 | cthulhu_warntype(args...) = cthulhu_warntype(stdout, args...) 22 | function cthulhu_warntype(io::IO, src, rettype, debuginfo) 23 | if VERSION < v"1.1.0-DEV.762" 24 | elseif VERSION < v"1.2.0-DEV.229" 25 | lineprinter = Base.IRShow.debuginfo[debuginfo] 26 | else 27 | debuginfo = Base.IRShow.debuginfo(debuginfo) 28 | lineprinter = Base.IRShow.__debuginfo[debuginfo] 29 | end 30 | 31 | lambda_io::IOContext = stdout 32 | if src.slotnames !== nothing 33 | lambda_io = IOContext(lambda_io, :SOURCE_SLOTNAMES => Base.sourceinfo_slotnames(src)) 34 | end 35 | print(io, "Body") 36 | InteractiveUtils.warntype_type_printer(io, rettype, true) 37 | println(io) 38 | if VERSION < v"1.1.0-DEV.762" 39 | Base.IRShow.show_ir(lambda_io, src, InteractiveUtils.warntype_type_printer) 40 | else 41 | Base.IRShow.show_ir(lambda_io, src, lineprinter(src), InteractiveUtils.warntype_type_printer) 42 | end 43 | return nothing 44 | end 45 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using Cthulhu 2 | using Test 3 | 4 | function process(@nospecialize(f), @nospecialize(TT); optimize=true) 5 | mi = Cthulhu.first_method_instance(f, TT) 6 | (ci, rt, slottypes) = Cthulhu.do_typeinf_slottypes(mi, optimize, Cthulhu.current_params()) 7 | Cthulhu.preprocess_ci!(ci, mi, optimize) 8 | ci, mi, rt, slottypes 9 | end 10 | 11 | function find_callsites_by_ftt(@nospecialize(f), @nospecialize(TT); optimize=true) 12 | ci, mi, _, slottypes = process(f, TT; optimize=optimize) 13 | callsites = Cthulhu.find_callsites(ci, mi, slottypes) 14 | end 15 | 16 | # Testing that we don't have spurious calls from `Type` 17 | callsites = find_callsites_by_ftt(Base.throw_boundserror, Tuple{UnitRange{Int64},Int64}) 18 | @test length(callsites) == 1 19 | 20 | function test() 21 | T = rand() > 0.5 ? Int64 : Float64 22 | sum(rand(T, 100)) 23 | end 24 | 25 | callsites = find_callsites_by_ftt(test, Tuple{}) 26 | @test length(callsites) == 3 27 | 28 | callsites = find_callsites_by_ftt(test, Tuple{}; optimize=false) 29 | @test length(callsites) == 2 30 | 31 | # Check that we see callsites that are the rhs of assignments 32 | @noinline bar_callsite_assign() = nothing 33 | function foo_callsite_assign() 34 | x = bar_callsite_assign() 35 | x 36 | end 37 | callsites = find_callsites_by_ftt(foo_callsite_assign, Tuple{}; optimize=false) 38 | @test length(callsites) == 1 39 | 40 | @eval function call_rt() 41 | S = $(Core.Compiler.return_type)(+, Tuple{Int, Int}) 42 | end 43 | let callsites = find_callsites_by_ftt(call_rt, Tuple{}; optimize=false) 44 | @test length(callsites) == 1 45 | end 46 | 47 | function call_by_apply(args...) 48 | identity(args...) 49 | end 50 | let callsites = find_callsites_by_ftt(call_by_apply, Tuple{Tuple{Int}}; optimize=false) 51 | @test length(callsites) == 1 52 | end 53 | 54 | if VERSION >= v"1.1.0-DEV.215" && Base.JLOptions().check_bounds == 0 55 | Base.@propagate_inbounds function f(x) 56 | @boundscheck error() 57 | end 58 | g(x) = @inbounds f(x) 59 | h(x) = f(x) 60 | 61 | let (CI, _, _, _) = process(g, Tuple{Vector{Float64}}) 62 | @test length(CI.code) == 3 63 | end 64 | 65 | let (CI, _, _, _) = process(h, Tuple{Vector{Float64}}) 66 | @test length(CI.code) == 2 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /src/ui.jl: -------------------------------------------------------------------------------- 1 | import TerminalMenus 2 | import TerminalMenus: request 3 | 4 | mutable struct CthulhuMenu <: TerminalMenus.AbstractMenu 5 | options::Vector{String} 6 | pagesize::Int 7 | pageoffset::Int 8 | selected::Int 9 | toggle::Union{Nothing, Symbol} 10 | end 11 | 12 | function show_as_line(el) 13 | reduced_displaysize = displaysize(stdout) .- (0, 3) 14 | buf = IOBuffer() 15 | show(IOContext(buf, :limit=>true, :displaysize=>reduced_displaysize), el) 16 | String(take!(buf)) 17 | end 18 | 19 | 20 | function CthulhuMenu(callsites; pagesize::Int=10) 21 | options = vcat(map(show_as_line, callsites), ["↩"]) 22 | length(options) < 1 && error("CthulhuMenu must have at least one option") 23 | 24 | # if pagesize is -1, use automatic paging 25 | pagesize = pagesize == -1 ? length(options) : pagesize 26 | # pagesize shouldn't be bigger than options 27 | pagesize = min(length(options), pagesize) 28 | # after other checks, pagesize must be greater than 1 29 | pagesize < 1 && error("pagesize must be >= 1") 30 | 31 | pageoffset = 0 32 | selected = -1 # none 33 | 34 | CthulhuMenu(options, pagesize, pageoffset, selected, nothing) 35 | end 36 | 37 | TerminalMenus.options(m::CthulhuMenu) = m.options 38 | TerminalMenus.cancel(m::CthulhuMenu) = m.selected = -1 39 | 40 | function TerminalMenus.header(m::CthulhuMenu) 41 | """ 42 | Select a call to descend into or ↩ to ascend. [q]uit. 43 | Toggles: [o]ptimize, [w]arn, [d]ebuginfo. 44 | Show: [L]LVM IR, [N]ative code 45 | Advanced: dump [P]arams cache. 46 | """ 47 | # Display: [L] for code_llvm, [N] for code_native 48 | end 49 | 50 | function TerminalMenus.keypress(m::CthulhuMenu, key::UInt32) 51 | if key == UInt32('w') 52 | m.toggle = :warn 53 | return true 54 | elseif key == UInt32('o') 55 | m.toggle = :optimize 56 | return true 57 | elseif key == UInt32('d') 58 | m.toggle = :debuginfo 59 | return true 60 | elseif key == UInt32('L') 61 | m.toggle = :llvm 62 | return true 63 | elseif key == UInt32('N') 64 | m.toggle = :native 65 | return true 66 | elseif key == UInt32('P') 67 | m.toggle = :dump_params 68 | return true 69 | end 70 | return false 71 | end 72 | 73 | function TerminalMenus.pick(menu::CthulhuMenu, cursor::Int) 74 | menu.selected = cursor 75 | return true #break out of the menu 76 | end 77 | 78 | function TerminalMenus.writeLine(buf::IOBuffer, menu::CthulhuMenu, idx::Int, cursor::Bool, term_width::Int) 79 | cursor_len = length(TerminalMenus.CONFIG[:cursor]) 80 | # print a ">" on the selected entry 81 | cursor ? print(buf, TerminalMenus.CONFIG[:cursor]) : print(buf, repeat(" ", cursor_len)) 82 | print(buf, " ") # Space between cursor and text 83 | 84 | line = replace(menu.options[idx], "\n" => "\\n") 85 | line = TerminalMenus.trimWidth(line, term_width, !cursor, cursor_len) 86 | 87 | print(buf, line) 88 | end 89 | -------------------------------------------------------------------------------- /src/callsite.jl: -------------------------------------------------------------------------------- 1 | abstract type CallInfo; end 2 | 3 | struct MICallInfo <: CallInfo 4 | mi::MethodInstance 5 | rt 6 | end 7 | get_mi(mici::MICallInfo) = mici.mi 8 | 9 | struct ReturnTypeCallInfo <: CallInfo 10 | called_mi::MICallInfo 11 | end 12 | get_mi(rtci::ReturnTypeCallInfo) = get_mi(rtci.called_mi) 13 | 14 | struct Callsite 15 | id::Int # ssa-id 16 | info::CallInfo 17 | end 18 | get_mi(c::Callsite) = get_mi(c.info) 19 | 20 | # Callsite printing 21 | mutable struct TextWidthLimiter 22 | io::IO 23 | width::Int 24 | limit::Int 25 | end 26 | TextWidthLimiter(io::IO, limit) = TextWidthLimiter(io, 0, limit) 27 | has_space(limiter::TextWidthLimiter, width::Int) = limiter.width + width < limiter.limit - 1 28 | has_space(limiter::TextWidthLimiter, s) = has_space(limiter, textwidth(string(s))) 29 | function Base.print(io::TextWidthLimiter, s::String) 30 | io.width == io.limit && return 0 31 | width = textwidth(s::String) 32 | if has_space(io, width) 33 | print(io.io, s) 34 | io.width += width 35 | return 36 | else 37 | for c in graphemes(s) 38 | cwidth = textwidth(c) 39 | if has_space(io, cwidth) 40 | print(io, c) 41 | io.width += cwidth 42 | else 43 | break 44 | end 45 | end 46 | print(io, '…') 47 | io.width += 1 48 | end 49 | end 50 | 51 | function show_callinfo(limiter, mici::MICallInfo) 52 | mi = mici.mi 53 | if !has_space(limiter, mi.def.name) 54 | print(limiter, '…') 55 | return 56 | end 57 | print(limiter, string(mi.def.name)) 58 | tt = mi.specTypes.parameters[2:end] 59 | pstrings = map(string, tt) 60 | headstrings = map(x->isa(x, Union) ? string(x) : string(Base.unwrap_unionall(x).name), tt) 61 | print(limiter, "(") 62 | if length(pstrings) != 0 63 | # See if we have space to print all the parameters fully 64 | if has_space(limiter, sum(textwidth, pstrings) + 3*length(pstrings)) 65 | print(limiter, join(map(T->string("::", T), pstrings), ",")) 66 | # Alright, see if we at least have enough space for each head 67 | elseif has_space(limiter, sum(textwidth, headstrings) + 6*length(pstrings)) 68 | print(limiter, join(map(T->string("::", T, "{…}"), headstrings), ",")) 69 | # Fine, what about just indicating the number of arguments 70 | elseif has_space(limiter, 2*(length(tt))) 71 | print(limiter, join(map(T->"…", pstrings), ",")) 72 | else 73 | print(limiter, "…") 74 | end 75 | end 76 | print(limiter, ")") 77 | 78 | # If we have space for the return type, print it 79 | rts = string(mici.rt) 80 | if has_space(limiter, textwidth(rts)+2) 81 | print(limiter, string("::", rts)) 82 | end 83 | end 84 | 85 | 86 | function Base.show(io::IO, c::Callsite) 87 | limit = get(io, :limit, false) 88 | cols = limit ? displaysize(io)[2] : typemax(Int) 89 | limiter = TextWidthLimiter(io, cols) 90 | print(limiter, string("%", c.id, " ")) 91 | if isa(c.info, MICallInfo) 92 | print(limiter, " = invoke ") 93 | show_callinfo(limiter, c.info) 94 | elseif isa(c.info, ReturnTypeCallInfo) 95 | print(limiter, " = return_type < ") 96 | show_callinfo(limiter, c.info.called_mi) 97 | print(limiter, " >") 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /src/Cthulhu.jl: -------------------------------------------------------------------------------- 1 | module Cthulhu 2 | 3 | using InteractiveUtils 4 | 5 | using Core: MethodInstance 6 | const Compiler = Core.Compiler 7 | 8 | include("callsite.jl") 9 | include("reflection.jl") 10 | include("ui.jl") 11 | include("codeview.jl") 12 | 13 | export descend, @descend, descend_code_typed, descend_code_warntype, @descend_code_typed, @descend_code_warntype 14 | 15 | """ 16 | @descend_code_typed 17 | 18 | Evaluates the arguments to the function or macro call, determines their 19 | types, and calls `code_typed` on the resulting expression. 20 | """ 21 | macro descend_code_typed(ex0...) 22 | InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :descend_code_typed, ex0) 23 | end 24 | 25 | """ 26 | @descend_code_warntype 27 | 28 | Evaluates the arguments to the function or macro call, determines their 29 | types, and calls `code_warntype` on the resulting expression. 30 | """ 31 | macro descend_code_warntype(ex0...) 32 | InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :descend_code_warntype, ex0) 33 | end 34 | 35 | """ 36 | @descend 37 | 38 | Shortcut for [`@descend_code_typed`](@ref). 39 | """ 40 | macro descend(ex0...) 41 | esc(:(@descend_code_typed($(ex0...)))) 42 | end 43 | 44 | """ 45 | descend_code_typed(f, tt; kwargs...) 46 | 47 | Given a function and a tuple-type, interactively explore the output of 48 | `code_typed` by descending into `invoke` statements. Type enter to select an 49 | `invoke` to descend into, select ↩ to ascend, and press q or control-c to 50 | quit. 51 | 52 | # Usage: 53 | ```julia 54 | function foo() 55 | T = rand() > 0.5 ? Int64 : Float64 56 | sum(rand(T, 100)) 57 | end 58 | 59 | descend_code_typed(foo, Tuple{}) 60 | ``` 61 | """ 62 | descend_code_typed(f, @nospecialize(tt); kwargs...) = 63 | _descend_with_error_handling(f, tt; iswarn=false, kwargs...) 64 | 65 | """ 66 | descend_code_warntype(f, tt) 67 | 68 | Given a function and a tuple-type, interactively explore the output of 69 | `code_warntype` by descending into `invoke` statements. Type enter to select an 70 | `invoke` to descend into, select ↩ to ascend, and press q or control-c to 71 | quit. 72 | 73 | # Usage: 74 | ```julia 75 | function foo() 76 | T = rand() > 0.5 ? Int64 : Float64 77 | sum(rand(T, 100)) 78 | end 79 | 80 | descend_code_warntype(foo, Tuple{}) 81 | ``` 82 | """ 83 | descend_code_warntype(f, @nospecialize(tt)) = 84 | _descend_with_error_handling(f, tt; iswarn=true) 85 | 86 | function _descend_with_error_handling(f, @nospecialize(tt); kwargs...) 87 | try 88 | _descend(f, tt; kwargs...) 89 | catch x 90 | if x isa InterruptException 91 | return nothing 92 | else 93 | rethrow(x) 94 | end 95 | end 96 | return nothing 97 | end 98 | 99 | """ 100 | descend 101 | 102 | Shortcut for [`descend_code_typed`](@ref). 103 | """ 104 | const descend = descend_code_typed 105 | 106 | ## 107 | # _descend is the main driver function. 108 | # src/reflection.jl has the tools to discover methods 109 | # src/ui.jl provides the user facing interface to which _descend responds 110 | ## 111 | function _descend(mi::MethodInstance; iswarn::Bool, params=current_params(), optimize::Bool=true, kwargs...) 112 | display_CI = true 113 | debuginfo = true 114 | if :debuginfo in keys(kwargs) 115 | selected = kwargs[:debuginfo] 116 | # TODO: respect default 117 | debuginfo = selected == :source 118 | end 119 | 120 | while true 121 | (CI, rt, slottypes) = do_typeinf_slottypes(mi, optimize, params) 122 | preprocess_ci!(CI, mi, optimize) 123 | callsites = find_callsites(CI, mi, slottypes; params=params, kwargs...) 124 | 125 | debuginfo_key = debuginfo ? :source : :none 126 | if display_CI 127 | println() 128 | println("│ ─ $(string(Callsite(-1, MICallInfo(mi, rt))))") 129 | 130 | if iswarn 131 | cthulhu_warntype(stdout, CI, rt, debuginfo_key) 132 | elseif VERSION >= v"1.1.0-DEV.762" 133 | show(stdout, CI, debuginfo = debuginfo_key) 134 | else 135 | display(CI=>rt) 136 | end 137 | println() 138 | end 139 | display_CI = true 140 | 141 | TerminalMenus.config(cursor = '•', scroll = :wrap) 142 | menu = CthulhuMenu(callsites) 143 | cid = request(menu) 144 | toggle = menu.toggle 145 | 146 | if toggle === nothing 147 | if cid == length(callsites) + 1 148 | break 149 | end 150 | if cid == -1 151 | throw(InterruptException()) 152 | end 153 | callsite = callsites[cid] 154 | 155 | # recurse 156 | _descend(get_mi(callsite); params=params, optimize=optimize, 157 | iswarn=iswarn, debuginfo=debuginfo_key, kwargs...) 158 | elseif toggle === :warn 159 | iswarn ⊻= true 160 | elseif toggle === :optimize 161 | optimize ⊻= true 162 | elseif toggle === :debuginfo 163 | debuginfo ⊻= true 164 | elseif toggle === :llvm 165 | cthulhu_llvm(stdout, mi, optimize, debuginfo, params) 166 | display_CI = false 167 | elseif toggle === :native 168 | cthulhu_native(stdout, mi, optimize, debuginfo, params) 169 | display_CI = false 170 | elseif toggle === :dump_params 171 | @info "Dumping inference cache" 172 | Core.show(map(((i, x),) -> (i, x.result, x.linfo), enumerate(params.cache))) 173 | display_CI = false 174 | else 175 | error("Unknown option $toggle") 176 | end 177 | end 178 | end 179 | 180 | function _descend(@nospecialize(F), @nospecialize(TT); params=current_params(), kwargs...) 181 | mi = first_method_instance(F, TT; params=params) 182 | _descend(mi; params=params, kwargs...) 183 | end 184 | 185 | end 186 | -------------------------------------------------------------------------------- /src/reflection.jl: -------------------------------------------------------------------------------- 1 | ### 2 | # Reflection tooling 3 | ## 4 | 5 | using Base.Meta 6 | using .Compiler: widenconst, argextype, Const 7 | 8 | if VERSION >= v"1.2.0-DEV.249" 9 | sptypes_from_meth_instance(mi) = Core.Compiler.sptypes_from_meth_instance(mi) 10 | else 11 | sptypes_from_meth_instance(mi) = Core.Compiler.spvals_from_meth_instance(mi) 12 | end 13 | 14 | if VERSION >= v"1.1.0-DEV.157" 15 | const is_return_type = Core.Compiler.is_return_type 16 | else 17 | is_return_type(f) = f === Core.Compiler.return_type 18 | end 19 | 20 | function find_callsites(CI, mi, slottypes; params=current_params(), kwargs...) 21 | sptypes = sptypes_from_meth_instance(mi) 22 | callsites = Callsite[] 23 | 24 | function process_return_type(id, c, rt) 25 | is_call = isexpr(c, :call) 26 | arg_base = is_call ? 0 : 1 27 | length(c.args) == (arg_base + 3) || return nothing 28 | ft = argextype(c.args[arg_base + 2], CI, sptypes, slottypes) 29 | argTs = argextype(c.args[arg_base + 3], CI, sptypes, slottypes) 30 | isa(argTs, Const) || return nothing 31 | mi = first_method_instance(Tuple{widenconst(ft), argTs.val.parameters...}) 32 | return Callsite(id, ReturnTypeCallInfo(MICallInfo(mi, rt.val))) 33 | end 34 | 35 | for (id, c) in enumerate(CI.code) 36 | if c isa Expr 37 | callsite = nothing 38 | if c.head === :(=) 39 | c = c.args[2] 40 | (c isa Expr) || continue 41 | end 42 | if c.head === :invoke 43 | rt = CI.ssavaluetypes[id] 44 | at = argextype(c.args[2], CI, sptypes, slottypes) 45 | if isa(at, Const) && is_return_type(at.val) 46 | callsite = process_return_type(id, c, rt) 47 | else 48 | callsite = Callsite(id, MICallInfo(c.args[1], rt)) 49 | end 50 | elseif c.head === :call 51 | rt = CI.ssavaluetypes[id] 52 | types = map(arg -> widenconst(argextype(arg, CI, sptypes, slottypes)), c.args) 53 | 54 | # Look through _apply 55 | ok = true 56 | while types[1] === typeof(Core._apply) 57 | new_types = Any[types[2]] 58 | for t in types[3:end] 59 | if !(t <: Tuple) 60 | ok = false 61 | break 62 | end 63 | append!(new_types, t.parameters) 64 | end 65 | ok || break 66 | types = new_types 67 | end 68 | ok || continue 69 | 70 | # Filter out builtin functions and intrinsic function 71 | if types[1] <: Core.Builtin || types[1] <: Core.IntrinsicFunction 72 | continue 73 | end 74 | 75 | if isdefined(types[1], :instance) && is_return_type(types[1].instance) 76 | callsite = process_return_type(id, c, rt) 77 | else 78 | # Filter out abstract signatures 79 | # otherwise generated functions get crumpy 80 | if any(isabstracttype, types) || any(T->(T isa Union || T isa UnionAll), types) 81 | continue 82 | end 83 | 84 | mi = first_method_instance(Tuple{types...}) 85 | mi == nothing && continue 86 | callsite = Callsite(id, MICallInfo(mi, rt)) 87 | end 88 | else c.head === :foreigncall 89 | # special handling of jl_threading_run 90 | length(c.args) > 0 || continue 91 | if c.args[1] isa QuoteNode && c.args[1].value === :jl_threading_run 92 | func = c.args[7] 93 | ftype = widenconst(argextype(func, CI, sptypes, slottypes)) 94 | mi = first_method_instance(Tuple{ftype.parameters...}, params=params) 95 | mi == nothing && continue 96 | callsite = Callsite(id, MICallInfo(mi, Nothing)) 97 | end 98 | end 99 | 100 | if callsite !== nothing 101 | push!(callsites, callsite) 102 | end 103 | end 104 | end 105 | return callsites 106 | end 107 | 108 | if VERSION >= v"1.1.0-DEV.215" 109 | function dce!(ci, mi) 110 | argtypes = Core.Compiler.matching_cache_argtypes(mi, nothing)[1] 111 | ir = Compiler.inflate_ir(ci, sptypes_from_meth_instance(mi), 112 | argtypes) 113 | compact = Core.Compiler.IncrementalCompact(ir, true) 114 | # Just run through the iterator without any processing 115 | Core.Compiler.foreach(x -> nothing, compact) 116 | ir = Core.Compiler.finish(compact) 117 | Core.Compiler.replace_code_newstyle!(ci, ir, length(argtypes)-1) 118 | end 119 | else 120 | function dce!(ci, mi) 121 | end 122 | end 123 | 124 | function do_typeinf_slottypes(mi::Core.Compiler.MethodInstance, run_optimizer::Bool, params::Core.Compiler.Params) 125 | ccall(:jl_typeinf_begin, Cvoid, ()) 126 | result = Core.Compiler.InferenceResult(mi) 127 | frame = Core.Compiler.InferenceState(result, false, params) 128 | frame === nothing && return (nothing, Any) 129 | if Compiler.typeinf(frame) && run_optimizer 130 | opt = Compiler.OptimizationState(frame) 131 | Compiler.optimize(opt, result.result) 132 | opt.src.inferred = true 133 | end 134 | ccall(:jl_typeinf_end, Cvoid, ()) 135 | frame.inferred || return (nothing, Any) 136 | return (frame.src, result.result, frame.slottypes) 137 | end 138 | 139 | function preprocess_ci!(ci, mi, optimize) 140 | if optimize 141 | # if the optimizer hasn't run, the IR hasn't been converted 142 | # to SSA form yet and dce is not legal 143 | dce!(ci, mi) 144 | dce!(ci, mi) 145 | end 146 | end 147 | 148 | if :trace_inference_limits in fieldnames(Core.Compiler.Params) 149 | current_params() = Core.Compiler.CustomParams(ccall(:jl_get_world_counter, UInt, ()); trace_inference_limits=true) 150 | else 151 | current_params() = Core.Compiler.Params(ccall(:jl_get_world_counter, UInt, ())) 152 | end 153 | 154 | function first_method_instance(F, TT; params=current_params()) 155 | sig = Tuple{typeof(F), TT.parameters...} 156 | first_method_instance(sig; params=params) 157 | end 158 | 159 | function first_method_instance(sig; params=current_params()) 160 | methds = Base._methods_by_ftype(sig, 1, params.world) 161 | (methds === false || length(methds) < 1) && return nothing 162 | x = methds[1] 163 | meth = x[3] 164 | if isdefined(meth, :generator) && !isdispatchtuple(Tuple{sig.parameters[2:end]...}) 165 | return nothing 166 | end 167 | mi = Compiler.code_for_method(meth, sig, x[2], params.world) 168 | end 169 | --------------------------------------------------------------------------------