Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UnresolvedRuntimeCall for unresolved function calls #86

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/AllocCheck.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 6 additions & 4 deletions src/classify.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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

Expand Down
17 changes: 17 additions & 0 deletions src/types.jl
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 4 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using AllocCheck
using AllocCheck: AllocatingRuntimeCall, DynamicDispatch, AllocationSite
using AllocCheck: AllocatingRuntimeCall, DynamicDispatch, AllocationSite, UnresolvedRuntimeCall
using Test

mutable struct Foo{T}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading