`unsafe_wrap` on memory created by `:jl_malloc`

I’m probably diving too deep trying to make things fast (again) but I was hoping there was a way to make this work. Sorry to bother, I’ve asked a lot of questions about memory management stuff recently.

I have a C library that can take ownership of memory and return it (SuiteSparse:GraphBLAS). I give the library the :jl_malloc and friends functions at init time.

Currently to give the C library a vector for instance I need to do an unsafe_copyto! to memory created by :jl_malloc. That’s fine, although it’s slower and limits my arrays to << 1/2 of memory since there’s a copy.

Up until today I did the same in reverse, by doing something like v = Vector{T}(undef, n) and unsafe_copyto!(pointer(v), Ptr{T}(ptrtocarray[]), length(v)).

I’d really like to use unsafe_wrap to do this. Unfortunately I’m doing something wrong with the combination of unsafe_wrap and :jl_malloc. See MWE here:

MWE:

               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.6.2 (2021-07-14)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> x = [1,2,3]
3-element Vector{Int64}:
 1
 2
 3

julia> z = ccall(:jl_malloc, Ptr{Int64}, (Int64, ), 3)
Ptr{Int64} @0x00000000026a1e30

julia> unsafe_copyto!(z, pointer(x), length(x))
Ptr{Int64} @0x00000000026a1e30

julia> z2 = unsafe_wrap(Array, z, 3; own = true)
3-element Vector{Int64}:
 1
 2
 3

julia> z2 = nothing

julia> GC.gc()
free(): invalid pointer

signal (6): Aborted
in expression starting at REPL[7]:1
gsignal at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
abort at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
unknown function (ip: 0x7f7edc8fe507)
unknown function (ip: 0x7f7edc904c19)
unknown function (ip: 0x7f7edc90642b)
jl_gc_free_array at /buildworker/worker/package_linux64/build/src/gc.c:1111 [inlined]
sweep_malloced_arrays at /buildworker/worker/package_linux64/build/src/gc.c:1132 [inlined]
gc_sweep_other at /buildworker/worker/package_linux64/build/src/gc.c:1467 [inlined]
_jl_gc_collect at /buildworker/worker/package_linux64/build/src/gc.c:3144
jl_gc_collect at /buildworker/worker/package_linux64/build/src/gc.c:3240
gc at ./gcutils.jl:94 [inlined]
gc at ./gcutils.jl:94
unknown function (ip: 0x7f7e82a806dc)
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2237 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2419
jl_apply at /buildworker/worker/package_linux64/build/src/julia.h:1703 [inlined]
do_call at /buildworker/worker/package_linux64/build/src/interpreter.c:115
eval_value at /buildworker/worker/package_linux64/build/src/interpreter.c:204
eval_stmt_value at /buildworker/worker/package_linux64/build/src/interpreter.c:155 [inlined]
eval_body at /buildworker/worker/package_linux64/build/src/interpreter.c:562
jl_interpret_toplevel_thunk at /buildworker/worker/package_linux64/build/src/interpreter.c:670
jl_toplevel_eval_flex at /buildworker/worker/package_linux64/build/src/toplevel.c:877
jl_toplevel_eval_flex at /buildworker/worker/package_linux64/build/src/toplevel.c:825
jl_toplevel_eval_flex at /buildworker/worker/package_linux64/build/src/toplevel.c:825
jl_toplevel_eval_in at /buildworker/worker/package_linux64/build/src/toplevel.c:929
eval at ./boot.jl:360 [inlined]
eval_user_input at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/REPL/src/REPL.jl:139
repl_backend_loop at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/REPL/src/REPL.jl:200
start_repl_backend at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/REPL/src/REPL.jl:185
#run_repl#42 at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/REPL/src/REPL.jl:317
run_repl at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/REPL/src/REPL.jl:305
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2237 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2419
#874 at ./client.jl:387
jfptr_YY.874_23032.clone_1 at /home/will/julia-1.6.2/lib/julia/sys.so (unknown line)
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2237 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2419
jl_apply at /buildworker/worker/package_linux64/build/src/julia.h:1703 [inlined]
jl_f__call_latest at /buildworker/worker/package_linux64/build/src/builtins.c:714
#invokelatest#2 at ./essentials.jl:708 [inlined]
invokelatest at ./essentials.jl:706 [inlined]
run_main_repl at ./client.jl:372
exec_options at ./client.jl:302
_start at ./client.jl:485
jfptr__start_34281.clone_1 at /home/will/julia-1.6.2/lib/julia/sys.so (unknown line)
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2237 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2419
jl_apply at /buildworker/worker/package_linux64/build/src/julia.h:1703 [inlined]
true_main at /buildworker/worker/package_linux64/build/src/jlapi.c:560
repl_entrypoint at /buildworker/worker/package_linux64/build/src/jlapi.c:702
main at julia (unknown line)
__libc_start_main at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
unknown function (ip: 0x4007d8)
Allocations: 4258497 (Pool: 4256858; Big: 1639); GC: 5
Aborted

Perhaps unsafe_wrap is the wrong function for the job since the garbage collector is already technically aware of this memory, and I’m somehow confusing it (more likely is that it’s smarter than me)?

There’s another option to unsafe_wrap with own=false and then register a finalizer that calls :jl_free. Is that what I should do? I don’t think this really works.

1 Like

Seems like you’ve allocated 3 bytes for your 24-byte array. Try

z = ccall(:jl_malloc, Ptr{Cvoid}, (UInt64, ), sizeof(x))

instead.

I still get a segfault on GC.gc() replacing that line? Do you?

Hm, I dont:

               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.6.2 (2021-07-14)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> x = [1,2,3]
3-element Vector{Int64}:
 1
 2
 3

julia> z = ccall(:jl_malloc, Ptr{Int64}, (UInt64, ), sizeof(x))
Ptr{Int64} @0x00000000026c1320

julia> unsafe_copyto!(z, pointer(x), 3)
Ptr{Int64} @0x00000000026c1320

julia> z2 = unsafe_wrap(Array, z, 3; own = true)
3-element Vector{Int64}:
 1
 2
 3

julia> GC.gc()

julia> 

Need to do z2 = nothing. You’re not GCing anything (AFAIK)

The docstring of unsafe_wrap says that it calls free for own = true. But free cannot directly be used on jl_malloc’ed memory. Either free on malloc’ed or jl_free on jl_malloced’ed memory. At least this is my understanding.