ruby/gc.c
Matt Valentine-House f543c68e1c Provide GC.config to disable major GC collections
This feature provides a new method `GC.config` that configures internal
GC configuration variables provided by an individual GC implementation.

Implemented in this PR is the option `full_mark`: a boolean value that
will determine whether the Ruby GC is allowed to run a major collection
while the process is running.

It has the following semantics

This feature configures Ruby's GC to only run minor GC's. It's designed
to give users relying on Out of Band GC complete control over when a
major GC is run. Configuring `full_mark: false` does two main things:

* Never runs a Major GC. When the heap runs out of space during a minor
  and when a major would traditionally be run, instead we allocate more
  heap pages, and mark objspace as needing a major GC.
* Don't increment object ages. We don't promote objects during GC, this
  will cause every object to be scanned on every minor. This is an
  intentional trade-off between minor GC's doing more work every time,
  and potentially promoting objects that will then never be GC'd.

The intention behind not aging objects is that users of this feature
should use a preforking web server, or some other method of pre-warming
the oldgen (like Nakayoshi fork)before disabling Majors. That way most
objects that are going to be old will have already been promoted.

This will interleave major and minor GC collections in exactly the same
what that the Ruby GC runs in versions previously to this. This is the
default behaviour.

* This new method has the following extra semantics:
  - `GC.config` with no arguments returns a hash of the keys of the
    currently configured GC
  - `GC.config` with a key pair (eg. `GC.config(full_mark: true)` sets
    the matching config key to the corresponding value and returns the
    entire known config hash, including the new values. If the key does
    not exist, `nil` is returned

* When a minor GC is run, Ruby sets an internal status flag to determine
  whether the next GC will be a major or a minor. When `full_mark:
  false` this flag is ignored and every GC will be a minor.

  This status flag can be accessed at
  `GC.latest_gc_info(:needs_major_by)`. Any value other than `nil` means
  that the next collection would have been a major.

  Thus it's possible to use this feature to check at a predetermined
  time, whether a major GC is necessary and run one if it is. eg. After
  a request has finished processing.

  ```ruby
  if GC.latest_gc_info(:needs_major_by)
    GC.start(full_mark: true)
  end
  ```

[Feature #20443]
2024-07-12 14:43:33 +01:00

4559 lines
125 KiB
C

/**********************************************************************
gc.c -
$Author$
created at: Tue Oct 5 09:44:46 JST 1993
Copyright (C) 1993-2007 Yukihiro Matsumoto
Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
Copyright (C) 2000 Information-technology Promotion Agency, Japan
**********************************************************************/
#define rb_data_object_alloc rb_data_object_alloc
#define rb_data_typed_object_alloc rb_data_typed_object_alloc
#include "ruby/internal/config.h"
#ifdef _WIN32
# include "ruby/ruby.h"
#endif
#if defined(__wasm__) && !defined(__EMSCRIPTEN__)
# include "wasm/setjmp.h"
# include "wasm/machine.h"
#else
# include <setjmp.h>
#endif
#include <stdarg.h>
#include <stdio.h>
/* MALLOC_HEADERS_BEGIN */
#ifndef HAVE_MALLOC_USABLE_SIZE
# ifdef RUBY_ALTERNATIVE_MALLOC_HEADER
/* Alternative malloc header is included in ruby/missing.h */
# elif defined(HAVE_MALLOC_H)
# include <malloc.h>
# elif defined(HAVE_MALLOC_NP_H)
# include <malloc_np.h>
# elif defined(HAVE_MALLOC_MALLOC_H)
# include <malloc/malloc.h>
# endif
# ifdef _WIN32
# define HAVE_MALLOC_USABLE_SIZE
# define malloc_usable_size(a) _msize(a)
# elif defined HAVE_MALLOC_SIZE
# define HAVE_MALLOC_USABLE_SIZE
# define malloc_usable_size(a) malloc_size(a)
# endif
#else
# include <malloc.h>
#endif
# define GC_ASSERT
/* MALLOC_HEADERS_END */
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
# include <sys/resource.h>
#endif
#if defined _WIN32 || defined __CYGWIN__
# include <windows.h>
#elif defined(HAVE_POSIX_MEMALIGN)
#elif defined(HAVE_MEMALIGN)
# include <malloc.h>
#endif
#include <sys/types.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#undef LIST_HEAD /* ccan/list conflicts with BSD-origin sys/queue.h. */
#include "constant.h"
#include "darray.h"
#include "debug_counter.h"
#include "eval_intern.h"
#include "gc/gc_impl.h"
#include "id_table.h"
#include "internal.h"
#include "internal/class.h"
#include "internal/compile.h"
#include "internal/complex.h"
#include "internal/cont.h"
#include "internal/error.h"
#include "internal/eval.h"
#include "internal/gc.h"
#include "internal/hash.h"
#include "internal/imemo.h"
#include "internal/io.h"
#include "internal/numeric.h"
#include "internal/object.h"
#include "internal/proc.h"
#include "internal/rational.h"
#include "internal/sanitizers.h"
#include "internal/struct.h"
#include "internal/symbol.h"
#include "internal/thread.h"
#include "internal/variable.h"
#include "internal/warnings.h"
#include "rjit.h"
#include "probes.h"
#include "regint.h"
#include "ruby/debug.h"
#include "ruby/io.h"
#include "ruby/re.h"
#include "ruby/st.h"
#include "ruby/thread.h"
#include "ruby/util.h"
#include "ruby/vm.h"
#include "ruby_assert.h"
#include "ruby_atomic.h"
#include "symbol.h"
#include "vm_core.h"
#include "vm_sync.h"
#include "vm_callinfo.h"
#include "ractor_core.h"
#include "builtin.h"
#include "shape.h"
RUBY_SYMBOL_EXPORT_BEGIN
size_t rb_size_mul_or_raise(size_t, size_t, VALUE);
bool rb_gc_obj_free(void *objspace, VALUE obj);
size_t rb_gc_obj_optimal_size(VALUE obj);
void rb_gc_mark_children(void *objspace, VALUE obj);
void rb_gc_update_object_references(void *objspace, VALUE obj);
void rb_gc_update_vm_references(void *objspace);
void rb_gc_reachable_objects_from_callback(VALUE obj);
void rb_gc_event_hook(VALUE obj, rb_event_flag_t event);
void *rb_gc_get_objspace(void);
size_t rb_size_mul_or_raise(size_t x, size_t y, VALUE exc);
void rb_gc_run_obj_finalizer(VALUE objid, long count, VALUE (*callback)(long i, void *data), void *data);
void rb_gc_set_pending_interrupt(void);
void rb_gc_unset_pending_interrupt(void);
bool rb_gc_obj_free(void *objspace, VALUE obj);
void rb_gc_mark_roots(void *objspace, const char **categoryp);
void rb_gc_ractor_newobj_cache_foreach(void (*func)(void *cache, void *data), void *data);
bool rb_gc_multi_ractor_p(void);
void rb_objspace_reachable_objects_from_root(void (func)(const char *category, VALUE, void *), void *passing_data);
void rb_objspace_reachable_objects_from(VALUE obj, void (func)(VALUE, void *), void *data);
void rb_obj_info_dump(VALUE obj);
const char *rb_obj_info(VALUE obj);
bool rb_gc_shutdown_call_finalizer_p(VALUE obj);
uint32_t rb_gc_get_shape(VALUE obj);
void rb_gc_set_shape(VALUE obj, uint32_t shape_id);
uint32_t rb_gc_rebuild_shape(VALUE obj, size_t size_pool_id);
size_t rb_obj_memsize_of(VALUE obj);
unsigned int
rb_gc_vm_lock(void)
{
unsigned int lev;
RB_VM_LOCK_ENTER_LEV(&lev);
return lev;
}
void
rb_gc_vm_unlock(unsigned int lev)
{
RB_VM_LOCK_LEAVE_LEV(&lev);
}
unsigned int
rb_gc_cr_lock(void)
{
unsigned int lev;
RB_VM_LOCK_ENTER_CR_LEV(GET_RACTOR(), &lev);
return lev;
}
void
rb_gc_cr_unlock(unsigned int lev)
{
RB_VM_LOCK_LEAVE_CR_LEV(GET_RACTOR(), &lev);
}
unsigned int
rb_gc_vm_lock_no_barrier(void)
{
unsigned int lev;
RB_VM_LOCK_ENTER_LEV_NB(&lev);
return lev;
}
void
rb_gc_vm_unlock_no_barrier(unsigned int lev)
{
RB_VM_LOCK_LEAVE_LEV(&lev);
}
void
rb_gc_vm_barrier(void)
{
rb_vm_barrier();
}
void
rb_gc_event_hook(VALUE obj, rb_event_flag_t event)
{
if (LIKELY(!(ruby_vm_event_flags & event))) return;
rb_execution_context_t *ec = GET_EC();
if (!ec->cfp) return;
EXEC_EVENT_HOOK(ec, event, ec->cfp->self, 0, 0, 0, obj);
}
void *
rb_gc_get_objspace(void)
{
return GET_VM()->objspace;
}
void
rb_gc_ractor_newobj_cache_foreach(void (*func)(void *cache, void *data), void *data)
{
rb_ractor_t *r = NULL;
ccan_list_for_each(&GET_VM()->ractor.set, r, vmlr_node) {
func(r->newobj_cache, data);
}
}
void
rb_gc_run_obj_finalizer(VALUE objid, long count, VALUE (*callback)(long i, void *data), void *data)
{
volatile struct {
VALUE errinfo;
VALUE final;
rb_control_frame_t *cfp;
VALUE *sp;
long finished;
} saved;
rb_execution_context_t * volatile ec = GET_EC();
#define RESTORE_FINALIZER() (\
ec->cfp = saved.cfp, \
ec->cfp->sp = saved.sp, \
ec->errinfo = saved.errinfo)
saved.errinfo = ec->errinfo;
saved.cfp = ec->cfp;
saved.sp = ec->cfp->sp;
saved.finished = 0;
saved.final = Qundef;
EC_PUSH_TAG(ec);
enum ruby_tag_type state = EC_EXEC_TAG();
if (state != TAG_NONE) {
++saved.finished; /* skip failed finalizer */
VALUE failed_final = saved.final;
saved.final = Qundef;
if (!UNDEF_P(failed_final) && !NIL_P(ruby_verbose)) {
rb_warn("Exception in finalizer %+"PRIsVALUE, failed_final);
rb_ec_error_print(ec, ec->errinfo);
}
}
for (long i = saved.finished; RESTORE_FINALIZER(), i < count; saved.finished = ++i) {
saved.final = callback(i, data);
rb_check_funcall(saved.final, idCall, 1, &objid);
}
EC_POP_TAG();
#undef RESTORE_FINALIZER
}
void
rb_gc_set_pending_interrupt(void)
{
rb_execution_context_t *ec = GET_EC();
ec->interrupt_mask |= PENDING_INTERRUPT_MASK;
}
void
rb_gc_unset_pending_interrupt(void)
{
rb_execution_context_t *ec = GET_EC();
ec->interrupt_mask &= ~PENDING_INTERRUPT_MASK;
}
bool
rb_gc_multi_ractor_p(void)
{
return rb_multi_ractor_p();
}
bool rb_obj_is_main_ractor(VALUE gv);
bool
rb_gc_shutdown_call_finalizer_p(VALUE obj)
{
switch (BUILTIN_TYPE(obj)) {
case T_DATA:
if (!ruby_free_at_exit_p() && (!DATA_PTR(obj) || !RDATA(obj)->dfree)) return false;
if (rb_obj_is_thread(obj)) return false;
if (rb_obj_is_mutex(obj)) return false;
if (rb_obj_is_fiber(obj)) return false;
if (rb_obj_is_main_ractor(obj)) return false;
return true;
case T_FILE:
return true;
case T_SYMBOL:
if (RSYMBOL(obj)->fstr &&
(BUILTIN_TYPE(RSYMBOL(obj)->fstr) == T_NONE ||
BUILTIN_TYPE(RSYMBOL(obj)->fstr) == T_ZOMBIE)) {
RSYMBOL(obj)->fstr = 0;
}
return true;
case T_NONE:
return false;
default:
return ruby_free_at_exit_p();
}
}
uint32_t
rb_gc_get_shape(VALUE obj)
{
return (uint32_t)rb_shape_get_shape_id(obj);
}
void
rb_gc_set_shape(VALUE obj, uint32_t shape_id)
{
rb_shape_set_shape_id(obj, (uint32_t)shape_id);
}
uint32_t
rb_gc_rebuild_shape(VALUE obj, size_t size_pool_id)
{
rb_shape_t *orig_shape = rb_shape_get_shape(obj);
if (rb_shape_obj_too_complex(obj)) return (uint32_t)OBJ_TOO_COMPLEX_SHAPE_ID;
rb_shape_t *initial_shape = rb_shape_get_shape_by_id((shape_id_t)(size_pool_id + FIRST_T_OBJECT_SHAPE_ID));
rb_shape_t *new_shape = rb_shape_traverse_from_new_root(initial_shape, orig_shape);
if (!new_shape) return 0;
return (uint32_t)rb_shape_id(new_shape);
}
RUBY_SYMBOL_EXPORT_END
void rb_vm_update_references(void *ptr);
#define rb_setjmp(env) RUBY_SETJMP(env)
#define rb_jmp_buf rb_jmpbuf_t
#undef rb_data_object_wrap
#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
#define MAP_ANONYMOUS MAP_ANON
#endif
#define unless_objspace(objspace) \
void *objspace; \
rb_vm_t *unless_objspace_vm = GET_VM(); \
if (unless_objspace_vm) objspace = unless_objspace_vm->objspace; \
else /* return; or objspace will be warned uninitialized */
#define RMOVED(obj) ((struct RMoved *)(obj))
#define TYPED_UPDATE_IF_MOVED(_objspace, _type, _thing) do { \
if (rb_gc_impl_object_moved_p((_objspace), (VALUE)(_thing))) { \
*(_type *)&(_thing) = (_type)rb_gc_impl_location(_objspace, (VALUE)_thing); \
} \
} while (0)
#define UPDATE_IF_MOVED(_objspace, _thing) TYPED_UPDATE_IF_MOVED(_objspace, VALUE, _thing)
#if RUBY_MARK_FREE_DEBUG
int ruby_gc_debug_indent = 0;
#endif
#ifndef RGENGC_CHECK_MODE
# define RGENGC_CHECK_MODE 0
#endif
#ifndef RGENGC_OBJ_INFO
# define RGENGC_OBJ_INFO RGENGC_CHECK_MODE
#endif
#ifndef CALC_EXACT_MALLOC_SIZE
# define CALC_EXACT_MALLOC_SIZE 0
#endif
VALUE rb_mGC;
static size_t malloc_offset = 0;
#if defined(HAVE_MALLOC_USABLE_SIZE)
static size_t
gc_compute_malloc_offset(void)
{
// Different allocators use different metadata storage strategies which result in different
// ideal sizes.
// For instance malloc(64) will waste 8B with glibc, but waste 0B with jemalloc.
// But malloc(56) will waste 0B with glibc, but waste 8B with jemalloc.
// So we try allocating 64, 56 and 48 bytes and select the first offset that doesn't
// waste memory.
// This was tested on Linux with glibc 2.35 and jemalloc 5, and for both it result in
// no wasted memory.
size_t offset = 0;
for (offset = 0; offset <= 16; offset += 8) {
size_t allocated = (64 - offset);
void *test_ptr = malloc(allocated);
size_t wasted = malloc_usable_size(test_ptr) - allocated;
free(test_ptr);
if (wasted == 0) {
return offset;
}
}
return 0;
}
#else
static size_t
gc_compute_malloc_offset(void)
{
// If we don't have malloc_usable_size, we use powers of 2.
return 0;
}
#endif
size_t
rb_malloc_grow_capa(size_t current, size_t type_size)
{
size_t current_capacity = current;
if (current_capacity < 4) {
current_capacity = 4;
}
current_capacity *= type_size;
// We double the current capacity.
size_t new_capacity = (current_capacity * 2);
// And round up to the next power of 2 if it's not already one.
if (rb_popcount64(new_capacity) != 1) {
new_capacity = (size_t)(1 << (64 - nlz_int64(new_capacity)));
}
new_capacity -= malloc_offset;
new_capacity /= type_size;
if (current > new_capacity) {
rb_bug("rb_malloc_grow_capa: current_capacity=%zu, new_capacity=%zu, malloc_offset=%zu", current, new_capacity, malloc_offset);
}
RUBY_ASSERT(new_capacity > current);
return new_capacity;
}
static inline struct rbimpl_size_mul_overflow_tag
size_add_overflow(size_t x, size_t y)
{
size_t z;
bool p;
#if 0
#elif defined(ckd_add)
p = ckd_add(&z, x, y);
#elif __has_builtin(__builtin_add_overflow)
p = __builtin_add_overflow(x, y, &z);
#elif defined(DSIZE_T)
RB_GNUC_EXTENSION DSIZE_T dx = x;
RB_GNUC_EXTENSION DSIZE_T dy = y;
RB_GNUC_EXTENSION DSIZE_T dz = dx + dy;
p = dz > SIZE_MAX;
z = (size_t)dz;
#else
z = x + y;
p = z < y;
#endif
return (struct rbimpl_size_mul_overflow_tag) { p, z, };
}
static inline struct rbimpl_size_mul_overflow_tag
size_mul_add_overflow(size_t x, size_t y, size_t z) /* x * y + z */
{
struct rbimpl_size_mul_overflow_tag t = rbimpl_size_mul_overflow(x, y);
struct rbimpl_size_mul_overflow_tag u = size_add_overflow(t.right, z);
return (struct rbimpl_size_mul_overflow_tag) { t.left || u.left, u.right };
}
static inline struct rbimpl_size_mul_overflow_tag
size_mul_add_mul_overflow(size_t x, size_t y, size_t z, size_t w) /* x * y + z * w */
{
struct rbimpl_size_mul_overflow_tag t = rbimpl_size_mul_overflow(x, y);
struct rbimpl_size_mul_overflow_tag u = rbimpl_size_mul_overflow(z, w);
struct rbimpl_size_mul_overflow_tag v = size_add_overflow(t.right, u.right);
return (struct rbimpl_size_mul_overflow_tag) { t.left || u.left || v.left, v.right };
}
PRINTF_ARGS(NORETURN(static void gc_raise(VALUE, const char*, ...)), 2, 3);
static inline size_t
size_mul_or_raise(size_t x, size_t y, VALUE exc)
{
struct rbimpl_size_mul_overflow_tag t = rbimpl_size_mul_overflow(x, y);
if (LIKELY(!t.left)) {
return t.right;
}
else if (rb_during_gc()) {
rb_memerror(); /* or...? */
}
else {
gc_raise(
exc,
"integer overflow: %"PRIuSIZE
" * %"PRIuSIZE
" > %"PRIuSIZE,
x, y, (size_t)SIZE_MAX);
}
}
size_t
rb_size_mul_or_raise(size_t x, size_t y, VALUE exc)
{
return size_mul_or_raise(x, y, exc);
}
static inline size_t
size_mul_add_or_raise(size_t x, size_t y, size_t z, VALUE exc)
{
struct rbimpl_size_mul_overflow_tag t = size_mul_add_overflow(x, y, z);
if (LIKELY(!t.left)) {
return t.right;
}
else if (rb_during_gc()) {
rb_memerror(); /* or...? */
}
else {
gc_raise(
exc,
"integer overflow: %"PRIuSIZE
" * %"PRIuSIZE
" + %"PRIuSIZE
" > %"PRIuSIZE,
x, y, z, (size_t)SIZE_MAX);
}
}
size_t
rb_size_mul_add_or_raise(size_t x, size_t y, size_t z, VALUE exc)
{
return size_mul_add_or_raise(x, y, z, exc);
}
static inline size_t
size_mul_add_mul_or_raise(size_t x, size_t y, size_t z, size_t w, VALUE exc)
{
struct rbimpl_size_mul_overflow_tag t = size_mul_add_mul_overflow(x, y, z, w);
if (LIKELY(!t.left)) {
return t.right;
}
else if (rb_during_gc()) {
rb_memerror(); /* or...? */
}
else {
gc_raise(
exc,
"integer overflow: %"PRIdSIZE
" * %"PRIdSIZE
" + %"PRIdSIZE
" * %"PRIdSIZE
" > %"PRIdSIZE,
x, y, z, w, (size_t)SIZE_MAX);
}
}
#if defined(HAVE_RB_GC_GUARDED_PTR_VAL) && HAVE_RB_GC_GUARDED_PTR_VAL
/* trick the compiler into thinking a external signal handler uses this */
volatile VALUE rb_gc_guarded_val;
volatile VALUE *
rb_gc_guarded_ptr_val(volatile VALUE *ptr, VALUE val)
{
rb_gc_guarded_val = val;
return ptr;
}
#endif
#if USE_SHARED_GC && !defined(HAVE_DLOPEN)
# error "Shared GC requires dlopen"
#elif USE_SHARED_GC
#include <dlfcn.h>
typedef struct gc_function_map {
// Bootup
void *(*objspace_alloc)(void);
void (*objspace_init)(void *objspace_ptr);
void (*objspace_free)(void *objspace_ptr);
void *(*ractor_cache_alloc)(void *objspace_ptr);
void (*ractor_cache_free)(void *objspace_ptr, void *cache);
void (*set_params)(void *objspace_ptr);
void (*init)(void);
void (*initial_stress_set)(VALUE flag);
size_t *(*size_pool_sizes)(void *objspace_ptr);
// Shutdown
void (*shutdown_free_objects)(void *objspace_ptr);
// GC
void (*start)(void *objspace_ptr, bool full_mark, bool immediate_mark, bool immediate_sweep, bool compact);
bool (*during_gc_p)(void *objspace_ptr);
void (*prepare_heap)(void *objspace_ptr);
void (*gc_enable)(void *objspace_ptr);
void (*gc_disable)(void *objspace_ptr, bool finish_current_gc);
bool (*gc_enabled_p)(void *objspace_ptr);
VALUE (*config_get)(void *objpace_ptr);
VALUE (*config_set)(void *objspace_ptr, VALUE hash);
void (*stress_set)(void *objspace_ptr, VALUE flag);
VALUE (*stress_get)(void *objspace_ptr);
// Object allocation
VALUE (*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);
size_t (*obj_slot_size)(VALUE obj);
size_t (*size_pool_id_for_size)(void *objspace_ptr, size_t size);
bool (*size_allocatable_p)(size_t size);
// Malloc
void *(*malloc)(void *objspace_ptr, size_t size);
void *(*calloc)(void *objspace_ptr, size_t size);
void *(*realloc)(void *objspace_ptr, void *ptr, size_t new_size, size_t old_size);
void (*free)(void *objspace_ptr, void *ptr, size_t old_size);
void (*adjust_memory_usage)(void *objspace_ptr, ssize_t diff);
// Marking
void (*mark)(void *objspace_ptr, VALUE obj);
void (*mark_and_move)(void *objspace_ptr, VALUE *ptr);
void (*mark_and_pin)(void *objspace_ptr, VALUE obj);
void (*mark_maybe)(void *objspace_ptr, VALUE obj);
void (*mark_weak)(void *objspace_ptr, VALUE *ptr);
void (*remove_weak)(void *objspace_ptr, VALUE parent_obj, VALUE *ptr);
void (*objspace_mark)(void *objspace_ptr);
// Compaction
bool (*object_moved_p)(void *objspace_ptr, VALUE obj);
VALUE (*location)(void *objspace_ptr, VALUE value);
// Write barriers
void (*writebarrier)(void *objspace_ptr, VALUE a, VALUE b);
void (*writebarrier_unprotect)(void *objspace_ptr, VALUE obj);
void (*writebarrier_remember)(void *objspace_ptr, VALUE obj);
// Heap walking
void (*each_objects)(void *objspace_ptr, int (*callback)(void *, void *, size_t, void *), void *data);
void (*each_object)(void *objspace_ptr, void (*func)(VALUE obj, void *data), void *data);
// Finalizers
void (*make_zombie)(void *objspace_ptr, VALUE obj, void (*dfree)(void *), void *data);
VALUE (*define_finalizer)(void *objspace_ptr, VALUE obj, VALUE block);
VALUE (*undefine_finalizer)(void *objspace_ptr, VALUE obj);
void (*copy_finalizer)(void *objspace_ptr, VALUE dest, VALUE obj);
void (*shutdown_call_finalizer)(void *objspace_ptr);
// Object ID
VALUE (*object_id)(void *objspace_ptr, VALUE obj);
VALUE (*object_id_to_ref)(void *objspace_ptr, VALUE object_id);
// Statistics
VALUE (*set_measure_total_time)(void *objspace_ptr, VALUE flag);
VALUE (*get_measure_total_time)(void *objspace_ptr);
VALUE (*get_profile_total_time)(void *objspace_ptr);
size_t (*gc_count)(void *objspace_ptr);
VALUE (*latest_gc_info)(void *objspace_ptr, VALUE key);
size_t (*stat)(void *objspace_ptr, VALUE hash_or_sym);
size_t (*stat_heap)(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym);
// Miscellaneous
size_t (*obj_flags)(void *objspace_ptr, VALUE obj, ID* flags, size_t max);
bool (*pointer_to_heap_p)(void *objspace_ptr, const void *ptr);
bool (*garbage_object_p)(void *objspace_ptr, VALUE obj);
void (*set_event_hook)(void *objspace_ptr, const rb_event_flag_t event);
void (*copy_attributes)(void *objspace_ptr, VALUE dest, VALUE obj);
} rb_gc_function_map_t;
static rb_gc_function_map_t rb_gc_functions;
# define RUBY_GC_LIBRARY "RUBY_GC_LIBRARY"
static void
ruby_external_gc_init(void)
{
// Assert that the directory path ends with a /
GC_ASSERT(SHARED_GC_DIR[strlen(SHARED_GC_DIR) - 2] == '/');
char *gc_so_file = getenv(RUBY_GC_LIBRARY);
char *gc_so_path = NULL;
void *handle = NULL;
if (gc_so_file) {
/* Check to make sure that gc_so_file matches /[\w-_.]+/ so that it does
* not load a shared object outside of the directory. */
for (size_t i = 0; i < strlen(gc_so_file); i++) {
char c = gc_so_file[i];
if (isalnum(c)) continue;
switch (c) {
case '-':
case '_':
case '.':
break;
default:
rb_bug("Only alphanumeric, dash, underscore, and period is allowed in "RUBY_GC_LIBRARY"");
}
}
gc_so_path = alloca(strlen(SHARED_GC_DIR) + strlen(gc_so_file) + 1);
strcpy(gc_so_path, SHARED_GC_DIR);
strcpy(gc_so_path + strlen(SHARED_GC_DIR), gc_so_file);
gc_so_path[strlen(SHARED_GC_DIR) + strlen(gc_so_file)] = '\0';
handle = dlopen(gc_so_path, RTLD_LAZY | RTLD_GLOBAL);
if (!handle) {
fprintf(stderr, "%s", dlerror());
rb_bug("ruby_external_gc_init: Shared library %s cannot be opened", gc_so_path);
}
}
# define load_external_gc_func(name) do { \
if (handle) { \
rb_gc_functions.name = dlsym(handle, "rb_gc_impl_" #name); \
if (!rb_gc_functions.name) { \
rb_bug("ruby_external_gc_init: " #name " func not exported by library %s", gc_so_path); \
} \
} \
else { \
rb_gc_functions.name = rb_gc_impl_##name; \
} \
} while (0)
// Bootup
load_external_gc_func(objspace_alloc);
load_external_gc_func(objspace_init);
load_external_gc_func(objspace_free);
load_external_gc_func(ractor_cache_alloc);
load_external_gc_func(ractor_cache_free);
load_external_gc_func(set_params);
load_external_gc_func(init);
load_external_gc_func(initial_stress_set);
load_external_gc_func(size_pool_sizes);
// Shutdown
load_external_gc_func(shutdown_free_objects);
// GC
load_external_gc_func(start);
load_external_gc_func(during_gc_p);
load_external_gc_func(prepare_heap);
load_external_gc_func(gc_enable);
load_external_gc_func(gc_disable);
load_external_gc_func(gc_enabled_p);
load_external_gc_func(config_set);
load_external_gc_func(config_get);
load_external_gc_func(stress_set);
load_external_gc_func(stress_get);
// Object allocation
load_external_gc_func(new_obj);
load_external_gc_func(obj_slot_size);
load_external_gc_func(size_pool_id_for_size);
load_external_gc_func(size_allocatable_p);
// Malloc
load_external_gc_func(malloc);
load_external_gc_func(calloc);
load_external_gc_func(realloc);
load_external_gc_func(free);
load_external_gc_func(adjust_memory_usage);
// Marking
load_external_gc_func(mark);
load_external_gc_func(mark_and_move);
load_external_gc_func(mark_and_pin);
load_external_gc_func(mark_maybe);
load_external_gc_func(mark_weak);
load_external_gc_func(remove_weak);
load_external_gc_func(objspace_mark);
// Compaction
load_external_gc_func(object_moved_p);
load_external_gc_func(location);
// Write barriers
load_external_gc_func(writebarrier);
load_external_gc_func(writebarrier_unprotect);
load_external_gc_func(writebarrier_remember);
// Heap walking
load_external_gc_func(each_objects);
load_external_gc_func(each_object);
// Finalizers
load_external_gc_func(make_zombie);
load_external_gc_func(define_finalizer);
load_external_gc_func(undefine_finalizer);
load_external_gc_func(copy_finalizer);
load_external_gc_func(shutdown_call_finalizer);
// Object ID
load_external_gc_func(object_id);
load_external_gc_func(object_id_to_ref);
// Statistics
load_external_gc_func(set_measure_total_time);
load_external_gc_func(get_measure_total_time);
load_external_gc_func(get_profile_total_time);
load_external_gc_func(gc_count);
load_external_gc_func(latest_gc_info);
load_external_gc_func(stat);
load_external_gc_func(stat_heap);
// Miscellaneous
load_external_gc_func(obj_flags);
load_external_gc_func(pointer_to_heap_p);
load_external_gc_func(garbage_object_p);
load_external_gc_func(set_event_hook);
load_external_gc_func(copy_attributes);
# undef load_external_gc_func
}
// Bootup
# define rb_gc_impl_objspace_alloc rb_gc_functions.objspace_alloc
# define rb_gc_impl_objspace_init rb_gc_functions.objspace_init
# define rb_gc_impl_objspace_free rb_gc_functions.objspace_free
# define rb_gc_impl_ractor_cache_alloc rb_gc_functions.ractor_cache_alloc
# define rb_gc_impl_ractor_cache_free rb_gc_functions.ractor_cache_free
# define rb_gc_impl_set_params rb_gc_functions.set_params
# define rb_gc_impl_init rb_gc_functions.init
# define rb_gc_impl_initial_stress_set rb_gc_functions.initial_stress_set
# define rb_gc_impl_size_pool_sizes rb_gc_functions.size_pool_sizes
// Shutdown
# define rb_gc_impl_shutdown_free_objects rb_gc_functions.shutdown_free_objects
// GC
# define rb_gc_impl_start rb_gc_functions.start
# define rb_gc_impl_during_gc_p rb_gc_functions.during_gc_p
# define rb_gc_impl_prepare_heap rb_gc_functions.prepare_heap
# define rb_gc_impl_gc_enable rb_gc_functions.gc_enable
# define rb_gc_impl_gc_disable rb_gc_functions.gc_disable
# define rb_gc_impl_gc_enabled_p rb_gc_functions.gc_enabled_p
# define rb_gc_impl_config_get rb_gc_functions.config_get
# define rb_gc_impl_config_set rb_gc_functions.config_set
# define rb_gc_impl_stress_set rb_gc_functions.stress_set
# define rb_gc_impl_stress_get rb_gc_functions.stress_get
// Object allocation
# define rb_gc_impl_new_obj rb_gc_functions.new_obj
# define rb_gc_impl_obj_slot_size rb_gc_functions.obj_slot_size
# define rb_gc_impl_size_pool_id_for_size rb_gc_functions.size_pool_id_for_size
# define rb_gc_impl_size_allocatable_p rb_gc_functions.size_allocatable_p
// Malloc
# define rb_gc_impl_malloc rb_gc_functions.malloc
# define rb_gc_impl_calloc rb_gc_functions.calloc
# define rb_gc_impl_realloc rb_gc_functions.realloc
# define rb_gc_impl_free rb_gc_functions.free
# define rb_gc_impl_adjust_memory_usage rb_gc_functions.adjust_memory_usage
// Marking
# define rb_gc_impl_mark rb_gc_functions.mark
# define rb_gc_impl_mark_and_move rb_gc_functions.mark_and_move
# define rb_gc_impl_mark_and_pin rb_gc_functions.mark_and_pin
# define rb_gc_impl_mark_maybe rb_gc_functions.mark_maybe
# define rb_gc_impl_mark_weak rb_gc_functions.mark_weak
# define rb_gc_impl_remove_weak rb_gc_functions.remove_weak
# define rb_gc_impl_objspace_mark rb_gc_functions.objspace_mark
// Compaction
# define rb_gc_impl_object_moved_p rb_gc_functions.object_moved_p
# define rb_gc_impl_location rb_gc_functions.location
// Write barriers
# define rb_gc_impl_writebarrier rb_gc_functions.writebarrier
# define rb_gc_impl_writebarrier_unprotect rb_gc_functions.writebarrier_unprotect
# define rb_gc_impl_writebarrier_remember rb_gc_functions.writebarrier_remember
// Heap walking
# define rb_gc_impl_each_objects rb_gc_functions.each_objects
# define rb_gc_impl_each_object rb_gc_functions.each_object
// Finalizers
# define rb_gc_impl_make_zombie rb_gc_functions.make_zombie
# define rb_gc_impl_define_finalizer rb_gc_functions.define_finalizer
# define rb_gc_impl_undefine_finalizer rb_gc_functions.undefine_finalizer
# define rb_gc_impl_copy_finalizer rb_gc_functions.copy_finalizer
# define rb_gc_impl_shutdown_call_finalizer rb_gc_functions.shutdown_call_finalizer
// Object ID
# define rb_gc_impl_object_id rb_gc_functions.object_id
# define rb_gc_impl_object_id_to_ref rb_gc_functions.object_id_to_ref
// Statistics
# define rb_gc_impl_set_measure_total_time rb_gc_functions.set_measure_total_time
# define rb_gc_impl_get_measure_total_time rb_gc_functions.get_measure_total_time
# define rb_gc_impl_get_profile_total_time rb_gc_functions.get_profile_total_time
# define rb_gc_impl_gc_count rb_gc_functions.gc_count
# define rb_gc_impl_latest_gc_info rb_gc_functions.latest_gc_info
# define rb_gc_impl_stat rb_gc_functions.stat
# define rb_gc_impl_stat_heap rb_gc_functions.stat_heap
// Miscellaneous
# define rb_gc_impl_obj_flags rb_gc_functions.obj_flags
# define rb_gc_impl_pointer_to_heap_p rb_gc_functions.pointer_to_heap_p
# define rb_gc_impl_garbage_object_p rb_gc_functions.garbage_object_p
# define rb_gc_impl_set_event_hook rb_gc_functions.set_event_hook
# define rb_gc_impl_copy_attributes rb_gc_functions.copy_attributes
#endif
void *
rb_objspace_alloc(void)
{
#if USE_SHARED_GC
ruby_external_gc_init();
#endif
void *objspace = rb_gc_impl_objspace_alloc();
ruby_current_vm_ptr->objspace = objspace;
rb_gc_impl_objspace_init(objspace);
return objspace;
}
void
rb_objspace_free(void *objspace)
{
rb_gc_impl_objspace_free(objspace);
}
size_t
rb_gc_obj_slot_size(VALUE obj)
{
return rb_gc_impl_obj_slot_size(obj);
}
static inline VALUE
newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, bool wb_protected, size_t size)
{
VALUE obj = rb_gc_impl_new_obj(rb_gc_get_objspace(), GET_RACTOR()->newobj_cache, klass, flags, v1, v2, v3, wb_protected, size);
if (UNLIKELY(ruby_vm_event_flags & RUBY_INTERNAL_EVENT_NEWOBJ)) {
unsigned int lev;
RB_VM_LOCK_ENTER_CR_LEV(GET_RACTOR(), &lev);
{
memset((char *)obj + RVALUE_SIZE, 0, rb_gc_obj_slot_size(obj) - RVALUE_SIZE);
rb_gc_event_hook(obj, RUBY_INTERNAL_EVENT_NEWOBJ);
}
RB_VM_LOCK_LEAVE_CR_LEV(GET_RACTOR(), &lev);
}
return obj;
}
VALUE
rb_wb_unprotected_newobj_of(VALUE klass, VALUE flags, size_t size)
{
GC_ASSERT((flags & FL_WB_PROTECTED) == 0);
return newobj_of(GET_RACTOR(), klass, flags, 0, 0, 0, FALSE, size);
}
VALUE
rb_wb_protected_newobj_of(rb_execution_context_t *ec, VALUE klass, VALUE flags, size_t size)
{
GC_ASSERT((flags & FL_WB_PROTECTED) == 0);
return newobj_of(rb_ec_ractor_ptr(ec), klass, flags, 0, 0, 0, TRUE, size);
}
#define UNEXPECTED_NODE(func) \
rb_bug(#func"(): GC does not handle T_NODE 0x%x(%p) 0x%"PRIxVALUE, \
BUILTIN_TYPE(obj), (void*)(obj), RBASIC(obj)->flags)
static inline void
rb_data_object_check(VALUE klass)
{
if (klass != rb_cObject && (rb_get_alloc_func(klass) == rb_class_allocate_instance)) {
rb_undef_alloc_func(klass);
rb_warn("undefining the allocator of T_DATA class %"PRIsVALUE, klass);
}
}
VALUE
rb_data_object_wrap(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_DATA_FUNC dfree)
{
RUBY_ASSERT_ALWAYS(dfree != (RUBY_DATA_FUNC)1);
if (klass) rb_data_object_check(klass);
return newobj_of(GET_RACTOR(), klass, T_DATA, (VALUE)dmark, (VALUE)dfree, (VALUE)datap, !dmark, sizeof(struct RTypedData));
}
VALUE
rb_data_object_zalloc(VALUE klass, size_t size, RUBY_DATA_FUNC dmark, RUBY_DATA_FUNC dfree)
{
VALUE obj = rb_data_object_wrap(klass, 0, dmark, dfree);
DATA_PTR(obj) = xcalloc(1, size);
return obj;
}
static VALUE
typed_data_alloc(VALUE klass, VALUE typed_flag, void *datap, const rb_data_type_t *type, size_t size)
{
RBIMPL_NONNULL_ARG(type);
if (klass) rb_data_object_check(klass);
bool wb_protected = (type->flags & RUBY_FL_WB_PROTECTED) || !type->function.dmark;
return newobj_of(GET_RACTOR(), klass, T_DATA, (VALUE)type, 1 | typed_flag, (VALUE)datap, wb_protected, size);
}
VALUE
rb_data_typed_object_wrap(VALUE klass, void *datap, const rb_data_type_t *type)
{
if (UNLIKELY(type->flags & RUBY_TYPED_EMBEDDABLE)) {
rb_raise(rb_eTypeError, "Cannot wrap an embeddable TypedData");
}
return typed_data_alloc(klass, 0, datap, type, sizeof(struct RTypedData));
}
VALUE
rb_data_typed_object_zalloc(VALUE klass, size_t size, const rb_data_type_t *type)
{
if (type->flags & RUBY_TYPED_EMBEDDABLE) {
if (!(type->flags & RUBY_TYPED_FREE_IMMEDIATELY)) {
rb_raise(rb_eTypeError, "Embeddable TypedData must be freed immediately");
}
size_t embed_size = offsetof(struct RTypedData, data) + size;
if (rb_gc_size_allocatable_p(embed_size)) {
VALUE obj = typed_data_alloc(klass, TYPED_DATA_EMBEDDED, 0, type, embed_size);
memset((char *)obj + offsetof(struct RTypedData, data), 0, size);
return obj;
}
}
VALUE obj = typed_data_alloc(klass, 0, NULL, type, sizeof(struct RTypedData));
DATA_PTR(obj) = xcalloc(1, size);
return obj;
}
static size_t
rb_objspace_data_type_memsize(VALUE obj)
{
size_t size = 0;
if (RTYPEDDATA_P(obj)) {
const rb_data_type_t *type = RTYPEDDATA_TYPE(obj);
const void *ptr = RTYPEDDATA_GET_DATA(obj);
if (RTYPEDDATA_TYPE(obj)->flags & RUBY_TYPED_EMBEDDABLE && !RTYPEDDATA_EMBEDDED_P(obj)) {
#ifdef HAVE_MALLOC_USABLE_SIZE
size += malloc_usable_size((void *)ptr);
#endif
}
if (ptr && type->function.dsize) {
size += type->function.dsize(ptr);
}
}
return size;
}
const char *
rb_objspace_data_type_name(VALUE obj)
{
if (RTYPEDDATA_P(obj)) {
return RTYPEDDATA_TYPE(obj)->wrap_struct_name;
}
else {
return 0;
}
}
static enum rb_id_table_iterator_result
cvar_table_free_i(VALUE value, void *ctx)
{
xfree((void *)value);
return ID_TABLE_CONTINUE;
}
static inline void
make_io_zombie(void *objspace, VALUE obj)
{
rb_io_t *fptr = RFILE(obj)->fptr;
rb_gc_impl_make_zombie(objspace, obj, rb_io_fptr_finalize_internal, fptr);
}
static bool
rb_data_free(void *objspace, VALUE obj)
{
void *data = RTYPEDDATA_P(obj) ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
if (data) {
int free_immediately = false;
void (*dfree)(void *);
if (RTYPEDDATA_P(obj)) {
free_immediately = (RTYPEDDATA(obj)->type->flags & RUBY_TYPED_FREE_IMMEDIATELY) != 0;
dfree = RTYPEDDATA(obj)->type->function.dfree;
}
else {
dfree = RDATA(obj)->dfree;
}
if (dfree) {
if (dfree == RUBY_DEFAULT_FREE) {
if (!RTYPEDDATA_EMBEDDED_P(obj)) {
xfree(data);
RB_DEBUG_COUNTER_INC(obj_data_xfree);
}
}
else if (free_immediately) {
(*dfree)(data);
if (RTYPEDDATA_TYPE(obj)->flags & RUBY_TYPED_EMBEDDABLE && !RTYPEDDATA_EMBEDDED_P(obj)) {
xfree(data);
}
RB_DEBUG_COUNTER_INC(obj_data_imm_free);
}
else {
rb_gc_impl_make_zombie(rb_gc_get_objspace(), obj, dfree, data);
RB_DEBUG_COUNTER_INC(obj_data_zombie);
return FALSE;
}
}
else {
RB_DEBUG_COUNTER_INC(obj_data_empty);
}
}
return true;
}
bool
rb_gc_obj_free(void *objspace, VALUE obj)
{
RB_DEBUG_COUNTER_INC(obj_free);
rb_gc_event_hook(obj, RUBY_INTERNAL_EVENT_FREEOBJ);
switch (BUILTIN_TYPE(obj)) {
case T_NIL:
case T_FIXNUM:
case T_TRUE:
case T_FALSE:
rb_bug("obj_free() called for broken object");
break;
default:
break;
}
if (FL_TEST(obj, FL_EXIVAR)) {
rb_free_generic_ivar((VALUE)obj);
FL_UNSET(obj, FL_EXIVAR);
}
switch (BUILTIN_TYPE(obj)) {
case T_OBJECT:
if (rb_shape_obj_too_complex(obj)) {
RB_DEBUG_COUNTER_INC(obj_obj_too_complex);
st_free_table(ROBJECT_IV_HASH(obj));
}
else if (RBASIC(obj)->flags & ROBJECT_EMBED) {
RB_DEBUG_COUNTER_INC(obj_obj_embed);
}
else {
xfree(ROBJECT(obj)->as.heap.ivptr);
RB_DEBUG_COUNTER_INC(obj_obj_ptr);
}
break;
case T_MODULE:
case T_CLASS:
rb_id_table_free(RCLASS_M_TBL(obj));
rb_cc_table_free(obj);
if (rb_shape_obj_too_complex(obj)) {
st_free_table((st_table *)RCLASS_IVPTR(obj));
}
else {
xfree(RCLASS_IVPTR(obj));
}
if (RCLASS_CONST_TBL(obj)) {
rb_free_const_table(RCLASS_CONST_TBL(obj));
}
if (RCLASS_CVC_TBL(obj)) {
rb_id_table_foreach_values(RCLASS_CVC_TBL(obj), cvar_table_free_i, NULL);
rb_id_table_free(RCLASS_CVC_TBL(obj));
}
rb_class_remove_subclass_head(obj);
rb_class_remove_from_module_subclasses(obj);
rb_class_remove_from_super_subclasses(obj);
if (FL_TEST_RAW(obj, RCLASS_SUPERCLASSES_INCLUDE_SELF)) {
xfree(RCLASS_SUPERCLASSES(obj));
}
(void)RB_DEBUG_COUNTER_INC_IF(obj_module_ptr, BUILTIN_TYPE(obj) == T_MODULE);
(void)RB_DEBUG_COUNTER_INC_IF(obj_class_ptr, BUILTIN_TYPE(obj) == T_CLASS);
break;
case T_STRING:
rb_str_free(obj);
break;
case T_ARRAY:
rb_ary_free(obj);
break;
case T_HASH:
#if USE_DEBUG_COUNTER
switch (RHASH_SIZE(obj)) {
case 0:
RB_DEBUG_COUNTER_INC(obj_hash_empty);
break;
case 1:
RB_DEBUG_COUNTER_INC(obj_hash_1);
break;
case 2:
RB_DEBUG_COUNTER_INC(obj_hash_2);
break;
case 3:
RB_DEBUG_COUNTER_INC(obj_hash_3);
break;
case 4:
RB_DEBUG_COUNTER_INC(obj_hash_4);
break;
case 5:
case 6:
case 7:
case 8:
RB_DEBUG_COUNTER_INC(obj_hash_5_8);
break;
default:
GC_ASSERT(RHASH_SIZE(obj) > 8);
RB_DEBUG_COUNTER_INC(obj_hash_g8);
}
if (RHASH_AR_TABLE_P(obj)) {
if (RHASH_AR_TABLE(obj) == NULL) {
RB_DEBUG_COUNTER_INC(obj_hash_null);
}
else {
RB_DEBUG_COUNTER_INC(obj_hash_ar);
}
}
else {
RB_DEBUG_COUNTER_INC(obj_hash_st);
}
#endif
rb_hash_free(obj);
break;
case T_REGEXP:
if (RREGEXP(obj)->ptr) {
onig_free(RREGEXP(obj)->ptr);
RB_DEBUG_COUNTER_INC(obj_regexp_ptr);
}
break;
case T_DATA:
if (!rb_data_free(objspace, obj)) return false;
break;
case T_MATCH:
{
rb_matchext_t *rm = RMATCH_EXT(obj);
#if USE_DEBUG_COUNTER
if (rm->regs.num_regs >= 8) {
RB_DEBUG_COUNTER_INC(obj_match_ge8);
}
else if (rm->regs.num_regs >= 4) {
RB_DEBUG_COUNTER_INC(obj_match_ge4);
}
else if (rm->regs.num_regs >= 1) {
RB_DEBUG_COUNTER_INC(obj_match_under4);
}
#endif
onig_region_free(&rm->regs, 0);
xfree(rm->char_offset);
RB_DEBUG_COUNTER_INC(obj_match_ptr);
}
break;
case T_FILE:
if (RFILE(obj)->fptr) {
make_io_zombie(objspace, obj);
RB_DEBUG_COUNTER_INC(obj_file_ptr);
return FALSE;
}
break;
case T_RATIONAL:
RB_DEBUG_COUNTER_INC(obj_rational);
break;
case T_COMPLEX:
RB_DEBUG_COUNTER_INC(obj_complex);
break;
case T_MOVED:
break;
case T_ICLASS:
/* Basically , T_ICLASS shares table with the module */
if (RICLASS_OWNS_M_TBL_P(obj)) {
/* Method table is not shared for origin iclasses of classes */
rb_id_table_free(RCLASS_M_TBL(obj));
}
if (RCLASS_CALLABLE_M_TBL(obj) != NULL) {
rb_id_table_free(RCLASS_CALLABLE_M_TBL(obj));
}
rb_class_remove_subclass_head(obj);
rb_cc_table_free(obj);
rb_class_remove_from_module_subclasses(obj);
rb_class_remove_from_super_subclasses(obj);
RB_DEBUG_COUNTER_INC(obj_iclass_ptr);
break;
case T_FLOAT:
RB_DEBUG_COUNTER_INC(obj_float);
break;
case T_BIGNUM:
if (!BIGNUM_EMBED_P(obj) && BIGNUM_DIGITS(obj)) {
xfree(BIGNUM_DIGITS(obj));
RB_DEBUG_COUNTER_INC(obj_bignum_ptr);
}
else {
RB_DEBUG_COUNTER_INC(obj_bignum_embed);
}
break;
case T_NODE:
UNEXPECTED_NODE(obj_free);
break;
case T_STRUCT:
if ((RBASIC(obj)->flags & RSTRUCT_EMBED_LEN_MASK) ||
RSTRUCT(obj)->as.heap.ptr == NULL) {
RB_DEBUG_COUNTER_INC(obj_struct_embed);
}
else {
xfree((void *)RSTRUCT(obj)->as.heap.ptr);
RB_DEBUG_COUNTER_INC(obj_struct_ptr);
}
break;
case T_SYMBOL:
{
rb_gc_free_dsymbol(obj);
RB_DEBUG_COUNTER_INC(obj_symbol);
}
break;
case T_IMEMO:
rb_imemo_free((VALUE)obj);
break;
default:
rb_bug("gc_sweep(): unknown data type 0x%x(%p) 0x%"PRIxVALUE,
BUILTIN_TYPE(obj), (void*)obj, RBASIC(obj)->flags);
}
if (FL_TEST(obj, FL_FINALIZE)) {
rb_gc_impl_make_zombie(rb_gc_get_objspace(), obj, 0, 0);
return FALSE;
}
else {
RBASIC(obj)->flags = 0;
return TRUE;
}
}
void
rb_objspace_set_event_hook(const rb_event_flag_t event)
{
rb_gc_impl_set_event_hook(rb_gc_get_objspace(), event);
}
static int
internal_object_p(VALUE obj)
{
void *ptr = asan_unpoison_object_temporary(obj);
if (RBASIC(obj)->flags) {
switch (BUILTIN_TYPE(obj)) {
case T_NODE:
UNEXPECTED_NODE(internal_object_p);
break;
case T_NONE:
case T_MOVED:
case T_IMEMO:
case T_ICLASS:
case T_ZOMBIE:
break;
case T_CLASS:
if (!RBASIC(obj)->klass) break;
if (RCLASS_SINGLETON_P(obj)) {
return rb_singleton_class_internal_p(obj);
}
return 0;
default:
if (!RBASIC(obj)->klass) break;
return 0;
}
}
if (ptr || !RBASIC(obj)->flags) {
asan_poison_object(obj);
}
return 1;
}
int
rb_objspace_internal_object_p(VALUE obj)
{
return internal_object_p(obj);
}
struct os_each_struct {
size_t num;
VALUE of;
};
static int
os_obj_of_i(void *vstart, void *vend, size_t stride, void *data)
{
struct os_each_struct *oes = (struct os_each_struct *)data;
VALUE v = (VALUE)vstart;
for (; v != (VALUE)vend; v += stride) {
if (!internal_object_p(v)) {
if (!oes->of || rb_obj_is_kind_of(v, oes->of)) {
if (!rb_multi_ractor_p() || rb_ractor_shareable_p(v)) {
rb_yield(v);
oes->num++;
}
}
}
}
return 0;
}
static VALUE
os_obj_of(VALUE of)
{
struct os_each_struct oes;
oes.num = 0;
oes.of = of;
rb_objspace_each_objects(os_obj_of_i, &oes);
return SIZET2NUM(oes.num);
}
/*
* call-seq:
* ObjectSpace.each_object([module]) {|obj| ... } -> integer
* ObjectSpace.each_object([module]) -> an_enumerator
*
* Calls the block once for each living, nonimmediate object in this
* Ruby process. If <i>module</i> is specified, calls the block
* for only those classes or modules that match (or are a subclass of)
* <i>module</i>. Returns the number of objects found. Immediate
* objects (<code>Fixnum</code>s, <code>Symbol</code>s
* <code>true</code>, <code>false</code>, and <code>nil</code>) are
* never returned. In the example below, #each_object returns both
* the numbers we defined and several constants defined in the Math
* module.
*
* If no block is given, an enumerator is returned instead.
*
* a = 102.7
* b = 95 # Won't be returned
* c = 12345678987654321
* count = ObjectSpace.each_object(Numeric) {|x| p x }
* puts "Total count: #{count}"
*
* <em>produces:</em>
*
* 12345678987654321
* 102.7
* 2.71828182845905
* 3.14159265358979
* 2.22044604925031e-16
* 1.7976931348623157e+308
* 2.2250738585072e-308
* Total count: 7
*
*/
static VALUE
os_each_obj(int argc, VALUE *argv, VALUE os)
{
VALUE of;
of = (!rb_check_arity(argc, 0, 1) ? 0 : argv[0]);
RETURN_ENUMERATOR(os, 1, &of);
return os_obj_of(of);
}
/*
* call-seq:
* ObjectSpace.undefine_finalizer(obj)
*
* Removes all finalizers for <i>obj</i>.
*
*/
static VALUE
undefine_final(VALUE os, VALUE obj)
{
return rb_gc_impl_undefine_finalizer(rb_gc_get_objspace(), obj);
}
static void
should_be_callable(VALUE block)
{
if (!rb_obj_respond_to(block, idCall, TRUE)) {
rb_raise(rb_eArgError, "wrong type argument %"PRIsVALUE" (should be callable)",
rb_obj_class(block));
}
}
static void
should_be_finalizable(VALUE obj)
{
if (!FL_ABLE(obj)) {
rb_raise(rb_eArgError, "cannot define finalizer for %s",
rb_obj_classname(obj));
}
rb_check_frozen(obj);
}
void
rb_gc_copy_finalizer(VALUE dest, VALUE obj)
{
rb_gc_impl_copy_finalizer(rb_gc_get_objspace(), dest, obj);
}
/*
* call-seq:
* ObjectSpace.define_finalizer(obj, aProc=proc())
*
* Adds <i>aProc</i> as a finalizer, to be called after <i>obj</i>
* was destroyed. The object ID of the <i>obj</i> will be passed
* as an argument to <i>aProc</i>. If <i>aProc</i> is a lambda or
* method, make sure it can be called with a single argument.
*
* The return value is an array <code>[0, aProc]</code>.
*
* The two recommended patterns are to either create the finaliser proc
* in a non-instance method where it can safely capture the needed state,
* or to use a custom callable object that stores the needed state
* explicitly as instance variables.
*
* class Foo
* def initialize(data_needed_for_finalization)
* ObjectSpace.define_finalizer(self, self.class.create_finalizer(data_needed_for_finalization))
* end
*
* def self.create_finalizer(data_needed_for_finalization)
* proc {
* puts "finalizing #{data_needed_for_finalization}"
* }
* end
* end
*
* class Bar
* class Remover
* def initialize(data_needed_for_finalization)
* @data_needed_for_finalization = data_needed_for_finalization
* end
*
* def call(id)
* puts "finalizing #{@data_needed_for_finalization}"
* end
* end
*
* def initialize(data_needed_for_finalization)
* ObjectSpace.define_finalizer(self, Remover.new(data_needed_for_finalization))
* end
* end
*
* Note that if your finalizer references the object to be
* finalized it will never be run on GC, although it will still be
* run at exit. You will get a warning if you capture the object
* to be finalized as the receiver of the finalizer.
*
* class CapturesSelf
* def initialize(name)
* ObjectSpace.define_finalizer(self, proc {
* # this finalizer will only be run on exit
* puts "finalizing #{name}"
* })
* end
* end
*
* Also note that finalization can be unpredictable and is never guaranteed
* to be run except on exit.
*/
static VALUE
define_final(int argc, VALUE *argv, VALUE os)
{
VALUE obj, block;
rb_scan_args(argc, argv, "11", &obj, &block);
should_be_finalizable(obj);
if (argc == 1) {
block = rb_block_proc();
}
else {
should_be_callable(block);
}
if (rb_callable_receiver(block) == obj) {
rb_warn("finalizer references object to be finalized");
}
return rb_gc_impl_define_finalizer(rb_gc_get_objspace(), obj, block);
}
VALUE
rb_define_finalizer(VALUE obj, VALUE block)
{
should_be_finalizable(obj);
should_be_callable(block);
return rb_gc_impl_define_finalizer(rb_gc_get_objspace(), obj, block);
}
void
rb_objspace_call_finalizer(void)
{
rb_gc_impl_shutdown_call_finalizer(rb_gc_get_objspace());
}
void
rb_objspace_free_objects(void *objspace)
{
rb_gc_impl_shutdown_free_objects(objspace);
}
int
rb_objspace_garbage_object_p(VALUE obj)
{
return rb_gc_impl_garbage_object_p(rb_gc_get_objspace(), obj);
}
/*
* call-seq:
* ObjectSpace._id2ref(object_id) -> an_object
*
* Converts an object id to a reference to the object. May not be
* called on an object id passed as a parameter to a finalizer.
*
* s = "I am a string" #=> "I am a string"
* r = ObjectSpace._id2ref(s.object_id) #=> "I am a string"
* r == s #=> true
*
* On multi-ractor mode, if the object is not shareable, it raises
* RangeError.
*/
static VALUE
id2ref(VALUE objid)
{
#if SIZEOF_LONG == SIZEOF_VOIDP
#define NUM2PTR(x) NUM2ULONG(x)
#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
#define NUM2PTR(x) NUM2ULL(x)
#endif
objid = rb_to_int(objid);
if (FIXNUM_P(objid) || rb_big_size(objid) <= SIZEOF_VOIDP) {
VALUE ptr = NUM2PTR(objid);
if (SPECIAL_CONST_P(ptr)) {
if (ptr == Qtrue) return Qtrue;
if (ptr == Qfalse) return Qfalse;
if (NIL_P(ptr)) return Qnil;
if (FIXNUM_P(ptr)) return ptr;
if (FLONUM_P(ptr)) return ptr;
if (SYMBOL_P(ptr)) {
// Check that the symbol is valid
if (rb_static_id_valid_p(SYM2ID(ptr))) {
return ptr;
}
else {
rb_raise(rb_eRangeError, "%p is not symbol id value", (void *)ptr);
}
}
rb_raise(rb_eRangeError, "%+"PRIsVALUE" is not id value", rb_int2str(objid, 10));
}
}
VALUE obj = rb_gc_impl_object_id_to_ref(rb_gc_get_objspace(), objid);
if (!rb_multi_ractor_p() || rb_ractor_shareable_p(obj)) {
return obj;
}
else {
rb_raise(rb_eRangeError, "%+"PRIsVALUE" is id of the unshareable object on multi-ractor", rb_int2str(objid, 10));
}
}
/* :nodoc: */
static VALUE
os_id2ref(VALUE os, VALUE objid)
{
return id2ref(objid);
}
static VALUE
rb_find_object_id(void *objspace, VALUE obj, VALUE (*get_heap_object_id)(void *, VALUE))
{
if (SPECIAL_CONST_P(obj)) {
#if SIZEOF_LONG == SIZEOF_VOIDP
return LONG2NUM((SIGNED_VALUE)obj);
#else
return LL2NUM((SIGNED_VALUE)obj);
#endif
}
return get_heap_object_id(objspace, obj);
}
static VALUE
nonspecial_obj_id(void *_objspace, VALUE obj)
{
#if SIZEOF_LONG == SIZEOF_VOIDP
return (VALUE)((SIGNED_VALUE)(obj)|FIXNUM_FLAG);
#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
return LL2NUM((SIGNED_VALUE)(obj) / 2);
#else
# error not supported
#endif
}
VALUE
rb_memory_id(VALUE obj)
{
return rb_find_object_id(NULL, obj, nonspecial_obj_id);
}
/*
* Document-method: __id__
* Document-method: object_id
*
* call-seq:
* obj.__id__ -> integer
* obj.object_id -> integer
*
* Returns an integer identifier for +obj+.
*
* The same number will be returned on all calls to +object_id+ for a given
* object, and no two active objects will share an id.
*
* Note: that some objects of builtin classes are reused for optimization.
* This is the case for immediate values and frozen string literals.
*
* BasicObject implements +__id__+, Kernel implements +object_id+.
*
* Immediate values are not passed by reference but are passed by value:
* +nil+, +true+, +false+, Fixnums, Symbols, and some Floats.
*
* Object.new.object_id == Object.new.object_id # => false
* (21 * 2).object_id == (21 * 2).object_id # => true
* "hello".object_id == "hello".object_id # => false
* "hi".freeze.object_id == "hi".freeze.object_id # => true
*/
VALUE
rb_obj_id(VALUE obj)
{
/*
* 32-bit VALUE space
* MSB ------------------------ LSB
* false 00000000000000000000000000000000
* true 00000000000000000000000000000010
* nil 00000000000000000000000000000100
* undef 00000000000000000000000000000110
* symbol ssssssssssssssssssssssss00001110
* object oooooooooooooooooooooooooooooo00 = 0 (mod sizeof(RVALUE))
* fixnum fffffffffffffffffffffffffffffff1
*
* object_id space
* LSB
* false 00000000000000000000000000000000
* true 00000000000000000000000000000010
* nil 00000000000000000000000000000100
* undef 00000000000000000000000000000110
* symbol 000SSSSSSSSSSSSSSSSSSSSSSSSSSS0 S...S % A = 4 (S...S = s...s * A + 4)
* object oooooooooooooooooooooooooooooo0 o...o % A = 0
* fixnum fffffffffffffffffffffffffffffff1 bignum if required
*
* where A = sizeof(RVALUE)/4
*
* sizeof(RVALUE) is
* 20 if 32-bit, double is 4-byte aligned
* 24 if 32-bit, double is 8-byte aligned
* 40 if 64-bit
*/
return rb_find_object_id(rb_gc_get_objspace(), obj, rb_gc_impl_object_id);
}
static enum rb_id_table_iterator_result
cc_table_memsize_i(VALUE ccs_ptr, void *data_ptr)
{
size_t *total_size = data_ptr;
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_ptr;
*total_size += sizeof(*ccs);
*total_size += sizeof(ccs->entries[0]) * ccs->capa;
return ID_TABLE_CONTINUE;
}
static size_t
cc_table_memsize(struct rb_id_table *cc_table)
{
size_t total = rb_id_table_memsize(cc_table);
rb_id_table_foreach_values(cc_table, cc_table_memsize_i, &total);
return total;
}
size_t
rb_obj_memsize_of(VALUE obj)
{
size_t size = 0;
if (SPECIAL_CONST_P(obj)) {
return 0;
}
if (FL_TEST(obj, FL_EXIVAR)) {
size += rb_generic_ivar_memsize(obj);
}
switch (BUILTIN_TYPE(obj)) {
case T_OBJECT:
if (rb_shape_obj_too_complex(obj)) {
size += rb_st_memsize(ROBJECT_IV_HASH(obj));
}
else if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) {
size += ROBJECT_IV_CAPACITY(obj) * sizeof(VALUE);
}
break;
case T_MODULE:
case T_CLASS:
if (RCLASS_M_TBL(obj)) {
size += rb_id_table_memsize(RCLASS_M_TBL(obj));
}
// class IV sizes are allocated as powers of two
size += SIZEOF_VALUE << bit_length(RCLASS_IV_COUNT(obj));
if (RCLASS_CVC_TBL(obj)) {
size += rb_id_table_memsize(RCLASS_CVC_TBL(obj));
}
if (RCLASS_EXT(obj)->const_tbl) {
size += rb_id_table_memsize(RCLASS_EXT(obj)->const_tbl);
}
if (RCLASS_CC_TBL(obj)) {
size += cc_table_memsize(RCLASS_CC_TBL(obj));
}
if (FL_TEST_RAW(obj, RCLASS_SUPERCLASSES_INCLUDE_SELF)) {
size += (RCLASS_SUPERCLASS_DEPTH(obj) + 1) * sizeof(VALUE);
}
break;
case T_ICLASS:
if (RICLASS_OWNS_M_TBL_P(obj)) {
if (RCLASS_M_TBL(obj)) {
size += rb_id_table_memsize(RCLASS_M_TBL(obj));
}
}
if (RCLASS_CC_TBL(obj)) {
size += cc_table_memsize(RCLASS_CC_TBL(obj));
}
break;
case T_STRING:
size += rb_str_memsize(obj);
break;
case T_ARRAY:
size += rb_ary_memsize(obj);
break;
case T_HASH:
if (RHASH_ST_TABLE_P(obj)) {
VM_ASSERT(RHASH_ST_TABLE(obj) != NULL);
/* st_table is in the slot */
size += st_memsize(RHASH_ST_TABLE(obj)) - sizeof(st_table);
}
break;
case T_REGEXP:
if (RREGEXP_PTR(obj)) {
size += onig_memsize(RREGEXP_PTR(obj));
}
break;
case T_DATA:
size += rb_objspace_data_type_memsize(obj);
break;
case T_MATCH:
{
rb_matchext_t *rm = RMATCH_EXT(obj);
size += onig_region_memsize(&rm->regs);
size += sizeof(struct rmatch_offset) * rm->char_offset_num_allocated;
}
break;
case T_FILE:
if (RFILE(obj)->fptr) {
size += rb_io_memsize(RFILE(obj)->fptr);
}
break;
case T_RATIONAL:
case T_COMPLEX:
break;
case T_IMEMO:
size += rb_imemo_memsize(obj);
break;
case T_FLOAT:
case T_SYMBOL:
break;
case T_BIGNUM:
if (!(RBASIC(obj)->flags & BIGNUM_EMBED_FLAG) && BIGNUM_DIGITS(obj)) {
size += BIGNUM_LEN(obj) * sizeof(BDIGIT);
}
break;
case T_NODE:
UNEXPECTED_NODE(obj_memsize_of);
break;
case T_STRUCT:
if ((RBASIC(obj)->flags & RSTRUCT_EMBED_LEN_MASK) == 0 &&
RSTRUCT(obj)->as.heap.ptr) {
size += sizeof(VALUE) * RSTRUCT_LEN(obj);
}
break;
case T_ZOMBIE:
case T_MOVED:
break;
default:
rb_bug("objspace/memsize_of(): unknown data type 0x%x(%p)",
BUILTIN_TYPE(obj), (void*)obj);
}
return size + rb_gc_obj_slot_size(obj);
}
static int
set_zero(st_data_t key, st_data_t val, st_data_t arg)
{
VALUE k = (VALUE)key;
VALUE hash = (VALUE)arg;
rb_hash_aset(hash, k, INT2FIX(0));
return ST_CONTINUE;
}
static VALUE
type_sym(size_t type)
{
switch (type) {
#define COUNT_TYPE(t) case (t): return ID2SYM(rb_intern(#t)); break;
COUNT_TYPE(T_NONE);
COUNT_TYPE(T_OBJECT);
COUNT_TYPE(T_CLASS);
COUNT_TYPE(T_MODULE);
COUNT_TYPE(T_FLOAT);
COUNT_TYPE(T_STRING);
COUNT_TYPE(T_REGEXP);
COUNT_TYPE(T_ARRAY);
COUNT_TYPE(T_HASH);
COUNT_TYPE(T_STRUCT);
COUNT_TYPE(T_BIGNUM);
COUNT_TYPE(T_FILE);
COUNT_TYPE(T_DATA);
COUNT_TYPE(T_MATCH);
COUNT_TYPE(T_COMPLEX);
COUNT_TYPE(T_RATIONAL);
COUNT_TYPE(T_NIL);
COUNT_TYPE(T_TRUE);
COUNT_TYPE(T_FALSE);
COUNT_TYPE(T_SYMBOL);
COUNT_TYPE(T_FIXNUM);
COUNT_TYPE(T_IMEMO);
COUNT_TYPE(T_UNDEF);
COUNT_TYPE(T_NODE);
COUNT_TYPE(T_ICLASS);
COUNT_TYPE(T_ZOMBIE);
COUNT_TYPE(T_MOVED);
#undef COUNT_TYPE
default: return SIZET2NUM(type); break;
}
}
struct count_objects_data {
size_t counts[T_MASK+1];
size_t freed;
size_t total;
};
static void
count_objects_i(VALUE obj, void *d)
{
struct count_objects_data *data = (struct count_objects_data *)d;
if (RBASIC(obj)->flags) {
data->counts[BUILTIN_TYPE(obj)]++;
}
else {
data->freed++;
}
data->total++;
}
/*
* call-seq:
* ObjectSpace.count_objects([result_hash]) -> hash
*
* Counts all objects grouped by type.
*
* It returns a hash, such as:
* {
* :TOTAL=>10000,
* :FREE=>3011,
* :T_OBJECT=>6,
* :T_CLASS=>404,
* # ...
* }
*
* The contents of the returned hash are implementation specific.
* It may be changed in future.
*
* The keys starting with +:T_+ means live objects.
* For example, +:T_ARRAY+ is the number of arrays.
* +:FREE+ means object slots which is not used now.
* +:TOTAL+ means sum of above.
*
* If the optional argument +result_hash+ is given,
* it is overwritten and returned. This is intended to avoid probe effect.
*
* h = {}
* ObjectSpace.count_objects(h)
* puts h
* # => { :TOTAL=>10000, :T_CLASS=>158280, :T_MODULE=>20672, :T_STRING=>527249 }
*
* This method is only expected to work on C Ruby.
*
*/
static VALUE
count_objects(int argc, VALUE *argv, VALUE os)
{
struct count_objects_data data = { 0 };
VALUE hash = Qnil;
if (rb_check_arity(argc, 0, 1) == 1) {
hash = argv[0];
if (!RB_TYPE_P(hash, T_HASH))
rb_raise(rb_eTypeError, "non-hash given");
}
rb_gc_impl_each_object(rb_gc_get_objspace(), count_objects_i, &data);
if (NIL_P(hash)) {
hash = rb_hash_new();
}
else if (!RHASH_EMPTY_P(hash)) {
rb_hash_stlike_foreach(hash, set_zero, hash);
}
rb_hash_aset(hash, ID2SYM(rb_intern("TOTAL")), SIZET2NUM(data.total));
rb_hash_aset(hash, ID2SYM(rb_intern("FREE")), SIZET2NUM(data.freed));
for (size_t i = 0; i <= T_MASK; i++) {
VALUE type = type_sym(i);
if (data.counts[i])
rb_hash_aset(hash, type, SIZET2NUM(data.counts[i]));
}
return hash;
}
#define SET_STACK_END SET_MACHINE_STACK_END(&ec->machine.stack_end)
#define STACK_START (ec->machine.stack_start)
#define STACK_END (ec->machine.stack_end)
#define STACK_LEVEL_MAX (ec->machine.stack_maxsize/sizeof(VALUE))
#if STACK_GROW_DIRECTION < 0
# define STACK_LENGTH (size_t)(STACK_START - STACK_END)
#elif STACK_GROW_DIRECTION > 0
# define STACK_LENGTH (size_t)(STACK_END - STACK_START + 1)
#else
# define STACK_LENGTH ((STACK_END < STACK_START) ? (size_t)(STACK_START - STACK_END) \
: (size_t)(STACK_END - STACK_START + 1))
#endif
#if !STACK_GROW_DIRECTION
int ruby_stack_grow_direction;
int
ruby_get_stack_grow_direction(volatile VALUE *addr)
{
VALUE *end;
SET_MACHINE_STACK_END(&end);
if (end > addr) return ruby_stack_grow_direction = 1;
return ruby_stack_grow_direction = -1;
}
#endif
size_t
ruby_stack_length(VALUE **p)
{
rb_execution_context_t *ec = GET_EC();
SET_STACK_END;
if (p) *p = STACK_UPPER(STACK_END, STACK_START, STACK_END);
return STACK_LENGTH;
}
#define PREVENT_STACK_OVERFLOW 1
#ifndef PREVENT_STACK_OVERFLOW
#if !(defined(POSIX_SIGNAL) && defined(SIGSEGV) && defined(HAVE_SIGALTSTACK))
# define PREVENT_STACK_OVERFLOW 1
#else
# define PREVENT_STACK_OVERFLOW 0
#endif
#endif
#if PREVENT_STACK_OVERFLOW && !defined(__EMSCRIPTEN__)
static int
stack_check(rb_execution_context_t *ec, int water_mark)
{
SET_STACK_END;
size_t length = STACK_LENGTH;
size_t maximum_length = STACK_LEVEL_MAX - water_mark;
return length > maximum_length;
}
#else
#define stack_check(ec, water_mark) FALSE
#endif
#define STACKFRAME_FOR_CALL_CFUNC 2048
int
rb_ec_stack_check(rb_execution_context_t *ec)
{
return stack_check(ec, STACKFRAME_FOR_CALL_CFUNC);
}
int
ruby_stack_check(void)
{
return stack_check(GET_EC(), STACKFRAME_FOR_CALL_CFUNC);
}
ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS(static void each_location(void *objspace, register const VALUE *x, register long n, void (*cb)(void *objspace, VALUE)));
static void
each_location(void *objspace, register const VALUE *x, register long n, void (*cb)(void *objspace, VALUE))
{
VALUE v;
while (n--) {
v = *x;
cb(objspace, v);
x++;
}
}
static void
gc_mark_locations(void *objspace, const VALUE *start, const VALUE *end, void (*cb)(void *, VALUE))
{
long n;
if (end <= start) return;
n = end - start;
each_location(objspace, start, n, cb);
}
void
rb_gc_mark_locations(const VALUE *start, const VALUE *end)
{
gc_mark_locations(rb_gc_get_objspace(), start, end, rb_gc_impl_mark_maybe);
}
void
rb_gc_mark_values(long n, const VALUE *values)
{
for (long i = 0; i < n; i++) {
rb_gc_impl_mark(rb_gc_get_objspace(), values[i]);
}
}
void
rb_gc_mark_vm_stack_values(long n, const VALUE *values)
{
for (long i = 0; i < n; i++) {
rb_gc_impl_mark_and_pin(rb_gc_get_objspace(), values[i]);
}
}
static int
mark_key(st_data_t key, st_data_t value, st_data_t data)
{
void *objspace = (void *)data;
rb_gc_impl_mark_and_pin(objspace, (VALUE)key);
return ST_CONTINUE;
}
void
rb_mark_set(st_table *tbl)
{
if (!tbl) return;
st_foreach(tbl, mark_key, (st_data_t)rb_gc_get_objspace());
}
static int
mark_keyvalue(st_data_t key, st_data_t value, st_data_t data)
{
void *objspace = (void *)data;
rb_gc_impl_mark(objspace, (VALUE)key);
rb_gc_impl_mark(objspace, (VALUE)value);
return ST_CONTINUE;
}
static int
pin_key_pin_value(st_data_t key, st_data_t value, st_data_t data)
{
void *objspace = (void *)data;
rb_gc_impl_mark_and_pin(objspace, (VALUE)key);
rb_gc_impl_mark_and_pin(objspace, (VALUE)value);
return ST_CONTINUE;
}
static int
pin_key_mark_value(st_data_t key, st_data_t value, st_data_t data)
{
void *objspace = (void *)data;
rb_gc_impl_mark_and_pin(objspace, (VALUE)key);
rb_gc_impl_mark(objspace, (VALUE)value);
return ST_CONTINUE;
}
static void
mark_hash(void *objspace, VALUE hash)
{
if (rb_hash_compare_by_id_p(hash)) {
rb_hash_stlike_foreach(hash, pin_key_mark_value, (st_data_t)objspace);
}
else {
rb_hash_stlike_foreach(hash, mark_keyvalue, (st_data_t)objspace);
}
rb_gc_impl_mark(objspace, RHASH(hash)->ifnone);
}
void
rb_mark_hash(st_table *tbl)
{
if (!tbl) return;
st_foreach(tbl, pin_key_pin_value, (st_data_t)rb_gc_get_objspace());
}
static enum rb_id_table_iterator_result
mark_method_entry_i(VALUE me, void *objspace)
{
rb_gc_impl_mark(objspace, me);
return ID_TABLE_CONTINUE;
}
static void
mark_m_tbl(void *objspace, struct rb_id_table *tbl)
{
if (tbl) {
rb_id_table_foreach_values(tbl, mark_method_entry_i, objspace);
}
}
#if STACK_GROW_DIRECTION < 0
#define GET_STACK_BOUNDS(start, end, appendix) ((start) = STACK_END, (end) = STACK_START)
#elif STACK_GROW_DIRECTION > 0
#define GET_STACK_BOUNDS(start, end, appendix) ((start) = STACK_START, (end) = STACK_END+(appendix))
#else
#define GET_STACK_BOUNDS(start, end, appendix) \
((STACK_END < STACK_START) ? \
((start) = STACK_END, (end) = STACK_START) : ((start) = STACK_START, (end) = STACK_END+(appendix)))
#endif
static void
each_stack_location(void *objspace, const rb_execution_context_t *ec,
const VALUE *stack_start, const VALUE *stack_end, void (*cb)(void *objspace, VALUE obj))
{
gc_mark_locations(objspace, stack_start, stack_end, cb);
#if defined(__mc68000__)
gc_mark_locations(objspace,
(VALUE*)((char*)stack_start + 2),
(VALUE*)((char*)stack_end - 2), cb);
#endif
}
struct mark_machine_stack_location_maybe_data {
void *objspace;
#ifdef RUBY_ASAN_ENABLED
const rb_execution_context_t *ec;
#endif
};
static void
gc_mark_machine_stack_location_maybe(void *data, VALUE obj)
{
void *objspace = ((struct mark_machine_stack_location_maybe_data *)data)->objspace;
rb_gc_impl_mark_maybe(objspace, obj);
#ifdef RUBY_ASAN_ENABLED
const rb_execution_context_t *ec = ((struct mark_machine_stack_location_maybe_data *)data)->ec;
void *fake_frame_start;
void *fake_frame_end;
bool is_fake_frame = asan_get_fake_stack_extents(
ec->machine.asan_fake_stack_handle, obj,
ec->machine.stack_start, ec->machine.stack_end,
&fake_frame_start, &fake_frame_end
);
if (is_fake_frame) {
each_stack_location(objspace, ec, fake_frame_start, fake_frame_end, rb_gc_impl_mark_maybe);
}
#endif
}
#if defined(__wasm__)
static VALUE *rb_stack_range_tmp[2];
static void
rb_mark_locations(void *begin, void *end)
{
rb_stack_range_tmp[0] = begin;
rb_stack_range_tmp[1] = end;
}
# if defined(__EMSCRIPTEN__)
static void
mark_current_machine_context(void *objspace, rb_execution_context_t *ec)
{
emscripten_scan_stack(rb_mark_locations);
each_stack_location(objspace, ec, rb_stack_range_tmp[0], rb_stack_range_tmp[1], rb_gc_impl_mark_maybe);
emscripten_scan_registers(rb_mark_locations);
each_stack_location(objspace, ec, rb_stack_range_tmp[0], rb_stack_range_tmp[1], rb_gc_impl_mark_maybe);
}
# else // use Asyncify version
static void
mark_current_machine_context(void *objspace, rb_execution_context_t *ec)
{
VALUE *stack_start, *stack_end;
SET_STACK_END;
GET_STACK_BOUNDS(stack_start, stack_end, 1);
each_stack_location(objspace, ec, stack_start, stack_end, rb_gc_impl_mark_maybe);
rb_wasm_scan_locals(rb_mark_locations);
each_stack_location(objspace, ec, rb_stack_range_tmp[0], rb_stack_range_tmp[1], rb_gc_impl_mark_maybe);
}
# endif
#else // !defined(__wasm__)
static void
mark_current_machine_context(void *objspace, rb_execution_context_t *ec)
{
union {
rb_jmp_buf j;
VALUE v[sizeof(rb_jmp_buf) / (sizeof(VALUE))];
} save_regs_gc_mark;
VALUE *stack_start, *stack_end;
FLUSH_REGISTER_WINDOWS;
memset(&save_regs_gc_mark, 0, sizeof(save_regs_gc_mark));
/* This assumes that all registers are saved into the jmp_buf (and stack) */
rb_setjmp(save_regs_gc_mark.j);
/* SET_STACK_END must be called in this function because
* the stack frame of this function may contain
* callee save registers and they should be marked. */
SET_STACK_END;
GET_STACK_BOUNDS(stack_start, stack_end, 1);
struct mark_machine_stack_location_maybe_data data = {
.objspace = objspace,
#ifdef RUBY_ASAN_ENABLED
.ec = ec
#endif
};
each_location((void *)&data, save_regs_gc_mark.v, numberof(save_regs_gc_mark.v), gc_mark_machine_stack_location_maybe);
each_stack_location((void *)&data, ec, stack_start, stack_end, gc_mark_machine_stack_location_maybe);
}
#endif
void
rb_gc_mark_machine_context(const rb_execution_context_t *ec)
{
VALUE *stack_start, *stack_end;
GET_STACK_BOUNDS(stack_start, stack_end, 0);
RUBY_DEBUG_LOG("ec->th:%u stack_start:%p stack_end:%p", rb_ec_thread_ptr(ec)->serial, stack_start, stack_end);
struct mark_machine_stack_location_maybe_data data = {
.objspace = rb_gc_get_objspace(),
#ifdef RUBY_ASAN_ENABLED
.ec = ec
#endif
};
each_stack_location((void *)&data, ec, stack_start, stack_end, gc_mark_machine_stack_location_maybe);
int num_regs = sizeof(ec->machine.regs)/(sizeof(VALUE));
each_location((void *)&data, (VALUE*)&ec->machine.regs, num_regs, gc_mark_machine_stack_location_maybe);
}
static int
rb_mark_tbl_i(st_data_t key, st_data_t value, st_data_t data)
{
void *objspace = (void *)data;
rb_gc_impl_mark_and_pin(objspace, (VALUE)value);
return ST_CONTINUE;
}
void
rb_mark_tbl(st_table *tbl)
{
if (!tbl || tbl->num_entries == 0) return;
st_foreach(tbl, rb_mark_tbl_i, (st_data_t)rb_gc_get_objspace());
}
static int
gc_mark_tbl_no_pin_i(st_data_t key, st_data_t value, st_data_t data)
{
void *objspace = (void *)data;
rb_gc_impl_mark(objspace, (VALUE)value);
return ST_CONTINUE;
}
static void
gc_mark_tbl_no_pin(void *objspace, st_table *tbl)
{
if (!tbl || tbl->num_entries == 0) return;
st_foreach(tbl, gc_mark_tbl_no_pin_i, (st_data_t)objspace);
}
void
rb_mark_tbl_no_pin(st_table *tbl)
{
gc_mark_tbl_no_pin(rb_gc_get_objspace(), tbl);
}
void
rb_gc_mark_maybe(VALUE obj)
{
rb_gc_impl_mark_maybe(rb_gc_get_objspace(), obj);
}
static enum rb_id_table_iterator_result
mark_cvc_tbl_i(VALUE cvc_entry, void *objspace)
{
struct rb_cvar_class_tbl_entry *entry;
entry = (struct rb_cvar_class_tbl_entry *)cvc_entry;
RUBY_ASSERT(entry->cref == 0 || (BUILTIN_TYPE((VALUE)entry->cref) == T_IMEMO && IMEMO_TYPE_P(entry->cref, imemo_cref)));
rb_gc_impl_mark(objspace, (VALUE)entry->cref);
return ID_TABLE_CONTINUE;
}
static void
mark_cvc_tbl(void *objspace, VALUE klass)
{
struct rb_id_table *tbl = RCLASS_CVC_TBL(klass);
if (tbl) {
rb_id_table_foreach_values(tbl, mark_cvc_tbl_i, objspace);
}
}
void
rb_gc_mark_movable(VALUE obj)
{
rb_gc_impl_mark(rb_gc_get_objspace(), obj);
}
void
rb_gc_mark(VALUE obj)
{
rb_gc_impl_mark_and_pin(rb_gc_get_objspace(), obj);
}
void
rb_gc_mark_and_move(VALUE *ptr)
{
rb_gc_impl_mark_and_move(rb_gc_get_objspace(), ptr);
}
void
rb_gc_mark_weak(VALUE *ptr)
{
rb_gc_impl_mark_weak(rb_gc_get_objspace(), ptr);
}
void
rb_gc_remove_weak(VALUE parent_obj, VALUE *ptr)
{
rb_gc_impl_remove_weak(rb_gc_get_objspace(), parent_obj, ptr);
}
static bool
gc_declarative_marking_p(const rb_data_type_t *type)
{
return (type->flags & RUBY_TYPED_DECL_MARKING) != 0;
}
static enum rb_id_table_iterator_result
mark_const_table_i(VALUE value, void *objspace)
{
const rb_const_entry_t *ce = (const rb_const_entry_t *)value;
rb_gc_impl_mark(objspace, ce->value);
rb_gc_impl_mark(objspace, ce->file);
return ID_TABLE_CONTINUE;
}
void
rb_gc_mark_roots(void *objspace, const char **categoryp)
{
rb_execution_context_t *ec = GET_EC();
rb_vm_t *vm = rb_ec_vm_ptr(ec);
#define MARK_CHECKPOINT(category) do { \
if (categoryp) *categoryp = category; \
} while (0)
MARK_CHECKPOINT("objspace");
rb_gc_impl_objspace_mark(objspace);
MARK_CHECKPOINT("vm");
SET_STACK_END;
rb_vm_mark(vm);
if (vm->self) rb_gc_impl_mark(objspace, vm->self);
MARK_CHECKPOINT("machine_context");
mark_current_machine_context(objspace, ec);
MARK_CHECKPOINT("end_proc");
rb_mark_end_proc();
MARK_CHECKPOINT("global_tbl");
rb_gc_mark_global_tbl();
MARK_CHECKPOINT("finish");
#undef MARK_CHECKPOINT
}
void
rb_gc_mark_children(void *objspace, VALUE obj)
{
if (FL_TEST(obj, FL_EXIVAR)) {
rb_mark_generic_ivar(obj);
}
switch (BUILTIN_TYPE(obj)) {
case T_FLOAT:
case T_BIGNUM:
case T_SYMBOL:
/* Not immediates, but does not have references and singleton class.
*
* RSYMBOL(obj)->fstr intentionally not marked. See log for 96815f1e
* ("symbol.c: remove rb_gc_mark_symbols()") */
return;
case T_NIL:
case T_FIXNUM:
rb_bug("rb_gc_mark() called for broken object");
break;
case T_NODE:
UNEXPECTED_NODE(rb_gc_mark);
break;
case T_IMEMO:
rb_imemo_mark_and_move(obj, false);
return;
default:
break;
}
rb_gc_impl_mark(objspace, RBASIC(obj)->klass);
switch (BUILTIN_TYPE(obj)) {
case T_CLASS:
if (FL_TEST(obj, FL_SINGLETON)) {
rb_gc_impl_mark(objspace, RCLASS_ATTACHED_OBJECT(obj));
}
// Continue to the shared T_CLASS/T_MODULE
case T_MODULE:
if (RCLASS_SUPER(obj)) {
rb_gc_impl_mark(objspace, RCLASS_SUPER(obj));
}
mark_m_tbl(objspace, RCLASS_M_TBL(obj));
mark_cvc_tbl(objspace, obj);
rb_cc_table_mark(obj);
if (rb_shape_obj_too_complex(obj)) {
gc_mark_tbl_no_pin(objspace, (st_table *)RCLASS_IVPTR(obj));
}
else {
for (attr_index_t i = 0; i < RCLASS_IV_COUNT(obj); i++) {
rb_gc_impl_mark(objspace, RCLASS_IVPTR(obj)[i]);
}
}
if (RCLASS_CONST_TBL(obj)) {
rb_id_table_foreach_values(RCLASS_CONST_TBL(obj), mark_const_table_i, objspace);
}
rb_gc_impl_mark(objspace, RCLASS_EXT(obj)->classpath);
break;
case T_ICLASS:
if (RICLASS_OWNS_M_TBL_P(obj)) {
mark_m_tbl(objspace, RCLASS_M_TBL(obj));
}
if (RCLASS_SUPER(obj)) {
rb_gc_impl_mark(objspace, RCLASS_SUPER(obj));
}
if (RCLASS_INCLUDER(obj)) {
rb_gc_impl_mark(objspace, RCLASS_INCLUDER(obj));
}
mark_m_tbl(objspace, RCLASS_CALLABLE_M_TBL(obj));
rb_cc_table_mark(obj);
break;
case T_ARRAY:
if (ARY_SHARED_P(obj)) {
VALUE root = ARY_SHARED_ROOT(obj);
rb_gc_impl_mark(objspace, root);
}
else {
long len = RARRAY_LEN(obj);
const VALUE *ptr = RARRAY_CONST_PTR(obj);
for (long i = 0; i < len; i++) {
rb_gc_impl_mark(objspace, ptr[i]);
}
}
break;
case T_HASH:
mark_hash(objspace, obj);
break;
case T_STRING:
if (STR_SHARED_P(obj)) {
if (STR_EMBED_P(RSTRING(obj)->as.heap.aux.shared)) {
/* Embedded shared strings cannot be moved because this string
* points into the slot of the shared string. There may be code
* using the RSTRING_PTR on the stack, which would pin this
* string but not pin the shared string, causing it to move. */
rb_gc_impl_mark_and_pin(objspace, RSTRING(obj)->as.heap.aux.shared);
}
else {
rb_gc_impl_mark(objspace, RSTRING(obj)->as.heap.aux.shared);
}
}
break;
case T_DATA: {
void *const ptr = RTYPEDDATA_P(obj) ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
if (ptr) {
if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(RTYPEDDATA(obj)->type)) {
size_t *offset_list = (size_t *)RTYPEDDATA(obj)->type->function.dmark;
for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) {
rb_gc_impl_mark(objspace, *(VALUE *)((char *)ptr + offset));
}
}
else {
RUBY_DATA_FUNC mark_func = RTYPEDDATA_P(obj) ?
RTYPEDDATA(obj)->type->function.dmark :
RDATA(obj)->dmark;
if (mark_func) (*mark_func)(ptr);
}
}
break;
}
case T_OBJECT: {
rb_shape_t *shape = rb_shape_get_shape_by_id(ROBJECT_SHAPE_ID(obj));
if (rb_shape_obj_too_complex(obj)) {
gc_mark_tbl_no_pin(objspace, ROBJECT_IV_HASH(obj));
}
else {
const VALUE * const ptr = ROBJECT_IVPTR(obj);
uint32_t len = ROBJECT_IV_COUNT(obj);
for (uint32_t i = 0; i < len; i++) {
rb_gc_impl_mark(objspace, ptr[i]);
}
}
if (shape) {
VALUE klass = RBASIC_CLASS(obj);
// Increment max_iv_count if applicable, used to determine size pool allocation
attr_index_t num_of_ivs = shape->next_iv_index;
if (RCLASS_EXT(klass)->max_iv_count < num_of_ivs) {
RCLASS_EXT(klass)->max_iv_count = num_of_ivs;
}
}
break;
}
case T_FILE:
if (RFILE(obj)->fptr) {
rb_gc_impl_mark(objspace, RFILE(obj)->fptr->self);
rb_gc_impl_mark(objspace, RFILE(obj)->fptr->pathv);
rb_gc_impl_mark(objspace, RFILE(obj)->fptr->tied_io_for_writing);
rb_gc_impl_mark(objspace, RFILE(obj)->fptr->writeconv_asciicompat);
rb_gc_impl_mark(objspace, RFILE(obj)->fptr->writeconv_pre_ecopts);
rb_gc_impl_mark(objspace, RFILE(obj)->fptr->encs.ecopts);
rb_gc_impl_mark(objspace, RFILE(obj)->fptr->write_lock);
rb_gc_impl_mark(objspace, RFILE(obj)->fptr->timeout);
}
break;
case T_REGEXP:
rb_gc_impl_mark(objspace, RREGEXP(obj)->src);
break;
case T_MATCH:
rb_gc_impl_mark(objspace, RMATCH(obj)->regexp);
if (RMATCH(obj)->str) {
rb_gc_impl_mark(objspace, RMATCH(obj)->str);
}
break;
case T_RATIONAL:
rb_gc_impl_mark(objspace, RRATIONAL(obj)->num);
rb_gc_impl_mark(objspace, RRATIONAL(obj)->den);
break;
case T_COMPLEX:
rb_gc_impl_mark(objspace, RCOMPLEX(obj)->real);
rb_gc_impl_mark(objspace, RCOMPLEX(obj)->imag);
break;
case T_STRUCT: {
const long len = RSTRUCT_LEN(obj);
const VALUE * const ptr = RSTRUCT_CONST_PTR(obj);
for (long i = 0; i < len; i++) {
rb_gc_impl_mark(objspace, ptr[i]);
}
break;
}
default:
if (BUILTIN_TYPE(obj) == T_MOVED) rb_bug("rb_gc_mark(): %p is T_MOVED", (void *)obj);
if (BUILTIN_TYPE(obj) == T_NONE) rb_bug("rb_gc_mark(): %p is T_NONE", (void *)obj);
if (BUILTIN_TYPE(obj) == T_ZOMBIE) rb_bug("rb_gc_mark(): %p is T_ZOMBIE", (void *)obj);
rb_bug("rb_gc_mark(): unknown data type 0x%x(%p) %s",
BUILTIN_TYPE(obj), (void *)obj,
rb_gc_impl_pointer_to_heap_p(rb_gc_get_objspace(), (void *)obj) ? "corrupted object" : "non object");
}
}
size_t
rb_gc_obj_optimal_size(VALUE obj)
{
switch (BUILTIN_TYPE(obj)) {
case T_ARRAY:
return rb_ary_size_as_embedded(obj);
case T_OBJECT:
if (rb_shape_obj_too_complex(obj)) {
return sizeof(struct RObject);
}
else {
return rb_obj_embedded_size(ROBJECT_IV_CAPACITY(obj));
}
case T_STRING:
return rb_str_size_as_embedded(obj);
case T_HASH:
return sizeof(struct RHash) + (RHASH_ST_TABLE_P(obj) ? sizeof(st_table) : sizeof(ar_table));
default:
return 0;
}
}
void
rb_gc_writebarrier(VALUE a, VALUE b)
{
rb_gc_impl_writebarrier(rb_gc_get_objspace(), a, b);
}
void
rb_gc_writebarrier_unprotect(VALUE obj)
{
rb_gc_impl_writebarrier_unprotect(rb_gc_get_objspace(), obj);
}
/*
* remember `obj' if needed.
*/
void
rb_gc_writebarrier_remember(VALUE obj)
{
rb_gc_impl_writebarrier_remember(rb_gc_get_objspace(), obj);
}
void
rb_gc_copy_attributes(VALUE dest, VALUE obj)
{
rb_gc_impl_copy_attributes(rb_gc_get_objspace(), dest, obj);
}
// TODO: rearchitect this function to work for a generic GC
size_t
rb_obj_gc_flags(VALUE obj, ID* flags, size_t max)
{
return rb_gc_impl_obj_flags(rb_gc_get_objspace(), obj, flags, max);
}
/* GC */
void *
rb_gc_ractor_cache_alloc(void)
{
return rb_gc_impl_ractor_cache_alloc(rb_gc_get_objspace());
}
void
rb_gc_ractor_cache_free(void *cache)
{
rb_gc_impl_ractor_cache_free(rb_gc_get_objspace(), cache);
}
void
rb_gc_register_mark_object(VALUE obj)
{
if (!rb_gc_impl_pointer_to_heap_p(rb_gc_get_objspace(), (void *)obj))
return;
rb_vm_register_global_object(obj);
}
void
rb_gc_register_address(VALUE *addr)
{
rb_vm_t *vm = GET_VM();
VALUE obj = *addr;
struct global_object_list *tmp = ALLOC(struct global_object_list);
tmp->next = vm->global_object_list;
tmp->varptr = addr;
vm->global_object_list = tmp;
/*
* Because some C extensions have assignment-then-register bugs,
* we guard `obj` here so that it would not get swept defensively.
*/
RB_GC_GUARD(obj);
if (0 && !SPECIAL_CONST_P(obj)) {
rb_warn("Object is assigned to registering address already: %"PRIsVALUE,
rb_obj_class(obj));
rb_print_backtrace(stderr);
}
}
void
rb_gc_unregister_address(VALUE *addr)
{
rb_vm_t *vm = GET_VM();
struct global_object_list *tmp = vm->global_object_list;
if (tmp->varptr == addr) {
vm->global_object_list = tmp->next;
xfree(tmp);
return;
}
while (tmp->next) {
if (tmp->next->varptr == addr) {
struct global_object_list *t = tmp->next;
tmp->next = tmp->next->next;
xfree(t);
break;
}
tmp = tmp->next;
}
}
void
rb_global_variable(VALUE *var)
{
rb_gc_register_address(var);
}
static VALUE
gc_start_internal(rb_execution_context_t *ec, VALUE self, VALUE full_mark, VALUE immediate_mark, VALUE immediate_sweep, VALUE compact)
{
rb_gc_impl_start(rb_gc_get_objspace(), RTEST(full_mark), RTEST(immediate_mark), RTEST(immediate_sweep), RTEST(compact));
return Qnil;
}
/*
* rb_objspace_each_objects() is special C API to walk through
* Ruby object space. This C API is too difficult to use it.
* To be frank, you should not use it. Or you need to read the
* source code of this function and understand what this function does.
*
* 'callback' will be called several times (the number of heap page,
* at current implementation) with:
* vstart: a pointer to the first living object of the heap_page.
* vend: a pointer to next to the valid heap_page area.
* stride: a distance to next VALUE.
*
* If callback() returns non-zero, the iteration will be stopped.
*
* This is a sample callback code to iterate liveness objects:
*
* static int
* sample_callback(void *vstart, void *vend, int stride, void *data)
* {
* VALUE v = (VALUE)vstart;
* for (; v != (VALUE)vend; v += stride) {
* if (!rb_objspace_internal_object_p(v)) { // liveness check
* // do something with live object 'v'
* }
* }
* return 0; // continue to iteration
* }
*
* Note: 'vstart' is not a top of heap_page. This point the first
* living object to grasp at least one object to avoid GC issue.
* This means that you can not walk through all Ruby object page
* including freed object page.
*
* Note: On this implementation, 'stride' is the same as sizeof(RVALUE).
* However, there are possibilities to pass variable values with
* 'stride' with some reasons. You must use stride instead of
* use some constant value in the iteration.
*/
void
rb_objspace_each_objects(int (*callback)(void *, void *, size_t, void *), void *data)
{
rb_gc_impl_each_objects(rb_gc_get_objspace(), callback, data);
}
static void
gc_ref_update_array(void *objspace, VALUE v)
{
if (ARY_SHARED_P(v)) {
VALUE old_root = RARRAY(v)->as.heap.aux.shared_root;
UPDATE_IF_MOVED(objspace, RARRAY(v)->as.heap.aux.shared_root);
VALUE new_root = RARRAY(v)->as.heap.aux.shared_root;
// If the root is embedded and its location has changed
if (ARY_EMBED_P(new_root) && new_root != old_root) {
size_t offset = (size_t)(RARRAY(v)->as.heap.ptr - RARRAY(old_root)->as.ary);
GC_ASSERT(RARRAY(v)->as.heap.ptr >= RARRAY(old_root)->as.ary);
RARRAY(v)->as.heap.ptr = RARRAY(new_root)->as.ary + offset;
}
}
else {
long len = RARRAY_LEN(v);
if (len > 0) {
VALUE *ptr = (VALUE *)RARRAY_CONST_PTR(v);
for (long i = 0; i < len; i++) {
UPDATE_IF_MOVED(objspace, ptr[i]);
}
}
if (rb_gc_obj_slot_size(v) >= rb_ary_size_as_embedded(v)) {
if (rb_ary_embeddable_p(v)) {
rb_ary_make_embedded(v);
}
}
}
}
static void gc_ref_update_table_values_only(void *objspace, st_table *tbl);
static void
gc_ref_update_object(void *objspace, VALUE v)
{
VALUE *ptr = ROBJECT_IVPTR(v);
if (rb_shape_obj_too_complex(v)) {
gc_ref_update_table_values_only(objspace, ROBJECT_IV_HASH(v));
return;
}
size_t slot_size = rb_gc_obj_slot_size(v);
size_t embed_size = rb_obj_embedded_size(ROBJECT_IV_CAPACITY(v));
if (slot_size >= embed_size && !RB_FL_TEST_RAW(v, ROBJECT_EMBED)) {
// Object can be re-embedded
memcpy(ROBJECT(v)->as.ary, ptr, sizeof(VALUE) * ROBJECT_IV_COUNT(v));
RB_FL_SET_RAW(v, ROBJECT_EMBED);
xfree(ptr);
ptr = ROBJECT(v)->as.ary;
}
for (uint32_t i = 0; i < ROBJECT_IV_COUNT(v); i++) {
UPDATE_IF_MOVED(objspace, ptr[i]);
}
}
static int
hash_replace_ref(st_data_t *key, st_data_t *value, st_data_t argp, int existing)
{
void *objspace = (void *)argp;
if (rb_gc_impl_object_moved_p(objspace, (VALUE)*key)) {
*key = rb_gc_impl_location(objspace, (VALUE)*key);
}
if (rb_gc_impl_object_moved_p(objspace, (VALUE)*value)) {
*value = rb_gc_impl_location(objspace, (VALUE)*value);
}
return ST_CONTINUE;
}
static int
hash_foreach_replace(st_data_t key, st_data_t value, st_data_t argp, int error)
{
void *objspace;
objspace = (void *)argp;
if (rb_gc_impl_object_moved_p(objspace, (VALUE)key)) {
return ST_REPLACE;
}
if (rb_gc_impl_object_moved_p(objspace, (VALUE)value)) {
return ST_REPLACE;
}
return ST_CONTINUE;
}
static int
hash_replace_ref_value(st_data_t *key, st_data_t *value, st_data_t argp, int existing)
{
void *objspace = (void *)argp;
if (rb_gc_impl_object_moved_p(objspace, (VALUE)*value)) {
*value = rb_gc_impl_location(objspace, (VALUE)*value);
}
return ST_CONTINUE;
}
static int
hash_foreach_replace_value(st_data_t key, st_data_t value, st_data_t argp, int error)
{
void *objspace;
objspace = (void *)argp;
if (rb_gc_impl_object_moved_p(objspace, (VALUE)value)) {
return ST_REPLACE;
}
return ST_CONTINUE;
}
static void
gc_ref_update_table_values_only(void *objspace, st_table *tbl)
{
if (!tbl || tbl->num_entries == 0) return;
if (st_foreach_with_replace(tbl, hash_foreach_replace_value, hash_replace_ref_value, (st_data_t)objspace)) {
rb_raise(rb_eRuntimeError, "hash modified during iteration");
}
}
void
rb_gc_ref_update_table_values_only(st_table *tbl)
{
gc_ref_update_table_values_only(rb_gc_get_objspace(), tbl);
}
static void
gc_update_table_refs(void *objspace, st_table *tbl)
{
if (!tbl || tbl->num_entries == 0) return;
if (st_foreach_with_replace(tbl, hash_foreach_replace, hash_replace_ref, (st_data_t)objspace)) {
rb_raise(rb_eRuntimeError, "hash modified during iteration");
}
}
/* Update MOVED references in a VALUE=>VALUE st_table */
void
rb_gc_update_tbl_refs(st_table *ptr)
{
gc_update_table_refs(rb_gc_get_objspace(), ptr);
}
static void
gc_ref_update_hash(void *objspace, VALUE v)
{
rb_hash_stlike_foreach_with_replace(v, hash_foreach_replace, hash_replace_ref, (st_data_t)objspace);
}
static void
gc_update_values(void *objspace, long n, VALUE *values)
{
for (long i = 0; i < n; i++) {
UPDATE_IF_MOVED(objspace, values[i]);
}
}
void
rb_gc_update_values(long n, VALUE *values)
{
gc_update_values(rb_gc_get_objspace(), n, values);
}
static enum rb_id_table_iterator_result
check_id_table_move(VALUE value, void *data)
{
void *objspace = (void *)data;
if (rb_gc_impl_object_moved_p(objspace, (VALUE)value)) {
return ID_TABLE_REPLACE;
}
return ID_TABLE_CONTINUE;
}
VALUE
rb_gc_location(VALUE value)
{
return rb_gc_impl_location(rb_gc_get_objspace(), value);
}
void
rb_gc_prepare_heap(void)
{
rb_gc_impl_prepare_heap(rb_gc_get_objspace());
}
size_t
rb_gc_size_pool_id_for_size(size_t size)
{
return rb_gc_impl_size_pool_id_for_size(rb_gc_get_objspace(), size);
}
bool
rb_gc_size_allocatable_p(size_t size)
{
return rb_gc_impl_size_allocatable_p(size);
}
static enum rb_id_table_iterator_result
update_id_table(VALUE *value, void *data, int existing)
{
void *objspace = (void *)data;
if (rb_gc_impl_object_moved_p(objspace, (VALUE)*value)) {
*value = rb_gc_impl_location(objspace, (VALUE)*value);
}
return ID_TABLE_CONTINUE;
}
static void
update_m_tbl(void *objspace, struct rb_id_table *tbl)
{
if (tbl) {
rb_id_table_foreach_values_with_replace(tbl, check_id_table_move, update_id_table, objspace);
}
}
static enum rb_id_table_iterator_result
update_cc_tbl_i(VALUE ccs_ptr, void *objspace)
{
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_ptr;
VM_ASSERT(vm_ccs_p(ccs));
if (rb_gc_impl_object_moved_p(objspace, (VALUE)ccs->cme)) {
ccs->cme = (const rb_callable_method_entry_t *)rb_gc_impl_location(objspace, (VALUE)ccs->cme);
}
for (int i=0; i<ccs->len; i++) {
if (rb_gc_impl_object_moved_p(objspace, (VALUE)ccs->entries[i].cc)) {
ccs->entries[i].cc = (struct rb_callcache *)rb_gc_location((VALUE)ccs->entries[i].cc);
}
}
// do not replace
return ID_TABLE_CONTINUE;
}
static void
update_cc_tbl(void *objspace, VALUE klass)
{
struct rb_id_table *tbl = RCLASS_CC_TBL(klass);
if (tbl) {
rb_id_table_foreach_values(tbl, update_cc_tbl_i, objspace);
}
}
static enum rb_id_table_iterator_result
update_cvc_tbl_i(VALUE cvc_entry, void *objspace)
{
struct rb_cvar_class_tbl_entry *entry;
entry = (struct rb_cvar_class_tbl_entry *)cvc_entry;
if (entry->cref) {
TYPED_UPDATE_IF_MOVED(objspace, rb_cref_t *, entry->cref);
}
entry->class_value = rb_gc_impl_location(objspace, entry->class_value);
return ID_TABLE_CONTINUE;
}
static void
update_cvc_tbl(void *objspace, VALUE klass)
{
struct rb_id_table *tbl = RCLASS_CVC_TBL(klass);
if (tbl) {
rb_id_table_foreach_values(tbl, update_cvc_tbl_i, objspace);
}
}
static enum rb_id_table_iterator_result
update_const_table(VALUE value, void *objspace)
{
rb_const_entry_t *ce = (rb_const_entry_t *)value;
if (rb_gc_impl_object_moved_p(objspace, ce->value)) {
ce->value = rb_gc_impl_location(objspace, ce->value);
}
if (rb_gc_impl_object_moved_p(objspace, ce->file)) {
ce->file = rb_gc_impl_location(objspace, ce->file);
}
return ID_TABLE_CONTINUE;
}
static void
update_const_tbl(void *objspace, struct rb_id_table *tbl)
{
if (!tbl) return;
rb_id_table_foreach_values(tbl, update_const_table, objspace);
}
static void
update_subclass_entries(void *objspace, rb_subclass_entry_t *entry)
{
while (entry) {
UPDATE_IF_MOVED(objspace, entry->klass);
entry = entry->next;
}
}
static void
update_class_ext(void *objspace, rb_classext_t *ext)
{
UPDATE_IF_MOVED(objspace, ext->origin_);
UPDATE_IF_MOVED(objspace, ext->includer);
UPDATE_IF_MOVED(objspace, ext->refined_class);
update_subclass_entries(objspace, ext->subclasses);
}
static void
update_superclasses(void *objspace, VALUE obj)
{
if (FL_TEST_RAW(obj, RCLASS_SUPERCLASSES_INCLUDE_SELF)) {
for (size_t i = 0; i < RCLASS_SUPERCLASS_DEPTH(obj) + 1; i++) {
UPDATE_IF_MOVED(objspace, RCLASS_SUPERCLASSES(obj)[i]);
}
}
}
extern rb_symbols_t ruby_global_symbols;
#define global_symbols ruby_global_symbols
void
rb_gc_update_vm_references(void *objspace)
{
rb_execution_context_t *ec = GET_EC();
rb_vm_t *vm = rb_ec_vm_ptr(ec);
rb_vm_update_references(vm);
rb_gc_update_global_tbl();
global_symbols.ids = rb_gc_impl_location(objspace, global_symbols.ids);
global_symbols.dsymbol_fstr_hash = rb_gc_impl_location(objspace, global_symbols.dsymbol_fstr_hash);
gc_update_table_refs(objspace, global_symbols.str_sym);
}
void
rb_gc_update_object_references(void *objspace, VALUE obj)
{
if (FL_TEST(obj, FL_EXIVAR)) {
rb_ref_update_generic_ivar(obj);
}
switch (BUILTIN_TYPE(obj)) {
case T_CLASS:
if (FL_TEST(obj, FL_SINGLETON)) {
UPDATE_IF_MOVED(objspace, RCLASS_ATTACHED_OBJECT(obj));
}
// Continue to the shared T_CLASS/T_MODULE
case T_MODULE:
if (RCLASS_SUPER((VALUE)obj)) {
UPDATE_IF_MOVED(objspace, RCLASS(obj)->super);
}
update_m_tbl(objspace, RCLASS_M_TBL(obj));
update_cc_tbl(objspace, obj);
update_cvc_tbl(objspace, obj);
update_superclasses(objspace, obj);
if (rb_shape_obj_too_complex(obj)) {
gc_ref_update_table_values_only(objspace, RCLASS_IV_HASH(obj));
}
else {
for (attr_index_t i = 0; i < RCLASS_IV_COUNT(obj); i++) {
UPDATE_IF_MOVED(objspace, RCLASS_IVPTR(obj)[i]);
}
}
update_class_ext(objspace, RCLASS_EXT(obj));
update_const_tbl(objspace, RCLASS_CONST_TBL(obj));
UPDATE_IF_MOVED(objspace, RCLASS_EXT(obj)->classpath);
break;
case T_ICLASS:
if (RICLASS_OWNS_M_TBL_P(obj)) {
update_m_tbl(objspace, RCLASS_M_TBL(obj));
}
if (RCLASS_SUPER((VALUE)obj)) {
UPDATE_IF_MOVED(objspace, RCLASS(obj)->super);
}
update_class_ext(objspace, RCLASS_EXT(obj));
update_m_tbl(objspace, RCLASS_CALLABLE_M_TBL(obj));
update_cc_tbl(objspace, obj);
break;
case T_IMEMO:
rb_imemo_mark_and_move(obj, true);
return;
case T_NIL:
case T_FIXNUM:
case T_NODE:
case T_MOVED:
case T_NONE:
/* These can't move */
return;
case T_ARRAY:
gc_ref_update_array(objspace, obj);
break;
case T_HASH:
gc_ref_update_hash(objspace, obj);
UPDATE_IF_MOVED(objspace, RHASH(obj)->ifnone);
break;
case T_STRING:
{
if (STR_SHARED_P(obj)) {
UPDATE_IF_MOVED(objspace, RSTRING(obj)->as.heap.aux.shared);
}
/* If, after move the string is not embedded, and can fit in the
* slot it's been placed in, then re-embed it. */
if (rb_gc_obj_slot_size(obj) >= rb_str_size_as_embedded(obj)) {
if (!STR_EMBED_P(obj) && rb_str_reembeddable_p(obj)) {
rb_str_make_embedded(obj);
}
}
break;
}
case T_DATA:
/* Call the compaction callback, if it exists */
{
void *const ptr = RTYPEDDATA_P(obj) ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
if (ptr) {
if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(RTYPEDDATA(obj)->type)) {
size_t *offset_list = (size_t *)RTYPEDDATA(obj)->type->function.dmark;
for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) {
VALUE *ref = (VALUE *)((char *)ptr + offset);
if (SPECIAL_CONST_P(*ref)) continue;
*ref = rb_gc_impl_location(objspace, *ref);
}
}
else if (RTYPEDDATA_P(obj)) {
RUBY_DATA_FUNC compact_func = RTYPEDDATA(obj)->type->function.dcompact;
if (compact_func) (*compact_func)(ptr);
}
}
}
break;
case T_OBJECT:
gc_ref_update_object(objspace, obj);
break;
case T_FILE:
if (RFILE(obj)->fptr) {
UPDATE_IF_MOVED(objspace, RFILE(obj)->fptr->self);
UPDATE_IF_MOVED(objspace, RFILE(obj)->fptr->pathv);
UPDATE_IF_MOVED(objspace, RFILE(obj)->fptr->tied_io_for_writing);
UPDATE_IF_MOVED(objspace, RFILE(obj)->fptr->writeconv_asciicompat);
UPDATE_IF_MOVED(objspace, RFILE(obj)->fptr->writeconv_pre_ecopts);
UPDATE_IF_MOVED(objspace, RFILE(obj)->fptr->encs.ecopts);
UPDATE_IF_MOVED(objspace, RFILE(obj)->fptr->write_lock);
}
break;
case T_REGEXP:
UPDATE_IF_MOVED(objspace, RREGEXP(obj)->src);
break;
case T_SYMBOL:
UPDATE_IF_MOVED(objspace, RSYMBOL(obj)->fstr);
break;
case T_FLOAT:
case T_BIGNUM:
break;
case T_MATCH:
UPDATE_IF_MOVED(objspace, RMATCH(obj)->regexp);
if (RMATCH(obj)->str) {
UPDATE_IF_MOVED(objspace, RMATCH(obj)->str);
}
break;
case T_RATIONAL:
UPDATE_IF_MOVED(objspace, RRATIONAL(obj)->num);
UPDATE_IF_MOVED(objspace, RRATIONAL(obj)->den);
break;
case T_COMPLEX:
UPDATE_IF_MOVED(objspace, RCOMPLEX(obj)->real);
UPDATE_IF_MOVED(objspace, RCOMPLEX(obj)->imag);
break;
case T_STRUCT:
{
long i, len = RSTRUCT_LEN(obj);
VALUE *ptr = (VALUE *)RSTRUCT_CONST_PTR(obj);
for (i = 0; i < len; i++) {
UPDATE_IF_MOVED(objspace, ptr[i]);
}
}
break;
default:
rb_bug("unreachable");
break;
}
UPDATE_IF_MOVED(objspace, RBASIC(obj)->klass);
}
VALUE
rb_gc_start(void)
{
rb_gc();
return Qnil;
}
void
rb_gc(void)
{
unless_objspace(objspace) { return; }
rb_gc_impl_start(objspace, true, true, true, false);
}
int
rb_during_gc(void)
{
unless_objspace(objspace) { return FALSE; }
return rb_gc_impl_during_gc_p(objspace);
}
size_t
rb_gc_count(void)
{
return rb_gc_impl_gc_count(rb_gc_get_objspace());
}
static VALUE
gc_count(rb_execution_context_t *ec, VALUE self)
{
return SIZET2NUM(rb_gc_count());
}
VALUE
rb_gc_latest_gc_info(VALUE key)
{
return rb_gc_impl_latest_gc_info(rb_gc_get_objspace(), key);
}
static VALUE
gc_latest_gc_info(rb_execution_context_t *ec, VALUE self, VALUE arg)
{
if (NIL_P(arg)) {
arg = rb_hash_new();
}
else if (!SYMBOL_P(arg) && !RB_TYPE_P(arg, T_HASH)) {
rb_raise(rb_eTypeError, "non-hash or symbol given");
}
return rb_gc_latest_gc_info(arg);
}
static VALUE
gc_stat(rb_execution_context_t *ec, VALUE self, VALUE arg) // arg is (nil || hash || symbol)
{
if (NIL_P(arg)) {
arg = rb_hash_new();
}
size_t val = rb_gc_impl_stat(rb_gc_get_objspace(), arg);
if (SYMBOL_P(arg)) {
return SIZET2NUM(val);
}
else {
return arg;
}
}
size_t
rb_gc_stat(VALUE key)
{
if (SYMBOL_P(key)) {
return rb_gc_impl_stat(rb_gc_get_objspace(), key);
}
else {
rb_gc_impl_stat(rb_gc_get_objspace(), key);
return 0;
}
}
static VALUE
gc_stat_heap(rb_execution_context_t *ec, VALUE self, VALUE heap_name, VALUE arg)
{
if (NIL_P(arg)) {
arg = rb_hash_new();
}
size_t val = rb_gc_impl_stat_heap(rb_gc_get_objspace(), heap_name, arg);
if (SYMBOL_P(arg)) {
return SIZET2NUM(val);
}
else {
return arg;
}
}
static VALUE
gc_config_get(rb_execution_context_t *ec, VALUE self)
{
return rb_gc_impl_config_get(rb_gc_get_objspace());
}
static VALUE
gc_config_set(rb_execution_context_t *ec, VALUE self, VALUE hash)
{
return rb_gc_impl_config_set(rb_gc_get_objspace(), hash);
}
static VALUE
gc_stress_get(rb_execution_context_t *ec, VALUE self)
{
return rb_gc_impl_stress_get(rb_gc_get_objspace());
}
static VALUE
gc_stress_set_m(rb_execution_context_t *ec, VALUE self, VALUE flag)
{
rb_gc_impl_stress_set(rb_gc_get_objspace(), flag);
return flag;
}
void
rb_gc_initial_stress_set(VALUE flag)
{
rb_gc_impl_initial_stress_set(flag);
}
size_t *
rb_gc_size_pool_sizes(void)
{
return rb_gc_impl_size_pool_sizes(rb_gc_get_objspace());
}
VALUE
rb_gc_enable(void)
{
return rb_objspace_gc_enable(rb_gc_get_objspace());
}
VALUE
rb_objspace_gc_enable(void *objspace)
{
bool disabled = !rb_gc_impl_gc_enabled_p(objspace);
rb_gc_impl_gc_enable(objspace);
return RBOOL(disabled);
}
static VALUE
gc_enable(rb_execution_context_t *ec, VALUE _)
{
return rb_gc_enable();
}
static VALUE
gc_disable_no_rest(void *objspace)
{
bool disabled = !rb_gc_impl_gc_enabled_p(objspace);
rb_gc_impl_gc_disable(objspace, false);
return RBOOL(disabled);
}
VALUE
rb_gc_disable_no_rest(void)
{
return gc_disable_no_rest(rb_gc_get_objspace());
}
VALUE
rb_gc_disable(void)
{
return rb_objspace_gc_disable(rb_gc_get_objspace());
}
VALUE
rb_objspace_gc_disable(void *objspace)
{
bool disabled = !rb_gc_impl_gc_enabled_p(objspace);
rb_gc_impl_gc_disable(objspace, true);
return RBOOL(disabled);
}
static VALUE
gc_disable(rb_execution_context_t *ec, VALUE _)
{
return rb_gc_disable();
}
// TODO: think about moving ruby_gc_set_params into Init_heap or Init_gc
void
ruby_gc_set_params(void)
{
rb_gc_impl_set_params(rb_gc_get_objspace());
}
void
rb_gc_reachable_objects_from_callback(VALUE obj)
{
rb_ractor_t *cr = GET_RACTOR();
cr->mfd->mark_func(obj, cr->mfd->data);
}
void
rb_objspace_reachable_objects_from(VALUE obj, void (func)(VALUE, void *), void *data)
{
RB_VM_LOCK_ENTER();
{
if (rb_gc_impl_during_gc_p(rb_gc_get_objspace())) rb_bug("rb_objspace_reachable_objects_from() is not supported while during GC");
if (!RB_SPECIAL_CONST_P(obj)) {
rb_ractor_t *cr = GET_RACTOR();
struct gc_mark_func_data_struct mfd = {
.mark_func = func,
.data = data,
}, *prev_mfd = cr->mfd;
cr->mfd = &mfd;
rb_gc_mark_children(rb_gc_get_objspace(), obj);
cr->mfd = prev_mfd;
}
}
RB_VM_LOCK_LEAVE();
}
struct root_objects_data {
const char *category;
void (*func)(const char *category, VALUE, void *);
void *data;
};
static void
root_objects_from(VALUE obj, void *ptr)
{
const struct root_objects_data *data = (struct root_objects_data *)ptr;
(*data->func)(data->category, obj, data->data);
}
void
rb_objspace_reachable_objects_from_root(void (func)(const char *category, VALUE, void *), void *passing_data)
{
if (rb_gc_impl_during_gc_p(rb_gc_get_objspace())) rb_bug("rb_gc_impl_objspace_reachable_objects_from_root() is not supported while during GC");
rb_ractor_t *cr = GET_RACTOR();
struct root_objects_data data = {
.func = func,
.data = passing_data,
};
struct gc_mark_func_data_struct mfd = {
.mark_func = root_objects_from,
.data = &data,
}, *prev_mfd = cr->mfd;
cr->mfd = &mfd;
rb_gc_mark_roots(rb_gc_get_objspace(), &data.category);
cr->mfd = prev_mfd;
}
/*
------------------------------ DEBUG ------------------------------
*/
static const char *
type_name(int type, VALUE obj)
{
switch (type) {
#define TYPE_NAME(t) case (t): return #t;
TYPE_NAME(T_NONE);
TYPE_NAME(T_OBJECT);
TYPE_NAME(T_CLASS);
TYPE_NAME(T_MODULE);
TYPE_NAME(T_FLOAT);
TYPE_NAME(T_STRING);
TYPE_NAME(T_REGEXP);
TYPE_NAME(T_ARRAY);
TYPE_NAME(T_HASH);
TYPE_NAME(T_STRUCT);
TYPE_NAME(T_BIGNUM);
TYPE_NAME(T_FILE);
TYPE_NAME(T_MATCH);
TYPE_NAME(T_COMPLEX);
TYPE_NAME(T_RATIONAL);
TYPE_NAME(T_NIL);
TYPE_NAME(T_TRUE);
TYPE_NAME(T_FALSE);
TYPE_NAME(T_SYMBOL);
TYPE_NAME(T_FIXNUM);
TYPE_NAME(T_UNDEF);
TYPE_NAME(T_IMEMO);
TYPE_NAME(T_ICLASS);
TYPE_NAME(T_MOVED);
TYPE_NAME(T_ZOMBIE);
case T_DATA:
if (obj && rb_objspace_data_type_name(obj)) {
return rb_objspace_data_type_name(obj);
}
return "T_DATA";
#undef TYPE_NAME
}
return "unknown";
}
static const char *
obj_type_name(VALUE obj)
{
return type_name(TYPE(obj), obj);
}
const char *
rb_method_type_name(rb_method_type_t type)
{
switch (type) {
case VM_METHOD_TYPE_ISEQ: return "iseq";
case VM_METHOD_TYPE_ATTRSET: return "attrest";
case VM_METHOD_TYPE_IVAR: return "ivar";
case VM_METHOD_TYPE_BMETHOD: return "bmethod";
case VM_METHOD_TYPE_ALIAS: return "alias";
case VM_METHOD_TYPE_REFINED: return "refined";
case VM_METHOD_TYPE_CFUNC: return "cfunc";
case VM_METHOD_TYPE_ZSUPER: return "zsuper";
case VM_METHOD_TYPE_MISSING: return "missing";
case VM_METHOD_TYPE_OPTIMIZED: return "optimized";
case VM_METHOD_TYPE_UNDEF: return "undef";
case VM_METHOD_TYPE_NOTIMPLEMENTED: return "notimplemented";
}
rb_bug("rb_method_type_name: unreachable (type: %d)", type);
}
static void
rb_raw_iseq_info(char *const buff, const size_t buff_size, const rb_iseq_t *iseq)
{
if (buff_size > 0 && ISEQ_BODY(iseq) && ISEQ_BODY(iseq)->location.label && !RB_TYPE_P(ISEQ_BODY(iseq)->location.pathobj, T_MOVED)) {
VALUE path = rb_iseq_path(iseq);
int n = ISEQ_BODY(iseq)->location.first_lineno;
snprintf(buff, buff_size, " %s@%s:%d",
RSTRING_PTR(ISEQ_BODY(iseq)->location.label),
RSTRING_PTR(path), n);
}
}
static int
str_len_no_raise(VALUE str)
{
long len = RSTRING_LEN(str);
if (len < 0) return 0;
if (len > INT_MAX) return INT_MAX;
return (int)len;
}
#define BUFF_ARGS buff + pos, buff_size - pos
#define APPEND_F(...) if ((pos += snprintf(BUFF_ARGS, "" __VA_ARGS__)) >= buff_size) goto end
#define APPEND_S(s) do { \
if ((pos + (int)rb_strlen_lit(s)) >= buff_size) { \
goto end; \
} \
else { \
memcpy(buff + pos, (s), rb_strlen_lit(s) + 1); \
} \
} while (0)
#define C(c, s) ((c) != 0 ? (s) : " ")
static size_t
rb_raw_obj_info_common(char *const buff, const size_t buff_size, const VALUE obj)
{
size_t pos = 0;
if (SPECIAL_CONST_P(obj)) {
APPEND_F("%s", obj_type_name(obj));
if (FIXNUM_P(obj)) {
APPEND_F(" %ld", FIX2LONG(obj));
}
else if (SYMBOL_P(obj)) {
APPEND_F(" %s", rb_id2name(SYM2ID(obj)));
}
}
else {
// const int age = RVALUE_AGE_GET(obj);
if (rb_gc_impl_pointer_to_heap_p(rb_gc_get_objspace(), (void *)obj)) {
// TODO: fixme
// APPEND_F("%p [%d%s%s%s%s%s%s] %s ",
// (void *)obj, age,
// C(RVALUE_UNCOLLECTIBLE_BITMAP(obj), "L"),
// C(RVALUE_MARK_BITMAP(obj), "M"),
// C(RVALUE_PIN_BITMAP(obj), "P"),
// C(RVALUE_MARKING_BITMAP(obj), "R"),
// C(RVALUE_WB_UNPROTECTED_BITMAP(obj), "U"),
// C(rb_objspace_garbage_object_p(obj), "G"),
// obj_type_name(obj));
}
else {
/* fake */
// APPEND_F("%p [%dXXXX] %s",
// (void *)obj, age,
// obj_type_name(obj));
}
if (internal_object_p(obj)) {
/* ignore */
}
else if (RBASIC(obj)->klass == 0) {
APPEND_S("(temporary internal)");
}
else if (RTEST(RBASIC(obj)->klass)) {
VALUE class_path = rb_class_path_cached(RBASIC(obj)->klass);
if (!NIL_P(class_path)) {
APPEND_F("(%s)", RSTRING_PTR(class_path));
}
}
}
end:
return pos;
}
const char *rb_raw_obj_info(char *const buff, const size_t buff_size, VALUE obj);
static size_t
rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALUE obj, size_t pos)
{
if (LIKELY(pos < buff_size) && !SPECIAL_CONST_P(obj)) {
const enum ruby_value_type type = BUILTIN_TYPE(obj);
switch (type) {
case T_NODE:
UNEXPECTED_NODE(rb_raw_obj_info);
break;
case T_ARRAY:
if (ARY_SHARED_P(obj)) {
APPEND_S("shared -> ");
rb_raw_obj_info(BUFF_ARGS, ARY_SHARED_ROOT(obj));
}
else if (ARY_EMBED_P(obj)) {
APPEND_F("[%s%s] len: %ld (embed)",
C(ARY_EMBED_P(obj), "E"),
C(ARY_SHARED_P(obj), "S"),
RARRAY_LEN(obj));
}
else {
APPEND_F("[%s%s] len: %ld, capa:%ld ptr:%p",
C(ARY_EMBED_P(obj), "E"),
C(ARY_SHARED_P(obj), "S"),
RARRAY_LEN(obj),
ARY_EMBED_P(obj) ? -1L : RARRAY(obj)->as.heap.aux.capa,
(void *)RARRAY_CONST_PTR(obj));
}
break;
case T_STRING: {
if (STR_SHARED_P(obj)) {
APPEND_F(" [shared] len: %ld", RSTRING_LEN(obj));
}
else {
if (STR_EMBED_P(obj)) APPEND_S(" [embed]");
APPEND_F(" len: %ld, capa: %" PRIdSIZE, RSTRING_LEN(obj), rb_str_capacity(obj));
}
APPEND_F(" \"%.*s\"", str_len_no_raise(obj), RSTRING_PTR(obj));
break;
}
case T_SYMBOL: {
VALUE fstr = RSYMBOL(obj)->fstr;
ID id = RSYMBOL(obj)->id;
if (RB_TYPE_P(fstr, T_STRING)) {
APPEND_F(":%s id:%d", RSTRING_PTR(fstr), (unsigned int)id);
}
else {
APPEND_F("(%p) id:%d", (void *)fstr, (unsigned int)id);
}
break;
}
case T_MOVED: {
APPEND_F("-> %p", (void*)rb_gc_impl_location(rb_gc_get_objspace(), obj));
break;
}
case T_HASH: {
APPEND_F("[%c] %"PRIdSIZE,
RHASH_AR_TABLE_P(obj) ? 'A' : 'S',
RHASH_SIZE(obj));
break;
}
case T_CLASS:
case T_MODULE:
{
VALUE class_path = rb_class_path_cached(obj);
if (!NIL_P(class_path)) {
APPEND_F("%s", RSTRING_PTR(class_path));
}
else {
APPEND_S("(anon)");
}
break;
}
case T_ICLASS:
{
VALUE class_path = rb_class_path_cached(RBASIC_CLASS(obj));
if (!NIL_P(class_path)) {
APPEND_F("src:%s", RSTRING_PTR(class_path));
}
break;
}
case T_OBJECT:
{
if (rb_shape_obj_too_complex(obj)) {
size_t hash_len = rb_st_table_size(ROBJECT_IV_HASH(obj));
APPEND_F("(too_complex) len:%zu", hash_len);
}
else {
uint32_t len = ROBJECT_IV_CAPACITY(obj);
if (RBASIC(obj)->flags & ROBJECT_EMBED) {
APPEND_F("(embed) len:%d", len);
}
else {
VALUE *ptr = ROBJECT_IVPTR(obj);
APPEND_F("len:%d ptr:%p", len, (void *)ptr);
}
}
}
break;
case T_DATA: {
const struct rb_block *block;
const rb_iseq_t *iseq;
if (rb_obj_is_proc(obj) &&
(block = vm_proc_block(obj)) != NULL &&
(vm_block_type(block) == block_type_iseq) &&
(iseq = vm_block_iseq(block)) != NULL) {
rb_raw_iseq_info(BUFF_ARGS, iseq);
}
else if (rb_ractor_p(obj)) {
rb_ractor_t *r = (void *)DATA_PTR(obj);
if (r) {
APPEND_F("r:%d", r->pub.id);
}
}
else {
const char * const type_name = rb_objspace_data_type_name(obj);
if (type_name) {
APPEND_F("%s", type_name);
}
}
break;
}
case T_IMEMO: {
APPEND_F("<%s> ", rb_imemo_name(imemo_type(obj)));
switch (imemo_type(obj)) {
case imemo_ment:
{
const rb_method_entry_t *me = (const rb_method_entry_t *)obj;
APPEND_F(":%s (%s%s%s%s) type:%s aliased:%d owner:%p defined_class:%p",
rb_id2name(me->called_id),
METHOD_ENTRY_VISI(me) == METHOD_VISI_PUBLIC ? "pub" :
METHOD_ENTRY_VISI(me) == METHOD_VISI_PRIVATE ? "pri" : "pro",
METHOD_ENTRY_COMPLEMENTED(me) ? ",cmp" : "",
METHOD_ENTRY_CACHED(me) ? ",cc" : "",
METHOD_ENTRY_INVALIDATED(me) ? ",inv" : "",
me->def ? rb_method_type_name(me->def->type) : "NULL",
me->def ? me->def->aliased : -1,
(void *)me->owner, // obj_info(me->owner),
(void *)me->defined_class); //obj_info(me->defined_class)));
if (me->def) {
switch (me->def->type) {
case VM_METHOD_TYPE_ISEQ:
APPEND_S(" (iseq:");
rb_raw_obj_info(BUFF_ARGS, (VALUE)me->def->body.iseq.iseqptr);
APPEND_S(")");
break;
default:
break;
}
}
break;
}
case imemo_iseq: {
const rb_iseq_t *iseq = (const rb_iseq_t *)obj;
rb_raw_iseq_info(BUFF_ARGS, iseq);
break;
}
case imemo_callinfo:
{
const struct rb_callinfo *ci = (const struct rb_callinfo *)obj;
APPEND_F("(mid:%s, flag:%x argc:%d, kwarg:%s)",
rb_id2name(vm_ci_mid(ci)),
vm_ci_flag(ci),
vm_ci_argc(ci),
vm_ci_kwarg(ci) ? "available" : "NULL");
break;
}
case imemo_callcache:
{
const struct rb_callcache *cc = (const struct rb_callcache *)obj;
VALUE class_path = cc->klass ? rb_class_path_cached(cc->klass) : Qnil;
const rb_callable_method_entry_t *cme = vm_cc_cme(cc);
APPEND_F("(klass:%s cme:%s%s (%p) call:%p",
NIL_P(class_path) ? (cc->klass ? "??" : "<NULL>") : RSTRING_PTR(class_path),
cme ? rb_id2name(cme->called_id) : "<NULL>",
cme ? (METHOD_ENTRY_INVALIDATED(cme) ? " [inv]" : "") : "",
(void *)cme,
(void *)vm_cc_call(cc));
break;
}
default:
break;
}
}
default:
break;
}
}
end:
return pos;
}
#undef C
#define asan_unpoisoning_object(obj) \
for (void *poisoned = asan_unpoison_object_temporary(obj), \
*unpoisoning = &poisoned; /* flag to loop just once */ \
unpoisoning; \
unpoisoning = asan_poison_object_restore(obj, poisoned))
const char *
rb_raw_obj_info(char *const buff, const size_t buff_size, VALUE obj)
{
asan_unpoisoning_object(obj) {
size_t pos = rb_raw_obj_info_common(buff, buff_size, obj);
pos = rb_raw_obj_info_buitin_type(buff, buff_size, obj, pos);
if (pos >= buff_size) {} // truncated
}
return buff;
}
#undef APPEND_S
#undef APPEND_F
#undef BUFF_ARGS
#if RGENGC_OBJ_INFO
#define OBJ_INFO_BUFFERS_NUM 10
#define OBJ_INFO_BUFFERS_SIZE 0x100
static rb_atomic_t obj_info_buffers_index = 0;
static char obj_info_buffers[OBJ_INFO_BUFFERS_NUM][OBJ_INFO_BUFFERS_SIZE];
/* Increments *var atomically and resets *var to 0 when maxval is
* reached. Returns the wraparound old *var value (0...maxval). */
static rb_atomic_t
atomic_inc_wraparound(rb_atomic_t *var, const rb_atomic_t maxval)
{
rb_atomic_t oldval = RUBY_ATOMIC_FETCH_ADD(*var, 1);
if (RB_UNLIKELY(oldval >= maxval - 1)) { // wraparound *var
const rb_atomic_t newval = oldval + 1;
RUBY_ATOMIC_CAS(*var, newval, newval % maxval);
oldval %= maxval;
}
return oldval;
}
static const char *
obj_info(VALUE obj)
{
rb_atomic_t index = atomic_inc_wraparound(&obj_info_buffers_index, OBJ_INFO_BUFFERS_NUM);
char *const buff = obj_info_buffers[index];
return rb_raw_obj_info(buff, OBJ_INFO_BUFFERS_SIZE, obj);
}
#else
static const char *
obj_info(VALUE obj)
{
return obj_type_name(obj);
}
#endif
/*
------------------------ Extended allocator ------------------------
*/
struct gc_raise_tag {
VALUE exc;
const char *fmt;
va_list *ap;
};
static void *
gc_vraise(void *ptr)
{
struct gc_raise_tag *argv = ptr;
rb_vraise(argv->exc, argv->fmt, *argv->ap);
UNREACHABLE_RETURN(NULL);
}
static void
gc_raise(VALUE exc, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
struct gc_raise_tag argv = {
exc, fmt, &ap,
};
if (ruby_thread_has_gvl_p()) {
gc_vraise(&argv);
UNREACHABLE;
}
else if (ruby_native_thread_p()) {
rb_thread_call_with_gvl(gc_vraise, &argv);
UNREACHABLE;
}
else {
/* Not in a ruby thread */
fprintf(stderr, "%s", "[FATAL] ");
vfprintf(stderr, fmt, ap);
}
va_end(ap);
abort();
}
NORETURN(static void negative_size_allocation_error(const char *));
static void
negative_size_allocation_error(const char *msg)
{
gc_raise(rb_eNoMemError, "%s", msg);
}
static void *
ruby_memerror_body(void *dummy)
{
rb_memerror();
return 0;
}
NORETURN(static void ruby_memerror(void));
RBIMPL_ATTR_MAYBE_UNUSED()
static void
ruby_memerror(void)
{
if (ruby_thread_has_gvl_p()) {
rb_memerror();
}
else {
if (ruby_native_thread_p()) {
rb_thread_call_with_gvl(ruby_memerror_body, 0);
}
else {
/* no ruby thread */
fprintf(stderr, "[FATAL] failed to allocate memory\n");
}
}
exit(EXIT_FAILURE);
}
void
rb_memerror(void)
{
rb_execution_context_t *ec = GET_EC();
VALUE exc = GET_VM()->special_exceptions[ruby_error_nomemory];
if (!exc ||
rb_ec_raised_p(ec, RAISED_NOMEMORY)) {
fprintf(stderr, "[FATAL] failed to allocate memory\n");
exit(EXIT_FAILURE);
}
if (rb_ec_raised_p(ec, RAISED_NOMEMORY)) {
rb_ec_raised_clear(ec);
}
else {
rb_ec_raised_set(ec, RAISED_NOMEMORY);
exc = ruby_vm_special_exception_copy(exc);
}
ec->errinfo = exc;
EC_JUMP_TAG(ec, TAG_RAISE);
}
void
rb_malloc_info_show_results(void)
{
}
void *
ruby_xmalloc(size_t size)
{
if ((ssize_t)size < 0) {
negative_size_allocation_error("too large allocation size");
}
return rb_gc_impl_malloc(rb_gc_get_objspace(), size);
}
void
ruby_malloc_size_overflow(size_t count, size_t elsize)
{
rb_raise(rb_eArgError,
"malloc: possible integer overflow (%"PRIuSIZE"*%"PRIuSIZE")",
count, elsize);
}
static inline size_t
xmalloc2_size(const size_t count, const size_t elsize)
{
return size_mul_or_raise(count, elsize, rb_eArgError);
}
void *
ruby_xmalloc2(size_t n, size_t size)
{
return rb_gc_impl_malloc(rb_gc_get_objspace(), xmalloc2_size(n, size));
}
void *
ruby_xcalloc(size_t n, size_t size)
{
return rb_gc_impl_calloc(rb_gc_get_objspace(), xmalloc2_size(n, size));
}
#ifdef ruby_sized_xrealloc
#undef ruby_sized_xrealloc
#endif
void *
ruby_sized_xrealloc(void *ptr, size_t new_size, size_t old_size)
{
if ((ssize_t)new_size < 0) {
negative_size_allocation_error("too large allocation size");
}
return rb_gc_impl_realloc(rb_gc_get_objspace(), ptr, new_size, old_size);
}
void *
ruby_xrealloc(void *ptr, size_t new_size)
{
return ruby_sized_xrealloc(ptr, new_size, 0);
}
#ifdef ruby_sized_xrealloc2
#undef ruby_sized_xrealloc2
#endif
void *
ruby_sized_xrealloc2(void *ptr, size_t n, size_t size, size_t old_n)
{
size_t len = xmalloc2_size(n, size);
return rb_gc_impl_realloc(rb_gc_get_objspace(), ptr, len, old_n * size);
}
void *
ruby_xrealloc2(void *ptr, size_t n, size_t size)
{
return ruby_sized_xrealloc2(ptr, n, size, 0);
}
#ifdef ruby_sized_xfree
#undef ruby_sized_xfree
#endif
void
ruby_sized_xfree(void *x, size_t size)
{
if (LIKELY(x)) {
/* It's possible for a C extension's pthread destructor function set by pthread_key_create
* to be called after ruby_vm_destruct and attempt to free memory. Fall back to mimfree in
* that case. */
if (LIKELY(GET_VM())) {
rb_gc_impl_free(rb_gc_get_objspace(), x, size);
}
else {
ruby_mimfree(x);
}
}
}
void
ruby_xfree(void *x)
{
ruby_sized_xfree(x, 0);
}
void *
rb_xmalloc_mul_add(size_t x, size_t y, size_t z) /* x * y + z */
{
size_t w = size_mul_add_or_raise(x, y, z, rb_eArgError);
return ruby_xmalloc(w);
}
void *
rb_xcalloc_mul_add(size_t x, size_t y, size_t z) /* x * y + z */
{
size_t w = size_mul_add_or_raise(x, y, z, rb_eArgError);
return ruby_xcalloc(w, 1);
}
void *
rb_xrealloc_mul_add(const void *p, size_t x, size_t y, size_t z) /* x * y + z */
{
size_t w = size_mul_add_or_raise(x, y, z, rb_eArgError);
return ruby_xrealloc((void *)p, w);
}
void *
rb_xmalloc_mul_add_mul(size_t x, size_t y, size_t z, size_t w) /* x * y + z * w */
{
size_t u = size_mul_add_mul_or_raise(x, y, z, w, rb_eArgError);
return ruby_xmalloc(u);
}
void *
rb_xcalloc_mul_add_mul(size_t x, size_t y, size_t z, size_t w) /* x * y + z * w */
{
size_t u = size_mul_add_mul_or_raise(x, y, z, w, rb_eArgError);
return ruby_xcalloc(u, 1);
}
/* Mimic ruby_xmalloc, but need not rb_objspace.
* should return pointer suitable for ruby_xfree
*/
void *
ruby_mimmalloc(size_t size)
{
void *mem;
#if CALC_EXACT_MALLOC_SIZE
size += sizeof(struct malloc_obj_info);
#endif
mem = malloc(size);
#if CALC_EXACT_MALLOC_SIZE
if (!mem) {
return NULL;
}
else
/* set 0 for consistency of allocated_size/allocations */
{
struct malloc_obj_info *info = mem;
info->size = 0;
mem = info + 1;
}
#endif
return mem;
}
void *
ruby_mimcalloc(size_t num, size_t size)
{
void *mem;
#if CALC_EXACT_MALLOC_SIZE
struct rbimpl_size_mul_overflow_tag t = rbimpl_size_mul_overflow(num, size);
if (UNLIKELY(t.left)) {
return NULL;
}
size = t.right + sizeof(struct malloc_obj_info);
mem = calloc1(size);
if (!mem) {
return NULL;
}
else
/* set 0 for consistency of allocated_size/allocations */
{
struct malloc_obj_info *info = mem;
info->size = 0;
mem = info + 1;
}
#else
mem = calloc(num, size);
#endif
return mem;
}
void
ruby_mimfree(void *ptr)
{
#if CALC_EXACT_MALLOC_SIZE
struct malloc_obj_info *info = (struct malloc_obj_info *)ptr - 1;
ptr = info;
#endif
free(ptr);
}
void
rb_gc_adjust_memory_usage(ssize_t diff)
{
unless_objspace(objspace) { return; }
rb_gc_impl_adjust_memory_usage(objspace, diff);
}
const char *
rb_obj_info(VALUE obj)
{
return obj_info(obj);
}
void
rb_obj_info_dump(VALUE obj)
{
char buff[0x100];
fprintf(stderr, "rb_obj_info_dump: %s\n", rb_raw_obj_info(buff, 0x100, obj));
}
void
rb_obj_info_dump_loc(VALUE obj, const char *file, int line, const char *func)
{
char buff[0x100];
fprintf(stderr, "<OBJ_INFO:%s@%s:%d> %s\n", func, file, line, rb_raw_obj_info(buff, 0x100, obj));
}
/*
* Document-module: ObjectSpace
*
* The ObjectSpace module contains a number of routines
* that interact with the garbage collection facility and allow you to
* traverse all living objects with an iterator.
*
* ObjectSpace also provides support for object finalizers, procs that will be
* called after a specific object was destroyed by garbage collection. See
* the documentation for +ObjectSpace.define_finalizer+ for important
* information on how to use this method correctly.
*
* a = "A"
* b = "B"
*
* ObjectSpace.define_finalizer(a, proc {|id| puts "Finalizer one on #{id}" })
* ObjectSpace.define_finalizer(b, proc {|id| puts "Finalizer two on #{id}" })
*
* a = nil
* b = nil
*
* _produces:_
*
* Finalizer two on 537763470
* Finalizer one on 537763480
*/
/* Document-class: GC::Profiler
*
* The GC profiler provides access to information on GC runs including time,
* length and object space size.
*
* Example:
*
* GC::Profiler.enable
*
* require 'rdoc/rdoc'
*
* GC::Profiler.report
*
* GC::Profiler.disable
*
* See also GC.count, GC.malloc_allocated_size and GC.malloc_allocations
*/
#include "gc.rbinc"
void
Init_GC(void)
{
#undef rb_intern
malloc_offset = gc_compute_malloc_offset();
rb_mGC = rb_define_module("GC");
VALUE rb_mObjSpace = rb_define_module("ObjectSpace");
rb_define_module_function(rb_mObjSpace, "each_object", os_each_obj, -1);
rb_define_module_function(rb_mObjSpace, "define_finalizer", define_final, -1);
rb_define_module_function(rb_mObjSpace, "undefine_finalizer", undefine_final, 1);
rb_define_module_function(rb_mObjSpace, "_id2ref", os_id2ref, 1);
rb_vm_register_special_exception(ruby_error_nomemory, rb_eNoMemError, "failed to allocate memory");
rb_define_method(rb_cBasicObject, "__id__", rb_obj_id, 0);
rb_define_method(rb_mKernel, "object_id", rb_obj_id, 0);
rb_define_module_function(rb_mObjSpace, "count_objects", count_objects, -1);
rb_gc_impl_init();
}