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

Making wrappers of C handle types more atomic #14107

Open
HertzDevil opened this issue Dec 17, 2023 · 1 comment
Open

Making wrappers of C handle types more atomic #14107

HertzDevil opened this issue Dec 17, 2023 · 1 comment

Comments

@HertzDevil
Copy link
Contributor

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.

IntPtr may be obtained from LibC, or we could also expose it publicly.

@oprypin
Copy link
Member

oprypin commented Dec 17, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants