Garbage Collector in LACE.jl

Overview

LACE.jl comes with a custom garbage collector for managing C objects. This collector provides a bridge between Julia and C memory management:

  1. C-side Memory Management:

    • C objects are allocated and managed by LACE's custom garbage collector
    • The collector implements a mark-and-compact strategy for C memory:
      • Objects are marked for retention using roots
      • During collection, objects are moved to new locations
      • Unused memory is freed
  2. Julia-side Memory Management:

    • Julia code holds Ptr{Cvoid} references to C objects
    • These pointers need to be updated when the collector moves objects
    • The LACE.ACTIVE_POINTERS dictionary in Julia tracks these pointers
    • After collection, pointers are updated to point to new object locations

How It Works

Basic rules

  1. Always mark the heap before creating new objects:

    LACE.jl_mark_default_heap()
  2. Register objects you want to keep:

    
    # For system pointers
    LACE.register_sys_pointer(:sys_ptr, sys_ptr, Cint(number_of_equations))
    
    # For polynomial pointers
    LACE.register_pol_pointer(:pol_ptr, pol_ptr)

    These functions handle both:

    • Registering in Julia's LACE.ACTIVE_POINTERS
    • Setting roots in the C garbage collector
  3. Unregister objects if you change your mind (before collection):

    # For system pointers
    LACE.unregister_sys_pointer(:sys_ptr, sys_ptr, Cint(number_of_equations))
    
    # For polynomial pointers
    LACE.unregister_pol_pointer(:pol_ptr, pol_ptr)

    Similarly, these functions handle both:

    • Unregistering in Julia's LACE.ACTIVE_POINTERS
    • Unsetting roots in the C garbage collector
  4. Clean up the heap periodically:

    LACE.collect_default_heap_and_update_pointers()
    LACE.jl_unmark_default_heap()

    or, if you are sure that there are no objects that need to be kept:

    LACE.collect_default_heap()
    LACE.jl_unmark_default_heap()
  5. Verify object status when needed:

    LACE.print_tab_cont_default_heap()  # Check what objects are tracked
    LACE.infos_default_heap()          # Check heap status

Examples

1. Managing Multiple Objects

When you need to create multiple objects but only keep some of them:

using LACE, MPFI, DynamicPolynomials

@polyvar x
f1 = BigInterval(3.5) * x^4 + BigInterval(8) * x^2 + BigInterval(1, 2)
f2 = BigInterval(4.5) * x^4 + BigInterval(8) * x^3 

# Mark the heap before creating any objects
LACE.jl_mark_default_heap()

# Create objects you do not want to keep (don't register them)
sys_ptr1 = LACE.sys_get_C_ptr([f1])
# Ptr{Nothing} @0x00000001704d0038

# Create objects you want to keep
sys_ptr2 = LACE.sys_get_C_ptr([f2])
# Ptr{Nothing} @0x00000001704d02a0
LACE.register_sys_pointer(:sys_ptr2, sys_ptr2, Cint(1))

# Verify that the objects are registered
LACE.print_tab_cont_default_heap()
LACE.ACTIVE_POINTERS
# Dict{Symbol, Ptr{Nothing}} with 1 entry:
#  :sys_ptr2 => Ptr{Nothing} @0x00000001704d02a0

# Clean up the heap
LACE.collect_default_heap_and_update_pointers()  # Clean up
LACE.jl_unmark_default_heap()

# Now only sys_ptr2 is still valid and it is updated to the new location
sys_ptr2
# Ptr{Nothing} @0x00000001704d0038

# No more objects are registered
LACE.ACTIVE_POINTERS

2. Unregistering and Cleaning Up Objects

When you need to unregister an object that was previously registered:

using LACE, MPFI, DynamicPolynomials

@polyvar x
f = BigInterval(3.5) * x^4 + BigInterval(8) * x^2 + BigInterval(1, 2)

LACE.jl_mark_default_heap()
# Create and register an object
sys_ptr = LACE.sys_get_C_ptr([f])
LACE.register_sys_pointer(:sys_ptr, sys_ptr, Cint(1))

# Later, when you want to unregister it
LACE.unregister_sys_pointer(:sys_ptr, sys_ptr, Cint(1))

# The object is now unregistered and will be collected
# You can verify this by checking tab_cont
LACE.print_tab_cont_default_heap()  # Should show 0 objects

# Clean up the heap
LACE.collect_default_heap()
LACE.jl_unmark_default_heap()