diff --git a/gc/mmtk.c b/gc/mmtk.c new file mode 100644 index 00000000000000..730b53f5b6242f --- /dev/null +++ b/gc/mmtk.c @@ -0,0 +1,363 @@ +// clang -I.. -L mmtk/target/debug -lmmtk_ruby -undefined dynamic_lookup -g -O3 -dynamiclib -o ../build/libgc.mmtk.dylib mmtk.c + +#include + +#include "gc/gc.h" +#include "gc/gc_impl.h" +#include "gc/mmtk.h" + +bool +rb_mmtk_is_mutator(void) +{ + return ruby_native_thread_p(); +} + +static size_t +rb_mmtk_vm_live_bytes(void) +{ + return 0; +} + +// Bootup +MMTk_RubyUpcalls ruby_upcalls = { + NULL, + NULL, + rb_mmtk_is_mutator, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + rb_mmtk_vm_live_bytes, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +void * +rb_gc_impl_objspace_alloc(void) +{ + MMTk_Builder *builder = mmtk_builder_default(); + mmtk_init_binding(builder, NULL, &ruby_upcalls); + + return NULL; +} + +static st_table *obj_id_to_obj_table_alloc(void); + +void rb_gc_impl_objspace_init(void *objspace_ptr) { } + +void rb_gc_impl_objspace_free(void *objspace_ptr) { } + +void * +rb_gc_impl_ractor_cache_alloc(void *objspace_ptr) +{ + // TODO: pass not NULL to tls + return mmtk_bind_mutator(NULL); +} + +void +rb_gc_impl_ractor_cache_free(void *objspace_ptr, void *cache) +{ + // TODO: implement mmtk_destroy_mutator +} + +void rb_gc_impl_set_params(void *objspace_ptr) { } + +void rb_gc_impl_init(void) { } + +void rb_gc_impl_initial_stress_set(VALUE flag) { } + +static size_t size_pool_sizes[6] = { + 40, 80, 160, 320, 640, 0 +}; + +size_t * +rb_gc_impl_size_pool_sizes(void *objspace_ptr) +{ + return size_pool_sizes; +} + +// Shutdown +void rb_gc_impl_shutdown_free_objects(void *objspace_ptr) { } + +// GC +void +rb_gc_impl_start(void *objspace_ptr, bool full_mark, bool immediate_mark, bool immediate_sweep, bool compact) +{ + // TODO +} + +bool +rb_gc_impl_during_gc_p(void *objspace_ptr) +{ + // TODO + return false; +} + +void +rb_gc_impl_prepare_heap(void *objspace_ptr) +{ + // TODO +} + +void +rb_gc_impl_gc_enable(void *objspace_ptr) +{ + // TODO +} + +void +rb_gc_impl_gc_disable(void *objspace_ptr, bool finish_current_gc) +{ + // TODO +} + +bool +rb_gc_impl_gc_enabled_p(void *objspace_ptr) +{ + // TODO + return true; +} + +void +rb_gc_impl_stress_set(void *objspace_ptr, VALUE flag) +{ + // TODO +} + +VALUE +rb_gc_impl_stress_get(void *objspace_ptr) +{ + // TODO + return Qfalse; +} + +VALUE +rb_gc_impl_config_get(void *objspace_ptr) +{ + // TODO + return rb_hash_new(); +} +VALUE +rb_gc_impl_config_set(void *objspace_ptr, VALUE hash) +{ + // TODO + return hash; +} + +// Object allocation + +VALUE +rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, bool wb_protected, size_t alloc_size) +{ +#define MMTK_MIN_OBJ_ALIGN 8 +#define MMTK_ALLOCATION_SEMANTICS_DEFAULT 0 + if (alloc_size > 640) rb_bug("too big"); + for (int i = 0; i < 5; i++) { + if (alloc_size == size_pool_sizes[i]) break; + if (alloc_size < size_pool_sizes[i]) { + alloc_size = size_pool_sizes[i]; + break; + } + } + + VALUE *alloc_obj = mmtk_alloc(cache_ptr, alloc_size + 8, MMTK_MIN_OBJ_ALIGN, 0, MMTK_ALLOCATION_SEMANTICS_DEFAULT); + alloc_obj++; + alloc_obj[-1] = alloc_size; + alloc_obj[0] = flags; + alloc_obj[1] = klass; + if (alloc_size > 16) alloc_obj[2] = v1; + if (alloc_size > 24) alloc_obj[3] = v2; + if (alloc_size > 32) alloc_obj[4] = v3; + + + if (rb_gc_shutdown_call_finalizer_p((VALUE)alloc_obj)) { + mmtk_add_obj_free_candidate(alloc_obj); + } + + return (VALUE)alloc_obj; +} + +size_t +rb_gc_impl_obj_slot_size(VALUE obj) +{ + return ((VALUE *)obj)[-1]; +} + +size_t +rb_gc_impl_size_pool_id_for_size(void *objspace_ptr, size_t size) +{ + for (int i = 0; i < 5; i++) { + if (size == size_pool_sizes[i]) return i; + if (size < size_pool_sizes[i]) return i; + } + + rb_bug("size too big"); +} + +bool +rb_gc_impl_size_allocatable_p(size_t size) +{ + return size <= 640; +} + +// Malloc +void * +rb_gc_impl_malloc(void *objspace_ptr, size_t size) +{ + // TODO: don't use system malloc + return malloc(size); +} + +void * +rb_gc_impl_calloc(void *objspace_ptr, size_t size) +{ + // TODO: don't use system calloc + return calloc(1, size); +} + +void * +rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_size) +{ + // TODO: don't use system realloc + return realloc(ptr, new_size); +} + +void +rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size) +{ + // TODO: don't use system free + free(ptr); +} + +void rb_gc_impl_adjust_memory_usage(void *objspace_ptr, ssize_t diff) { } +// Marking +void +rb_gc_impl_mark(void *objspace_ptr, VALUE obj) +{ + rb_bug("unimplemented"); +} + +void +rb_gc_impl_mark_and_move(void *objspace_ptr, VALUE *ptr) +{ + rb_bug("unimplemented"); +} + +void +rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj) +{ + rb_bug("unimplemented"); +} + +void +rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj) { + rb_bug("unimplemented"); +} + +void +rb_gc_impl_mark_weak(void *objspace_ptr, VALUE *ptr) { + rb_bug("unimplemented"); +} + +void +rb_gc_impl_remove_weak(void *objspace_ptr, VALUE parent_obj, VALUE *ptr) +{ + rb_bug("unimplemented"); +} + +void +rb_gc_impl_objspace_mark(void *objspace_ptr) +{ + rb_bug("unimplemented"); +} + +// Compaction +bool +rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj) +{ + rb_bug("unimplemented"); +} + +VALUE +rb_gc_impl_location(void *objspace_ptr, VALUE value) +{ + rb_bug("unimplemented"); +} +// Write barriers +void rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) { } +void rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj) { } +void rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj) { } +// Heap walking +void rb_gc_impl_each_objects(void *objspace_ptr, int (*callback)(void *, void *, size_t, void *), void *data) { } +void rb_gc_impl_each_object(void *objspace_ptr, void (*func)(VALUE obj, void *data), void *data) { } +// Finalizers +void +rb_gc_impl_make_zombie(void *objspace_ptr, VALUE obj, void (*dfree)(void *), void *data) +{ + // TODO: real implementation of making zombie + dfree(data); +} + +VALUE rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block) { } +VALUE rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj) { } +void rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj) { } + +void +rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr) +{ + struct MMTk_RawVecOfObjRef registered_candidates = mmtk_get_all_obj_free_candidates(); + for (size_t i = 0; i < registered_candidates.len; i++) { + VALUE obj = (VALUE)registered_candidates.ptr[i]; + + if (rb_gc_shutdown_call_finalizer_p(obj)) { + rb_gc_obj_free(objspace_ptr, obj); + } + } + mmtk_free_raw_vec_of_obj_ref(registered_candidates); +} + +// Object ID +VALUE rb_gc_impl_object_id(void *objspace_ptr, VALUE obj) { } +VALUE rb_gc_impl_object_id_to_ref(void *objspace_ptr, VALUE object_id) { } +// Statistics +VALUE rb_gc_impl_set_measure_total_time(void *objspace_ptr, VALUE flag) { } +VALUE rb_gc_impl_get_measure_total_time(void *objspace_ptr) { } +VALUE rb_gc_impl_get_profile_total_time(void *objspace_ptr) { } +size_t rb_gc_impl_gc_count(void *objspace_ptr) { } +VALUE rb_gc_impl_latest_gc_info(void *objspace_ptr, VALUE key) { } +size_t rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym) { } +size_t rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym) { } +// Miscellaneous +size_t rb_gc_impl_obj_flags(void *objspace_ptr, VALUE obj, ID* flags, size_t max) { } +bool rb_gc_impl_pointer_to_heap_p(void *objspace_ptr, const void *ptr) { } +bool rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE obj) { } +void rb_gc_impl_set_event_hook(void *objspace_ptr, const rb_event_flag_t event) { } +void rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj) { } diff --git a/gc/mmtk.h b/gc/mmtk.h new file mode 100644 index 00000000000000..347972fb243b4f --- /dev/null +++ b/gc/mmtk.h @@ -0,0 +1,134 @@ +#ifndef MMTK_H +#define MMTK_H + +/* Warning, this file is autogenerated by cbindgen from the mmtk-ruby repository. Don't modify this manually. */ + +#include +#include +#include +#include +#include + +typedef struct MMTk_Builder MMTk_Builder; +typedef struct MMTk_Mutator MMTk_Mutator; + +struct rb_thread_struct; +typedef struct rb_thread_struct rb_thread_t; +typedef rb_thread_t *MMTk_VMThread; +typedef rb_thread_t *MMTk_VMMutatorThread; +typedef struct MMTk_GCThreadTLS *MMTk_VMWorkerThread; +typedef void *MMTk_Address; +typedef void *MMTk_ObjectReference; +typedef void *MMTk_NullableObjectReference; +typedef uint32_t MMTk_AllocationSemantics; + + +#define MMTk_OBJREF_OFFSET 8 + +#define MMTk_MIN_OBJ_ALIGN 8 + +#define MMTk_GC_THREAD_KIND_WORKER 1 + +typedef struct MMTk_st_table MMTk_st_table; + +typedef struct MMTk_RubyBindingOptions { + bool ractor_check_mode; + size_t suffix_size; +} MMTk_RubyBindingOptions; + +typedef MMTk_ObjectReference (*MMTk_ObjectClosureFunction)(void*, void*, MMTk_ObjectReference, bool); + +typedef struct MMTk_ObjectClosure { + /** + * The function to be called from C. + */ + MMTk_ObjectClosureFunction c_function; + /** + * The pointer to the Rust-level closure object. + */ + void *rust_closure; +} MMTk_ObjectClosure; + +typedef struct MMTk_GCThreadTLS { + int kind; + void *gc_context; + struct MMTk_ObjectClosure object_closure; +} MMTk_GCThreadTLS; + +typedef struct MMTk_RubyUpcalls { + void (*init_gc_worker_thread)(struct MMTk_GCThreadTLS *gc_worker_tls); + struct MMTk_GCThreadTLS *(*get_gc_thread_tls)(void); + bool (*is_mutator)(void); + void (*stop_the_world)(MMTk_VMWorkerThread tls); + void (*resume_mutators)(MMTk_VMWorkerThread tls); + void (*block_for_gc)(MMTk_VMMutatorThread tls); + size_t (*number_of_mutators)(void); + void (*get_mutators)(void (*visit_mutator)(MMTk_Mutator*, void*), void *data); + void (*scan_vm_roots)(void); + void (*scan_finalizer_tbl_roots)(void); + void (*scan_end_proc_roots)(void); + void (*scan_global_tbl_roots)(void); + void (*scan_obj_to_id_tbl_roots)(void); + void (*scan_misc_roots)(void); + void (*scan_final_jobs_roots)(void); + void (*scan_roots_in_mutator_thread)(MMTk_VMMutatorThread mutator_tls, + MMTk_VMWorkerThread worker_tls); + void (*scan_object_ruby_style)(MMTk_ObjectReference object); + void (*call_gc_mark_children)(MMTk_ObjectReference object); + void (*call_obj_free)(MMTk_ObjectReference object); + void (*cleanup_generic_iv_tbl)(void); + void *(*get_original_givtbl)(MMTk_ObjectReference object); + void (*move_givtbl)(MMTk_ObjectReference old_objref, MMTk_ObjectReference new_objref); + size_t (*vm_live_bytes)(void); + void (*update_frozen_strings_table)(void); + void (*update_finalizer_table)(void); + void (*update_obj_id_tables)(void); + void (*update_global_symbols_table)(void); + void (*update_overloaded_cme_table)(void); + void (*update_ci_table)(void); + struct MMTk_st_table *(*get_frozen_strings_table)(void); + struct MMTk_st_table *(*get_finalizer_table)(void); + struct MMTk_st_table *(*get_obj_id_tables)(void); + struct MMTk_st_table *(*get_global_symbols_table)(void); + struct MMTk_st_table *(*get_overloaded_cme_table)(void); + struct MMTk_st_table *(*get_ci_table)(void); + void (*st_get_size_info)(const struct MMTk_st_table *table, + size_t *entries_start, + size_t *entries_bound, + size_t *bins_num); + void (*st_update_entries_range)(struct MMTk_st_table *table, + size_t begin, + size_t end, + bool weak_keys, + bool weak_records, + bool forward); + void (*st_update_bins_range)(struct MMTk_st_table *table, size_t begin, size_t end); +} MMTk_RubyUpcalls; + +typedef struct MMTk_RawVecOfObjRef { + MMTk_ObjectReference *ptr; + size_t len; + size_t capa; +} MMTk_RawVecOfObjRef; + +MMTk_Builder *mmtk_builder_default(void); + +void mmtk_init_binding(MMTk_Builder *builder, + const struct MMTk_RubyBindingOptions *_binding_options, + const struct MMTk_RubyUpcalls *upcalls); + +MMTk_Mutator *mmtk_bind_mutator(MMTk_VMMutatorThread tls); + +MMTk_Address mmtk_alloc(MMTk_Mutator *mutator, + size_t size, + size_t align, + size_t offset, + MMTk_AllocationSemantics semantics); + +void mmtk_add_obj_free_candidate(MMTk_ObjectReference object); + +struct MMTk_RawVecOfObjRef mmtk_get_all_obj_free_candidates(void); + +void mmtk_free_raw_vec_of_obj_ref(struct MMTk_RawVecOfObjRef raw_vec); + +#endif /* MMTK_H */ diff --git a/gc/mmtk/.gitignore b/gc/mmtk/.gitignore new file mode 100644 index 00000000000000..eb5a316cbd195d --- /dev/null +++ b/gc/mmtk/.gitignore @@ -0,0 +1 @@ +target diff --git a/gc/mmtk/Cargo.lock b/gc/mmtk/Cargo.lock new file mode 100644 index 00000000000000..61e47a601e5689 --- /dev/null +++ b/gc/mmtk/Cargo.lock @@ -0,0 +1,915 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "atomic-traits" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707f750b93bd1b739cf9ddf85f8fe7c97a4a62c60ccf8b6f232514bd9103bedc" +dependencies = [ + "cfg-if", + "rustc_version", +] + +[[package]] +name = "atomic_refcell" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "built" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a6c0b39c38fd754ac338b00a88066436389c0f029da5d37d1e01091d9b7c17" +dependencies = [ + "git2", +] + +[[package]] +name = "bytemuck" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "cc" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c891175c3fb232128f48de6590095e59198bbeb8620c310be349bfc3afd12c7b" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "delegate" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e018fccbeeb50ff26562ece792ed06659b9c2dae79ece77c4456bb10d9bf79b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "git2" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libgit2-sys" +version = "0.16.2+1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libz-sys" +version = "1.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mmtk" +version = "0.26.0" +source = "git+https://github.com/mmtk/mmtk-core.git?rev=a3a72f8e5795678eff06fdc1524f0b429a62ccc0#a3a72f8e5795678eff06fdc1524f0b429a62ccc0" +dependencies = [ + "atomic", + "atomic-traits", + "atomic_refcell", + "built", + "bytemuck", + "cfg-if", + "crossbeam", + "delegate", + "downcast-rs", + "enum-map", + "env_logger", + "is-terminal", + "itertools", + "lazy_static", + "libc", + "log", + "memoffset", + "mmtk-macros", + "num-traits", + "num_cpus", + "portable-atomic", + "probe", + "regex", + "spin", + "static_assertions", + "strum", + "strum_macros", + "sysinfo", +] + +[[package]] +name = "mmtk-macros" +version = "0.26.0" +source = "git+https://github.com/mmtk/mmtk-core.git?rev=a3a72f8e5795678eff06fdc1524f0b429a62ccc0#a3a72f8e5795678eff06fdc1524f0b429a62ccc0" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "mmtk_ruby" +version = "0.1.0" +dependencies = [ + "atomic_refcell", + "env_logger", + "lazy_static", + "libc", + "log", + "mmtk", + "once_cell", + "probe", +] + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "probe" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e2d2444b730c8f027344c60f9e1f1554d7a3342df9bdd425142ed119a6e5a3" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.68", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysinfo" +version = "0.30.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732ffa00f53e6b2af46208fba5718d9662a421049204e156328b66791ffa15ae" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/gc/mmtk/Cargo.toml b/gc/mmtk/Cargo.toml new file mode 100644 index 00000000000000..bf2cac26e030bc --- /dev/null +++ b/gc/mmtk/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "mmtk_ruby" +version = "0.1.0" +authors = [] +edition = "2021" + +# Metadata for the Ruby repository +[package.metadata.ci-repos.ruby] +repo = "ruby/ruby" +rev = "c9ff790aa7692fc941757cd9ca1d2e64e63cb9b6" + +[lib] +name = "mmtk_ruby" +crate-type = ["cdylib", "staticlib"] + +[profile.release] +lto = true + +[dependencies] +libc = "0.2" +lazy_static = "1.1" +log = "0.4.14" +env_logger = "0.11.3" +once_cell = "1.17.0" +atomic_refcell = "0.1.9" +probe = "0.5" + +[dependencies.mmtk] +features = ["is_mmtk_object", "object_pinning", "sticky_immix_non_moving_nursery"] + +# Uncomment the following lines to use mmtk-core from the official repository. +git = "https://github.com/mmtk/mmtk-core.git" +rev = "a3a72f8e5795678eff06fdc1524f0b429a62ccc0" + +# Uncomment the following line to use mmtk-core from a local repository. +# path = "../../mmtk-core" + +[features] +default = [] + +# When moving an object, clear its original copy. +clear_old_copy = [] diff --git a/gc/mmtk/cbindgen.toml b/gc/mmtk/cbindgen.toml new file mode 100644 index 00000000000000..726fb539c3eb21 --- /dev/null +++ b/gc/mmtk/cbindgen.toml @@ -0,0 +1,33 @@ +language = "C" + +include_guard = "MMTK_H" + +autogen_warning = "/* Warning, this file is autogenerated by cbindgen from the mmtk-ruby repository. Don't modify this manually. */" + +tab_width = 4 + +usize_is_size_t = true + +after_includes = """ + +typedef struct MMTk_Builder MMTk_Builder; +typedef struct MMTk_Mutator MMTk_Mutator; + +struct rb_thread_struct; +typedef struct rb_thread_struct rb_thread_t; +typedef rb_thread_t *MMTk_VMThread; +typedef rb_thread_t *MMTk_VMMutatorThread; +typedef struct MMTk_GCThreadTLS *MMTk_VMWorkerThread; +typedef void *MMTk_Address; +typedef void *MMTk_ObjectReference; +typedef void *MMTk_NullableObjectReference; +typedef uint32_t MMTk_AllocationSemantics; +""" + +[export] +exclude = ["RubyMutator"] +prefix = "MMTk_" + +[export.rename] +"MMTKBuilder" = "Builder" +"RubyMutator" = "Mutator" diff --git a/gc/mmtk/src/abi.rs b/gc/mmtk/src/abi.rs new file mode 100644 index 00000000000000..33ccbb77d6e94c --- /dev/null +++ b/gc/mmtk/src/abi.rs @@ -0,0 +1,404 @@ +use crate::api::RubyMutator; +use crate::{upcalls, Ruby}; +use mmtk::scheduler::GCWorker; +use mmtk::util::{Address, ObjectReference, VMMutatorThread, VMWorkerThread}; + +// For the C binding +pub const OBJREF_OFFSET: usize = 8; +pub const MIN_OBJ_ALIGN: usize = 8; // Even on 32-bit machine. A Ruby object is at least 40 bytes large. + +pub const GC_THREAD_KIND_WORKER: libc::c_int = 1; + +const HAS_MOVED_GIVTBL: usize = 1 << 63; +const HIDDEN_SIZE_MASK: usize = 0x0000FFFFFFFFFFFF; + +// Should keep in sync with C code. +const RUBY_FL_EXIVAR: usize = 1 << 10; + +// An opaque type for the C counterpart. +#[allow(non_camel_case_types)] +pub struct st_table; + +/// Provide convenient methods for accessing Ruby objects. +/// TODO: Wrap C functions in `RubyUpcalls` as Rust-friendly methods. +pub struct RubyObjectAccess { + objref: ObjectReference, +} + +impl RubyObjectAccess { + pub fn from_objref(objref: ObjectReference) -> Self { + Self { objref } + } + + pub fn obj_start(&self) -> Address { + self.objref.to_raw_address().sub(Self::prefix_size()) + } + + pub fn payload_addr(&self) -> Address { + self.objref.to_raw_address() + } + + pub fn suffix_addr(&self) -> Address { + self.objref.to_raw_address().add(self.payload_size()) + } + + pub fn obj_end(&self) -> Address { + self.suffix_addr() + Self::suffix_size() + } + + fn hidden_field(&self) -> Address { + self.obj_start() + } + + fn load_hidden_field(&self) -> usize { + unsafe { self.hidden_field().load::() } + } + + fn update_hidden_field(&self, f: F) + where + F: FnOnce(usize) -> usize, + { + let old_value = self.load_hidden_field(); + let new_value = f(old_value); + unsafe { + self.hidden_field().store(new_value); + } + } + + pub fn payload_size(&self) -> usize { + self.load_hidden_field() & HIDDEN_SIZE_MASK + } + + pub fn set_payload_size(&self, size: usize) { + debug_assert!((size & HIDDEN_SIZE_MASK) == size); + self.update_hidden_field(|old| old & !HIDDEN_SIZE_MASK | size & HIDDEN_SIZE_MASK); + } + + fn flags_field(&self) -> Address { + self.objref.to_raw_address() + } + + pub fn load_flags(&self) -> usize { + unsafe { self.flags_field().load::() } + } + + pub fn has_exivar_flag(&self) -> bool { + (self.load_flags() & RUBY_FL_EXIVAR) != 0 + } + + pub fn has_moved_givtbl(&self) -> bool { + (self.load_hidden_field() & HAS_MOVED_GIVTBL) != 0 + } + + pub fn set_has_moved_givtbl(&self) { + self.update_hidden_field(|old| old | HAS_MOVED_GIVTBL) + } + + pub fn clear_has_moved_givtbl(&self) { + self.update_hidden_field(|old| old & !HAS_MOVED_GIVTBL) + } + + pub fn prefix_size() -> usize { + // Currently, a hidden size field of word size is placed before each object. + OBJREF_OFFSET + } + + pub fn suffix_size() -> usize { + // In RACTOR_CHECK_MODE, Ruby hides a field after each object to hold the Ractor ID. + unsafe { crate::BINDING_FAST_MUT.suffix_size } + } + + pub fn object_size(&self) -> usize { + Self::prefix_size() + self.payload_size() + Self::suffix_size() + } + + pub fn get_givtbl(&self) -> *mut libc::c_void { + if self.has_moved_givtbl() { + let moved_givtbl = crate::binding().moved_givtbl.lock().unwrap(); + moved_givtbl + .get(&self.objref) + .unwrap_or_else(|| { + panic!( + "Object {} has HAS_MOVED_GIVTBL flag but not an entry in `moved_givtbl`", + self.objref + ) + }) + .gen_ivtbl + } else { + self.get_original_givtbl().unwrap_or_else(|| { + panic!( + "Object {} does not have HAS_MOVED_GIVTBL flag or original givtbl", + self.objref + ) + }) + } + } + + pub fn get_original_givtbl(&self) -> Option<*mut libc::c_void> { + let addr = (upcalls().get_original_givtbl)(self.objref); + if addr.is_null() { + None + } else { + Some(addr) + } + } +} + +type ObjectClosureFunction = + extern "C" fn(*mut libc::c_void, *mut libc::c_void, ObjectReference, bool) -> ObjectReference; + +#[repr(C)] +pub struct ObjectClosure { + /// The function to be called from C. + pub c_function: ObjectClosureFunction, + /// The pointer to the Rust-level closure object. + pub rust_closure: *mut libc::c_void, +} + +impl Default for ObjectClosure { + fn default() -> Self { + Self { + c_function: THE_UNREGISTERED_CLOSURE_FUNC, + rust_closure: std::ptr::null_mut(), + } + } +} + +/// Rust doesn't require function items to have a unique address. +/// We therefore force using this particular constant. +/// +/// See: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons +const THE_UNREGISTERED_CLOSURE_FUNC: ObjectClosureFunction = ObjectClosure::c_function_unregistered; + +impl ObjectClosure { + /// Set this ObjectClosure temporarily to `visit_object`, and execute `f`. During the execution of + /// `f`, the Ruby VM may call this ObjectClosure. When the Ruby VM calls this ObjectClosure, + /// it effectively calls `visit_object`. + /// + /// This method is intended to run Ruby VM code in `f` with temporarily modified behavior of + /// `rb_gc_mark`, `rb_gc_mark_movable` and `rb_gc_location` + /// + /// Both `f` and `visit_object` may access and modify local variables in the environment where + /// `set_temporarily_and_run_code` called. + /// + /// Note that this function is not reentrant. Don't call this function in either `callback` or + /// `f`. + pub fn set_temporarily_and_run_code<'env, T, F1, F2>( + &mut self, + mut visit_object: F1, + f: F2, + ) -> T + where + F1: 'env + FnMut(&'static mut GCWorker, ObjectReference, bool) -> ObjectReference, + F2: 'env + FnOnce() -> T, + { + debug_assert!( + self.c_function == THE_UNREGISTERED_CLOSURE_FUNC, + "set_temporarily_and_run_code is recursively called." + ); + self.c_function = Self::c_function_registered::; + self.rust_closure = &mut visit_object as *mut F1 as *mut libc::c_void; + let result = f(); + *self = Default::default(); + result + } + + extern "C" fn c_function_registered( + rust_closure: *mut libc::c_void, + worker: *mut libc::c_void, + object: ObjectReference, + pin: bool, + ) -> ObjectReference + where + F: FnMut(&'static mut GCWorker, ObjectReference, bool) -> ObjectReference, + { + let rust_closure = unsafe { &mut *(rust_closure as *mut F) }; + let worker = unsafe { &mut *(worker as *mut GCWorker) }; + rust_closure(worker, object, pin) + } + + extern "C" fn c_function_unregistered( + _rust_closure: *mut libc::c_void, + worker: *mut libc::c_void, + object: ObjectReference, + pin: bool, + ) -> ObjectReference { + let worker = unsafe { &mut *(worker as *mut GCWorker) }; + panic!( + "object_closure is not set. worker ordinal: {}, object: {}, pin: {}", + worker.ordinal, object, pin + ); + } +} + +#[repr(C)] +pub struct GCThreadTLS { + pub kind: libc::c_int, + pub gc_context: *mut libc::c_void, + pub object_closure: ObjectClosure, +} + +impl GCThreadTLS { + fn new(kind: libc::c_int, gc_context: *mut libc::c_void) -> Self { + Self { + kind, + gc_context, + object_closure: Default::default(), + } + } + + pub fn for_worker(gc_context: *mut GCWorker) -> Self { + Self::new(GC_THREAD_KIND_WORKER, gc_context as *mut libc::c_void) + } + + pub fn from_vwt(vwt: VMWorkerThread) -> *mut GCThreadTLS { + unsafe { std::mem::transmute(vwt) } + } + + /// Cast a pointer to `GCThreadTLS` to a ref, with assertion for null pointer. + /// + /// # Safety + /// + /// Has undefined behavior if `ptr` is invalid. + pub unsafe fn check_cast(ptr: *mut GCThreadTLS) -> &'static mut GCThreadTLS { + assert!(!ptr.is_null()); + let result = &mut *ptr; + debug_assert!({ + let kind = result.kind; + kind == GC_THREAD_KIND_WORKER + }); + result + } + + /// Cast a pointer to `VMWorkerThread` to a ref, with assertion for null pointer. + /// + /// # Safety + /// + /// Has undefined behavior if `ptr` is invalid. + pub unsafe fn from_vwt_check(vwt: VMWorkerThread) -> &'static mut GCThreadTLS { + let ptr = Self::from_vwt(vwt); + Self::check_cast(ptr) + } + + #[allow(clippy::not_unsafe_ptr_arg_deref)] // `transmute` does not dereference pointer + pub fn to_vwt(ptr: *mut Self) -> VMWorkerThread { + unsafe { std::mem::transmute(ptr) } + } + + /// Get a ref to `GCThreadTLS` from C-level thread-local storage, with assertion for null + /// pointer. + /// + /// # Safety + /// + /// Has undefined behavior if the pointer held in C-level TLS is invalid. + pub unsafe fn from_upcall_check() -> &'static mut GCThreadTLS { + let ptr = (upcalls().get_gc_thread_tls)(); + Self::check_cast(ptr) + } + + pub fn worker<'w>(&mut self) -> &'w mut GCWorker { + // NOTE: The returned ref points to the worker which does not have the same lifetime as self. + assert!(self.kind == GC_THREAD_KIND_WORKER); + unsafe { &mut *(self.gc_context as *mut GCWorker) } + } +} + +#[repr(C)] +#[derive(Clone)] +pub struct RawVecOfObjRef { + pub ptr: *mut ObjectReference, + pub len: usize, + pub capa: usize, +} + +impl RawVecOfObjRef { + pub fn from_vec(vec: Vec) -> RawVecOfObjRef { + // Note: Vec::into_raw_parts is unstable. We implement it manually. + let mut vec = std::mem::ManuallyDrop::new(vec); + let (ptr, len, capa) = (vec.as_mut_ptr(), vec.len(), vec.capacity()); + + RawVecOfObjRef { ptr, len, capa } + } + + /// # Safety + /// + /// This function turns raw pointer into a Vec without check. + pub unsafe fn into_vec(self) -> Vec { + Vec::from_raw_parts(self.ptr, self.len, self.capa) + } +} + +impl From> for RawVecOfObjRef { + fn from(v: Vec) -> Self { + Self::from_vec(v) + } +} + +#[repr(C)] +#[derive(Clone)] +pub struct RubyBindingOptions { + pub ractor_check_mode: bool, + pub suffix_size: usize, +} + +#[repr(C)] +#[derive(Clone)] +pub struct RubyUpcalls { + pub init_gc_worker_thread: extern "C" fn(gc_worker_tls: *mut GCThreadTLS), + pub get_gc_thread_tls: extern "C" fn() -> *mut GCThreadTLS, + pub is_mutator: extern "C" fn() -> bool, + pub stop_the_world: extern "C" fn(tls: VMWorkerThread), + pub resume_mutators: extern "C" fn(tls: VMWorkerThread), + pub block_for_gc: extern "C" fn(tls: VMMutatorThread), + pub number_of_mutators: extern "C" fn() -> usize, + pub get_mutators: extern "C" fn( + visit_mutator: extern "C" fn(*mut RubyMutator, *mut libc::c_void), + data: *mut libc::c_void, + ), + pub scan_vm_roots: extern "C" fn(), + pub scan_finalizer_tbl_roots: extern "C" fn(), + pub scan_end_proc_roots: extern "C" fn(), + pub scan_global_tbl_roots: extern "C" fn(), + pub scan_obj_to_id_tbl_roots: extern "C" fn(), + pub scan_misc_roots: extern "C" fn(), + pub scan_final_jobs_roots: extern "C" fn(), + pub scan_roots_in_mutator_thread: + extern "C" fn(mutator_tls: VMMutatorThread, worker_tls: VMWorkerThread), + pub scan_object_ruby_style: extern "C" fn(object: ObjectReference), + pub call_gc_mark_children: extern "C" fn(object: ObjectReference), + pub call_obj_free: extern "C" fn(object: ObjectReference), + pub cleanup_generic_iv_tbl: extern "C" fn(), + pub get_original_givtbl: extern "C" fn(object: ObjectReference) -> *mut libc::c_void, + pub move_givtbl: extern "C" fn(old_objref: ObjectReference, new_objref: ObjectReference), + pub vm_live_bytes: extern "C" fn() -> usize, + pub update_frozen_strings_table: extern "C" fn(), + pub update_finalizer_table: extern "C" fn(), + pub update_obj_id_tables: extern "C" fn(), + pub update_global_symbols_table: extern "C" fn(), + pub update_overloaded_cme_table: extern "C" fn(), + pub update_ci_table: extern "C" fn(), + pub get_frozen_strings_table: extern "C" fn() -> *mut st_table, + pub get_finalizer_table: extern "C" fn() -> *mut st_table, + pub get_obj_id_tables: extern "C" fn() -> *mut st_table, + pub get_global_symbols_table: extern "C" fn() -> *mut st_table, + pub get_overloaded_cme_table: extern "C" fn() -> *mut st_table, + pub get_ci_table: extern "C" fn() -> *mut st_table, + pub st_get_size_info: extern "C" fn( + table: *const st_table, + entries_start: *mut libc::size_t, + entries_bound: *mut libc::size_t, + bins_num: *mut libc::size_t, + ), + pub st_update_entries_range: extern "C" fn( + table: *mut st_table, + begin: libc::size_t, + end: libc::size_t, + weak_keys: bool, + weak_records: bool, + forward: bool, + ), + pub st_update_bins_range: + extern "C" fn(table: *mut st_table, begin: libc::size_t, end: libc::size_t), +} + +unsafe impl Sync for RubyUpcalls {} diff --git a/gc/mmtk/src/active_plan.rs b/gc/mmtk/src/active_plan.rs new file mode 100644 index 00000000000000..80372a7576ca07 --- /dev/null +++ b/gc/mmtk/src/active_plan.rs @@ -0,0 +1,56 @@ +use std::collections::VecDeque; +use std::marker::PhantomData; + +use crate::mmtk; +use crate::upcalls; +use crate::Ruby; +use mmtk::util::opaque_pointer::*; +use mmtk::vm::ActivePlan; +use mmtk::Mutator; + +pub struct VMActivePlan {} + +impl ActivePlan for VMActivePlan { + fn number_of_mutators() -> usize { + (upcalls().number_of_mutators)() + } + + fn is_mutator(_tls: VMThread) -> bool { + (upcalls().is_mutator)() + } + + fn mutator(_tls: VMMutatorThread) -> &'static mut Mutator { + unimplemented!() + } + + fn mutators<'a>() -> Box> + 'a> { + let mut mutators = VecDeque::new(); + (upcalls().get_mutators)( + add_mutator_to_vec, + &mut mutators as *mut VecDeque<&mut Mutator> as _, + ); + + Box::new(RubyMutatorIterator { + mutators, + phantom_data: PhantomData, + }) + } +} + +extern "C" fn add_mutator_to_vec(mutator: *mut Mutator, mutators: *mut libc::c_void) { + let mutators = unsafe { &mut *(mutators as *mut VecDeque<*mut Mutator>) }; + mutators.push_back(unsafe { &mut *mutator }); +} + +struct RubyMutatorIterator<'a> { + mutators: VecDeque<&'a mut Mutator>, + phantom_data: PhantomData<&'a ()>, +} + +impl<'a> Iterator for RubyMutatorIterator<'a> { + type Item = &'a mut Mutator; + + fn next(&mut self) -> Option { + self.mutators.pop_front() + } +} diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs new file mode 100644 index 00000000000000..88ba17fe517aac --- /dev/null +++ b/gc/mmtk/src/api.rs @@ -0,0 +1,97 @@ +use crate::abi::RawVecOfObjRef; +use crate::abi::RubyBindingOptions; +use crate::abi::RubyUpcalls; +use crate::binding; +use crate::binding::RubyBinding; +use crate::mmtk; +use crate::Ruby; +use mmtk::memory_manager; +use mmtk::memory_manager::mmtk_init; +use mmtk::util::constants::MIN_OBJECT_SIZE; +use mmtk::util::options::PlanSelector; +use mmtk::util::Address; +use mmtk::util::ObjectReference; +use mmtk::util::VMMutatorThread; +use mmtk::AllocationSemantics; +use mmtk::MMTKBuilder; +use mmtk::Mutator; + +pub type RubyMutator = Mutator; + +// =============== Bootup =============== + +#[no_mangle] +pub extern "C" fn mmtk_builder_default() -> *mut MMTKBuilder { + let mut builder = MMTKBuilder::new_no_env_vars(); + builder.options.no_finalizer.set(true); + + // Hard code NoGC for now + let plan_selector = "NoGC".parse::().unwrap(); + builder.options.plan.set(plan_selector); + + Box::into_raw(Box::new(builder)) +} + +#[no_mangle] +pub extern "C" fn mmtk_init_binding( + builder: *mut MMTKBuilder, + _binding_options: *const RubyBindingOptions, + upcalls: *const RubyUpcalls, +) { + crate::set_panic_hook(); + + let builder = unsafe { Box::from_raw(builder) }; + let binding_options = RubyBindingOptions {ractor_check_mode: false, suffix_size: 0}; + let mmtk_boxed = mmtk_init(&builder); + let mmtk_static = Box::leak(Box::new(mmtk_boxed)); + + let binding = RubyBinding::new(mmtk_static, &binding_options, upcalls); + + crate::BINDING + .set(binding) + .unwrap_or_else(|_| panic!("Binding is already initialized")); +} + +#[no_mangle] +pub extern "C" fn mmtk_bind_mutator(tls: VMMutatorThread) -> *mut RubyMutator { + Box::into_raw(memory_manager::bind_mutator(mmtk(), tls)) +} + +// =============== Object allocation =============== + +#[no_mangle] +pub extern "C" fn mmtk_alloc( + mutator: *mut RubyMutator, + size: usize, + align: usize, + offset: usize, + semantics: AllocationSemantics, +) -> Address { + let clamped_size = size.max(MIN_OBJECT_SIZE); + memory_manager::alloc::( + unsafe { &mut *mutator }, + clamped_size, + align, + offset, + semantics, + ) +} + +// TODO: Replace with buffered mmtk_add_obj_free_candidates +#[no_mangle] +pub extern "C" fn mmtk_add_obj_free_candidate(object: ObjectReference) { + binding().weak_proc.add_obj_free_candidate(object) +} + +// =============== Finalizers =============== + +#[no_mangle] +pub extern "C" fn mmtk_get_all_obj_free_candidates() -> RawVecOfObjRef { + let vec = binding().weak_proc.get_all_obj_free_candidates(); + RawVecOfObjRef::from_vec(vec) +} + +#[no_mangle] +pub extern "C" fn mmtk_free_raw_vec_of_obj_ref(raw_vec: RawVecOfObjRef) { + unsafe { raw_vec.into_vec() }; +} diff --git a/gc/mmtk/src/binding.rs b/gc/mmtk/src/binding.rs new file mode 100644 index 00000000000000..51f815ae9cbd21 --- /dev/null +++ b/gc/mmtk/src/binding.rs @@ -0,0 +1,158 @@ +use std::collections::{HashMap, HashSet}; +use std::ffi::CString; +use std::str::FromStr; +use std::sync::atomic::AtomicBool; +use std::sync::Mutex; +use std::thread::JoinHandle; + +use libc::c_void; +use mmtk::util::ObjectReference; +use mmtk::MMTK; + +use crate::abi; +use crate::abi::RubyBindingOptions; +use crate::ppp::PPPRegistry; +use crate::weak_proc::WeakProcessor; +use crate::Ruby; + +pub struct RubyBindingFast { + pub gc_enabled: AtomicBool, +} + +impl Default for RubyBindingFast { + fn default() -> Self { + Self::new() + } +} + +impl RubyBindingFast { + pub const fn new() -> Self { + Self { + // Mimic the old behavior when the gc_enabled flag was in mmtk-core. + // We may refactor it so that it is false by default. + gc_enabled: AtomicBool::new(true), + } + } +} + +pub struct RubyBindingFastMut { + pub suffix_size: usize, +} + +impl Default for RubyBindingFastMut { + fn default() -> Self { + Self::new() + } +} + +impl RubyBindingFastMut { + pub const fn new() -> Self { + Self { suffix_size: 0 } + } +} + +pub(crate) struct MovedGIVTblEntry { + pub old_objref: ObjectReference, + pub gen_ivtbl: *mut c_void, +} + +pub struct RubyBinding { + pub mmtk: &'static MMTK, + pub options: RubyBindingOptions, + pub upcalls: *const abi::RubyUpcalls, + pub plan_name: Mutex>, + pub weak_proc: WeakProcessor, + pub ppp_registry: PPPRegistry, + pub(crate) moved_givtbl: Mutex>, + pub gc_thread_join_handles: Mutex>>, + pub wb_unprotected_objects: Mutex>, + pub st_entries_chunk_size: usize, + pub st_bins_chunk_size: usize, +} + +unsafe impl Sync for RubyBinding {} +unsafe impl Send for RubyBinding {} + +fn env_default(name: &str, default: T) -> T +where + T: FromStr, +{ + std::env::var(name) + .ok() + .and_then(|x| x.parse::().ok()) + .unwrap_or(default) +} + +impl RubyBinding { + pub fn new( + mmtk: &'static MMTK, + binding_options: &RubyBindingOptions, + upcalls: *const abi::RubyUpcalls, + ) -> Self { + unsafe { + crate::BINDING_FAST_MUT.suffix_size = binding_options.suffix_size; + } + + let st_entries_chunk_size = env_default::("RUBY_MMTK_ENTRIES_CHUNK_SIZE", 1024); + let st_bins_chunk_size = env_default::("RUBY_MMTK_BINS_CHUNK_SIZE", 4096); + + debug!("st_entries_chunk_size: {st_entries_chunk_size}"); + debug!("st_bins_chunk_size: {st_bins_chunk_size}"); + + Self { + mmtk, + options: binding_options.clone(), + upcalls, + plan_name: Mutex::new(None), + weak_proc: WeakProcessor::new(), + ppp_registry: PPPRegistry::new(), + moved_givtbl: Default::default(), + gc_thread_join_handles: Default::default(), + wb_unprotected_objects: Default::default(), + st_entries_chunk_size, + st_bins_chunk_size, + } + } + + pub fn upcalls(&self) -> &'static abi::RubyUpcalls { + unsafe { &*self.upcalls as &'static abi::RubyUpcalls } + } + + pub fn get_plan_name_c(&self) -> *const libc::c_char { + let mut plan_name = self.plan_name.lock().unwrap(); + if plan_name.is_none() { + let name_string = format!("{:?}", *self.mmtk.get_options().plan); + let c_string = CString::new(name_string) + .unwrap_or_else(|e| panic!("Failed converting plan name to CString: {e}")); + *plan_name = Some(c_string); + } + plan_name.as_deref().unwrap().as_ptr() + } + + pub fn join_all_gc_threads(&self) { + let handles = { + let mut guard = self.gc_thread_join_handles.lock().unwrap(); + std::mem::take(&mut *guard) + }; + + debug!("Joining GC threads..."); + let total = handles.len(); + let mut joined = 0; + for handle in handles { + handle.join().unwrap(); + joined += 1; + debug!("{joined}/{total} GC threads joined."); + } + } + + pub fn register_wb_unprotected_object(&self, object: ObjectReference) { + debug!("Registering WB-unprotected object: {}", object); + let mut objects = self.wb_unprotected_objects.lock().unwrap(); + objects.insert(object); + } + + pub fn is_object_wb_unprotected(&self, object: ObjectReference) -> bool { + let objects = self.wb_unprotected_objects.lock().unwrap(); + objects.contains(&object) + } +} diff --git a/gc/mmtk/src/collection.rs b/gc/mmtk/src/collection.rs new file mode 100644 index 00000000000000..ad4da8ba7edee6 --- /dev/null +++ b/gc/mmtk/src/collection.rs @@ -0,0 +1,88 @@ +use crate::abi::GCThreadTLS; + +use crate::api::RubyMutator; +use crate::{mmtk, upcalls, Ruby}; +use mmtk::memory_manager; +use mmtk::scheduler::*; +use mmtk::util::{VMMutatorThread, VMThread, VMWorkerThread}; +use mmtk::vm::{Collection, GCThreadContext}; +use std::sync::atomic::Ordering; +use std::thread; + +pub struct VMCollection {} + +impl Collection for VMCollection { + fn is_collection_enabled() -> bool { + crate::BINDING_FAST.gc_enabled.load(Ordering::Relaxed) + } + + fn stop_all_mutators(tls: VMWorkerThread, mut mutator_visitor: F) + where + F: FnMut(&'static mut mmtk::Mutator), + { + (upcalls().stop_the_world)(tls); + crate::binding().ppp_registry.pin_ppp_children(tls); + (upcalls().get_mutators)( + Self::notify_mutator_ready::, + &mut mutator_visitor as *mut F as *mut _, + ); + } + + fn resume_mutators(tls: VMWorkerThread) { + (upcalls().resume_mutators)(tls); + } + + fn block_for_gc(tls: VMMutatorThread) { + (upcalls().block_for_gc)(tls); + } + + fn spawn_gc_thread(_tls: VMThread, ctx: GCThreadContext) { + let join_handle = match ctx { + GCThreadContext::Worker(mut worker) => thread::Builder::new() + .name("MMTk Worker Thread".to_string()) + .spawn(move || { + let ordinal = worker.ordinal; + debug!( + "Hello! This is MMTk Worker Thread running! ordinal: {}", + ordinal + ); + crate::register_gc_thread(thread::current().id()); + let ptr_worker = &mut *worker as *mut GCWorker; + let gc_thread_tls = + Box::into_raw(Box::new(GCThreadTLS::for_worker(ptr_worker))); + (upcalls().init_gc_worker_thread)(gc_thread_tls); + memory_manager::start_worker( + mmtk(), + GCThreadTLS::to_vwt(gc_thread_tls), + worker, + ); + debug!( + "An MMTk Worker Thread is quitting. Good bye! ordinal: {}", + ordinal + ); + crate::unregister_gc_thread(thread::current().id()); + }) + .unwrap(), + }; + + { + let mut handles = crate::binding().gc_thread_join_handles.lock().unwrap(); + handles.push(join_handle); + } + } + + fn vm_live_bytes() -> usize { + (upcalls().vm_live_bytes)() + } +} + +impl VMCollection { + extern "C" fn notify_mutator_ready(mutator_ptr: *mut RubyMutator, data: *mut libc::c_void) + where + F: FnMut(&'static mut mmtk::Mutator), + { + let mutator = unsafe { &mut *mutator_ptr }; + let mutator_visitor = unsafe { &mut *(data as *mut F) }; + mutator_visitor(mutator); + } +} diff --git a/gc/mmtk/src/lib.rs b/gc/mmtk/src/lib.rs new file mode 100644 index 00000000000000..d9b43daed3f750 --- /dev/null +++ b/gc/mmtk/src/lib.rs @@ -0,0 +1,135 @@ +extern crate libc; +extern crate mmtk; +#[macro_use] +extern crate log; +#[macro_use] +extern crate probe; + +use std::collections::HashSet; +use std::panic::PanicInfo; +use std::sync::Mutex; +use std::thread::ThreadId; + +use abi::RubyUpcalls; +use binding::{RubyBinding, RubyBindingFast, RubyBindingFastMut}; +use mmtk::vm::slot::{SimpleSlot, UnimplementedMemorySlice}; +use mmtk::vm::VMBinding; +use mmtk::MMTK; +use once_cell::sync::OnceCell; + +pub mod abi; +pub mod active_plan; +pub mod api; +pub mod binding; +pub mod collection; +pub mod object_model; +pub mod ppp; +pub mod reference_glue; +pub mod scanning; +pub mod utils; +pub mod weak_proc; + +#[derive(Default)] +pub struct Ruby; + +/// Ruby slot type, i.e. a slot that holds a VALUE. +/// Currently we use SimpleSlot. +/// It doesn't matter, becaues we have not started using slot-enqueuing, yet. +pub type RubySlot = SimpleSlot; + +/// Ruby memory slice, i.e. an array of VALUEs. +/// It is used by array-copy barriers which is supposed to perform bettern than copying array +/// elements one by one. At this moment, we just leave it unimplemented. +pub type RubyMemorySlice = UnimplementedMemorySlice; + +impl VMBinding for Ruby { + type VMObjectModel = object_model::VMObjectModel; + type VMScanning = scanning::VMScanning; + type VMCollection = collection::VMCollection; + type VMActivePlan = active_plan::VMActivePlan; + type VMReferenceGlue = reference_glue::VMReferenceGlue; + + type VMSlot = RubySlot; + type VMMemorySlice = RubyMemorySlice; +} + +/// The singleton object for the Ruby binding itself. +pub static BINDING: OnceCell = OnceCell::new(); + +/// Some data needs to be accessed fast. +pub static BINDING_FAST: RubyBindingFast = RubyBindingFast::new(); + +/// Some data needs to be accessed fast. +/// We sacrifice safety for speed using unsynchronized global variables. +pub static mut BINDING_FAST_MUT: RubyBindingFastMut = RubyBindingFastMut::new(); + +pub fn binding<'b>() -> &'b RubyBinding { + BINDING + .get() + .expect("Attempt to use the binding before it is initialization") +} + +pub fn mmtk() -> &'static MMTK { + binding().mmtk +} + +pub fn upcalls() -> &'static RubyUpcalls { + binding().upcalls() +} + +pub static GC_THREADS: OnceCell>> = OnceCell::new(); + +pub(crate) fn register_gc_thread(thread_id: ThreadId) { + let mut gc_threads = GC_THREADS.get().unwrap().lock().unwrap(); + gc_threads.insert(thread_id); +} + +pub(crate) fn unregister_gc_thread(thread_id: ThreadId) { + let mut gc_threads = GC_THREADS.get().unwrap().lock().unwrap(); + gc_threads.remove(&thread_id); +} + +pub(crate) fn is_gc_thread(thread_id: ThreadId) -> bool { + let gc_threads = GC_THREADS.get().unwrap().lock().unwrap(); + gc_threads.contains(&thread_id) +} + +fn handle_gc_thread_panic(panic_info: &PanicInfo) { + eprintln!("ERROR: An MMTk GC thread panicked. This is a bug."); + eprintln!("{panic_info}"); + + let bt = std::backtrace::Backtrace::capture(); + match bt.status() { + std::backtrace::BacktraceStatus::Unsupported => { + eprintln!("Backtrace is unsupported.") + } + std::backtrace::BacktraceStatus::Disabled => { + eprintln!("Backtrace is disabled."); + eprintln!("run with `RUST_BACKTRACE=1` environment variable to display a backtrace"); + } + std::backtrace::BacktraceStatus::Captured => { + eprintln!("{bt}"); + } + s => { + eprintln!("Unknown backtrace status: {s:?}"); + } + } + + std::process::abort(); +} + +pub(crate) fn set_panic_hook() { + if GC_THREADS.set(Default::default()).is_err() { + return; + } + + let old_hook = std::panic::take_hook(); + + std::panic::set_hook(Box::new(move |panic_info| { + if is_gc_thread(std::thread::current().id()) { + handle_gc_thread_panic(panic_info); + } else { + old_hook(panic_info); + } + })); +} diff --git a/gc/mmtk/src/object_model copy.rs b/gc/mmtk/src/object_model copy.rs new file mode 100644 index 00000000000000..f7dac5cb9a0e40 --- /dev/null +++ b/gc/mmtk/src/object_model copy.rs @@ -0,0 +1,140 @@ +use std::ptr::copy_nonoverlapping; + +use crate::abi::{RubyObjectAccess, MIN_OBJ_ALIGN, OBJREF_OFFSET}; +use crate::{abi, Ruby}; +use mmtk::util::constants::BITS_IN_BYTE; +use mmtk::util::copy::{CopySemantics, GCWorkerCopyContext}; +use mmtk::util::{Address, ObjectReference}; +use mmtk::vm::*; + +pub struct VMObjectModel {} + +impl VMObjectModel { + const OBJREF_OFFSET: usize = abi::OBJREF_OFFSET; +} + +impl ObjectModel for VMObjectModel { + const GLOBAL_LOG_BIT_SPEC: VMGlobalLogBitSpec = VMGlobalLogBitSpec::side_first(); + + // We overwrite the prepended word which were used to hold object sizes. + const LOCAL_FORWARDING_POINTER_SPEC: VMLocalForwardingPointerSpec = + VMLocalForwardingPointerSpec::in_header(-((OBJREF_OFFSET * BITS_IN_BYTE) as isize)); + + const LOCAL_FORWARDING_BITS_SPEC: VMLocalForwardingBitsSpec = + VMLocalForwardingBitsSpec::side_first(); + + const LOCAL_MARK_BIT_SPEC: VMLocalMarkBitSpec = + VMLocalMarkBitSpec::side_after(Self::LOCAL_FORWARDING_BITS_SPEC.as_spec()); + + const LOCAL_PINNING_BIT_SPEC: VMLocalPinningBitSpec = + VMLocalPinningBitSpec::side_after(Self::LOCAL_MARK_BIT_SPEC.as_spec()); + + const LOCAL_LOS_MARK_NURSERY_SPEC: VMLocalLOSMarkNurserySpec = + VMLocalLOSMarkNurserySpec::side_after(Self::LOCAL_PINNING_BIT_SPEC.as_spec()); + + const UNIFIED_OBJECT_REFERENCE_ADDRESS: bool = false; + const OBJECT_REF_OFFSET_LOWER_BOUND: isize = Self::OBJREF_OFFSET as isize; + + const NEED_VO_BITS_DURING_TRACING: bool = true; + + fn copy( + from: ObjectReference, + semantics: CopySemantics, + copy_context: &mut GCWorkerCopyContext, + ) -> ObjectReference { + let from_acc = RubyObjectAccess::from_objref(from); + let maybe_givtbl = from_acc.has_exivar_flag().then(|| { + from_acc + .get_original_givtbl() + .unwrap_or_else(|| panic!("Object {} has FL_EXIVAR but no givtbl.", from)) + }); + let from_start = from_acc.obj_start(); + let object_size = from_acc.object_size(); + let to_start = copy_context.alloc_copy(from, object_size, MIN_OBJ_ALIGN, 0, semantics); + debug_assert!(!to_start.is_zero()); + let to_payload = to_start.add(OBJREF_OFFSET); + unsafe { + copy_nonoverlapping::(from_start.to_ptr(), to_start.to_mut_ptr(), object_size); + } + // unsafe: `to_payload`` cannot be zero because `alloc_copy`` never returns zero. + let to_obj = unsafe { ObjectReference::from_raw_address_unchecked(to_payload) }; + copy_context.post_copy(to_obj, object_size, semantics); + trace!("Copied object from {} to {}", from, to_obj); + + #[cfg(feature = "clear_old_copy")] + { + trace!( + "Clearing old copy {} ({}-{})", + from, + from_start, + from_start + object_size + ); + // For debug purpose, we clear the old copy so that if the Ruby VM reads from the old + // copy again, it will likely result in an error. + unsafe { std::ptr::write_bytes::(from_start.to_mut_ptr(), 0, object_size) } + } + + if let Some(givtbl) = maybe_givtbl { + { + let mut moved_givtbl = crate::binding().moved_givtbl.lock().unwrap(); + moved_givtbl.insert( + to_obj, + crate::binding::MovedGIVTblEntry { + old_objref: from, + gen_ivtbl: givtbl, + }, + ); + } + let to_acc = RubyObjectAccess::from_objref(to_obj); + to_acc.set_has_moved_givtbl(); + } + + to_obj + } + + fn copy_to(_from: ObjectReference, _to: ObjectReference, _region: Address) -> Address { + unimplemented!( + "This function cannot be called because we do not support MarkCompact for Ruby." + ) + } + + fn get_reference_when_copied_to(_from: ObjectReference, _to: Address) -> ObjectReference { + unimplemented!( + "This function cannot be called because we do not support MarkCompact for Ruby." + ) + } + + fn get_current_size(object: ObjectReference) -> usize { + RubyObjectAccess::from_objref(object).object_size() + } + + fn get_type_descriptor(_reference: ObjectReference) -> &'static [i8] { + todo!() + } + + fn ref_to_object_start(object: ObjectReference) -> Address { + RubyObjectAccess::from_objref(object).obj_start() + } + + fn ref_to_header(object: ObjectReference) -> Address { + RubyObjectAccess::from_objref(object).payload_addr() + } + + const IN_OBJECT_ADDRESS_OFFSET: isize = 0; + + fn get_size_when_copied(object: ObjectReference) -> usize { + Self::get_current_size(object) + } + + fn get_align_when_copied(_object: ObjectReference) -> usize { + todo!() + } + + fn get_align_offset_when_copied(_object: ObjectReference) -> usize { + todo!() + } + + fn dump_object(_object: ObjectReference) { + todo!() + } +} diff --git a/gc/mmtk/src/object_model.rs b/gc/mmtk/src/object_model.rs new file mode 100644 index 00000000000000..2bd93ebd568597 --- /dev/null +++ b/gc/mmtk/src/object_model.rs @@ -0,0 +1,140 @@ +use std::ptr::copy_nonoverlapping; + +use crate::abi::{RubyObjectAccess, MIN_OBJ_ALIGN, OBJREF_OFFSET}; +use crate::{abi, Ruby}; +use mmtk::util::constants::BITS_IN_BYTE; +use mmtk::util::copy::{CopySemantics, GCWorkerCopyContext}; +use mmtk::util::{Address, ObjectReference}; +use mmtk::vm::*; + +pub struct VMObjectModel {} + +impl VMObjectModel { + const OBJREF_OFFSET: usize = abi::OBJREF_OFFSET; +} + +impl ObjectModel for VMObjectModel { + const GLOBAL_LOG_BIT_SPEC: VMGlobalLogBitSpec = VMGlobalLogBitSpec::side_first(); + + // We overwrite the prepended word which were used to hold object sizes. + const LOCAL_FORWARDING_POINTER_SPEC: VMLocalForwardingPointerSpec = + VMLocalForwardingPointerSpec::in_header(-((OBJREF_OFFSET * BITS_IN_BYTE) as isize)); + + const LOCAL_FORWARDING_BITS_SPEC: VMLocalForwardingBitsSpec = + VMLocalForwardingBitsSpec::side_first(); + + const LOCAL_MARK_BIT_SPEC: VMLocalMarkBitSpec = + VMLocalMarkBitSpec::side_after(Self::LOCAL_FORWARDING_BITS_SPEC.as_spec()); + + const LOCAL_PINNING_BIT_SPEC: VMLocalPinningBitSpec = + VMLocalPinningBitSpec::side_after(Self::LOCAL_MARK_BIT_SPEC.as_spec()); + + const LOCAL_LOS_MARK_NURSERY_SPEC: VMLocalLOSMarkNurserySpec = + VMLocalLOSMarkNurserySpec::side_after(Self::LOCAL_PINNING_BIT_SPEC.as_spec()); + + const UNIFIED_OBJECT_REFERENCE_ADDRESS: bool = false; + const OBJECT_REF_OFFSET_LOWER_BOUND: isize = Self::OBJREF_OFFSET as isize; + + const NEED_VO_BITS_DURING_TRACING: bool = true; + + fn copy( + from: ObjectReference, + semantics: CopySemantics, + copy_context: &mut GCWorkerCopyContext, + ) -> ObjectReference { + let from_acc = RubyObjectAccess::from_objref(from); + let maybe_givtbl = from_acc.has_exivar_flag().then(|| { + from_acc + .get_original_givtbl() + .unwrap_or_else(|| panic!("Object {} has FL_EXIVAR but no givtbl.", from)) + }); + let from_start = from_acc.obj_start(); + let object_size = from_acc.object_size(); + let to_start = copy_context.alloc_copy(from, object_size, MIN_OBJ_ALIGN, 0, semantics); + debug_assert!(!to_start.is_zero()); + let to_payload = to_start.add(OBJREF_OFFSET); + unsafe { + copy_nonoverlapping::(from_start.to_ptr(), to_start.to_mut_ptr(), object_size); + } + // unsafe: `to_payload`` cannot be zero because `alloc_copy`` never returns zero. + let to_obj = unsafe { ObjectReference::from_raw_address_unchecked(to_payload) }; + copy_context.post_copy(to_obj, object_size, semantics); + log::trace!("Copied object from {} to {}", from, to_obj); + + #[cfg(feature = "clear_old_copy")] + { + log::trace!( + "Clearing old copy {} ({}-{})", + from, + from_start, + from_start + object_size + ); + // For debug purpose, we clear the old copy so that if the Ruby VM reads from the old + // copy again, it will likely result in an error. + unsafe { std::ptr::write_bytes::(from_start.to_mut_ptr(), 0, object_size) } + } + + if let Some(givtbl) = maybe_givtbl { + { + let mut moved_givtbl = crate::binding().moved_givtbl.lock().unwrap(); + moved_givtbl.insert( + to_obj, + crate::binding::MovedGIVTblEntry { + old_objref: from, + gen_ivtbl: givtbl, + }, + ); + } + let to_acc = RubyObjectAccess::from_objref(to_obj); + to_acc.set_has_moved_givtbl(); + } + + to_obj + } + + fn copy_to(_from: ObjectReference, _to: ObjectReference, _region: Address) -> Address { + unimplemented!( + "This function cannot be called because we do not support MarkCompact for Ruby." + ) + } + + fn get_reference_when_copied_to(_from: ObjectReference, _to: Address) -> ObjectReference { + unimplemented!( + "This function cannot be called because we do not support MarkCompact for Ruby." + ) + } + + fn get_current_size(object: ObjectReference) -> usize { + RubyObjectAccess::from_objref(object).object_size() + } + + fn get_type_descriptor(_reference: ObjectReference) -> &'static [i8] { + todo!() + } + + fn ref_to_object_start(object: ObjectReference) -> Address { + RubyObjectAccess::from_objref(object).obj_start() + } + + fn ref_to_header(object: ObjectReference) -> Address { + RubyObjectAccess::from_objref(object).payload_addr() + } + + const IN_OBJECT_ADDRESS_OFFSET: isize = 0; + + fn get_size_when_copied(object: ObjectReference) -> usize { + Self::get_current_size(object) + } + + fn get_align_when_copied(_object: ObjectReference) -> usize { + todo!() + } + + fn get_align_offset_when_copied(_object: ObjectReference) -> usize { + todo!() + } + + fn dump_object(_object: ObjectReference) { + todo!() + } +} diff --git a/gc/mmtk/src/ppp.rs b/gc/mmtk/src/ppp.rs new file mode 100644 index 00000000000000..9cab0e7709945f --- /dev/null +++ b/gc/mmtk/src/ppp.rs @@ -0,0 +1,164 @@ +use std::sync::Mutex; + +use mmtk::{ + memory_manager, + scheduler::{GCWork, WorkBucketStage}, + util::{ObjectReference, VMWorkerThread}, +}; + +use crate::{abi::GCThreadTLS, upcalls, Ruby}; + +pub struct PPPRegistry { + ppps: Mutex>, + pinned_ppp_children: Mutex>, +} + +impl PPPRegistry { + pub fn new() -> Self { + Self { + ppps: Default::default(), + pinned_ppp_children: Default::default(), + } + } + + pub fn register(&self, object: ObjectReference) { + let mut ppps = self.ppps.lock().unwrap(); + ppps.push(object); + } + + pub fn register_many(&self, objects: &[ObjectReference]) { + let mut ppps = self.ppps.lock().unwrap(); + for object in objects.iter().copied() { + ppps.push(object); + } + } + + pub fn pin_ppp_children(&self, tls: VMWorkerThread) { + log::debug!("Pin children of PPPs..."); + + if !crate::mmtk().get_plan().current_gc_may_move_object() { + log::debug!("The current GC is non-moving. Skipped pinning PPP children."); + return; + } + + let gc_tls = unsafe { GCThreadTLS::from_vwt_check(tls) }; + let worker = gc_tls.worker(); + + { + let ppps = self + .ppps + .try_lock() + .expect("PPPRegistry should not have races during GC."); + + // I tried several packet sizes and 512 works pretty well. It should be adjustable. + let packet_size = 512; + let work_packets = ppps + .chunks(packet_size) + .map(|chunk| { + Box::new(PinPPPChildren { + ppps: chunk.to_vec(), + }) as _ + }) + .collect(); + + worker.scheduler().work_buckets[WorkBucketStage::Prepare].bulk_add(work_packets); + } + } + + pub fn cleanup_ppps(&self) { + log::debug!("Removing dead PPPs..."); + { + let mut ppps = self + .ppps + .try_lock() + .expect("PPPRegistry::ppps should not have races during GC."); + + probe!(mmtk_ruby, remove_dead_ppps_start, ppps.len()); + ppps.retain_mut(|obj| { + if obj.is_live::() { + *obj = obj.get_forwarded_object::().unwrap_or(*obj); + true + } else { + log::trace!(" PPP removed: {}", *obj); + false + } + }); + probe!(mmtk_ruby, remove_dead_ppps_end); + } + + log::debug!("Unpinning pinned PPP children..."); + + if !crate::mmtk().get_plan().current_gc_may_move_object() { + log::debug!("The current GC is non-moving. Skipped unpinning PPP children."); + } else { + let mut pinned_ppps = self + .pinned_ppp_children + .try_lock() + .expect("PPPRegistry::pinned_ppp_children should not have races during GC."); + probe!(mmtk_ruby, unpin_ppp_children_start, pinned_ppps.len()); + for obj in pinned_ppps.drain(..) { + let unpinned = memory_manager::unpin_object::(obj); + debug_assert!(unpinned); + } + probe!(mmtk_ruby, unpin_ppp_children_end); + } + } +} + +impl Default for PPPRegistry { + fn default() -> Self { + Self::new() + } +} + +struct PinPPPChildren { + ppps: Vec, +} + +impl GCWork for PinPPPChildren { + fn do_work( + &mut self, + worker: &mut mmtk::scheduler::GCWorker, + _mmtk: &'static mmtk::MMTK, + ) { + let gc_tls = unsafe { GCThreadTLS::from_vwt_check(worker.tls) }; + let mut ppp_children = vec![]; + let mut newly_pinned_ppp_children = vec![]; + + let visit_object = |_worker, target_object: ObjectReference, pin| { + log::trace!( + " -> {} {}", + if pin { "(pin)" } else { " " }, + target_object + ); + if pin { + ppp_children.push(target_object); + } + target_object + }; + + gc_tls + .object_closure + .set_temporarily_and_run_code(visit_object, || { + for obj in self.ppps.iter().cloned() { + log::trace!(" PPP: {}", obj); + (upcalls().call_gc_mark_children)(obj); + } + }); + + for target_object in ppp_children { + if memory_manager::pin_object::(target_object) { + newly_pinned_ppp_children.push(target_object); + } + } + + { + let mut pinned_ppp_children = crate::binding() + .ppp_registry + .pinned_ppp_children + .lock() + .unwrap(); + pinned_ppp_children.append(&mut newly_pinned_ppp_children); + } + } +} diff --git a/gc/mmtk/src/reference_glue.rs b/gc/mmtk/src/reference_glue.rs new file mode 100644 index 00000000000000..1272bd54c164e9 --- /dev/null +++ b/gc/mmtk/src/reference_glue.rs @@ -0,0 +1,26 @@ +use crate::Ruby; +use mmtk::util::ObjectReference; +use mmtk::util::VMWorkerThread; +use mmtk::vm::ReferenceGlue; + +pub struct VMReferenceGlue {} + +impl ReferenceGlue for VMReferenceGlue { + type FinalizableType = ObjectReference; + + fn get_referent(_object: ObjectReference) -> Option { + unimplemented!() + } + + fn set_referent(_reff: ObjectReference, _referent: ObjectReference) { + unimplemented!() + } + + fn enqueue_references(_references: &[ObjectReference], _tls: VMWorkerThread) { + unimplemented!() + } + + fn clear_referent(_new_reference: ObjectReference) { + unimplemented!() + } +} diff --git a/gc/mmtk/src/scanning.rs b/gc/mmtk/src/scanning.rs new file mode 100644 index 00000000000000..991bb7a0b4664f --- /dev/null +++ b/gc/mmtk/src/scanning.rs @@ -0,0 +1,293 @@ +use crate::abi::GCThreadTLS; + +use crate::utils::ChunkedVecCollector; +use crate::{upcalls, Ruby, RubySlot}; +use mmtk::scheduler::{GCWork, GCWorker, WorkBucketStage}; +use mmtk::util::{ObjectReference, VMWorkerThread}; +use mmtk::vm::{ObjectTracer, RootsWorkFactory, Scanning, SlotVisitor}; +use mmtk::{Mutator, MutatorContext}; + +pub struct VMScanning {} + +impl Scanning for VMScanning { + fn support_slot_enqueuing(_tls: VMWorkerThread, _object: ObjectReference) -> bool { + false + } + + fn scan_object>( + _tls: VMWorkerThread, + _object: ObjectReference, + _slot_visitor: &mut EV, + ) { + unreachable!("We have not enabled slot enqueuing for any types, yet."); + } + + fn scan_object_and_trace_edges( + tls: VMWorkerThread, + object: ObjectReference, + object_tracer: &mut OT, + ) { + debug_assert!( + mmtk::memory_manager::is_mmtk_object(object.to_raw_address()), + "Not an MMTk object: {object}", + ); + let gc_tls = unsafe { GCThreadTLS::from_vwt_check(tls) }; + let visit_object = |_worker, target_object: ObjectReference, pin| { + trace!( + "Tracing edge: {} -> {}{}", + object, + target_object, + if pin { " pin" } else { "" } + ); + debug_assert!( + mmtk::memory_manager::is_mmtk_object(target_object.to_raw_address()), + "Destination is not an MMTk object. Src: {object} dst: {target_object}" + ); + let forwarded_target = object_tracer.trace_object(target_object); + if forwarded_target != target_object { + trace!( + " Forwarded target {} -> {}", + target_object, + forwarded_target + ); + } + forwarded_target + }; + gc_tls + .object_closure + .set_temporarily_and_run_code(visit_object, || { + (upcalls().scan_object_ruby_style)(object); + }); + } + + fn notify_initial_thread_scan_complete(_partial_scan: bool, _tls: VMWorkerThread) { + // Do nothing + } + + fn scan_roots_in_mutator_thread( + tls: VMWorkerThread, + mutator: &'static mut Mutator, + mut factory: impl RootsWorkFactory, + ) { + let gc_tls = unsafe { GCThreadTLS::from_vwt_check(tls) }; + Self::collect_object_roots_in("scan_thread_root", gc_tls, &mut factory, || { + (upcalls().scan_roots_in_mutator_thread)(mutator.get_tls(), tls); + }); + } + + fn scan_vm_specific_roots(tls: VMWorkerThread, factory: impl RootsWorkFactory) { + let gc_tls = unsafe { GCThreadTLS::from_vwt_check(tls) }; + let root_scanning_work_packets: Vec>> = vec![ + Box::new(ScanVMRoots::new(factory.clone())), + Box::new(ScanFinalizerTblRoots::new(factory.clone())), + Box::new(ScanEndProcRoots::new(factory.clone())), + Box::new(ScanGlobalTblRoots::new(factory.clone())), + Box::new(ScanObjToIdTblRoots::new(factory.clone())), + Box::new(ScanMiscRoots::new(factory.clone())), + Box::new(ScanFinalJobsRoots::new(factory.clone())), + ]; + gc_tls.worker().scheduler().work_buckets[WorkBucketStage::Prepare] + .bulk_add(root_scanning_work_packets); + + // Generate WB-unprotected roots scanning work packets + + 'gen_wb_unprotected_work: { + let is_nursery_gc = (crate::mmtk().get_plan().generational()) + .is_some_and(|gen| gen.is_current_gc_nursery()); + if !is_nursery_gc { + break 'gen_wb_unprotected_work; + } + + let vecs = { + let guard = crate::binding() + .wb_unprotected_objects + .try_lock() + .expect("Someone is holding the lock of wb_unprotected_objects?"); + if guard.is_empty() { + break 'gen_wb_unprotected_work; + } + + let mut collector = ChunkedVecCollector::new(128); + collector.extend(guard.iter().copied()); + collector.into_vecs() + }; + + let packets = vecs + .into_iter() + .map(|objects| { + let factory = factory.clone(); + Box::new(ScanWbUnprotectedRoots { factory, objects }) as _ + }) + .collect::>(); + + gc_tls.worker().scheduler().work_buckets[WorkBucketStage::Prepare].bulk_add(packets); + } + } + + fn supports_return_barrier() -> bool { + false + } + + fn prepare_for_roots_re_scanning() { + todo!() + } + + fn process_weak_refs( + worker: &mut GCWorker, + tracer_context: impl mmtk::vm::ObjectTracerContext, + ) -> bool { + crate::binding() + .weak_proc + .process_weak_stuff(worker, tracer_context); + crate::binding().ppp_registry.cleanup_ppps(); + false + } + + fn forward_weak_refs( + _worker: &mut GCWorker, + _tracer_context: impl mmtk::vm::ObjectTracerContext, + ) { + panic!("We can't use MarkCompact in Ruby."); + } +} + +impl VMScanning { + const OBJECT_BUFFER_SIZE: usize = 4096; + + fn collect_object_roots_in( + root_scan_kind: &str, + gc_tls: &mut GCThreadTLS, + factory: &mut impl RootsWorkFactory, + callback: F, + ) { + let mut buffer: Vec = Vec::new(); + let visit_object = |_, object: ObjectReference, pin| { + debug!( + "[{}] Visiting object: {}{}", + root_scan_kind, + object, + if pin { + "(unmovable root)" + } else { + "(movable, but we pin it anyway)" + } + ); + debug_assert!( + mmtk::memory_manager::is_mmtk_object(object.to_raw_address()), + "Root does not point to MMTk object. object: {object}" + ); + buffer.push(object); + if buffer.len() >= Self::OBJECT_BUFFER_SIZE { + factory.create_process_pinning_roots_work(std::mem::take(&mut buffer)); + } + object + }; + gc_tls + .object_closure + .set_temporarily_and_run_code(visit_object, callback); + + if !buffer.is_empty() { + factory.create_process_pinning_roots_work(buffer); + } + } +} + +trait GlobaRootScanningWork { + type F: RootsWorkFactory; + const NAME: &'static str; + + fn new(factory: Self::F) -> Self; + fn scan_roots(); + fn roots_work_factory(&mut self) -> &mut Self::F; + + fn do_work(&mut self, worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { + let gc_tls = unsafe { GCThreadTLS::from_vwt_check(worker.tls) }; + + let factory = self.roots_work_factory(); + + VMScanning::collect_object_roots_in(Self::NAME, gc_tls, factory, || { + Self::scan_roots(); + }); + } +} + +macro_rules! define_global_root_scanner { + ($name: ident, $code: expr) => { + struct $name> { + factory: F, + } + impl> GlobaRootScanningWork for $name { + type F = F; + const NAME: &'static str = stringify!($name); + fn new(factory: Self::F) -> Self { + Self { factory } + } + fn scan_roots() { + $code + } + fn roots_work_factory(&mut self) -> &mut Self::F { + &mut self.factory + } + } + impl> GCWork for $name { + fn do_work(&mut self, worker: &mut GCWorker, mmtk: &'static mmtk::MMTK) { + GlobaRootScanningWork::do_work(self, worker, mmtk); + } + } + }; +} + +define_global_root_scanner!(ScanVMRoots, { + (crate::upcalls().scan_vm_roots)(); +}); + +define_global_root_scanner!(ScanFinalizerTblRoots, { + (crate::upcalls().scan_finalizer_tbl_roots)(); +}); + +define_global_root_scanner!(ScanEndProcRoots, { + (crate::upcalls().scan_end_proc_roots)(); +}); + +define_global_root_scanner!(ScanGlobalTblRoots, { + (crate::upcalls().scan_global_tbl_roots)(); +}); + +define_global_root_scanner!(ScanObjToIdTblRoots, { + (crate::upcalls().scan_obj_to_id_tbl_roots)(); +}); + +define_global_root_scanner!(ScanMiscRoots, { + (crate::upcalls().scan_misc_roots)(); +}); + +define_global_root_scanner!(ScanFinalJobsRoots, { + (crate::upcalls().scan_final_jobs_roots)(); +}); + +struct ScanWbUnprotectedRoots> { + factory: F, + objects: Vec, +} + +impl> GCWork for ScanWbUnprotectedRoots { + fn do_work(&mut self, worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { + let gc_tls = unsafe { GCThreadTLS::from_vwt_check(worker.tls) }; + VMScanning::collect_object_roots_in("wb_unprot_roots", gc_tls, &mut self.factory, || { + for object in self.objects.iter().copied() { + if object.is_reachable::() { + debug!( + "[wb_unprot_roots] Visiting WB-unprotected object (parent): {}", + object + ); + (upcalls().scan_object_ruby_style)(object); + } else { + debug!( + "[wb_unprot_roots] Skipping young WB-unprotected object (parent): {}", + object + ); + } + } + }); + } +} diff --git a/gc/mmtk/src/utils.rs b/gc/mmtk/src/utils.rs new file mode 100644 index 00000000000000..b61973cbfe0344 --- /dev/null +++ b/gc/mmtk/src/utils.rs @@ -0,0 +1,88 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + +use atomic_refcell::AtomicRefCell; +use mmtk::scheduler::{GCWork, GCWorker, WorkBucketStage}; + +use crate::Ruby; + +pub struct ChunkedVecCollector { + vecs: Vec>, + current_vec: Vec, + chunk_size: usize, +} + +impl ChunkedVecCollector { + pub fn new(chunk_size: usize) -> Self { + Self { + vecs: vec![], + current_vec: Vec::with_capacity(chunk_size), + chunk_size, + } + } + + pub fn add(&mut self, item: T) { + self.current_vec.push(item); + if self.current_vec.len() == self.chunk_size { + self.flush(); + } + } + + fn flush(&mut self) { + let new_vec = Vec::with_capacity(self.chunk_size); + let old_vec = std::mem::replace(&mut self.current_vec, new_vec); + self.vecs.push(old_vec); + } + + pub fn into_vecs(mut self) -> Vec> { + if !self.current_vec.is_empty() { + self.flush(); + } + self.vecs + } +} + +impl Extend for ChunkedVecCollector { + fn extend>(&mut self, iter: T) { + for item in iter { + self.add(item); + } + } +} + +pub struct AfterAll { + counter: AtomicUsize, + stage: WorkBucketStage, + packets: AtomicRefCell>>>, +} + +unsafe impl Sync for AfterAll {} + +impl AfterAll { + pub fn new(stage: WorkBucketStage) -> Self { + Self { + counter: AtomicUsize::new(0), + stage, + packets: AtomicRefCell::new(vec![]), + } + } + + pub fn add_packets(&self, mut packets: Vec>>) { + let mut borrow = self.packets.borrow_mut(); + borrow.append(&mut packets); + } + + pub fn count_up(&self, n: usize) { + self.counter.fetch_add(n, Ordering::SeqCst); + } + + pub fn count_down(&self, worker: &mut GCWorker) { + let old = self.counter.fetch_sub(1, Ordering::SeqCst); + if old == 1 { + let packets = { + let mut borrow = self.packets.borrow_mut(); + std::mem::take(borrow.as_mut()) + }; + worker.scheduler().work_buckets[self.stage].bulk_add(packets); + } + } +} diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs new file mode 100644 index 00000000000000..fb04f1d414128c --- /dev/null +++ b/gc/mmtk/src/weak_proc.rs @@ -0,0 +1,382 @@ +use std::sync::{Arc, Mutex}; + +use mmtk::{ + scheduler::{GCWork, GCWorker, WorkBucketStage}, + util::ObjectReference, + vm::ObjectTracerContext, +}; + +use crate::{ + abi::{st_table, GCThreadTLS, RubyObjectAccess}, + binding::MovedGIVTblEntry, + upcalls, + utils::AfterAll, + Ruby, +}; + +pub struct WeakProcessor { + /// Objects that needs `obj_free` called when dying. + /// If it is a bottleneck, replace it with a lock-free data structure, + /// or add candidates in batch. + obj_free_candidates: Mutex>, +} + +impl Default for WeakProcessor { + fn default() -> Self { + Self::new() + } +} + +impl WeakProcessor { + pub fn new() -> Self { + Self { + obj_free_candidates: Mutex::new(Vec::new()), + } + } + + /// Add an object as a candidate for `obj_free`. + /// + /// Multiple mutators can call it concurrently, so it has `&self`. + pub fn add_obj_free_candidate(&self, object: ObjectReference) { + let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); + obj_free_candidates.push(object); + } + + /// Add many objects as candidates for `obj_free`. + /// + /// Multiple mutators can call it concurrently, so it has `&self`. + pub fn add_obj_free_candidates(&self, objects: &[ObjectReference]) { + let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); + for object in objects.iter().copied() { + obj_free_candidates.push(object); + } + } + + pub fn get_all_obj_free_candidates(&self) -> Vec { + let mut obj_free_candidates = self.obj_free_candidates.lock().unwrap(); + std::mem::take(obj_free_candidates.as_mut()) + } + + pub fn process_weak_stuff( + &self, + worker: &mut GCWorker, + _tracer_context: impl ObjectTracerContext, + ) { + worker.add_work(WorkBucketStage::VMRefClosure, ProcessObjFreeCandidates); + + worker.scheduler().work_buckets[WorkBucketStage::VMRefClosure].bulk_add(vec![ + Box::new(UpdateGenericIvTbl) as _, + // Box::new(UpdateFrozenStringsTable) as _, + Box::new(UpdateFinalizerTable) as _, + Box::new(UpdateObjIdTables) as _, + // Box::new(UpdateGlobalSymbolsTable) as _, + Box::new(UpdateOverloadedCmeTable) as _, + Box::new(UpdateCiTable) as _, + Box::new(UpdateWbUnprotectedObjectsList) as _, + ]); + + let forward = crate::mmtk().get_plan().current_gc_may_move_object(); + + // Experimenting with frozen strings table + Self::process_weak_table_chunked( + "frozen strings", + (upcalls().get_frozen_strings_table)(), + true, + false, + forward, + worker, + ); + + Self::process_weak_table_chunked( + "global symbols", + (upcalls().get_global_symbols_table)(), + false, + true, + forward, + worker, + ); + } + + pub fn process_weak_table_chunked( + name: &str, + table: *mut st_table, + weak_keys: bool, + weak_values: bool, + forward: bool, + worker: &mut GCWorker, + ) { + let mut entries_start = 0; + let mut entries_bound = 0; + let mut bins_num = 0; + (upcalls().st_get_size_info)(table, &mut entries_start, &mut entries_bound, &mut bins_num); + debug!( + "name: {name}, entries_start: {entries_start}, entries_bound: {entries_bound}, bins_num: {bins_num}" + ); + + let entries_chunk_size = crate::binding().st_entries_chunk_size; + let bins_chunk_size = crate::binding().st_bins_chunk_size; + + let after_all = Arc::new(AfterAll::new(WorkBucketStage::VMRefClosure)); + + let entries_packets = (entries_start..entries_bound) + .step_by(entries_chunk_size) + .map(|begin| { + let end = (begin + entries_chunk_size).min(entries_bound); + let after_all = after_all.clone(); + Box::new(UpdateTableEntriesParallel { + name: name.to_string(), + table, + begin, + end, + weak_keys, + weak_values, + forward, + after_all, + }) as _ + }) + .collect::>(); + after_all.count_up(entries_packets.len()); + + let bins_packets = (0..bins_num) + .step_by(entries_chunk_size) + .map(|begin| { + let end = (begin + bins_chunk_size).min(bins_num); + Box::new(UpdateTableBinsParallel { + name: name.to_string(), + table, + begin, + end, + }) as _ + }) + .collect::>(); + after_all.add_packets(bins_packets); + + worker.scheduler().work_buckets[WorkBucketStage::VMRefClosure].bulk_add(entries_packets); + } + + /// Update generic instance variable tables. + /// + /// Objects moved during GC should have their entries in the global `generic_iv_tbl_` hash + /// table updated, and dead objects should have their entries removed. + fn update_generic_iv_tbl() { + // Update `generic_iv_tbl_` entries for moved objects. We could update the entries in + // `ObjectModel::move`. However, because `st_table` is not thread-safe, we postpone the + // update until now in the VMRefClosure stage. + log::debug!("Updating global ivtbl entries..."); + { + let mut moved_givtbl = crate::binding() + .moved_givtbl + .try_lock() + .expect("Should have no race in weak_proc"); + for (new_objref, MovedGIVTblEntry { old_objref, .. }) in moved_givtbl.drain() { + trace!(" givtbl {} -> {}", old_objref, new_objref); + RubyObjectAccess::from_objref(new_objref).clear_has_moved_givtbl(); + (upcalls().move_givtbl)(old_objref, new_objref); + } + } + log::debug!("Updated global ivtbl entries."); + + // Clean up entries for dead objects. + log::debug!("Cleaning up global ivtbl entries..."); + (crate::upcalls().cleanup_generic_iv_tbl)(); + log::debug!("Cleaning up global ivtbl entries."); + } +} + +struct ProcessObjFreeCandidates; + +impl GCWork for ProcessObjFreeCandidates { + fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { + // If it blocks, it is a bug. + let mut obj_free_candidates = crate::binding() + .weak_proc + .obj_free_candidates + .try_lock() + .expect("It's GC time. No mutators should hold this lock at this time."); + + let n_cands = obj_free_candidates.len(); + + debug!("Total: {} candidates", n_cands); + + // Process obj_free + let mut new_candidates = Vec::new(); + + for object in obj_free_candidates.iter().copied() { + if object.is_reachable::() { + // Forward and add back to the candidate list. + let new_object = object.forward(); + trace!( + "Forwarding obj_free candidate: {} -> {}", + object, + new_object + ); + new_candidates.push(new_object); + } else { + (upcalls().call_obj_free)(object); + } + } + + *obj_free_candidates = new_candidates; + } +} + +trait GlobalTableProcessingWork { + fn process_table(&mut self); + + fn do_work(&mut self, worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { + let gc_tls = unsafe { GCThreadTLS::from_vwt_check(worker.tls) }; + + // `hash_foreach_replace` depends on `gb_object_moved_p` which has to have the semantics + // of `trace_object` due to the way it is used in `UPDATE_IF_MOVED`. + let forward_object = |_worker, object: ObjectReference, _pin| { + debug_assert!( + mmtk::memory_manager::is_mmtk_object(object.to_address::()), + "{} is not an MMTk object", + object + ); + let result = object.forward(); + trace!("Forwarding reference: {} -> {}", object, result); + result + }; + + gc_tls + .object_closure + .set_temporarily_and_run_code(forward_object, || { + self.process_table(); + }); + } +} + +macro_rules! define_global_table_processor { + ($name: ident, $code: expr) => { + struct $name; + impl GlobalTableProcessingWork for $name { + fn process_table(&mut self) { + $code + } + } + impl GCWork for $name { + fn do_work(&mut self, worker: &mut GCWorker, mmtk: &'static mmtk::MMTK) { + GlobalTableProcessingWork::do_work(self, worker, mmtk); + } + } + }; +} + +define_global_table_processor!(UpdateGenericIvTbl, { + WeakProcessor::update_generic_iv_tbl(); +}); + +define_global_table_processor!(UpdateFrozenStringsTable, { + (crate::upcalls().update_frozen_strings_table)() +}); + +define_global_table_processor!(UpdateFinalizerTable, { + (crate::upcalls().update_finalizer_table)() +}); + +define_global_table_processor!(UpdateObjIdTables, { + (crate::upcalls().update_obj_id_tables)() +}); + +define_global_table_processor!(UpdateGlobalSymbolsTable, { + (crate::upcalls().update_global_symbols_table)() +}); + +define_global_table_processor!(UpdateOverloadedCmeTable, { + (crate::upcalls().update_overloaded_cme_table)() +}); + +define_global_table_processor!(UpdateCiTable, (crate::upcalls().update_ci_table)()); + +struct UpdateTableEntriesParallel { + name: String, + table: *mut st_table, + begin: usize, + end: usize, + weak_keys: bool, + weak_values: bool, + forward: bool, + after_all: Arc, +} + +unsafe impl Send for UpdateTableEntriesParallel {} + +impl UpdateTableEntriesParallel {} + +impl GCWork for UpdateTableEntriesParallel { + fn do_work(&mut self, worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { + debug!("Updating entries of {} table", self.name); + (upcalls().st_update_entries_range)( + self.table, + self.begin, + self.end, + self.weak_keys, + self.weak_values, + self.forward, + ); + debug!("Done updating entries of {} table", self.name); + self.after_all.count_down(worker); + } +} + +struct UpdateTableBinsParallel { + name: String, + table: *mut st_table, + begin: usize, + end: usize, +} + +unsafe impl Send for UpdateTableBinsParallel {} + +impl UpdateTableBinsParallel {} + +impl GCWork for UpdateTableBinsParallel { + fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { + debug!("Updating bins of {} table", self.name); + (upcalls().st_update_bins_range)(self.table, self.begin, self.end); + debug!("Done updating bins of {} table", self.name); + } +} + +struct UpdateWbUnprotectedObjectsList; + +impl GCWork for UpdateWbUnprotectedObjectsList { + fn do_work(&mut self, _worker: &mut GCWorker, _mmtk: &'static mmtk::MMTK) { + let mut objects = crate::binding().wb_unprotected_objects.try_lock().expect( + "Someone is holding the lock of wb_unprotected_objects during weak processing phase?", + ); + + let old_objects = std::mem::take(&mut *objects); + + debug!("Updating {} WB-unprotected objects", old_objects.len()); + + for object in old_objects { + if object.is_reachable::() { + // Forward and add back to the candidate list. + let new_object = object.forward(); + trace!( + "Forwarding WB-unprotected object: {} -> {}", + object, + new_object + ); + objects.insert(new_object); + } else { + trace!("Removing WB-unprotected object from list: {}", object); + } + } + + debug!("Retained {} live WB-unprotected objects.", objects.len()); + } +} + +// Provide a shorthand `object.forward()`. +trait Forwardable { + fn forward(&self) -> Self; +} + +impl Forwardable for ObjectReference { + fn forward(&self) -> Self { + self.get_forwarded_object::().unwrap_or(*self) + } +}