A lot of handle types coming from C libs are declared as Void*, but this also has the effect of turning their wrapper classes non-atomic. Take LLVM::PassBuilderOptions as an example:
lib LibLLVM
type PassBuilderOptionsRef = Void*
fun create_pass_builder_options = LLVMCreatePassBuilderOptions : PassBuilderOptionsRef
fun dispose_pass_builder_options = LLVMDisposePassBuilderOptions(options : PassBuilderOptionsRef)
end
# allocation calls `GC.malloc` rather than `.malloc_atomic`, because
# `@options` is an internal pointer
class LLVM::PassBuilderOptions
def initialize
@options = LibLLVM.create_pass_builder_options
@disposed = false
end
def to_unsafe
@options
end
def finalize
return if @disposed
@disposed = true
LibLLVM.dispose_pass_builder_options(self)
end
end
Given an unreachable LLVM::PassBuilderOptions object on the heap, Boehm GC will scan the object's contents, but not the contents referred by its @options variable, because it knows that the pointer doesn't belong to its own heap (we cannot pass the GC's allocator functions to LLVM). In some other C libraries, the Void* might not even physically refer to a (virtual) memory address, e.g. most LibC::HANDLEs. If we could guarantee this, we may as well use a regular integer type rather than Void*:
lib LibLLVM
type PassBuilderOptionsRef = IntPtr
end
Since Crystal no longer sees any pointers inside LLVM::PassBuilderOptions's instance variables, LLVM::PassBuilderOptions will now use GC.malloc_atomic instead, and the GC won't scan the object contents at all.
A similar argument holds for struct wrappers:
lib LibLLVM
type ValueRef = Void*
end
module LLVM::ValueMethods
def initialize(@unwrap : LibLLVM::ValueRef)
end
end
struct LLVM::Value
include ValueMethods
end
An Array(LLVM::Value) maintains its buffer via Pointer(LLVM::Value).malloc. This uses non-atomic allocation because @unwrap is a pointer, but can be made atomic if LibLLVM::ValueRefValueRef becomes an IntPtr instead. Note that in this case LLVM manages the lifetimes of all LibLLVM::ValueRefs; there is no C API to dispose a value.
FWIW this will need to be nuanced for sure because it's possible to tell the C library (if it's flexible in that way) to allocate with Crystal's allocator and then actually rely on GC for the C type.
A lot of handle types coming from C libs are declared as
Void*
, but this also has the effect of turning their wrapper classes non-atomic. TakeLLVM::PassBuilderOptions
as an example:Given an unreachable
LLVM::PassBuilderOptions
object on the heap, Boehm GC will scan the object's contents, but not the contents referred by its@options
variable, because it knows that the pointer doesn't belong to its own heap (we cannot pass the GC's allocator functions to LLVM). In some other C libraries, theVoid*
might not even physically refer to a (virtual) memory address, e.g. mostLibC::HANDLE
s. If we could guarantee this, we may as well use a regular integer type rather thanVoid*
:Since Crystal no longer sees any pointers inside
LLVM::PassBuilderOptions
's instance variables,LLVM::PassBuilderOptions
will now useGC.malloc_atomic
instead, and the GC won't scan the object contents at all.A similar argument holds for struct wrappers:
An
Array(LLVM::Value)
maintains its buffer viaPointer(LLVM::Value).malloc
. This uses non-atomic allocation because@unwrap
is a pointer, but can be made atomic ifLibLLVM::ValueRefValueRef
becomes anIntPtr
instead. Note that in this case LLVM manages the lifetimes of allLibLLVM::ValueRef
s; there is no C API to dispose a value.IntPtr
may be obtained fromLibC
, or we could also expose it publicly.