diff --git a/src/AllocCheck.jl b/src/AllocCheck.jl index 5834dd5..96a77fa 100644 --- a/src/AllocCheck.jl +++ b/src/AllocCheck.jl @@ -162,6 +162,9 @@ function find_allocs!(mod::LLVM.Module, meta, entry_name::String; ignore_throw=t bt = backtrace_(inst; compiled) fname = replace(name(decl), r"^ijl_"=>"jl_") push!(errors, AllocatingRuntimeCall(fname, bt)) + elseif class === :unresolved + bt = backtrace_(inst; compiled) + push!(errors, UnresolvedRuntimeCall(bt)) end if decl isa LLVM.Function && length(blocks(decl)) > 0 && !in(decl, seen) diff --git a/src/classify.jl b/src/classify.jl index 72b59cf..eaa8078 100644 --- a/src/classify.jl +++ b/src/classify.jl @@ -13,7 +13,7 @@ perform allocation, but which might allocate to get its job done (e.g. jl_subtyp function classify_runtime_fn(name::AbstractString; ignore_throw::Bool) match_ = match(r"^(ijl_|jl_)(.*)$", name) - isnothing(match_) && return (:unknown, false) + isnothing(match_) && return (:none, false) name = match_[2] may_alloc = fn_may_allocate(name; ignore_throw) @@ -28,8 +28,10 @@ function classify_runtime_fn(name::AbstractString; ignore_throw::Bool) "f__call_in_world", "f__call_in_world_total", "f_intrinsic_call", "f_invoke", "f_opaque_closure_call", "apply", "apply_generic", "gf_invoke", "gf_invoke_by_method", "gf_invoke_lookup_worlds", "invoke", "invoke_api", - "call", "call0", "call1", "call2", "call3", "unknown_fptr") + "call", "call0", "call1", "call2", "call3") return (:dispatch, may_alloc) + elseif name == "unknown_fptr" + return (:unresolved, false) else return (:runtime, may_alloc) end @@ -222,7 +224,7 @@ and replace it with a new locally-declared function that has the resolved name as its identifier. """ function rename_call!(call::LLVM.CallInst, mod::LLVM.Module) - callee = called_operand(call) + callee = LLVM.called_operand(call) if isa(callee, LLVM.LoadInst) fn_got = unwrap_ptr_casts(operands(callee)[1]) @@ -252,7 +254,7 @@ function rename_call!(call::LLVM.CallInst, mod::LLVM.Module) # Call to a runtime-determined function pointer, usually an OpaqueClosure # or a ccall that we were not able to fully resolve. # - # We label this as a DynamicDispatch to an unknown function target. + # We label this as an UnresolvedRuntimeCall, and request that the user report our bug fname = "jl_unknown_fptr" end diff --git a/src/types.jl b/src/types.jl index 953a233..ac346a2 100644 --- a/src/types.jl +++ b/src/types.jl @@ -1,3 +1,20 @@ +struct UnresolvedRuntimeCall + backtrace::Vector{Base.StackTraces.StackFrame} +end + +Base.hash(self::UnresolvedRuntimeCall, h::UInt) = nice_hash(self.backtrace, h) +Base.:(==)(self::UnresolvedRuntimeCall, other::UnresolvedRuntimeCall) = nice_isequal(self.backtrace,other.backtrace) + +function Base.show(io::IO, call::UnresolvedRuntimeCall) + if length(call.backtrace) == 0 + Base.printstyled(io, "Unresolved runtime call", color=:red, bold=true) + else + Base.printstyled(io, "Unresolved runtime call", color=:red, bold=true) + Base.println(io, " in ", call.backtrace[1].file, ":", call.backtrace[1].line) + show_backtrace_and_excerpt(io, call.backtrace) + end + Base.println(io, " (This is an AllocCheck.jl bug, please report it at https://github.com/JuliaLang/AllocCheck.jl/issues)") +end struct AllocatingRuntimeCall name::String diff --git a/test/runtests.jl b/test/runtests.jl index d5c86c1..fb5af92 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,5 @@ using AllocCheck -using AllocCheck: AllocatingRuntimeCall, DynamicDispatch, AllocationSite +using AllocCheck: AllocatingRuntimeCall, DynamicDispatch, AllocationSite, UnresolvedRuntimeCall using Test mutable struct Foo{T} @@ -75,7 +75,7 @@ end for alloc in allocs) allocs = check_allocs(call_opaque_closure, (Int, Int); ignore_throw = false) - @test length(allocs) > 0 && any(alloc isa DynamicDispatch for alloc in allocs) + @test length(allocs) > 0 && any(alloc isa UnresolvedRuntimeCall for alloc in allocs) end @testset "@check_allocs macro (syntax)" begin @@ -205,11 +205,11 @@ end @test any(x isa AllocationSite && x.type == Memory # uses jl_genericmemory_copy_slice for x in check_allocs(copy, (Vector{Int},))) - @test all(x isa DynamicDispatch || (x isa AllocationSite && x.type == Memory{UInt8}) # uses jl_string_to_genericmemory + @test all(x isa UnresolvedRuntimeCall || (x isa AllocationSite && x.type == Memory{UInt8}) # uses jl_string_to_genericmemory for x in check_allocs(Base.array_new_memory, (Memory{UInt8}, Int))) # Marked broken because the `Expr(:foreigncall, QuoteNode(:jl_alloc_string), ...)` should be resolved - # by AllocCheck.jl, but is instead (conservatively) marked as a DynamicDisaptch. + # by AllocCheck.jl, but is instead (conservatively) marked as a UnresolvedRuntimeCall # # We get thrown off by the `jl_load_and_lookup` machinery here. @test_broken all(x isa AllocationSite && x.type == Memory{UInt8} # uses jl_string_to_genericmemory