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

Change allocation function logic to be negative. #3

Merged
merged 10 commits into from
Oct 12, 2023
4 changes: 2 additions & 2 deletions src/AllocCheck.jl
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ function rename_calls_and_throws!(f::LLVM.Function, job)
end

# `catch`: Add pseudo-edge from any_catch
if name(decl) == "__sigsetjmp"
if name(decl) == "__sigsetjmp" || name(decl) == "sigsetjmp"
icmp_ = user(only(uses(inst))) # Asserts one usage
@assert icmp_ isa LLVM.ICmpInst
br_ = user(only(uses(icmp_))) # Asserts one usage
Expand Down Expand Up @@ -269,7 +269,7 @@ function check_allocs(@nospecialize(func), @nospecialize(types); entry_abi=:spec
catch_only = dominates(domtree, catch_, inst)
ignore_throw && catch_only && continue

if is_alloc_function(name(decl))
if is_alloc_function(name(decl), ignore_throw)
bt = backtrace_(inst; compiled)
alloc = AllocInstance(inst, bt)
push!(allocs, alloc)
Expand Down
109 changes: 61 additions & 48 deletions src/allocfunc.jl
Original file line number Diff line number Diff line change
@@ -1,49 +1,46 @@
# List of methods to location of arg which is the mi/function, then start of args
const generic_method_offsets = Dict{String, Tuple{Int,Int}}(("jl_f__apply_latest" => (2,3), "ijl_f__apply_latest" => (2,3), "jl_f__call_latest" => (2,3), "ijl_f__call_latest" => (2,3), "jl_f_invoke" => (2,3), "jl_invoke" => (1,3), "jl_apply_generic" => (1,2), "ijl_f_invoke" => (2,3), "ijl_invoke" => (1,3), "ijl_apply_generic" => (1,2)))

const alloc_funcs = (
"ijl_f__apply_latest", "ijl_f__call_latest", "ijl_f_invoke", "ijl_invoke", "ijl_apply_generic",
"ijl_alloc_array_1d", "ijl_alloc_array_2d", "ijl_alloc_array_3d",
"ijl_new_array",
"ijl_array_copy",
"ijl_alloc_string",
"ijl_in_threaded_region", "ijl_enter_threaded_region", "ijl_exit_threaded_region", "ijl_set_task_tid", "ijl_new_task",
"ijl_array_grow_beg",
"ijl_array_grow_end",
"ijl_array_grow_at",
"ijl_array_del_beg",
"ijl_array_del_end",
"ijl_array_del_at",
"ijl_gc_add_finalizer_th",
"ijl_symbol_n", "ijl_",
"ijl_reshape_array", "ijl_reshape_array",
"ijl_matching_methods", "ijl_matching_methods",
"ijl_array_sizehint", "ijl_array_sizehint",
"ijl_get_keyword_sorter", "ijl_get_keyword_sorter",
"ijl_ptr_to_array",
"ijl_box_float32",
"ijl_box_float64",
"ijl_box_int16",
"ijl_box_int32",
"ijl_box_int64",
"ijl_box_int8",
"ijl_box_slotnumber",
"ijl_box_ssavalue",
"ijl_box_uint16",
"ijl_box_uint32",
"ijl_box_uint64",
"ijl_box_uint8",
"ijl_box_uint8pointer",
"ijl_box_voidpointer",
"ijl_ptr_to_array_1d",
"ijl_eqtable_get", "ijl_eqtable_get",
"ijl_get_nth_field_checked",
"ijl_gc_alloc_typed", "ijl_gc_pool_alloc", "ijl_gc_big_alloc",
"ijl_gc_pool_alloc_instrumented", "ijl_gc_big_alloc_instrumented"
)

function is_alloc_function(name)
name in alloc_funcs
const generic_method_offsets = Dict{String,Tuple{Int,Int}}(("jl_f__apply_latest" => (2, 3), "ijl_f__apply_latest" => (2, 3), "jl_f__call_latest" => (2, 3), "ijl_f__call_latest" => (2, 3), "jl_f_invoke" => (2, 3), "jl_invoke" => (1, 3), "jl_apply_generic" => (1, 2), "ijl_f_invoke" => (2, 3), "ijl_invoke" => (1, 3), "ijl_apply_generic" => (1, 2)))


const known_nonalloc_funcs = (
"jl_egal__unboxed", "ijl_egal__unboxed",
"jl_lock_value", "ijl_lock_value",
"jl_unlock_value", "ijl_unlock_value",
"jl_get_nth_field_noalloc", "ijl_get_nth_field_noalloc",
gbaraldi marked this conversation as resolved.
Show resolved Hide resolved
"jl_load_and_lookup", "ijl_load_and_lookup",
"jl_lazy_load_and_lookup", "ijl_lazy_load_and_lookup",
"jl_box_bool", "ijl_box_bool",
"jl_box_int8", "ijl_box_int8",
"jl_box_uint8", "ijl_box_uint8",
r"(ijl|jl)_unbox.*",
"jl_excstack_state", "ijl_excstack_state",
"jl_restore_excstack", "ijl_restore_excstack",
"jl_enter_handler", "ijl_enter_handler",
"jl_pop_handler", "ijl_pop_handler",
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jl_array_data_owner and jl_object_id should be in this list too


const known_alloc_with_throw_funcs = (
"jl_f_ifelse", "ijl_f_ifelse",
"jl_f_typeassert", "ijl_f_typeassert",
"jl_f_isa", "ijl_f_isa",
"jl_f_issubtype", "ijl_f_issubtype",
"jl_f_is", "ijl_f_is",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, isn't jl_f_is non-allocating? jl_egal is

I don't think jl_f_throw or jl_throw are technically allocating either

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's also possible for us to call jl_typeassert directly

"jl_f_typeof", "ijl_f_typeof",
"jl_f_sizeof", "ijl_f_sizeof",
gbaraldi marked this conversation as resolved.
Show resolved Hide resolved
"jl_f_throw", "ijl_f_throw",
)

function is_alloc_function(name, ignore_throw)
maybe_alloc = occursin(r"(ijl_|jl_).*", name)
if maybe_alloc
has_alloc = false
if ignore_throw
gbaraldi marked this conversation as resolved.
Show resolved Hide resolved
has_alloc = any(x -> contains(name, x), known_alloc_with_throw_funcs)
end
has_alloc |= !any(x -> contains(name, x), known_nonalloc_funcs)
gbaraldi marked this conversation as resolved.
Show resolved Hide resolved
return has_alloc
end
return false
end

function guess_julia_type(val::LLVM.Value, typeof=true)
Expand Down Expand Up @@ -110,6 +107,24 @@ function rename_ir!(job, inst::LLVM.CallInst)
method_table = Core.Compiler.method_table(interp)
dest = called_operand(inst)

if isa(dest, LLVM.LoadInst)
fptr = LLVM.Value(LLVM.LLVM.API.LLVMGetOperand(dest, 0))
if occursin("bitcast", string(dest))
gbaraldi marked this conversation as resolved.
Show resolved Hide resolved
fn_got = LLVM.Value(LLVM.LLVM.API.LLVMGetOperand(fptr, 0))
fname = name(fn_got)
if startswith(fname, "jlplt_") && endswith(fname, "_got")
fname = fname[7:end]
fname = replace(fname, r"_\d+_got$" => "")
gbaraldi marked this conversation as resolved.
Show resolved Hide resolved
mod = LLVM.parent(LLVM.parent(LLVM.parent(inst)))
lfn = LLVM.API.LLVMGetNamedFunction(mod, fname)
if lfn == C_NULL
lfn = LLVM.API.LLVMAddFunction(mod, Symbol(fname), LLVM.API.LLVMGetCalledFunctionType(inst))
end
LLVM.API.LLVMSetOperand(inst, LLVM.API.LLVMGetNumOperands(inst) - 1, lfn)
end
end
end

if isa(dest, ConstantExpr)
# Enzyme should be able to handle these
# detect calls to literal pointers and replace with function name, if possible
Expand All @@ -132,10 +147,8 @@ function rename_ir!(job, inst::LLVM.CallInst)
lfn = LLVM.API.LLVMGetNamedFunction(mod, fn_str)
if lfn == C_NULL
lfn = LLVM.API.LLVMAddFunction(mod, fn, LLVM.API.LLVMGetCalledFunctionType(inst))
else
lfn = LLVM.API.LLVMConstBitCast(lfn, LLVM.PointerType(LLVM.FunctionType(LLVM.API.LLVMGetCalledFunctionType(inst))))
end
LLVM.API.LLVMSetOperand(inst, LLVM.API.LLVMGetNumOperands(inst)-1, lfn)
LLVM.API.LLVMSetOperand(inst, LLVM.API.LLVMGetNumOperands(inst) - 1, lfn)
end
end
end
Expand Down
9 changes: 9 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ function alloc_in_catch()
return Int64[]
end

function same_ccall()
a = Array{Int}(undef,5,5)
b = Array{Int}(undef,5,5)
a,b
end

@testset "AllocCheck.jl" begin
@test length(check_allocs(mod, (Float64,Float64))) == 0
@test length(check_allocs(sin, (Float64,); ignore_throw=false)) > 0
Expand All @@ -17,4 +23,7 @@ end

@test length(check_allocs(alloc_in_catch, (); ignore_throw=false)) == 2
@test length(check_allocs(alloc_in_catch, (); ignore_throw=true)) == 1

@test length(check_allocs(same_ccall, (), ignore_throw=false)) == 2
@test length(check_allocs(same_ccall, (), ignore_throw=true)) == 2
gbaraldi marked this conversation as resolved.
Show resolved Hide resolved
end