diff --git a/Project.toml b/Project.toml index 00aadb1..5dd95ea 100644 --- a/Project.toml +++ b/Project.toml @@ -4,7 +4,6 @@ authors = ["JuliaHub Inc."] version = "1.0.0-DEV" [deps] -Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" GPUCompiler = "61eb1bfa-7361-4325-ad38-22787b887f55" LLVM = "929cbde3-209d-540e-8aea-75f648917ca0" diff --git a/README.md b/README.md index 601b19b..6bf080f 100644 --- a/README.md +++ b/README.md @@ -16,23 +16,16 @@ julia> linsolve(a, b) = a \ b julia> length(check_allocs(linsolve, (Matrix{Float64}, Vector{Float64}))) 175 -``` - -#### Known Limitations - 1. Error paths +julia> length(check_allocs(sin, (Float64,))) +2 - Allocations in error-throwing paths are not distinguished from other allocations: +julia> length(check_allocs(sin, (Float64,); ignore_throw=true)) # ignore allocations that only happen when throwing errors +0 +``` -```julia -julia> check_allocs(sin, (Float64,))[1] +#### Limitations -Allocation of Float64 in ./special/trig.jl:28 - | @noinline sin_domain_error(x) = throw(DomainError(x, "sin(x) is only defined for finite x.")) + 1. Runtime dispatch -Stacktrace: - [1] sin_domain_error(x::Float64) - @ Base.Math ./special/trig.jl:28 - [2] sin(x::Float64) - @ Base.Math ./special/trig.jl:39 -``` + Any runtime dispatch is conservatively assumed to allocate. diff --git a/src/AllocCheck.jl b/src/AllocCheck.jl index 1172908..a9ea39f 100644 --- a/src/AllocCheck.jl +++ b/src/AllocCheck.jl @@ -148,6 +148,31 @@ function Base.show(io::IO, alloc::AllocInstance) end end +function rename_calls_and_throws!(f::LLVM.Function, job) + any_throw = BasicBlock(f, "any_throw") + builder = IRBuilder() + position!(builder, any_throw) + throw_ret = ret!(builder) + for block in blocks(f) + for inst in instructions(block) + if isa(inst, LLVM.CallInst) + rename_ir!(job, inst) + + decl = called_operand(inst) + # TODO: Identify catch blocks and filter any functions + # called only in throw-only contexts + if name(decl) == "ijl_throw" || name(decl) == "llvm.trap" + position!(builder, block) + brinst = br!(builder, any_throw) + end + end + end + end + + dispose(builder) + return throw_ret +end + """ check_allocs(func, types; entry_abi=:specfunc, ret_mod=false) @@ -167,7 +192,7 @@ AllocCheck.AllocInstance[] ``` """ -function check_allocs(@nospecialize(func), @nospecialize(types); entry_abi=:specfunc, ret_mod=false) +function check_allocs(@nospecialize(func), @nospecialize(types); entry_abi=:specfunc, ret_mod=false, ignore_throw=false) job = create_job(func, types; entry_abi) allocs = AllocInstance[] mod = JuliaContext() do ctx @@ -177,12 +202,16 @@ function check_allocs(@nospecialize(func), @nospecialize(types); entry_abi=:spec # display(mod) (; compiled) = ir[2] for f in functions(mod) + throw = rename_calls_and_throws!(f, job) + postdom = LLVM.PostDomTree(f) for block in blocks(f) for inst in instructions(block) if isa(inst, LLVM.CallInst) - rename_ir!(job, inst) decl = called_operand(inst) if is_alloc_function(name(decl)) + throw_only = dominates(postdom, throw, inst) + ignore_throw && throw_only && continue + bt = backtrace_(inst; compiled) alloc = AllocInstance(inst, bt) push!(allocs, alloc) @@ -190,6 +219,7 @@ function check_allocs(@nospecialize(func), @nospecialize(types); entry_abi=:spec end end end + dispose(postdom) end mod end diff --git a/src/utils.jl b/src/utils.jl index ddb17e2..75ae1b8 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,28 +1,3 @@ -using Base64 - -file_exists_at(x) = try isfile(x); catch; false end -const BUILDBOT_STDLIB_PATH = dirname(abspath(String(pathof(Base64)), "..", "..", "..")) -replace_buildbot_stdlibpath(str::String) = replace(str, BUILDBOT_STDLIB_PATH => Sys.STDLIB) - -""" - path = fixup_stdlib_source_path(path::String) - -Return `path` corrected for julia issue [#26314](https://github.com/JuliaLang/julia/issues/26314) if applicable. -Otherwise, return the input `path` unchanged. - -Due to the issue mentioned above, location info for methods defined one of Julia's standard libraries -are, for non source Julia builds, given as absolute paths on the worker that built the `julia` executable. -This function corrects such a path to instead refer to the local path on the users drive. -""" -function fixup_stdlib_source_path(path) - if !file_exists_at(path) - maybe_stdlib_path = replace_buildbot_stdlibpath(path) - file_exists_at(maybe_stdlib_path) && return maybe_stdlib_path - end - return path -end - - """ path = fixup_source_path(path) @@ -37,5 +12,5 @@ function fixup_source_path(file) file = normpath(newfile) end end - return fixup_stdlib_source_path(file) + return Base.fixup_stdlib_path(file) end