1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/gc.c
shirosaki 3fac4abc7b wmap_finalize: refactoring to rename variables
* gc.c (wmap_final_func): rename variables to clarify the meaning.
  In wmap2obj the key is WeakRef and the value is referenced object.
  In obj2wmap the key is referenced object and the value is an array
  of WeakRef.

* gc.c (wmap_finalize): ditto.
  [ruby-core:49044] [Bug #7304]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@37827 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2012-11-24 04:01:37 +00:00

4486 lines
108 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
**********************************************************************/
#include "ruby/ruby.h"
#include "ruby/st.h"
#include "ruby/re.h"
#include "ruby/io.h"
#include "ruby/thread.h"
#include "ruby/util.h"
#include "eval_intern.h"
#include "vm_core.h"
#include "internal.h"
#include "gc.h"
#include "constant.h"
#include "ruby_atomic.h"
#include "probes.h"
#include <stdio.h>
#include <setjmp.h>
#include <sys/types.h>
#include <assert.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#if defined(__native_client__) && defined(NACL_NEWLIB)
# include "nacl/resource.h"
# undef HAVE_POSIX_MEMALIGN
# undef HAVE_MEMALIGN
#endif
#if defined _WIN32 || defined __CYGWIN__
#include <windows.h>
#elif defined(HAVE_POSIX_MEMALIGN)
#elif defined(HAVE_MEMALIGN)
#include <malloc.h>
#endif
#ifdef HAVE_VALGRIND_MEMCHECK_H
# include <valgrind/memcheck.h>
# ifndef VALGRIND_MAKE_MEM_DEFINED
# define VALGRIND_MAKE_MEM_DEFINED(p, n) VALGRIND_MAKE_READABLE((p), (n))
# endif
# ifndef VALGRIND_MAKE_MEM_UNDEFINED
# define VALGRIND_MAKE_MEM_UNDEFINED(p, n) VALGRIND_MAKE_WRITABLE((p), (n))
# endif
#else
# define VALGRIND_MAKE_MEM_DEFINED(p, n) /* empty */
# define VALGRIND_MAKE_MEM_UNDEFINED(p, n) /* empty */
#endif
#define rb_setjmp(env) RUBY_SETJMP(env)
#define rb_jmp_buf rb_jmpbuf_t
#ifndef GC_MALLOC_LIMIT
#define GC_MALLOC_LIMIT 8000000
#endif
#define HEAP_MIN_SLOTS 10000
#define FREE_MIN 4096
typedef struct {
unsigned int initial_malloc_limit;
unsigned int initial_heap_min_slots;
unsigned int initial_free_min;
#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE
int gc_stress;
#endif
} ruby_gc_params_t;
static ruby_gc_params_t initial_params = {
GC_MALLOC_LIMIT,
HEAP_MIN_SLOTS,
FREE_MIN,
#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE
FALSE,
#endif
};
#define nomem_error GET_VM()->special_exceptions[ruby_error_nomemory]
#ifndef GC_PROFILE_MORE_DETAIL
#define GC_PROFILE_MORE_DETAIL 0
#endif
typedef struct gc_profile_record {
double gc_time;
double gc_invoke_time;
size_t heap_total_objects;
size_t heap_use_size;
size_t heap_total_size;
int is_marked;
#if GC_PROFILE_MORE_DETAIL
double gc_mark_time;
double gc_sweep_time;
size_t heap_use_slots;
size_t heap_live_objects;
size_t heap_free_objects;
int have_finalize;
size_t allocate_increase;
size_t allocate_limit;
#endif
} gc_profile_record;
#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__CYGWIN__)
#pragma pack(push, 1) /* magic for reducing sizeof(RVALUE): 24 -> 20 */
#endif
typedef struct RVALUE {
union {
struct {
VALUE flags; /* always 0 for freed obj */
struct RVALUE *next;
} free;
struct RBasic basic;
struct RObject object;
struct RClass klass;
struct RFloat flonum;
struct RString string;
struct RArray array;
struct RRegexp regexp;
struct RHash hash;
struct RData data;
struct RTypedData typeddata;
struct RStruct rstruct;
struct RBignum bignum;
struct RFile file;
struct RNode node;
struct RMatch match;
struct RRational rational;
struct RComplex complex;
} as;
#ifdef GC_DEBUG
const char *file;
int line;
#endif
} RVALUE;
#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__CYGWIN__)
#pragma pack(pop)
#endif
struct heaps_slot {
struct heaps_header *header;
uintptr_t *bits;
RVALUE *freelist;
struct heaps_slot *next;
struct heaps_slot *prev;
struct heaps_slot *free_next;
};
struct heaps_header {
struct heaps_slot *base;
uintptr_t *bits;
RVALUE *start;
RVALUE *end;
size_t limit;
};
struct heaps_free_bitmap {
struct heaps_free_bitmap *next;
};
struct gc_list {
VALUE *varptr;
struct gc_list *next;
};
#define STACK_CHUNK_SIZE 500
typedef struct stack_chunk {
VALUE data[STACK_CHUNK_SIZE];
struct stack_chunk *next;
} stack_chunk_t;
typedef struct mark_stack {
stack_chunk_t *chunk;
stack_chunk_t *cache;
size_t index;
size_t limit;
size_t cache_size;
size_t unused_cache_size;
} mark_stack_t;
#ifndef CALC_EXACT_MALLOC_SIZE
#define CALC_EXACT_MALLOC_SIZE 0
#endif
typedef struct rb_objspace {
struct {
size_t limit;
size_t increase;
#if CALC_EXACT_MALLOC_SIZE
size_t allocated_size;
size_t allocations;
#endif
} malloc_params;
struct {
size_t increment;
struct heaps_slot *ptr;
struct heaps_slot *sweep_slots;
struct heaps_slot *free_slots;
struct heaps_header **sorted;
size_t length;
size_t used;
struct heaps_free_bitmap *free_bitmap;
RVALUE *range[2];
struct heaps_header *freed;
size_t live_num;
size_t free_num;
size_t free_min;
size_t final_num;
size_t do_heap_free;
} heap;
struct {
int dont_gc;
int dont_lazy_sweep;
int during_gc;
rb_atomic_t finalizing;
} flags;
struct {
st_table *table;
RVALUE *deferred;
} final;
mark_stack_t mark_stack;
struct {
int run;
gc_profile_record *record;
size_t count;
size_t size;
double invoke_time;
} profile;
struct gc_list *global_list;
size_t count;
int gc_stress;
struct mark_func_data_struct {
void *data;
void (*mark_func)(VALUE v, void *data);
} *mark_func_data;
} rb_objspace_t;
#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE
#define rb_objspace (*GET_VM()->objspace)
#define ruby_initial_gc_stress initial_params.gc_stress
int *ruby_initial_gc_stress_ptr = &ruby_initial_gc_stress;
#else
static rb_objspace_t rb_objspace = {{GC_MALLOC_LIMIT}};
int *ruby_initial_gc_stress_ptr = &rb_objspace.gc_stress;
#endif
#define malloc_limit objspace->malloc_params.limit
#define malloc_increase objspace->malloc_params.increase
#define heaps objspace->heap.ptr
#define heaps_length objspace->heap.length
#define heaps_used objspace->heap.used
#define lomem objspace->heap.range[0]
#define himem objspace->heap.range[1]
#define heaps_inc objspace->heap.increment
#define heaps_freed objspace->heap.freed
#define dont_gc objspace->flags.dont_gc
#define during_gc objspace->flags.during_gc
#define finalizing objspace->flags.finalizing
#define finalizer_table objspace->final.table
#define deferred_final_list objspace->final.deferred
#define global_List objspace->global_list
#define ruby_gc_stress objspace->gc_stress
#define initial_malloc_limit initial_params.initial_malloc_limit
#define initial_heap_min_slots initial_params.initial_heap_min_slots
#define initial_free_min initial_params.initial_free_min
#define is_lazy_sweeping(objspace) ((objspace)->heap.sweep_slots != 0)
#define nonspecial_obj_id(obj) (VALUE)((SIGNED_VALUE)(obj)|FIXNUM_FLAG)
#define RANY(o) ((RVALUE*)(o))
#define has_free_object (objspace->heap.free_slots && objspace->heap.free_slots->freelist)
#define HEAP_HEADER(p) ((struct heaps_header *)(p))
#define GET_HEAP_HEADER(x) (HEAP_HEADER((uintptr_t)(x) & ~(HEAP_ALIGN_MASK)))
#define GET_HEAP_SLOT(x) (GET_HEAP_HEADER(x)->base)
#define GET_HEAP_BITMAP(x) (GET_HEAP_HEADER(x)->bits)
#define NUM_IN_SLOT(p) (((uintptr_t)(p) & HEAP_ALIGN_MASK)/sizeof(RVALUE))
#define BITMAP_INDEX(p) (NUM_IN_SLOT(p) / (sizeof(uintptr_t) * CHAR_BIT))
#define BITMAP_OFFSET(p) (NUM_IN_SLOT(p) & ((sizeof(uintptr_t) * CHAR_BIT)-1))
#define MARKED_IN_BITMAP(bits, p) (bits[BITMAP_INDEX(p)] & ((uintptr_t)1 << BITMAP_OFFSET(p)))
#ifndef HEAP_ALIGN_LOG
/* default tiny heap size: 16KB */
#define HEAP_ALIGN_LOG 14
#endif
#define CEILDIV(i, mod) (((i) + (mod) - 1)/(mod))
enum {
HEAP_ALIGN = (1UL << HEAP_ALIGN_LOG),
HEAP_ALIGN_MASK = (~(~0UL << HEAP_ALIGN_LOG)),
REQUIRED_SIZE_BY_MALLOC = (sizeof(size_t) * 5),
HEAP_SIZE = (HEAP_ALIGN - REQUIRED_SIZE_BY_MALLOC),
HEAP_OBJ_LIMIT = (unsigned int)((HEAP_SIZE - sizeof(struct heaps_header))/sizeof(struct RVALUE)),
HEAP_BITMAP_LIMIT = CEILDIV(CEILDIV(HEAP_SIZE, sizeof(struct RVALUE)), sizeof(uintptr_t) * CHAR_BIT)
};
int ruby_gc_debug_indent = 0;
VALUE rb_mGC;
extern st_table *rb_class_tbl;
int ruby_disable_gc_stress = 0;
static void rb_objspace_call_finalizer(rb_objspace_t *objspace);
static VALUE define_final0(VALUE obj, VALUE block);
VALUE rb_define_final(VALUE obj, VALUE block);
VALUE rb_undefine_final(VALUE obj);
static void run_final(rb_objspace_t *objspace, VALUE obj);
static void initial_expand_heap(rb_objspace_t *objspace);
static void negative_size_allocation_error(const char *);
static void *aligned_malloc(size_t, size_t);
static void aligned_free(void *);
static void init_mark_stack(mark_stack_t *stack);
static VALUE lazy_sweep_enable(void);
static int garbage_collect(rb_objspace_t *);
static int gc_prepare_free_objects(rb_objspace_t *);
static void mark_tbl(rb_objspace_t *, st_table *);
static void rest_sweep(rb_objspace_t *);
static void gc_mark_stacked_objects(rb_objspace_t *);
static double getrusage_time(void);
static inline void gc_prof_timer_start(rb_objspace_t *);
static inline void gc_prof_timer_stop(rb_objspace_t *, int);
static inline void gc_prof_mark_timer_start(rb_objspace_t *);
static inline void gc_prof_mark_timer_stop(rb_objspace_t *);
static inline void gc_prof_sweep_timer_start(rb_objspace_t *);
static inline void gc_prof_sweep_timer_stop(rb_objspace_t *);
static inline void gc_prof_set_malloc_info(rb_objspace_t *);
static inline void gc_prof_inc_live_num(rb_objspace_t *);
static inline void gc_prof_dec_live_num(rb_objspace_t *);
/*
--------------------------- ObjectSpace -----------------------------
*/
#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE
rb_objspace_t *
rb_objspace_alloc(void)
{
rb_objspace_t *objspace = malloc(sizeof(rb_objspace_t));
memset(objspace, 0, sizeof(*objspace));
malloc_limit = initial_malloc_limit;
ruby_gc_stress = ruby_initial_gc_stress;
return objspace;
}
#endif
#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE
static void free_stack_chunks(mark_stack_t *);
void
rb_objspace_free(rb_objspace_t *objspace)
{
rest_sweep(objspace);
if (objspace->profile.record) {
free(objspace->profile.record);
objspace->profile.record = 0;
}
if (global_List) {
struct gc_list *list, *next;
for (list = global_List; list; list = next) {
next = list->next;
xfree(list);
}
}
if (objspace->heap.free_bitmap) {
struct heaps_free_bitmap *list, *next;
for (list = objspace->heap.free_bitmap; list; list = next) {
next = list->next;
free(list);
}
}
if (objspace->heap.sorted) {
size_t i;
for (i = 0; i < heaps_used; ++i) {
free(objspace->heap.sorted[i]->bits);
aligned_free(objspace->heap.sorted[i]);
}
free(objspace->heap.sorted);
heaps_used = 0;
heaps = 0;
}
free_stack_chunks(&objspace->mark_stack);
free(objspace);
}
#endif
void
rb_global_variable(VALUE *var)
{
rb_gc_register_address(var);
}
static void
allocate_sorted_heaps(rb_objspace_t *objspace, size_t next_heaps_length)
{
struct heaps_header **p;
struct heaps_free_bitmap *bits;
size_t size, add, i;
size = next_heaps_length*sizeof(struct heaps_header *);
add = next_heaps_length - heaps_used;
if (heaps_used > 0) {
p = (struct heaps_header **)realloc(objspace->heap.sorted, size);
if (p) objspace->heap.sorted = p;
}
else {
p = objspace->heap.sorted = (struct heaps_header **)malloc(size);
}
if (p == 0) {
during_gc = 0;
rb_memerror();
}
for (i = 0; i < add; i++) {
bits = (struct heaps_free_bitmap *)malloc(HEAP_BITMAP_LIMIT * sizeof(uintptr_t));
if (bits == 0) {
during_gc = 0;
rb_memerror();
return;
}
bits->next = objspace->heap.free_bitmap;
objspace->heap.free_bitmap = bits;
}
}
static void
link_free_heap_slot(rb_objspace_t *objspace, struct heaps_slot *slot)
{
slot->free_next = objspace->heap.free_slots;
objspace->heap.free_slots = slot;
}
static void
unlink_free_heap_slot(rb_objspace_t *objspace, struct heaps_slot *slot)
{
objspace->heap.free_slots = slot->free_next;
slot->free_next = NULL;
}
static void
assign_heap_slot(rb_objspace_t *objspace)
{
RVALUE *p, *pend, *membase;
struct heaps_slot *slot;
size_t hi, lo, mid;
size_t objs;
objs = HEAP_OBJ_LIMIT;
p = (RVALUE*)aligned_malloc(HEAP_ALIGN, HEAP_SIZE);
if (p == 0) {
during_gc = 0;
rb_memerror();
}
slot = (struct heaps_slot *)malloc(sizeof(struct heaps_slot));
if (slot == 0) {
aligned_free(p);
during_gc = 0;
rb_memerror();
}
MEMZERO((void*)slot, struct heaps_slot, 1);
slot->next = heaps;
if (heaps) heaps->prev = slot;
heaps = slot;
membase = p;
p = (RVALUE*)((VALUE)p + sizeof(struct heaps_header));
if ((VALUE)p % sizeof(RVALUE) != 0) {
p = (RVALUE*)((VALUE)p + sizeof(RVALUE) - ((VALUE)p % sizeof(RVALUE)));
objs = (HEAP_SIZE - (size_t)((VALUE)p - (VALUE)membase))/sizeof(RVALUE);
}
lo = 0;
hi = heaps_used;
while (lo < hi) {
register RVALUE *mid_membase;
mid = (lo + hi) / 2;
mid_membase = (RVALUE *)objspace->heap.sorted[mid];
if (mid_membase < membase) {
lo = mid + 1;
}
else if (mid_membase > membase) {
hi = mid;
}
else {
rb_bug("same heap slot is allocated: %p at %"PRIuVALUE, (void *)membase, (VALUE)mid);
}
}
if (hi < heaps_used) {
MEMMOVE(&objspace->heap.sorted[hi+1], &objspace->heap.sorted[hi], struct heaps_header*, heaps_used - hi);
}
heaps->header = (struct heaps_header *)membase;
objspace->heap.sorted[hi] = heaps->header;
objspace->heap.sorted[hi]->start = p;
objspace->heap.sorted[hi]->end = (p + objs);
objspace->heap.sorted[hi]->base = heaps;
objspace->heap.sorted[hi]->limit = objs;
assert(objspace->heap.free_bitmap != NULL);
heaps->bits = (uintptr_t *)objspace->heap.free_bitmap;
objspace->heap.sorted[hi]->bits = (uintptr_t *)objspace->heap.free_bitmap;
objspace->heap.free_bitmap = objspace->heap.free_bitmap->next;
memset(heaps->bits, 0, HEAP_BITMAP_LIMIT * sizeof(uintptr_t));
objspace->heap.free_num += objs;
pend = p + objs;
if (lomem == 0 || lomem > p) lomem = p;
if (himem < pend) himem = pend;
heaps_used++;
while (p < pend) {
p->as.free.flags = 0;
p->as.free.next = heaps->freelist;
heaps->freelist = p;
p++;
}
link_free_heap_slot(objspace, heaps);
}
static void
add_heap_slots(rb_objspace_t *objspace, size_t add)
{
size_t i;
size_t next_heaps_length;
next_heaps_length = heaps_used + add;
if (next_heaps_length > heaps_length) {
allocate_sorted_heaps(objspace, next_heaps_length);
heaps_length = next_heaps_length;
}
for (i = 0; i < add; i++) {
assign_heap_slot(objspace);
}
heaps_inc = 0;
}
static void
init_heap(rb_objspace_t *objspace)
{
add_heap_slots(objspace, HEAP_MIN_SLOTS / HEAP_OBJ_LIMIT);
init_mark_stack(&objspace->mark_stack);
#ifdef USE_SIGALTSTACK
{
/* altstack of another threads are allocated in another place */
rb_thread_t *th = GET_THREAD();
void *tmp = th->altstack;
th->altstack = malloc(ALT_STACK_SIZE);
free(tmp); /* free previously allocated area */
}
#endif
objspace->profile.invoke_time = getrusage_time();
finalizer_table = st_init_numtable();
}
static void
initial_expand_heap(rb_objspace_t *objspace)
{
size_t min_size = initial_heap_min_slots / HEAP_OBJ_LIMIT;
if (min_size > heaps_used) {
add_heap_slots(objspace, min_size - heaps_used);
}
}
static void
set_heaps_increment(rb_objspace_t *objspace)
{
size_t next_heaps_length = (size_t)(heaps_used * 1.8);
if (next_heaps_length == heaps_used) {
next_heaps_length++;
}
heaps_inc = next_heaps_length - heaps_used;
if (next_heaps_length > heaps_length) {
allocate_sorted_heaps(objspace, next_heaps_length);
heaps_length = next_heaps_length;
}
}
static int
heaps_increment(rb_objspace_t *objspace)
{
if (heaps_inc > 0) {
assign_heap_slot(objspace);
heaps_inc--;
return TRUE;
}
return FALSE;
}
static VALUE
newobj(VALUE klass, VALUE flags)
{
rb_objspace_t *objspace = &rb_objspace;
VALUE obj;
if (UNLIKELY(during_gc)) {
dont_gc = 1;
during_gc = 0;
rb_bug("object allocation during garbage collection phase");
}
if (UNLIKELY(ruby_gc_stress && !ruby_disable_gc_stress)) {
if (!garbage_collect(objspace)) {
during_gc = 0;
rb_memerror();
}
}
if (UNLIKELY(!has_free_object)) {
if (!gc_prepare_free_objects(objspace)) {
during_gc = 0;
rb_memerror();
}
}
obj = (VALUE)objspace->heap.free_slots->freelist;
objspace->heap.free_slots->freelist = RANY(obj)->as.free.next;
if (objspace->heap.free_slots->freelist == NULL) {
unlink_free_heap_slot(objspace, objspace->heap.free_slots);
}
MEMZERO((void*)obj, RVALUE, 1);
#ifdef GC_DEBUG
RANY(obj)->file = rb_sourcefile();
RANY(obj)->line = rb_sourceline();
#endif
gc_prof_inc_live_num(objspace);
return obj;
}
VALUE
rb_newobj(void)
{
return newobj(0, T_NONE);
}
VALUE
rb_newobj_of(VALUE klass, VALUE flags)
{
VALUE obj;
obj = newobj(klass, flags);
OBJSETUP(obj, klass, flags);
return obj;
}
NODE*
rb_node_newnode(enum node_type type, VALUE a0, VALUE a1, VALUE a2)
{
NODE *n = (NODE*)rb_newobj();
n->flags |= T_NODE;
nd_set_type(n, type);
n->u1.value = a0;
n->u2.value = a1;
n->u3.value = a2;
return n;
}
VALUE
rb_data_object_alloc(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_DATA_FUNC dfree)
{
NEWOBJ(data, struct RData);
if (klass) Check_Type(klass, T_CLASS);
OBJSETUP(data, klass, T_DATA);
data->data = datap;
data->dfree = dfree;
data->dmark = dmark;
return (VALUE)data;
}
VALUE
rb_data_typed_object_alloc(VALUE klass, void *datap, const rb_data_type_t *type)
{
NEWOBJ(data, struct RTypedData);
if (klass) Check_Type(klass, T_CLASS);
OBJSETUP(data, klass, T_DATA);
data->data = datap;
data->typed_flag = 1;
data->type = type;
return (VALUE)data;
}
size_t
rb_objspace_data_type_memsize(VALUE obj)
{
if (RTYPEDDATA_P(obj) && RTYPEDDATA_TYPE(obj)->function.dsize) {
return RTYPEDDATA_TYPE(obj)->function.dsize(RTYPEDDATA_DATA(obj));
}
else {
return 0;
}
}
const char *
rb_objspace_data_type_name(VALUE obj)
{
if (RTYPEDDATA_P(obj)) {
return RTYPEDDATA_TYPE(obj)->wrap_struct_name;
}
else {
return 0;
}
}
static void gc_mark(rb_objspace_t *objspace, VALUE ptr);
static void gc_mark_children(rb_objspace_t *objspace, VALUE ptr);
static inline int
is_pointer_to_heap(rb_objspace_t *objspace, void *ptr)
{
register RVALUE *p = RANY(ptr);
register struct heaps_header *heap;
register size_t hi, lo, mid;
if (p < lomem || p > himem) return FALSE;
if ((VALUE)p % sizeof(RVALUE) != 0) return FALSE;
/* check if p looks like a pointer using bsearch*/
lo = 0;
hi = heaps_used;
while (lo < hi) {
mid = (lo + hi) / 2;
heap = objspace->heap.sorted[mid];
if (heap->start <= p) {
if (p < heap->end)
return TRUE;
lo = mid + 1;
}
else {
hi = mid;
}
}
return FALSE;
}
static int
free_method_entry_i(ID key, rb_method_entry_t *me, st_data_t data)
{
if (!me->mark) {
rb_free_method_entry(me);
}
return ST_CONTINUE;
}
void
rb_free_m_table(st_table *tbl)
{
st_foreach(tbl, free_method_entry_i, 0);
st_free_table(tbl);
}
static int
free_const_entry_i(ID key, rb_const_entry_t *ce, st_data_t data)
{
xfree(ce);
return ST_CONTINUE;
}
void
rb_free_const_table(st_table *tbl)
{
st_foreach(tbl, free_const_entry_i, 0);
st_free_table(tbl);
}
static int obj_free(rb_objspace_t *, VALUE);
static inline struct heaps_slot *
add_slot_local_freelist(rb_objspace_t *objspace, RVALUE *p)
{
struct heaps_slot *slot;
VALGRIND_MAKE_MEM_UNDEFINED((void*)p, sizeof(RVALUE));
p->as.free.flags = 0;
slot = GET_HEAP_SLOT(p);
p->as.free.next = slot->freelist;
slot->freelist = p;
return slot;
}
static void
unlink_heap_slot(rb_objspace_t *objspace, struct heaps_slot *slot)
{
if (slot->prev)
slot->prev->next = slot->next;
if (slot->next)
slot->next->prev = slot->prev;
if (heaps == slot)
heaps = slot->next;
if (objspace->heap.sweep_slots == slot)
objspace->heap.sweep_slots = slot->next;
slot->prev = NULL;
slot->next = NULL;
}
static void
free_unused_heaps(rb_objspace_t *objspace)
{
size_t i, j;
struct heaps_header *last = 0;
for (i = j = 1; j < heaps_used; i++) {
if (objspace->heap.sorted[i]->limit == 0) {
struct heaps_header* h = objspace->heap.sorted[i];
((struct heaps_free_bitmap *)(h->bits))->next =
objspace->heap.free_bitmap;
objspace->heap.free_bitmap = (struct heaps_free_bitmap *)h->bits;
if (!last) {
last = objspace->heap.sorted[i];
}
else {
aligned_free(objspace->heap.sorted[i]);
}
heaps_used--;
}
else {
if (i != j) {
objspace->heap.sorted[j] = objspace->heap.sorted[i];
}
j++;
}
}
if (last) {
if (last < heaps_freed) {
aligned_free(heaps_freed);
heaps_freed = last;
}
else {
aligned_free(last);
}
}
}
static inline void
make_deferred(RVALUE *p)
{
p->as.basic.flags = (p->as.basic.flags & ~T_MASK) | T_ZOMBIE;
}
static inline void
make_io_deferred(RVALUE *p)
{
rb_io_t *fptr = p->as.file.fptr;
make_deferred(p);
p->as.data.dfree = (void (*)(void*))rb_io_fptr_finalize;
p->as.data.data = fptr;
}
static int
obj_free(rb_objspace_t *objspace, VALUE obj)
{
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;
}
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 (!(RANY(obj)->as.basic.flags & ROBJECT_EMBED) &&
RANY(obj)->as.object.as.heap.ivptr) {
xfree(RANY(obj)->as.object.as.heap.ivptr);
}
break;
case T_MODULE:
case T_CLASS:
rb_clear_cache_by_class((VALUE)obj);
if (RCLASS_M_TBL(obj)) {
rb_free_m_table(RCLASS_M_TBL(obj));
}
if (RCLASS_IV_TBL(obj)) {
st_free_table(RCLASS_IV_TBL(obj));
}
if (RCLASS_CONST_TBL(obj)) {
rb_free_const_table(RCLASS_CONST_TBL(obj));
}
if (RCLASS_IV_INDEX_TBL(obj)) {
st_free_table(RCLASS_IV_INDEX_TBL(obj));
}
xfree(RANY(obj)->as.klass.ptr);
break;
case T_STRING:
rb_str_free(obj);
break;
case T_ARRAY:
rb_ary_free(obj);
break;
case T_HASH:
if (RANY(obj)->as.hash.ntbl) {
st_free_table(RANY(obj)->as.hash.ntbl);
}
break;
case T_REGEXP:
if (RANY(obj)->as.regexp.ptr) {
onig_free(RANY(obj)->as.regexp.ptr);
}
break;
case T_DATA:
if (DATA_PTR(obj)) {
if (RTYPEDDATA_P(obj)) {
RDATA(obj)->dfree = RANY(obj)->as.typeddata.type->function.dfree;
}
if (RANY(obj)->as.data.dfree == (RUBY_DATA_FUNC)-1) {
xfree(DATA_PTR(obj));
}
else if (RANY(obj)->as.data.dfree) {
make_deferred(RANY(obj));
return 1;
}
}
break;
case T_MATCH:
if (RANY(obj)->as.match.rmatch) {
struct rmatch *rm = RANY(obj)->as.match.rmatch;
onig_region_free(&rm->regs, 0);
if (rm->char_offset)
xfree(rm->char_offset);
xfree(rm);
}
break;
case T_FILE:
if (RANY(obj)->as.file.fptr) {
make_io_deferred(RANY(obj));
return 1;
}
break;
case T_RATIONAL:
case T_COMPLEX:
break;
case T_ICLASS:
/* iClass shares table with the module */
xfree(RANY(obj)->as.klass.ptr);
break;
case T_FLOAT:
break;
case T_BIGNUM:
if (!(RBASIC(obj)->flags & RBIGNUM_EMBED_FLAG) && RBIGNUM_DIGITS(obj)) {
xfree(RBIGNUM_DIGITS(obj));
}
break;
case T_NODE:
switch (nd_type(obj)) {
case NODE_SCOPE:
if (RANY(obj)->as.node.u1.tbl) {
xfree(RANY(obj)->as.node.u1.tbl);
}
break;
case NODE_ARGS:
if (RANY(obj)->as.node.u3.args) {
xfree(RANY(obj)->as.node.u3.args);
}
break;
case NODE_ALLOCA:
xfree(RANY(obj)->as.node.u1.node);
break;
}
break; /* no need to free iv_tbl */
case T_STRUCT:
if ((RBASIC(obj)->flags & RSTRUCT_EMBED_LEN_MASK) == 0 &&
RANY(obj)->as.rstruct.as.heap.ptr) {
xfree(RANY(obj)->as.rstruct.as.heap.ptr);
}
break;
default:
rb_bug("gc_sweep(): unknown data type 0x%x(%p) 0x%"PRIxVALUE,
BUILTIN_TYPE(obj), (void*)obj, RBASIC(obj)->flags);
}
return 0;
}
void
Init_heap(void)
{
init_heap(&rb_objspace);
}
typedef int each_obj_callback(void *, void *, size_t, void *);
struct each_obj_args {
each_obj_callback *callback;
void *data;
};
static VALUE
objspace_each_objects(VALUE arg)
{
size_t i;
RVALUE *membase = 0;
RVALUE *pstart, *pend;
rb_objspace_t *objspace = &rb_objspace;
struct each_obj_args *args = (struct each_obj_args *)arg;
volatile VALUE v;
i = 0;
while (i < heaps_used) {
while (0 < i && (uintptr_t)membase < (uintptr_t)objspace->heap.sorted[i-1])
i--;
while (i < heaps_used && (uintptr_t)objspace->heap.sorted[i] <= (uintptr_t)membase)
i++;
if (heaps_used <= i)
break;
membase = (RVALUE *)objspace->heap.sorted[i];
pstart = objspace->heap.sorted[i]->start;
pend = pstart + objspace->heap.sorted[i]->limit;
for (; pstart != pend; pstart++) {
if (pstart->as.basic.flags) {
v = (VALUE)pstart; /* acquire to save this object */
break;
}
}
if (pstart != pend) {
if ((*args->callback)(pstart, pend, sizeof(RVALUE), args->data)) {
break;
}
}
}
RB_GC_GUARD(v);
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 slot,
* at current implementation) with:
* vstart: a pointer to the first living object of the heap_slot.
* vend: a pointer to next to the valid heap_slot 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:
*
* int
* sample_callback(void *vstart, void *vend, int stride, void *data) {
* VALUE v = (VALUE)vstart;
* for (; v != (VALUE)vend; v += stride) {
* if (RBASIC(v)->flags) { // liveness check
* // do something with live object 'v'
* }
* return 0; // continue to iteration
* }
*
* Note: 'vstart' is not a top of heap_slot. 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 slot
* including freed object slot.
*
* Note: On this implementation, 'stride' is 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(each_obj_callback *callback, void *data)
{
struct each_obj_args args;
rb_objspace_t *objspace = &rb_objspace;
rest_sweep(objspace);
objspace->flags.dont_lazy_sweep = TRUE;
args.callback = callback;
args.data = data;
rb_ensure(objspace_each_objects, (VALUE)&args, lazy_sweep_enable, Qnil);
}
struct os_each_struct {
size_t num;
VALUE of;
};
static int
internal_object_p(VALUE obj)
{
RVALUE *p = (RVALUE *)obj;
if (p->as.basic.flags) {
switch (BUILTIN_TYPE(p)) {
case T_NONE:
case T_ICLASS:
case T_NODE:
case T_ZOMBIE:
break;
case T_CLASS:
if (FL_TEST(p, FL_SINGLETON))
break;
default:
if (!p->as.basic.klass) break;
return 0;
}
}
return 1;
}
int
rb_objspace_internal_object_p(VALUE obj)
{
return internal_object_p(obj);
}
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;
RVALUE *p = (RVALUE *)vstart, *pend = (RVALUE *)vend;
for (; p != pend; p++) {
volatile VALUE v = (VALUE)p;
if (!internal_object_p(v)) {
if (!oes->of || rb_obj_is_kind_of(v, oes->of)) {
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| ... } -> fixnum
* 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, <code>each_object</code>
* returns both the numbers we defined and several constants defined in
* the <code>Math</code> 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;
rb_secure(4);
if (argc == 0) {
of = 0;
}
else {
rb_scan_args(argc, argv, "01", &of);
}
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_undefine_final(obj);
}
VALUE
rb_undefine_final(VALUE obj)
{
rb_objspace_t *objspace = &rb_objspace;
st_data_t data = obj;
rb_check_frozen(obj);
st_delete(finalizer_table, &data, 0);
FL_UNSET(obj, FL_FINALIZE);
return 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.
*
*/
static VALUE
define_final(int argc, VALUE *argv, VALUE os)
{
VALUE obj, block;
rb_scan_args(argc, argv, "11", &obj, &block);
rb_check_frozen(obj);
if (argc == 1) {
block = rb_block_proc();
}
else if (!rb_respond_to(block, rb_intern("call"))) {
rb_raise(rb_eArgError, "wrong type argument %s (should be callable)",
rb_obj_classname(block));
}
return define_final0(obj, block);
}
static VALUE
define_final0(VALUE obj, VALUE block)
{
rb_objspace_t *objspace = &rb_objspace;
VALUE table;
st_data_t data;
if (!FL_ABLE(obj)) {
rb_raise(rb_eArgError, "cannot define finalizer for %s",
rb_obj_classname(obj));
}
RBASIC(obj)->flags |= FL_FINALIZE;
block = rb_ary_new3(2, INT2FIX(rb_safe_level()), block);
OBJ_FREEZE(block);
if (st_lookup(finalizer_table, obj, &data)) {
table = (VALUE)data;
rb_ary_push(table, block);
}
else {
table = rb_ary_new3(1, block);
RBASIC(table)->klass = 0;
st_add_direct(finalizer_table, obj, table);
}
return block;
}
VALUE
rb_define_final(VALUE obj, VALUE block)
{
rb_check_frozen(obj);
if (!rb_respond_to(block, rb_intern("call"))) {
rb_raise(rb_eArgError, "wrong type argument %s (should be callable)",
rb_obj_classname(block));
}
return define_final0(obj, block);
}
void
rb_gc_copy_finalizer(VALUE dest, VALUE obj)
{
rb_objspace_t *objspace = &rb_objspace;
VALUE table;
st_data_t data;
if (!FL_TEST(obj, FL_FINALIZE)) return;
if (st_lookup(finalizer_table, obj, &data)) {
table = (VALUE)data;
st_insert(finalizer_table, dest, table);
}
FL_SET(dest, FL_FINALIZE);
}
static VALUE
run_single_final(VALUE arg)
{
VALUE *args = (VALUE *)arg;
rb_eval_cmd(args[0], args[1], (int)args[2]);
return Qnil;
}
static void
run_finalizer(rb_objspace_t *objspace, VALUE obj, VALUE table)
{
long i;
int status;
VALUE args[3];
VALUE objid = nonspecial_obj_id(obj);
if (RARRAY_LEN(table) > 0) {
args[1] = rb_obj_freeze(rb_ary_new3(1, objid));
}
else {
args[1] = 0;
}
args[2] = (VALUE)rb_safe_level();
for (i=0; i<RARRAY_LEN(table); i++) {
VALUE final = RARRAY_PTR(table)[i];
args[0] = RARRAY_PTR(final)[1];
args[2] = FIX2INT(RARRAY_PTR(final)[0]);
status = 0;
rb_protect(run_single_final, (VALUE)args, &status);
if (status)
rb_set_errinfo(Qnil);
}
}
static void
run_final(rb_objspace_t *objspace, VALUE obj)
{
RUBY_DATA_FUNC free_func = 0;
st_data_t key, table;
objspace->heap.final_num--;
RBASIC(obj)->klass = 0;
if (RTYPEDDATA_P(obj)) {
free_func = RTYPEDDATA_TYPE(obj)->function.dfree;
}
else {
free_func = RDATA(obj)->dfree;
}
if (free_func) {
(*free_func)(DATA_PTR(obj));
}
key = (st_data_t)obj;
if (st_delete(finalizer_table, &key, &table)) {
run_finalizer(objspace, obj, (VALUE)table);
}
}
static void
finalize_list(rb_objspace_t *objspace, RVALUE *p)
{
while (p) {
RVALUE *tmp = p->as.free.next;
run_final(objspace, (VALUE)p);
if (!FL_TEST(p, FL_SINGLETON)) { /* not freeing page */
add_slot_local_freelist(objspace, p);
if (!is_lazy_sweeping(objspace)) {
gc_prof_dec_live_num(objspace);
}
}
else {
struct heaps_slot *slot = (struct heaps_slot *)(VALUE)RDATA(p)->dmark;
slot->header->limit--;
}
p = tmp;
}
}
static void
finalize_deferred(rb_objspace_t *objspace)
{
RVALUE *p = deferred_final_list;
deferred_final_list = 0;
if (p) {
finalize_list(objspace, p);
}
}
void
rb_gc_finalize_deferred(void)
{
rb_objspace_t *objspace = &rb_objspace;
if (ATOMIC_EXCHANGE(finalizing, 1)) return;
finalize_deferred(objspace);
ATOMIC_SET(finalizing, 0);
}
static int
chain_finalized_object(st_data_t key, st_data_t val, st_data_t arg)
{
RVALUE *p = (RVALUE *)key, **final_list = (RVALUE **)arg;
if ((p->as.basic.flags & FL_FINALIZE) == FL_FINALIZE &&
!MARKED_IN_BITMAP(GET_HEAP_BITMAP(p), p)) {
if (BUILTIN_TYPE(p) != T_ZOMBIE) {
p->as.free.flags = T_ZOMBIE;
RDATA(p)->dfree = 0;
}
p->as.free.next = *final_list;
*final_list = p;
}
return ST_CONTINUE;
}
struct force_finalize_list {
VALUE obj;
VALUE table;
struct force_finalize_list *next;
};
static int
force_chain_object(st_data_t key, st_data_t val, st_data_t arg)
{
struct force_finalize_list **prev = (struct force_finalize_list **)arg;
struct force_finalize_list *curr = ALLOC(struct force_finalize_list);
curr->obj = key;
curr->table = val;
curr->next = *prev;
*prev = curr;
return ST_CONTINUE;
}
void
rb_gc_call_finalizer_at_exit(void)
{
rb_objspace_call_finalizer(&rb_objspace);
}
static void
rb_objspace_call_finalizer(rb_objspace_t *objspace)
{
RVALUE *p, *pend;
RVALUE *final_list = 0;
size_t i;
rest_sweep(objspace);
if (ATOMIC_EXCHANGE(finalizing, 1)) return;
/* run finalizers */
do {
finalize_deferred(objspace);
/* mark reachable objects from finalizers */
/* They might be not referred from any place here */
mark_tbl(objspace, finalizer_table);
gc_mark_stacked_objects(objspace);
st_foreach(finalizer_table, chain_finalized_object,
(st_data_t)&deferred_final_list);
} while (deferred_final_list);
/* force to run finalizer */
while (finalizer_table->num_entries) {
struct force_finalize_list *list = 0;
st_foreach(finalizer_table, force_chain_object, (st_data_t)&list);
while (list) {
struct force_finalize_list *curr = list;
st_data_t obj = (st_data_t)curr->obj;
run_finalizer(objspace, curr->obj, curr->table);
st_delete(finalizer_table, &obj, 0);
list = curr->next;
xfree(curr);
}
}
/* finalizers are part of garbage collection */
during_gc++;
/* run data object's finalizers */
for (i = 0; i < heaps_used; i++) {
p = objspace->heap.sorted[i]->start; pend = p + objspace->heap.sorted[i]->limit;
while (p < pend) {
if (BUILTIN_TYPE(p) == T_DATA &&
DATA_PTR(p) && RANY(p)->as.data.dfree &&
!rb_obj_is_thread((VALUE)p) && !rb_obj_is_mutex((VALUE)p) &&
!rb_obj_is_fiber((VALUE)p)) {
p->as.free.flags = 0;
if (RTYPEDDATA_P(p)) {
RDATA(p)->dfree = RANY(p)->as.typeddata.type->function.dfree;
}
if (RANY(p)->as.data.dfree == (RUBY_DATA_FUNC)-1) {
xfree(DATA_PTR(p));
}
else if (RANY(p)->as.data.dfree) {
make_deferred(RANY(p));
RANY(p)->as.free.next = final_list;
final_list = p;
}
}
else if (BUILTIN_TYPE(p) == T_FILE) {
if (RANY(p)->as.file.fptr) {
make_io_deferred(RANY(p));
RANY(p)->as.free.next = final_list;
final_list = p;
}
}
p++;
}
}
during_gc = 0;
if (final_list) {
finalize_list(objspace, final_list);
}
st_free_table(finalizer_table);
finalizer_table = 0;
ATOMIC_SET(finalizing, 0);
}
static inline int
is_id_value(rb_objspace_t *objspace, VALUE ptr)
{
if (!is_pointer_to_heap(objspace, (void *)ptr)) return FALSE;
if (BUILTIN_TYPE(ptr) > T_FIXNUM) return FALSE;
if (BUILTIN_TYPE(ptr) == T_ICLASS) return FALSE;
return TRUE;
}
static inline int
is_swept_object(rb_objspace_t *objspace, VALUE ptr)
{
struct heaps_slot *slot = objspace->heap.sweep_slots;
while (slot) {
if ((VALUE)slot->header->start <= ptr && ptr < (VALUE)(slot->header->end))
return FALSE;
slot = slot->next;
}
return TRUE;
}
static inline int
is_dead_object(rb_objspace_t *objspace, VALUE ptr)
{
if (!is_lazy_sweeping(objspace) || MARKED_IN_BITMAP(GET_HEAP_BITMAP(ptr), ptr))
return FALSE;
if (!is_swept_object(objspace, ptr))
return TRUE;
return FALSE;
}
static inline int
is_live_object(rb_objspace_t *objspace, VALUE ptr)
{
if (BUILTIN_TYPE(ptr) == 0) return FALSE;
if (RBASIC(ptr)->klass == 0) return FALSE;
if (is_dead_object(objspace, ptr)) return FALSE;
return TRUE;
}
/*
* 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
*
*/
static VALUE
id2ref(VALUE obj, VALUE objid)
{
#if SIZEOF_LONG == SIZEOF_VOIDP
#define NUM2PTR(x) NUM2ULONG(x)
#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
#define NUM2PTR(x) NUM2ULL(x)
#endif
rb_objspace_t *objspace = &rb_objspace;
VALUE ptr;
void *p0;
rb_secure(4);
ptr = NUM2PTR(objid);
p0 = (void *)ptr;
if (ptr == Qtrue) return Qtrue;
if (ptr == Qfalse) return Qfalse;
if (ptr == Qnil) return Qnil;
if (FIXNUM_P(ptr)) return (VALUE)ptr;
if (FLONUM_P(ptr)) return (VALUE)ptr;
ptr = objid ^ FIXNUM_FLAG; /* unset FIXNUM_FLAG */
if ((ptr % sizeof(RVALUE)) == (4 << 2)) {
ID symid = ptr / sizeof(RVALUE);
if (rb_id2name(symid) == 0)
rb_raise(rb_eRangeError, "%p is not symbol id value", p0);
return ID2SYM(symid);
}
if (!is_id_value(objspace, ptr)) {
rb_raise(rb_eRangeError, "%p is not id value", p0);
}
if (!is_live_object(objspace, ptr)) {
rb_raise(rb_eRangeError, "%p is recycled object", p0);
}
return (VALUE)ptr;
}
/*
* Document-method: __id__
* Document-method: object_id
*
* call-seq:
* obj.__id__ -> fixnum
* obj.object_id -> fixnum
*
* Returns an integer identifier for <i>obj</i>. The same number will
* be returned on all calls to <code>id</code> for a given object, and
* no two active objects will share an id.
* <code>Object#object_id</code> is a different concept from the
* <code>:name</code> notation, which returns the symbol id of
* <code>name</code>. Replaces the deprecated <code>Object#id</code>.
*/
/*
* call-seq:
* obj.hash -> fixnum
*
* Generates a <code>Fixnum</code> hash value for this object. This
* function must have the property that <code>a.eql?(b)</code> implies
* <code>a.hash == b.hash</code>. The hash value is used by class
* <code>Hash</code>. Any hash value that exceeds the capacity of a
* <code>Fixnum</code> will be truncated before being used.
*/
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
*/
if (SYMBOL_P(obj)) {
return (SYM2ID(obj) * sizeof(RVALUE) + (4 << 2)) | FIXNUM_FLAG;
}
else if (FLONUM_P(obj)) {
#if SIZEOF_LONG == SIZEOF_VOIDP
return LONG2NUM((SIGNED_VALUE)obj);
#else
return LL2NUM((SIGNED_VALUE)obj);
#endif
}
else if (SPECIAL_CONST_P(obj)) {
return LONG2NUM((SIGNED_VALUE)obj);
}
return nonspecial_obj_id(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;
}
/*
* call-seq:
* ObjectSpace.count_objects([result_hash]) -> hash
*
* Counts objects for each type.
*
* It returns a hash as:
* {:TOTAL=>10000, :FREE=>3011, :T_OBJECT=>6, :T_CLASS=>404, ...}
*
* If the optional argument, result_hash, is given,
* it is overwritten and returned.
* This is intended to avoid probe effect.
*
* The contents of the returned hash is implementation defined.
* It may be changed in future.
*
* This method is not expected to work except C Ruby.
*
*/
static VALUE
count_objects(int argc, VALUE *argv, VALUE os)
{
rb_objspace_t *objspace = &rb_objspace;
size_t counts[T_MASK+1];
size_t freed = 0;
size_t total = 0;
size_t i;
VALUE hash;
if (rb_scan_args(argc, argv, "01", &hash) == 1) {
if (!RB_TYPE_P(hash, T_HASH))
rb_raise(rb_eTypeError, "non-hash given");
}
for (i = 0; i <= T_MASK; i++) {
counts[i] = 0;
}
for (i = 0; i < heaps_used; i++) {
RVALUE *p, *pend;
p = objspace->heap.sorted[i]->start; pend = p + objspace->heap.sorted[i]->limit;
for (;p < pend; p++) {
if (p->as.basic.flags) {
counts[BUILTIN_TYPE(p)]++;
}
else {
freed++;
}
}
total += objspace->heap.sorted[i]->limit;
}
if (hash == Qnil) {
hash = rb_hash_new();
}
else if (!RHASH_EMPTY_P(hash)) {
st_foreach(RHASH_TBL(hash), set_zero, hash);
}
rb_hash_aset(hash, ID2SYM(rb_intern("TOTAL")), SIZET2NUM(total));
rb_hash_aset(hash, ID2SYM(rb_intern("FREE")), SIZET2NUM(freed));
for (i = 0; i <= T_MASK; i++) {
VALUE type;
switch (i) {
#define COUNT_TYPE(t) case (t): type = 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_UNDEF);
COUNT_TYPE(T_NODE);
COUNT_TYPE(T_ICLASS);
COUNT_TYPE(T_ZOMBIE);
#undef COUNT_TYPE
default: type = INT2NUM(i); break;
}
if (counts[i])
rb_hash_aset(hash, type, SIZET2NUM(counts[i]));
}
return hash;
}
/*
------------------------ Garbage Collection ------------------------
*/
/* Sweeping */
static VALUE
lazy_sweep_enable(void)
{
rb_objspace_t *objspace = &rb_objspace;
objspace->flags.dont_lazy_sweep = FALSE;
return Qnil;
}
static void
gc_clear_slot_bits(struct heaps_slot *slot)
{
memset(slot->bits, 0, HEAP_BITMAP_LIMIT * sizeof(uintptr_t));
}
static void
slot_sweep(rb_objspace_t *objspace, struct heaps_slot *sweep_slot)
{
size_t free_num = 0, final_num = 0;
RVALUE *p, *pend;
RVALUE *final = deferred_final_list;
int deferred;
uintptr_t *bits;
p = sweep_slot->header->start; pend = p + sweep_slot->header->limit;
bits = GET_HEAP_BITMAP(p);
while (p < pend) {
if ((!(MARKED_IN_BITMAP(bits, p))) && BUILTIN_TYPE(p) != T_ZOMBIE) {
if (p->as.basic.flags) {
if ((deferred = obj_free(objspace, (VALUE)p)) ||
(FL_TEST(p, FL_FINALIZE))) {
if (!deferred) {
p->as.free.flags = T_ZOMBIE;
RDATA(p)->dfree = 0;
}
p->as.free.next = deferred_final_list;
deferred_final_list = p;
assert(BUILTIN_TYPE(p) == T_ZOMBIE);
final_num++;
}
else {
VALGRIND_MAKE_MEM_UNDEFINED((void*)p, sizeof(RVALUE));
p->as.free.flags = 0;
p->as.free.next = sweep_slot->freelist;
sweep_slot->freelist = p;
free_num++;
}
}
else {
free_num++;
}
}
p++;
}
gc_clear_slot_bits(sweep_slot);
if (final_num + free_num == sweep_slot->header->limit &&
objspace->heap.free_num > objspace->heap.do_heap_free) {
RVALUE *pp;
for (pp = deferred_final_list; pp != final; pp = pp->as.free.next) {
RDATA(pp)->dmark = (void (*)(void *))(VALUE)sweep_slot;
pp->as.free.flags |= FL_SINGLETON; /* freeing page mark */
}
sweep_slot->header->limit = final_num;
unlink_heap_slot(objspace, sweep_slot);
}
else {
if (free_num > 0) {
link_free_heap_slot(objspace, sweep_slot);
}
else {
sweep_slot->free_next = NULL;
}
objspace->heap.free_num += free_num;
}
objspace->heap.final_num += final_num;
if (deferred_final_list && !finalizing) {
rb_thread_t *th = GET_THREAD();
if (th) {
RUBY_VM_SET_FINALIZER_INTERRUPT(th);
}
}
}
static int
ready_to_gc(rb_objspace_t *objspace)
{
if (dont_gc || during_gc) {
if (!has_free_object) {
if (!heaps_increment(objspace)) {
set_heaps_increment(objspace);
heaps_increment(objspace);
}
}
return FALSE;
}
return TRUE;
}
static void
before_gc_sweep(rb_objspace_t *objspace)
{
objspace->heap.do_heap_free = (size_t)((heaps_used * HEAP_OBJ_LIMIT) * 0.65);
objspace->heap.free_min = (size_t)((heaps_used * HEAP_OBJ_LIMIT) * 0.2);
if (objspace->heap.free_min < initial_free_min) {
objspace->heap.do_heap_free = heaps_used * HEAP_OBJ_LIMIT;
objspace->heap.free_min = initial_free_min;
}
objspace->heap.sweep_slots = heaps;
objspace->heap.free_num = 0;
objspace->heap.free_slots = NULL;
/* sweep unlinked method entries */
if (GET_VM()->unlinked_method_entry_list) {
rb_sweep_method_entry(GET_VM());
}
}
static void
after_gc_sweep(rb_objspace_t *objspace)
{
size_t inc;
gc_prof_set_malloc_info(objspace);
if (objspace->heap.free_num < objspace->heap.free_min) {
set_heaps_increment(objspace);
heaps_increment(objspace);
}
inc = ATOMIC_SIZE_EXCHANGE(malloc_increase, 0);
if (inc > malloc_limit) {
malloc_limit += (size_t)((inc - malloc_limit) * (double)objspace->heap.live_num / (heaps_used * HEAP_OBJ_LIMIT));
if (malloc_limit < initial_malloc_limit) malloc_limit = initial_malloc_limit;
}
free_unused_heaps(objspace);
}
static int
lazy_sweep(rb_objspace_t *objspace)
{
struct heaps_slot *next;
heaps_increment(objspace);
while (objspace->heap.sweep_slots) {
next = objspace->heap.sweep_slots->next;
slot_sweep(objspace, objspace->heap.sweep_slots);
objspace->heap.sweep_slots = next;
if (has_free_object) {
during_gc = 0;
return TRUE;
}
}
return FALSE;
}
static void
rest_sweep(rb_objspace_t *objspace)
{
if (objspace->heap.sweep_slots) {
while (objspace->heap.sweep_slots) {
lazy_sweep(objspace);
}
after_gc_sweep(objspace);
}
}
static void gc_marks(rb_objspace_t *objspace);
static int
gc_prepare_free_objects(rb_objspace_t *objspace)
{
int res;
if (objspace->flags.dont_lazy_sweep)
return garbage_collect(objspace);
if (!ready_to_gc(objspace)) return TRUE;
during_gc++;
gc_prof_timer_start(objspace);
gc_prof_sweep_timer_start(objspace);
if (objspace->heap.sweep_slots) {
res = lazy_sweep(objspace);
if (res) {
gc_prof_sweep_timer_stop(objspace);
gc_prof_set_malloc_info(objspace);
gc_prof_timer_stop(objspace, Qfalse);
return res;
}
after_gc_sweep(objspace);
}
else {
if (heaps_increment(objspace)) {
during_gc = 0;
return TRUE;
}
}
gc_marks(objspace);
before_gc_sweep(objspace);
if (objspace->heap.free_min > (heaps_used * HEAP_OBJ_LIMIT - objspace->heap.live_num)) {
set_heaps_increment(objspace);
}
gc_prof_sweep_timer_start(objspace);
if (!(res = lazy_sweep(objspace))) {
after_gc_sweep(objspace);
if (has_free_object) {
res = TRUE;
during_gc = 0;
}
}
gc_prof_sweep_timer_stop(objspace);
gc_prof_timer_stop(objspace, Qtrue);
return res;
}
static void
gc_sweep(rb_objspace_t *objspace)
{
struct heaps_slot *next;
before_gc_sweep(objspace);
while (objspace->heap.sweep_slots) {
next = objspace->heap.sweep_slots->next;
slot_sweep(objspace, objspace->heap.sweep_slots);
objspace->heap.sweep_slots = next;
}
after_gc_sweep(objspace);
during_gc = 0;
}
/* Marking stack */
static void push_mark_stack(mark_stack_t *, VALUE);
static int pop_mark_stack(mark_stack_t *, VALUE *);
static void shrink_stack_chunk_cache(mark_stack_t *stack);
static stack_chunk_t *
stack_chunk_alloc(void)
{
stack_chunk_t *res;
res = malloc(sizeof(stack_chunk_t));
if (!res)
rb_memerror();
return res;
}
static inline int
is_mark_stask_empty(mark_stack_t *stack)
{
return stack->chunk == NULL;
}
static void
add_stack_chunk_cache(mark_stack_t *stack, stack_chunk_t *chunk)
{
chunk->next = stack->cache;
stack->cache = chunk;
stack->cache_size++;
}
static void
shrink_stack_chunk_cache(mark_stack_t *stack)
{
stack_chunk_t *chunk;
if (stack->unused_cache_size > (stack->cache_size/2)) {
chunk = stack->cache;
stack->cache = stack->cache->next;
stack->cache_size--;
free(chunk);
}
stack->unused_cache_size = stack->cache_size;
}
static void
push_mark_stack_chunk(mark_stack_t *stack)
{
stack_chunk_t *next;
assert(stack->index == stack->limit);
if (stack->cache_size > 0) {
next = stack->cache;
stack->cache = stack->cache->next;
stack->cache_size--;
if (stack->unused_cache_size > stack->cache_size)
stack->unused_cache_size = stack->cache_size;
}
else {
next = stack_chunk_alloc();
}
next->next = stack->chunk;
stack->chunk = next;
stack->index = 0;
}
static void
pop_mark_stack_chunk(mark_stack_t *stack)
{
stack_chunk_t *prev;
prev = stack->chunk->next;
assert(stack->index == 0);
add_stack_chunk_cache(stack, stack->chunk);
stack->chunk = prev;
stack->index = stack->limit;
}
#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE
static void
free_stack_chunks(mark_stack_t *stack)
{
stack_chunk_t *chunk = stack->chunk;
stack_chunk_t *next = NULL;
while (chunk != NULL) {
next = chunk->next;
free(chunk);
chunk = next;
}
}
#endif
static void
push_mark_stack(mark_stack_t *stack, VALUE data)
{
if (stack->index == stack->limit) {
push_mark_stack_chunk(stack);
}
stack->chunk->data[stack->index++] = data;
}
static int
pop_mark_stack(mark_stack_t *stack, VALUE *data)
{
if (is_mark_stask_empty(stack)) {
return FALSE;
}
if (stack->index == 1) {
*data = stack->chunk->data[--stack->index];
pop_mark_stack_chunk(stack);
return TRUE;
}
*data = stack->chunk->data[--stack->index];
return TRUE;
}
static void
init_mark_stack(mark_stack_t *stack)
{
int i;
push_mark_stack_chunk(stack);
stack->limit = STACK_CHUNK_SIZE;
for(i=0; i < 4; i++) {
add_stack_chunk_cache(stack, stack_chunk_alloc());
}
stack->unused_cache_size = stack->cache_size;
}
/* Marking */
#define MARK_IN_BITMAP(bits, p) (bits[BITMAP_INDEX(p)] = bits[BITMAP_INDEX(p)] | ((uintptr_t)1 << BITMAP_OFFSET(p)))
#ifdef __ia64
#define SET_STACK_END (SET_MACHINE_STACK_END(&th->machine_stack_end), th->machine_register_stack_end = rb_ia64_bsp())
#else
#define SET_STACK_END SET_MACHINE_STACK_END(&th->machine_stack_end)
#endif
#define STACK_START (th->machine_stack_start)
#define STACK_END (th->machine_stack_end)
#define STACK_LEVEL_MAX (th->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_thread_t *th = GET_THREAD();
SET_STACK_END;
if (p) *p = STACK_UPPER(STACK_END, STACK_START, STACK_END);
return STACK_LENGTH;
}
#if !(defined(POSIX_SIGNAL) && defined(SIGSEGV) && defined(HAVE_SIGALTSTACK))
static int
stack_check(int water_mark)
{
int ret;
rb_thread_t *th = GET_THREAD();
SET_STACK_END;
ret = STACK_LENGTH > STACK_LEVEL_MAX - water_mark;
#ifdef __ia64
if (!ret) {
ret = (VALUE*)rb_ia64_bsp() - th->machine_register_stack_start >
th->machine_register_stack_maxsize/sizeof(VALUE) - water_mark;
}
#endif
return ret;
}
#endif
#define STACKFRAME_FOR_CALL_CFUNC 512
int
ruby_stack_check(void)
{
#if defined(POSIX_SIGNAL) && defined(SIGSEGV) && defined(HAVE_SIGALTSTACK)
return 0;
#else
return stack_check(STACKFRAME_FOR_CALL_CFUNC);
#endif
}
static void
mark_locations_array(rb_objspace_t *objspace, register VALUE *x, register long n)
{
VALUE v;
while (n--) {
v = *x;
VALGRIND_MAKE_MEM_DEFINED(&v, sizeof(v));
if (is_pointer_to_heap(objspace, (void *)v)) {
gc_mark(objspace, v);
}
x++;
}
}
static void
gc_mark_locations(rb_objspace_t *objspace, VALUE *start, VALUE *end)
{
long n;
if (end <= start) return;
n = end - start;
mark_locations_array(objspace, start, n);
}
void
rb_gc_mark_locations(VALUE *start, VALUE *end)
{
gc_mark_locations(&rb_objspace, start, end);
}
#define rb_gc_mark_locations(start, end) gc_mark_locations(objspace, (start), (end))
struct mark_tbl_arg {
rb_objspace_t *objspace;
};
static int
mark_entry(st_data_t key, st_data_t value, st_data_t data)
{
struct mark_tbl_arg *arg = (void*)data;
gc_mark(arg->objspace, (VALUE)value);
return ST_CONTINUE;
}
static void
mark_tbl(rb_objspace_t *objspace, st_table *tbl)
{
struct mark_tbl_arg arg;
if (!tbl || tbl->num_entries == 0) return;
arg.objspace = objspace;
st_foreach(tbl, mark_entry, (st_data_t)&arg);
}
static int
mark_key(st_data_t key, st_data_t value, st_data_t data)
{
struct mark_tbl_arg *arg = (void*)data;
gc_mark(arg->objspace, (VALUE)key);
return ST_CONTINUE;
}
static void
mark_set(rb_objspace_t *objspace, st_table *tbl)
{
struct mark_tbl_arg arg;
if (!tbl) return;
arg.objspace = objspace;
st_foreach(tbl, mark_key, (st_data_t)&arg);
}
void
rb_mark_set(st_table *tbl)
{
mark_set(&rb_objspace, tbl);
}
static int
mark_keyvalue(st_data_t key, st_data_t value, st_data_t data)
{
struct mark_tbl_arg *arg = (void*)data;
gc_mark(arg->objspace, (VALUE)key);
gc_mark(arg->objspace, (VALUE)value);
return ST_CONTINUE;
}
static void
mark_hash(rb_objspace_t *objspace, st_table *tbl)
{
struct mark_tbl_arg arg;
if (!tbl) return;
arg.objspace = objspace;
st_foreach(tbl, mark_keyvalue, (st_data_t)&arg);
}
void
rb_mark_hash(st_table *tbl)
{
mark_hash(&rb_objspace, tbl);
}
static void
mark_method_entry(rb_objspace_t *objspace, const rb_method_entry_t *me)
{
const rb_method_definition_t *def = me->def;
gc_mark(objspace, me->klass);
if (!def) return;
switch (def->type) {
case VM_METHOD_TYPE_ISEQ:
gc_mark(objspace, def->body.iseq->self);
break;
case VM_METHOD_TYPE_BMETHOD:
gc_mark(objspace, def->body.proc);
break;
case VM_METHOD_TYPE_ATTRSET:
case VM_METHOD_TYPE_IVAR:
gc_mark(objspace, def->body.attr.location);
break;
default:
break; /* ignore */
}
}
void
rb_mark_method_entry(const rb_method_entry_t *me)
{
mark_method_entry(&rb_objspace, me);
}
static int
mark_method_entry_i(ID key, const rb_method_entry_t *me, st_data_t data)
{
struct mark_tbl_arg *arg = (void*)data;
mark_method_entry(arg->objspace, me);
return ST_CONTINUE;
}
static void
mark_m_tbl(rb_objspace_t *objspace, st_table *tbl)
{
struct mark_tbl_arg arg;
if (!tbl) return;
arg.objspace = objspace;
st_foreach(tbl, mark_method_entry_i, (st_data_t)&arg);
}
static int
mark_const_entry_i(ID key, const rb_const_entry_t *ce, st_data_t data)
{
struct mark_tbl_arg *arg = (void*)data;
gc_mark(arg->objspace, ce->value);
gc_mark(arg->objspace, ce->file);
return ST_CONTINUE;
}
static void
mark_const_tbl(rb_objspace_t *objspace, st_table *tbl)
{
struct mark_tbl_arg arg;
if (!tbl) return;
arg.objspace = objspace;
st_foreach(tbl, mark_const_entry_i, (st_data_t)&arg);
}
#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
#define numberof(array) (int)(sizeof(array) / sizeof((array)[0]))
static void
mark_current_machine_context(rb_objspace_t *objspace, rb_thread_t *th)
{
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;
/* This assumes that all registers are saved into the jmp_buf (and stack) */
rb_setjmp(save_regs_gc_mark.j);
SET_STACK_END;
GET_STACK_BOUNDS(stack_start, stack_end, 1);
mark_locations_array(objspace, save_regs_gc_mark.v, numberof(save_regs_gc_mark.v));
rb_gc_mark_locations(stack_start, stack_end);
#ifdef __ia64
rb_gc_mark_locations(th->machine_register_stack_start, th->machine_register_stack_end);
#endif
#if defined(__mc68000__)
mark_locations_array(objspace, (VALUE*)((char*)STACK_END + 2),
(STACK_START - STACK_END));
#endif
}
void
rb_gc_mark_machine_stack(rb_thread_t *th)
{
rb_objspace_t *objspace = &rb_objspace;
VALUE *stack_start, *stack_end;
GET_STACK_BOUNDS(stack_start, stack_end, 0);
rb_gc_mark_locations(stack_start, stack_end);
#ifdef __ia64
rb_gc_mark_locations(th->machine_register_stack_start, th->machine_register_stack_end);
#endif
}
void
rb_mark_tbl(st_table *tbl)
{
mark_tbl(&rb_objspace, tbl);
}
void
rb_gc_mark_maybe(VALUE obj)
{
if (is_pointer_to_heap(&rb_objspace, (void *)obj)) {
gc_mark(&rb_objspace, obj);
}
}
static int
gc_mark_ptr(rb_objspace_t *objspace, VALUE ptr)
{
register uintptr_t *bits = GET_HEAP_BITMAP(ptr);
if (MARKED_IN_BITMAP(bits, ptr)) return 0;
MARK_IN_BITMAP(bits, ptr);
objspace->heap.live_num++;
return 1;
}
static int
markable_object_p(rb_objspace_t *objspace, VALUE ptr)
{
register RVALUE *obj = RANY(ptr);
if (rb_special_const_p(ptr)) return 0; /* special const not marked */
if (obj->as.basic.flags == 0) return 0 ; /* free cell */
return 1;
}
int
rb_objspace_markable_object_p(VALUE obj)
{
return markable_object_p(/* now it doesn't use &rb_objspace */ 0, obj);
}
static void
gc_mark(rb_objspace_t *objspace, VALUE ptr)
{
if (!markable_object_p(objspace, ptr)) {
return;
}
if (LIKELY(objspace->mark_func_data == 0)) {
if (!gc_mark_ptr(objspace, ptr)) return; /* already marked */
push_mark_stack(&objspace->mark_stack, ptr);
}
else {
objspace->mark_func_data->mark_func(ptr, objspace->mark_func_data->data);
}
}
void
rb_gc_mark(VALUE ptr)
{
gc_mark(&rb_objspace, ptr);
}
static void
gc_mark_children(rb_objspace_t *objspace, VALUE ptr)
{
register RVALUE *obj = RANY(ptr);
goto marking; /* skip */
again:
if (LIKELY(objspace->mark_func_data == 0)) {
obj = RANY(ptr);
if (!markable_object_p(objspace, ptr)) return;
if (!gc_mark_ptr(objspace, ptr)) return; /* already marked */
}
else {
gc_mark(objspace, ptr);
return;
}
marking:
if (FL_TEST(obj, FL_EXIVAR)) {
rb_mark_generic_ivar(ptr);
}
switch (BUILTIN_TYPE(obj)) {
case T_NIL:
case T_FIXNUM:
rb_bug("rb_gc_mark() called for broken object");
break;
case T_NODE:
switch (nd_type(obj)) {
case NODE_IF: /* 1,2,3 */
case NODE_FOR:
case NODE_ITER:
case NODE_WHEN:
case NODE_MASGN:
case NODE_RESCUE:
case NODE_RESBODY:
case NODE_CLASS:
case NODE_BLOCK_PASS:
gc_mark(objspace, (VALUE)obj->as.node.u2.node);
/* fall through */
case NODE_BLOCK: /* 1,3 */
case NODE_ARRAY:
case NODE_DSTR:
case NODE_DXSTR:
case NODE_DREGX:
case NODE_DREGX_ONCE:
case NODE_ENSURE:
case NODE_CALL:
case NODE_DEFS:
case NODE_OP_ASGN1:
gc_mark(objspace, (VALUE)obj->as.node.u1.node);
/* fall through */
case NODE_SUPER: /* 3 */
case NODE_FCALL:
case NODE_DEFN:
case NODE_ARGS_AUX:
ptr = (VALUE)obj->as.node.u3.node;
goto again;
case NODE_WHILE: /* 1,2 */
case NODE_UNTIL:
case NODE_AND:
case NODE_OR:
case NODE_CASE:
case NODE_SCLASS:
case NODE_DOT2:
case NODE_DOT3:
case NODE_FLIP2:
case NODE_FLIP3:
case NODE_MATCH2:
case NODE_MATCH3:
case NODE_OP_ASGN_OR:
case NODE_OP_ASGN_AND:
case NODE_MODULE:
case NODE_ALIAS:
case NODE_VALIAS:
case NODE_ARGSCAT:
gc_mark(objspace, (VALUE)obj->as.node.u1.node);
/* fall through */
case NODE_GASGN: /* 2 */
case NODE_LASGN:
case NODE_DASGN:
case NODE_DASGN_CURR:
case NODE_IASGN:
case NODE_IASGN2:
case NODE_CVASGN:
case NODE_COLON3:
case NODE_OPT_N:
case NODE_EVSTR:
case NODE_UNDEF:
case NODE_POSTEXE:
ptr = (VALUE)obj->as.node.u2.node;
goto again;
case NODE_HASH: /* 1 */
case NODE_LIT:
case NODE_STR:
case NODE_XSTR:
case NODE_DEFINED:
case NODE_MATCH:
case NODE_RETURN:
case NODE_BREAK:
case NODE_NEXT:
case NODE_YIELD:
case NODE_COLON2:
case NODE_SPLAT:
case NODE_TO_ARY:
ptr = (VALUE)obj->as.node.u1.node;
goto again;
case NODE_SCOPE: /* 2,3 */
case NODE_CDECL:
case NODE_OPT_ARG:
gc_mark(objspace, (VALUE)obj->as.node.u3.node);
ptr = (VALUE)obj->as.node.u2.node;
goto again;
case NODE_ARGS: /* custom */
{
struct rb_args_info *args = obj->as.node.u3.args;
if (args) {
if (args->pre_init) gc_mark(objspace, (VALUE)args->pre_init);
if (args->post_init) gc_mark(objspace, (VALUE)args->post_init);
if (args->opt_args) gc_mark(objspace, (VALUE)args->opt_args);
if (args->kw_args) gc_mark(objspace, (VALUE)args->kw_args);
if (args->kw_rest_arg) gc_mark(objspace, (VALUE)args->kw_rest_arg);
}
}
ptr = (VALUE)obj->as.node.u2.node;
goto again;
case NODE_ZARRAY: /* - */
case NODE_ZSUPER:
case NODE_VCALL:
case NODE_GVAR:
case NODE_LVAR:
case NODE_DVAR:
case NODE_IVAR:
case NODE_CVAR:
case NODE_NTH_REF:
case NODE_BACK_REF:
case NODE_REDO:
case NODE_RETRY:
case NODE_SELF:
case NODE_NIL:
case NODE_TRUE:
case NODE_FALSE:
case NODE_ERRINFO:
case NODE_BLOCK_ARG:
break;
case NODE_ALLOCA:
mark_locations_array(objspace,
(VALUE*)obj->as.node.u1.value,
obj->as.node.u3.cnt);
gc_mark(objspace, (VALUE)obj->as.node.u2.node);
break;
case NODE_CREF:
gc_mark(objspace, obj->as.node.nd_refinements);
gc_mark(objspace, (VALUE)obj->as.node.u1.node);
ptr = (VALUE)obj->as.node.u3.node;
goto again;
default: /* unlisted NODE */
if (is_pointer_to_heap(objspace, obj->as.node.u1.node)) {
gc_mark(objspace, (VALUE)obj->as.node.u1.node);
}
if (is_pointer_to_heap(objspace, obj->as.node.u2.node)) {
gc_mark(objspace, (VALUE)obj->as.node.u2.node);
}
if (is_pointer_to_heap(objspace, obj->as.node.u3.node)) {
gc_mark(objspace, (VALUE)obj->as.node.u3.node);
}
}
return; /* no need to mark class. */
}
gc_mark(objspace, obj->as.basic.klass);
switch (BUILTIN_TYPE(obj)) {
case T_ICLASS:
case T_CLASS:
case T_MODULE:
mark_m_tbl(objspace, RCLASS_M_TBL(obj));
if (!RCLASS_EXT(obj)) break;
mark_tbl(objspace, RCLASS_IV_TBL(obj));
mark_const_tbl(objspace, RCLASS_CONST_TBL(obj));
ptr = RCLASS_SUPER(obj);
goto again;
case T_ARRAY:
if (FL_TEST(obj, ELTS_SHARED)) {
ptr = obj->as.array.as.heap.aux.shared;
goto again;
}
else {
long i, len = RARRAY_LEN(obj);
VALUE *ptr = RARRAY_PTR(obj);
for (i=0; i < len; i++) {
gc_mark(objspace, *ptr++);
}
}
break;
case T_HASH:
mark_hash(objspace, obj->as.hash.ntbl);
ptr = obj->as.hash.ifnone;
goto again;
case T_STRING:
#define STR_ASSOC FL_USER3 /* copied from string.c */
if (FL_TEST(obj, RSTRING_NOEMBED) && FL_ANY(obj, ELTS_SHARED|STR_ASSOC)) {
ptr = obj->as.string.as.heap.aux.shared;
goto again;
}
break;
case T_DATA:
if (RTYPEDDATA_P(obj)) {
RUBY_DATA_FUNC mark_func = obj->as.typeddata.type->function.dmark;
if (mark_func) (*mark_func)(DATA_PTR(obj));
}
else {
if (obj->as.data.dmark) (*obj->as.data.dmark)(DATA_PTR(obj));
}
break;
case T_OBJECT:
{
long i, len = ROBJECT_NUMIV(obj);
VALUE *ptr = ROBJECT_IVPTR(obj);
for (i = 0; i < len; i++) {
gc_mark(objspace, *ptr++);
}
}
break;
case T_FILE:
if (obj->as.file.fptr) {
gc_mark(objspace, obj->as.file.fptr->pathv);
gc_mark(objspace, obj->as.file.fptr->tied_io_for_writing);
gc_mark(objspace, obj->as.file.fptr->writeconv_asciicompat);
gc_mark(objspace, obj->as.file.fptr->writeconv_pre_ecopts);
gc_mark(objspace, obj->as.file.fptr->encs.ecopts);
gc_mark(objspace, obj->as.file.fptr->write_lock);
}
break;
case T_REGEXP:
ptr = obj->as.regexp.src;
goto again;
case T_FLOAT:
case T_BIGNUM:
case T_ZOMBIE:
break;
case T_MATCH:
gc_mark(objspace, obj->as.match.regexp);
if (obj->as.match.str) {
ptr = obj->as.match.str;
goto again;
}
break;
case T_RATIONAL:
gc_mark(objspace, obj->as.rational.num);
ptr = obj->as.rational.den;
goto again;
case T_COMPLEX:
gc_mark(objspace, obj->as.complex.real);
ptr = obj->as.complex.imag;
goto again;
case T_STRUCT:
{
long len = RSTRUCT_LEN(obj);
VALUE *ptr = RSTRUCT_PTR(obj);
while (len--) {
gc_mark(objspace, *ptr++);
}
}
break;
default:
rb_bug("rb_gc_mark(): unknown data type 0x%x(%p) %s",
BUILTIN_TYPE(obj), (void *)obj,
is_pointer_to_heap(objspace, obj) ? "corrupted object" : "non object");
}
}
static void
gc_mark_stacked_objects(rb_objspace_t *objspace)
{
mark_stack_t *mstack = &objspace->mark_stack;
VALUE obj = 0;
if (!mstack->index) return;
while (pop_mark_stack(mstack, &obj)) {
gc_mark_children(objspace, obj);
}
shrink_stack_chunk_cache(mstack);
}
static void
gc_marks(rb_objspace_t *objspace)
{
struct gc_list *list;
rb_thread_t *th = GET_THREAD();
struct mark_func_data_struct *prev_mark_func_data;
prev_mark_func_data = objspace->mark_func_data;
objspace->mark_func_data = 0;
gc_prof_mark_timer_start(objspace);
objspace->heap.live_num = 0;
objspace->count++;
SET_STACK_END;
th->vm->self ? rb_gc_mark(th->vm->self) : rb_vm_mark(th->vm);
mark_tbl(objspace, finalizer_table);
mark_current_machine_context(objspace, th);
rb_gc_mark_symbols();
rb_gc_mark_encodings();
/* mark protected global variables */
for (list = global_List; list; list = list->next) {
rb_gc_mark_maybe(*list->varptr);
}
rb_mark_end_proc();
rb_gc_mark_global_tbl();
mark_tbl(objspace, rb_class_tbl);
/* mark generic instance variables for special constants */
rb_mark_generic_ivar_tbl();
rb_gc_mark_parser();
rb_gc_mark_unlinked_live_method_entries(th->vm);
/* marking-loop */
gc_mark_stacked_objects(objspace);
gc_prof_mark_timer_stop(objspace);
objspace->mark_func_data = prev_mark_func_data;
}
/* GC */
void
rb_gc_force_recycle(VALUE p)
{
rb_objspace_t *objspace = &rb_objspace;
struct heaps_slot *slot;
if (MARKED_IN_BITMAP(GET_HEAP_BITMAP(p), p)) {
add_slot_local_freelist(objspace, (RVALUE *)p);
}
else {
gc_prof_dec_live_num(objspace);
slot = add_slot_local_freelist(objspace, (RVALUE *)p);
if (slot->free_next == NULL) {
link_free_heap_slot(objspace, slot);
}
}
}
void
rb_gc_register_mark_object(VALUE obj)
{
VALUE ary = GET_THREAD()->vm->mark_object_ary;
rb_ary_push(ary, obj);
}
void
rb_gc_register_address(VALUE *addr)
{
rb_objspace_t *objspace = &rb_objspace;
struct gc_list *tmp;
tmp = ALLOC(struct gc_list);
tmp->next = global_List;
tmp->varptr = addr;
global_List = tmp;
}
void
rb_gc_unregister_address(VALUE *addr)
{
rb_objspace_t *objspace = &rb_objspace;
struct gc_list *tmp = global_List;
if (tmp->varptr == addr) {
global_List = tmp->next;
xfree(tmp);
return;
}
while (tmp->next) {
if (tmp->next->varptr == addr) {
struct gc_list *t = tmp->next;
tmp->next = tmp->next->next;
xfree(t);
break;
}
tmp = tmp->next;
}
}
#define GC_NOTIFY 0
static int
garbage_collect(rb_objspace_t *objspace)
{
if (GC_NOTIFY) printf("start garbage_collect()\n");
if (!heaps) {
return FALSE;
}
if (!ready_to_gc(objspace)) {
return TRUE;
}
gc_prof_timer_start(objspace);
rest_sweep(objspace);
during_gc++;
gc_marks(objspace);
gc_prof_sweep_timer_start(objspace);
gc_sweep(objspace);
gc_prof_sweep_timer_stop(objspace);
gc_prof_timer_stop(objspace, Qtrue);
if (GC_NOTIFY) printf("end garbage_collect()\n");
return TRUE;
}
static void *
gc_with_gvl(void *ptr)
{
return (void *)(VALUE)garbage_collect((rb_objspace_t *)ptr);
}
static int
garbage_collect_with_gvl(rb_objspace_t *objspace)
{
if (dont_gc) return TRUE;
if (ruby_thread_has_gvl_p()) {
return garbage_collect(objspace);
}
else {
if (ruby_native_thread_p()) {
return (int)(VALUE)rb_thread_call_with_gvl(gc_with_gvl, (void *)objspace);
}
else {
/* no ruby thread */
fprintf(stderr, "[FATAL] failed to allocate memory\n");
exit(EXIT_FAILURE);
}
}
}
int
rb_garbage_collect(void)
{
return garbage_collect(&rb_objspace);
}
#undef Init_stack
void
Init_stack(volatile VALUE *addr)
{
ruby_init_stack(addr);
}
/*
* call-seq:
* GC.start -> nil
* gc.garbage_collect -> nil
* ObjectSpace.garbage_collect -> nil
*
* Initiates garbage collection, unless manually disabled.
*
*/
VALUE
rb_gc_start(void)
{
rb_gc();
return Qnil;
}
void
rb_gc(void)
{
rb_objspace_t *objspace = &rb_objspace;
garbage_collect(objspace);
if (!finalizing) finalize_deferred(objspace);
free_unused_heaps(objspace);
}
int
rb_during_gc(void)
{
rb_objspace_t *objspace = &rb_objspace;
return during_gc;
}
/*
* call-seq:
* GC.count -> Integer
*
* The number of times GC occurred.
*
* It returns the number of times GC occurred since the process started.
*
*/
static VALUE
gc_count(VALUE self)
{
return UINT2NUM(rb_objspace.count);
}
/*
* call-seq:
* GC.stat -> Hash
*
* Returns a Hash containing information about the GC.
*
* The hash includes information about internal statistics about GC such as:
*
* {
* :count => 18,
* :heap_used => 77,
* :heap_length => 77,
* :heap_increment => 0,
* :heap_live_num => 23287,
* :heap_free_num => 8115,
* :heap_final_num => 0,
* }
*
* The contents of the hash are implementation defined and may be changed in
* the future.
*
* This method is only expected to work on C Ruby.
*
*/
static VALUE
gc_stat(int argc, VALUE *argv, VALUE self)
{
rb_objspace_t *objspace = &rb_objspace;
VALUE hash;
if (rb_scan_args(argc, argv, "01", &hash) == 1) {
if (!RB_TYPE_P(hash, T_HASH))
rb_raise(rb_eTypeError, "non-hash given");
}
if (hash == Qnil) {
hash = rb_hash_new();
}
rest_sweep(objspace);
rb_hash_aset(hash, ID2SYM(rb_intern("count")), SIZET2NUM(objspace->count));
/* implementation dependent counters */
rb_hash_aset(hash, ID2SYM(rb_intern("heap_used")), SIZET2NUM(objspace->heap.used));
rb_hash_aset(hash, ID2SYM(rb_intern("heap_length")), SIZET2NUM(objspace->heap.length));
rb_hash_aset(hash, ID2SYM(rb_intern("heap_increment")), SIZET2NUM(objspace->heap.increment));
rb_hash_aset(hash, ID2SYM(rb_intern("heap_live_num")), SIZET2NUM(objspace->heap.live_num));
rb_hash_aset(hash, ID2SYM(rb_intern("heap_free_num")), SIZET2NUM(objspace->heap.free_num));
rb_hash_aset(hash, ID2SYM(rb_intern("heap_final_num")), SIZET2NUM(objspace->heap.final_num));
return hash;
}
/*
* call-seq:
* GC.stress -> true or false
*
* returns current status of GC stress mode.
*/
static VALUE
gc_stress_get(VALUE self)
{
rb_objspace_t *objspace = &rb_objspace;
return ruby_gc_stress ? Qtrue : Qfalse;
}
/*
* call-seq:
* GC.stress = bool -> bool
*
* Updates the GC stress mode.
*
* When stress mode is enabled the GC is invoked at every GC opportunity:
* all memory and object allocations.
*
* Enabling stress mode makes Ruby very slow, it is only for debugging.
*/
static VALUE
gc_stress_set(VALUE self, VALUE flag)
{
rb_objspace_t *objspace = &rb_objspace;
rb_secure(2);
ruby_gc_stress = RTEST(flag);
return flag;
}
/*
* call-seq:
* GC.enable -> true or false
*
* Enables garbage collection, returning <code>true</code> if garbage
* collection was previously disabled.
*
* GC.disable #=> false
* GC.enable #=> true
* GC.enable #=> false
*
*/
VALUE
rb_gc_enable(void)
{
rb_objspace_t *objspace = &rb_objspace;
int old = dont_gc;
dont_gc = FALSE;
return old ? Qtrue : Qfalse;
}
/*
* call-seq:
* GC.disable -> true or false
*
* Disables garbage collection, returning <code>true</code> if garbage
* collection was already disabled.
*
* GC.disable #=> false
* GC.disable #=> true
*
*/
VALUE
rb_gc_disable(void)
{
rb_objspace_t *objspace = &rb_objspace;
int old = dont_gc;
dont_gc = TRUE;
return old ? Qtrue : Qfalse;
}
void
rb_gc_set_params(void)
{
char *malloc_limit_ptr, *heap_min_slots_ptr, *free_min_ptr;
if (rb_safe_level() > 0) return;
malloc_limit_ptr = getenv("RUBY_GC_MALLOC_LIMIT");
if (malloc_limit_ptr != NULL) {
int malloc_limit_i = atoi(malloc_limit_ptr);
if (RTEST(ruby_verbose))
fprintf(stderr, "malloc_limit=%d (%d)\n",
malloc_limit_i, initial_malloc_limit);
if (malloc_limit_i > 0) {
initial_malloc_limit = malloc_limit_i;
}
}
heap_min_slots_ptr = getenv("RUBY_HEAP_MIN_SLOTS");
if (heap_min_slots_ptr != NULL) {
int heap_min_slots_i = atoi(heap_min_slots_ptr);
if (RTEST(ruby_verbose))
fprintf(stderr, "heap_min_slots=%d (%d)\n",
heap_min_slots_i, initial_heap_min_slots);
if (heap_min_slots_i > 0) {
initial_heap_min_slots = heap_min_slots_i;
initial_expand_heap(&rb_objspace);
}
}
free_min_ptr = getenv("RUBY_FREE_MIN");
if (free_min_ptr != NULL) {
int free_min_i = atoi(free_min_ptr);
if (RTEST(ruby_verbose))
fprintf(stderr, "free_min=%d (%d)\n", free_min_i, initial_free_min);
if (free_min_i > 0) {
initial_free_min = free_min_i;
}
}
}
void
rb_objspace_reachable_objects_from(VALUE obj, void (func)(VALUE, void *), void *data)
{
rb_objspace_t *objspace = &rb_objspace;
if (markable_object_p(objspace, obj)) {
struct mark_func_data_struct mfd;
mfd.mark_func = func;
mfd.data = data;
objspace->mark_func_data = &mfd;
gc_mark_children(objspace, obj);
objspace->mark_func_data = 0;
}
}
/*
------------------------ Extended allocator ------------------------
*/
static void vm_xfree(rb_objspace_t *objspace, void *ptr);
static void *
negative_size_allocation_error_with_gvl(void *ptr)
{
rb_raise(rb_eNoMemError, "%s", (const char *)ptr);
return 0; /* should not be reached */
}
static void
negative_size_allocation_error(const char *msg)
{
if (ruby_thread_has_gvl_p()) {
rb_raise(rb_eNoMemError, "%s", msg);
}
else {
if (ruby_native_thread_p()) {
rb_thread_call_with_gvl(negative_size_allocation_error_with_gvl, (void *)msg);
}
else {
fprintf(stderr, "[FATAL] %s\n", msg);
exit(EXIT_FAILURE);
}
}
}
static void *
ruby_memerror_body(void *dummy)
{
rb_memerror();
return 0;
}
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_thread_t *th = GET_THREAD();
if (!nomem_error ||
(rb_thread_raised_p(th, RAISED_NOMEMORY) && rb_safe_level() < 4)) {
fprintf(stderr, "[FATAL] failed to allocate memory\n");
exit(EXIT_FAILURE);
}
if (rb_thread_raised_p(th, RAISED_NOMEMORY)) {
rb_thread_raised_clear(th);
GET_THREAD()->errinfo = nomem_error;
JUMP_TAG(TAG_RAISE);
}
rb_thread_raised_set(th, RAISED_NOMEMORY);
rb_exc_raise(nomem_error);
}
static void *
aligned_malloc(size_t alignment, size_t size)
{
void *res;
#if defined __MINGW32__
res = __mingw_aligned_malloc(size, alignment);
#elif defined _WIN32 && !defined __CYGWIN__
res = _aligned_malloc(size, alignment);
#elif defined(HAVE_POSIX_MEMALIGN)
if (posix_memalign(&res, alignment, size) == 0) {
return res;
}
else {
return NULL;
}
#elif defined(HAVE_MEMALIGN)
res = memalign(alignment, size);
#else
char* aligned;
res = malloc(alignment + size + sizeof(void*));
aligned = (char*)res + alignment + sizeof(void*);
aligned -= ((VALUE)aligned & (alignment - 1));
((void**)aligned)[-1] = res;
res = (void*)aligned;
#endif
#if defined(_DEBUG) || defined(GC_DEBUG)
/* alignment must be a power of 2 */
assert((alignment - 1) & alignment == 0);
assert(alignment % sizeof(void*) == 0);
#endif
return res;
}
static void
aligned_free(void *ptr)
{
#if defined __MINGW32__
__mingw_aligned_free(ptr);
#elif defined _WIN32 && !defined __CYGWIN__
_aligned_free(ptr);
#elif defined(HAVE_MEMALIGN) || defined(HAVE_POSIX_MEMALIGN)
free(ptr);
#else
free(((void**)ptr)[-1]);
#endif
}
static inline size_t
vm_malloc_prepare(rb_objspace_t *objspace, size_t size)
{
if ((ssize_t)size < 0) {
negative_size_allocation_error("negative allocation size (or too big)");
}
if (size == 0) size = 1;
#if CALC_EXACT_MALLOC_SIZE
size += sizeof(size_t);
#endif
if ((ruby_gc_stress && !ruby_disable_gc_stress) ||
(malloc_increase+size) > malloc_limit) {
garbage_collect_with_gvl(objspace);
}
return size;
}
static inline void *
vm_malloc_fixup(rb_objspace_t *objspace, void *mem, size_t size)
{
ATOMIC_SIZE_ADD(malloc_increase, size);
#if CALC_EXACT_MALLOC_SIZE
ATOMIC_SIZE_ADD(objspace->malloc_params.allocated_size, size);
ATOMIC_SIZE_INC(objspace->malloc_params.allocations);
((size_t *)mem)[0] = size;
mem = (size_t *)mem + 1;
#endif
return mem;
}
#define TRY_WITH_GC(alloc) do { \
if (!(alloc) && \
(!garbage_collect_with_gvl(objspace) || \
!(alloc))) { \
ruby_memerror(); \
} \
} while (0)
static void *
vm_xmalloc(rb_objspace_t *objspace, size_t size)
{
void *mem;
size = vm_malloc_prepare(objspace, size);
TRY_WITH_GC(mem = malloc(size));
return vm_malloc_fixup(objspace, mem, size);
}
static void *
vm_xrealloc(rb_objspace_t *objspace, void *ptr, size_t size)
{
void *mem;
#if CALC_EXACT_MALLOC_SIZE
size_t oldsize;
#endif
if ((ssize_t)size < 0) {
negative_size_allocation_error("negative re-allocation size");
}
if (!ptr) return vm_xmalloc(objspace, size);
if (size == 0) {
vm_xfree(objspace, ptr);
return 0;
}
if (ruby_gc_stress && !ruby_disable_gc_stress)
garbage_collect_with_gvl(objspace);
#if CALC_EXACT_MALLOC_SIZE
size += sizeof(size_t);
ptr = (size_t *)ptr - 1;
oldsize = ((size_t *)ptr)[0];
#endif
mem = realloc(ptr, size);
if (!mem) {
if (garbage_collect_with_gvl(objspace)) {
mem = realloc(ptr, size);
}
if (!mem) {
ruby_memerror();
}
}
ATOMIC_SIZE_ADD(malloc_increase, size);
#if CALC_EXACT_MALLOC_SIZE
ATOMIC_SIZE_ADD(objspace->malloc_params.allocated_size, size - oldsize);
((size_t *)mem)[0] = size;
mem = (size_t *)mem + 1;
#endif
return mem;
}
static void
vm_xfree(rb_objspace_t *objspace, void *ptr)
{
#if CALC_EXACT_MALLOC_SIZE
size_t size;
ptr = ((size_t *)ptr) - 1;
size = ((size_t*)ptr)[0];
if (size) {
ATOMIC_SIZE_SUB(objspace->malloc_params.allocated_size, size);
ATOMIC_SIZE_DEC(objspace->malloc_params.allocations);
}
#endif
free(ptr);
}
void *
ruby_xmalloc(size_t size)
{
return vm_xmalloc(&rb_objspace, size);
}
static inline size_t
xmalloc2_size(size_t n, size_t size)
{
size_t len = size * n;
if (n != 0 && size != len / n) {
rb_raise(rb_eArgError, "malloc: possible integer overflow");
}
return len;
}
void *
ruby_xmalloc2(size_t n, size_t size)
{
return vm_xmalloc(&rb_objspace, xmalloc2_size(n, size));
}
static void *
vm_xcalloc(rb_objspace_t *objspace, size_t count, size_t elsize)
{
void *mem;
size_t size;
size = xmalloc2_size(count, elsize);
size = vm_malloc_prepare(objspace, size);
TRY_WITH_GC(mem = calloc(1, size));
return vm_malloc_fixup(objspace, mem, size);
}
void *
ruby_xcalloc(size_t n, size_t size)
{
return vm_xcalloc(&rb_objspace, n, size);
}
void *
ruby_xrealloc(void *ptr, size_t size)
{
return vm_xrealloc(&rb_objspace, ptr, size);
}
void *
ruby_xrealloc2(void *ptr, size_t n, size_t size)
{
size_t len = size * n;
if (n != 0 && size != len / n) {
rb_raise(rb_eArgError, "realloc: possible integer overflow");
}
return ruby_xrealloc(ptr, len);
}
void
ruby_xfree(void *x)
{
if (x)
vm_xfree(&rb_objspace, x);
}
/* 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(size_t);
#endif
mem = malloc(size);
#if CALC_EXACT_MALLOC_SIZE
/* set 0 for consistency of allocated_size/allocations */
((size_t *)mem)[0] = 0;
mem = (size_t *)mem + 1;
#endif
return mem;
}
#if CALC_EXACT_MALLOC_SIZE
/*
* call-seq:
* GC.malloc_allocated_size -> Integer
*
* Returns the size of memory allocated by malloc(). Only available if ruby
* was built with CALC_EXACT_MALLOC_SIZE.
*/
static VALUE
gc_malloc_allocated_size(VALUE self)
{
return UINT2NUM(rb_objspace.malloc_params.allocated_size);
}
/*
* call-seq:
* GC.malloc_allocations -> Integer
*
* Returns the number of malloc() allocations. Only available if ruby was
* built with CALC_EXACT_MALLOC_SIZE.
*/
static VALUE
gc_malloc_allocations(VALUE self)
{
return UINT2NUM(rb_objspace.malloc_params.allocations);
}
#endif
/*
------------------------------ WeakMap ------------------------------
*/
struct weakmap {
st_table *obj2wmap; /* obj -> [ref,...] */
st_table *wmap2obj; /* ref -> obj */
VALUE final;
};
static int
wmap_mark_map(st_data_t key, st_data_t val, st_data_t arg)
{
gc_mark_ptr((rb_objspace_t *)arg, (VALUE)val);
return ST_CONTINUE;
}
static void
wmap_mark(void *ptr)
{
struct weakmap *w = ptr;
st_foreach(w->obj2wmap, wmap_mark_map, (st_data_t)&rb_objspace);
rb_gc_mark(w->final);
}
static int
wmap_free_map(st_data_t key, st_data_t val, st_data_t arg)
{
rb_ary_resize((VALUE)val, 0);
return ST_CONTINUE;
}
static void
wmap_free(void *ptr)
{
struct weakmap *w = ptr;
st_foreach(w->obj2wmap, wmap_free_map, 0);
st_free_table(w->obj2wmap);
st_free_table(w->wmap2obj);
}
size_t rb_ary_memsize(VALUE ary);
static int
wmap_memsize_map(st_data_t key, st_data_t val, st_data_t arg)
{
*(size_t *)arg += rb_ary_memsize((VALUE)val);
return ST_CONTINUE;
}
static size_t
wmap_memsize(const void *ptr)
{
size_t size;
const struct weakmap *w = ptr;
if (!w) return 0;
size = sizeof(*w);
size += st_memsize(w->obj2wmap);
size += st_memsize(w->wmap2obj);
st_foreach(w->obj2wmap, wmap_memsize_map, (st_data_t)&size);
return size;
}
static const rb_data_type_t weakmap_type = {
"weakmap",
{
wmap_mark,
wmap_free,
wmap_memsize,
}
};
static VALUE
wmap_allocate(VALUE klass)
{
struct weakmap *w;
VALUE obj = TypedData_Make_Struct(klass, struct weakmap, &weakmap_type, w);
w->obj2wmap = st_init_numtable();
w->wmap2obj = st_init_numtable();
w->final = rb_obj_method(obj, ID2SYM(rb_intern("finalize")));
return obj;
}
static int
wmap_final_func(st_data_t *key, st_data_t *value, st_data_t arg, int existing)
{
VALUE wmap, ary;
if (!existing) return ST_STOP;
wmap = (VALUE)arg, ary = (VALUE)*value;
rb_ary_delete(ary, wmap);
if (!RARRAY_LEN(ary)) return ST_DELETE;
return ST_CONTINUE;
}
static VALUE
wmap_finalize(VALUE self, VALUE objid)
{
st_data_t orig, wmap, data;
VALUE obj, rids;
long i;
struct weakmap *w;
TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
/* Get reference from object id. */
obj = objid ^ FIXNUM_FLAG; /* unset FIXNUM_FLAG */
/* obj is original referenced object and/or weak reference. */
orig = (st_data_t)obj;
if (st_delete(w->obj2wmap, &orig, &data)) {
rids = (VALUE)data;
for (i = 0; i < RARRAY_LEN(rids); ++i) {
wmap = (st_data_t)RARRAY_PTR(rids)[i];
st_delete(w->wmap2obj, &wmap, NULL);
}
}
wmap = (st_data_t)obj;
if (st_delete(w->wmap2obj, &wmap, &orig)) {
wmap = (st_data_t)obj;
st_update(w->obj2wmap, orig, wmap_final_func, wmap);
}
return self;
}
static VALUE
wmap_aset(VALUE self, VALUE wmap, VALUE orig)
{
st_data_t data;
VALUE rids;
struct weakmap *w;
TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
rb_define_final(orig, w->final);
rb_define_final(wmap, w->final);
if (st_lookup(w->obj2wmap, (st_data_t)orig, &data)) {
rids = (VALUE)data;
}
else {
rids = rb_ary_tmp_new(1);
st_insert(w->obj2wmap, (st_data_t)orig, (st_data_t)rids);
}
rb_ary_push(rids, wmap);
st_insert(w->wmap2obj, (st_data_t)wmap, (st_data_t)orig);
return nonspecial_obj_id(orig);
}
static VALUE
wmap_aref(VALUE self, VALUE wmap)
{
st_data_t data;
VALUE obj;
struct weakmap *w;
rb_objspace_t *objspace = &rb_objspace;
TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
if (!st_lookup(w->wmap2obj, (st_data_t)wmap, &data)) return Qnil;
obj = (VALUE)data;
if (!is_id_value(objspace, obj)) return Qnil;
if (!is_live_object(objspace, obj)) return Qnil;
return obj;
}
/*
------------------------------ GC profiler ------------------------------
*/
static inline void gc_prof_set_heap_info(rb_objspace_t *, gc_profile_record *);
#define GC_PROFILE_RECORD_DEFAULT_SIZE 100
static double
getrusage_time(void)
{
#ifdef RUSAGE_SELF
struct rusage usage;
struct timeval time;
getrusage(RUSAGE_SELF, &usage);
time = usage.ru_utime;
return time.tv_sec + time.tv_usec * 1e-6;
#elif defined _WIN32
FILETIME creation_time, exit_time, kernel_time, user_time;
ULARGE_INTEGER ui;
LONG_LONG q;
double t;
if (GetProcessTimes(GetCurrentProcess(),
&creation_time, &exit_time, &kernel_time, &user_time) == 0)
{
return 0.0;
}
memcpy(&ui, &user_time, sizeof(FILETIME));
q = ui.QuadPart / 10L;
t = (DWORD)(q % 1000000L) * 1e-6;
q /= 1000000L;
#ifdef __GNUC__
t += q;
#else
t += (double)(DWORD)(q >> 16) * (1 << 16);
t += (DWORD)q & ~(~0 << 16);
#endif
return t;
#else
return 0.0;
#endif
}
static inline void
gc_prof_timer_start(rb_objspace_t *objspace)
{
if (objspace->profile.run) {
size_t count = objspace->profile.count;
if (!objspace->profile.record) {
objspace->profile.size = GC_PROFILE_RECORD_DEFAULT_SIZE;
objspace->profile.record = malloc(sizeof(gc_profile_record) * objspace->profile.size);
}
if (count >= objspace->profile.size) {
objspace->profile.size += 1000;
objspace->profile.record = realloc(objspace->profile.record, sizeof(gc_profile_record) * objspace->profile.size);
}
if (!objspace->profile.record) {
rb_bug("gc_profile malloc or realloc miss");
}
MEMZERO(&objspace->profile.record[count], gc_profile_record, 1);
objspace->profile.record[count].gc_time = getrusage_time();
objspace->profile.record[objspace->profile.count].gc_invoke_time =
objspace->profile.record[count].gc_time - objspace->profile.invoke_time;
}
}
static inline void
gc_prof_timer_stop(rb_objspace_t *objspace, int marked)
{
if (objspace->profile.run) {
double gc_time = 0;
size_t count = objspace->profile.count;
gc_profile_record *record = &objspace->profile.record[count];
gc_time = getrusage_time() - record->gc_time;
if (gc_time < 0) gc_time = 0;
record->gc_time = gc_time;
record->is_marked = !!(marked);
gc_prof_set_heap_info(objspace, record);
objspace->profile.count++;
}
}
#if !GC_PROFILE_MORE_DETAIL
static inline void
gc_prof_mark_timer_start(rb_objspace_t *objspace)
{
if (RUBY_DTRACE_GC_MARK_BEGIN_ENABLED()) {
RUBY_DTRACE_GC_MARK_BEGIN();
}
}
static inline void
gc_prof_mark_timer_stop(rb_objspace_t *objspace)
{
if (RUBY_DTRACE_GC_MARK_END_ENABLED()) {
RUBY_DTRACE_GC_MARK_END();
}
}
static inline void
gc_prof_sweep_timer_start(rb_objspace_t *objspace)
{
if (RUBY_DTRACE_GC_SWEEP_BEGIN_ENABLED()) {
RUBY_DTRACE_GC_SWEEP_BEGIN();
}
}
static inline void
gc_prof_sweep_timer_stop(rb_objspace_t *objspace)
{
if (RUBY_DTRACE_GC_SWEEP_END_ENABLED()) {
RUBY_DTRACE_GC_SWEEP_END();
}
}
static inline void
gc_prof_set_malloc_info(rb_objspace_t *objspace)
{
}
static inline void
gc_prof_set_heap_info(rb_objspace_t *objspace, gc_profile_record *record)
{
size_t live = objspace->heap.live_num;
size_t total = heaps_used * HEAP_OBJ_LIMIT;
record->heap_total_objects = total;
record->heap_use_size = live * sizeof(RVALUE);
record->heap_total_size = total * sizeof(RVALUE);
}
static inline void
gc_prof_inc_live_num(rb_objspace_t *objspace)
{
}
static inline void
gc_prof_dec_live_num(rb_objspace_t *objspace)
{
}
#else
static inline void
gc_prof_mark_timer_start(rb_objspace_t *objspace)
{
if (RUBY_DTRACE_GC_MARK_BEGIN_ENABLED()) {
RUBY_DTRACE_GC_MARK_BEGIN();
}
if (objspace->profile.run) {
size_t count = objspace->profile.count;
objspace->profile.record[count].gc_mark_time = getrusage_time();
}
}
static inline void
gc_prof_mark_timer_stop(rb_objspace_t *objspace)
{
if (RUBY_DTRACE_GC_MARK_END_ENABLED()) {
RUBY_DTRACE_GC_MARK_END();
}
if (objspace->profile.run) {
double mark_time = 0;
size_t count = objspace->profile.count;
gc_profile_record *record = &objspace->profile.record[count];
mark_time = getrusage_time() - record->gc_mark_time;
if (mark_time < 0) mark_time = 0;
record->gc_mark_time = mark_time;
}
}
static inline void
gc_prof_sweep_timer_start(rb_objspace_t *objspace)
{
if (RUBY_DTRACE_GC_SWEEP_BEGIN_ENABLED()) {
RUBY_DTRACE_GC_SWEEP_BEGIN();
}
if (objspace->profile.run) {
size_t count = objspace->profile.count;
objspace->profile.record[count].gc_sweep_time = getrusage_time();
}
}
static inline void
gc_prof_sweep_timer_stop(rb_objspace_t *objspace)
{
if (RUBY_DTRACE_GC_SWEEP_END_ENABLED()) {
RUBY_DTRACE_GC_SWEEP_END();
}
if (objspace->profile.run) {
double sweep_time = 0;
size_t count = objspace->profile.count;
gc_profile_record *record = &objspace->profile.record[count];
sweep_time = getrusage_time() - record->gc_sweep_time;\
if (sweep_time < 0) sweep_time = 0;\
record->gc_sweep_time = sweep_time;
}
}
static inline void
gc_prof_set_malloc_info(rb_objspace_t *objspace)
{
if (objspace->profile.run) {
gc_profile_record *record = &objspace->profile.record[objspace->profile.count];
if (record) {
record->allocate_increase = malloc_increase;
record->allocate_limit = malloc_limit;
}
}
}
static inline void
gc_prof_set_heap_info(rb_objspace_t *objspace, gc_profile_record *record)
{
size_t live = objspace->heap.live_num;
size_t total = heaps_used * HEAP_OBJ_LIMIT;
record->heap_use_slots = heaps_used;
record->heap_live_objects = live;
record->heap_free_objects = total - live;
record->heap_total_objects = total;
record->have_finalize = deferred_final_list ? Qtrue : Qfalse;
record->heap_use_size = live * sizeof(RVALUE);
record->heap_total_size = total * sizeof(RVALUE);
}
static inline void
gc_prof_inc_live_num(rb_objspace_t *objspace)
{
objspace->heap.live_num++;
}
static inline void
gc_prof_dec_live_num(rb_objspace_t *objspace)
{
objspace->heap.live_num--;
}
#endif /* !GC_PROFILE_MORE_DETAIL */
/*
* call-seq:
* GC::Profiler.clear -> nil
*
* Clears the GC profiler data.
*
*/
static VALUE
gc_profile_clear(void)
{
rb_objspace_t *objspace = &rb_objspace;
if (GC_PROFILE_RECORD_DEFAULT_SIZE * 2 < objspace->profile.size) {
objspace->profile.size = GC_PROFILE_RECORD_DEFAULT_SIZE * 2;
objspace->profile.record = realloc(objspace->profile.record, sizeof(gc_profile_record) * objspace->profile.size);
if (!objspace->profile.record) {
rb_memerror();
}
}
MEMZERO(objspace->profile.record, gc_profile_record, objspace->profile.size);
objspace->profile.count = 0;
return Qnil;
}
/*
* call-seq:
* GC::Profiler.raw_data -> [Hash, ...]
*
* Returns an Array of individual raw profile data Hashes ordered
* from earliest to latest by <tt>:GC_INVOKE_TIME</tt>. For example:
*
* [{:GC_TIME=>1.3000000000000858e-05,
* :GC_INVOKE_TIME=>0.010634999999999999,
* :HEAP_USE_SIZE=>289640,
* :HEAP_TOTAL_SIZE=>588960,
* :HEAP_TOTAL_OBJECTS=>14724,
* :GC_IS_MARKED=>false},
* ...
* ]
*
* The keys mean:
*
* +:GC_TIME+:: Time taken for this run in seconds
* +:GC_INVOKE_TIME+:: Time the GC was invoked since startup in seconds
* +:HEAP_USE_SIZE+:: Bytes of heap used
* +:HEAP_TOTAL_SIZE+:: Size of heap in bytes
* +:HEAP_TOTAL_OBJECTS+:: Number of objects
* +:GC_IS_MARKED+:: Is the GC in the mark phase
*
*/
static VALUE
gc_profile_record_get(void)
{
VALUE prof;
VALUE gc_profile = rb_ary_new();
size_t i;
rb_objspace_t *objspace = (&rb_objspace);
if (!objspace->profile.run) {
return Qnil;
}
for (i =0; i < objspace->profile.count; i++) {
prof = rb_hash_new();
rb_hash_aset(prof, ID2SYM(rb_intern("GC_TIME")), DBL2NUM(objspace->profile.record[i].gc_time));
rb_hash_aset(prof, ID2SYM(rb_intern("GC_INVOKE_TIME")), DBL2NUM(objspace->profile.record[i].gc_invoke_time));
rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_USE_SIZE")), SIZET2NUM(objspace->profile.record[i].heap_use_size));
rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_TOTAL_SIZE")), SIZET2NUM(objspace->profile.record[i].heap_total_size));
rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_TOTAL_OBJECTS")), SIZET2NUM(objspace->profile.record[i].heap_total_objects));
rb_hash_aset(prof, ID2SYM(rb_intern("GC_IS_MARKED")), objspace->profile.record[i].is_marked);
#if GC_PROFILE_MORE_DETAIL
rb_hash_aset(prof, ID2SYM(rb_intern("GC_MARK_TIME")), DBL2NUM(objspace->profile.record[i].gc_mark_time));
rb_hash_aset(prof, ID2SYM(rb_intern("GC_SWEEP_TIME")), DBL2NUM(objspace->profile.record[i].gc_sweep_time));
rb_hash_aset(prof, ID2SYM(rb_intern("ALLOCATE_INCREASE")), SIZET2NUM(objspace->profile.record[i].allocate_increase));
rb_hash_aset(prof, ID2SYM(rb_intern("ALLOCATE_LIMIT")), SIZET2NUM(objspace->profile.record[i].allocate_limit));
rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_USE_SLOTS")), SIZET2NUM(objspace->profile.record[i].heap_use_slots));
rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_LIVE_OBJECTS")), SIZET2NUM(objspace->profile.record[i].heap_live_objects));
rb_hash_aset(prof, ID2SYM(rb_intern("HEAP_FREE_OBJECTS")), SIZET2NUM(objspace->profile.record[i].heap_free_objects));
rb_hash_aset(prof, ID2SYM(rb_intern("HAVE_FINALIZE")), objspace->profile.record[i].have_finalize);
#endif
rb_ary_push(gc_profile, prof);
}
return gc_profile;
}
static void
gc_profile_dump_on(VALUE out, VALUE (*append)(VALUE, VALUE))
{
rb_objspace_t *objspace = &rb_objspace;
size_t count = objspace->profile.count;
if (objspace->profile.run && count) {
int index = 1;
size_t i;
gc_profile_record r;
append(out, rb_sprintf("GC %"PRIuSIZE" invokes.\n", objspace->count));
append(out, rb_str_new_cstr("Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC Time(ms)\n"));
for (i = 0; i < count; i++) {
r = objspace->profile.record[i];
#if !GC_PROFILE_MORE_DETAIL
if (r.is_marked) {
#endif
append(out, rb_sprintf("%5d %19.3f %20"PRIuSIZE" %20"PRIuSIZE" %20"PRIuSIZE" %30.20f\n",
index++, r.gc_invoke_time, r.heap_use_size,
r.heap_total_size, r.heap_total_objects, r.gc_time*1000));
#if !GC_PROFILE_MORE_DETAIL
}
#endif
}
#if GC_PROFILE_MORE_DETAIL
append(out, rb_str_new_cstr("\n\n" \
"More detail.\n" \
"Index Allocate Increase Allocate Limit Use Slot Have Finalize Mark Time(ms) Sweep Time(ms)\n"));
index = 1;
for (i = 0; i < count; i++) {
r = objspace->profile.record[i];
append(out, rb_sprintf("%5d %17"PRIuSIZE" %17"PRIuSIZE" %9"PRIuSIZE" %14s %25.20f %25.20f\n",
index++, r.allocate_increase, r.allocate_limit,
r.heap_use_slots, (r.have_finalize ? "true" : "false"),
r.gc_mark_time*1000, r.gc_sweep_time*1000));
}
#endif
}
}
/*
* call-seq:
* GC::Profiler.result -> String
*
* Returns a profile data report such as:
*
* GC 1 invokes.
* Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC time(ms)
* 1 0.012 159240 212940 10647 0.00000000000001530000
*/
static VALUE
gc_profile_result(void)
{
VALUE str = rb_str_buf_new(0);
gc_profile_dump_on(str, rb_str_buf_append);
return str;
}
/*
* call-seq:
* GC::Profiler.report
* GC::Profiler.report io
*
* Writes the GC::Profiler#result to <tt>$stdout</tt> or the given IO object.
*
*/
static VALUE
gc_profile_report(int argc, VALUE *argv, VALUE self)
{
VALUE out;
if (argc == 0) {
out = rb_stdout;
}
else {
rb_scan_args(argc, argv, "01", &out);
}
gc_profile_dump_on(out, rb_io_write);
return Qnil;
}
/*
* call-seq:
* GC::Profiler.total_time -> float
*
* The total time used for garbage collection in seconds
*/
static VALUE
gc_profile_total_time(VALUE self)
{
double time = 0;
rb_objspace_t *objspace = &rb_objspace;
size_t i;
if (objspace->profile.run && objspace->profile.count) {
for (i = 0; i < objspace->profile.count; i++) {
time += objspace->profile.record[i].gc_time;
}
}
return DBL2NUM(time);
}
/*
* call-seq:
* GC::Profiler.enabled? -> true or false
*
* The current status of GC profile mode.
*/
static VALUE
gc_profile_enable_get(VALUE self)
{
rb_objspace_t *objspace = &rb_objspace;
return objspace->profile.run ? Qtrue : Qfalse;
}
/*
* call-seq:
* GC::Profiler.enable -> nil
*
* Starts the GC profiler.
*
*/
static VALUE
gc_profile_enable(void)
{
rb_objspace_t *objspace = &rb_objspace;
objspace->profile.run = TRUE;
return Qnil;
}
/*
* call-seq:
* GC::Profiler.disable -> nil
*
* Stops the GC profiler.
*
*/
static VALUE
gc_profile_disable(void)
{
rb_objspace_t *objspace = &rb_objspace;
objspace->profile.run = FALSE;
return Qnil;
}
#ifdef GC_DEBUG
/*
------------------------------ DEBUG ------------------------------
*/
void
rb_gcdebug_print_obj_condition(VALUE obj)
{
rb_objspace_t *objspace = &rb_objspace;
if (is_pointer_to_heap(objspace, (void *)obj)) {
fprintf(stderr, "pointer to heap?: true\n");
}
else {
fprintf(stderr, "pointer to heap?: false\n");
return;
}
fprintf(stderr, "marked?: %s\n",
MARKED_IN_BITMAP(GET_HEAP_BITMAP(obj), obj) ? "true" : "false");
if (is_lazy_sweeping(objspace)) {
fprintf(stderr, "lazy sweeping?: true\n");
fprintf(stderr, "swept?: %s\n",
is_swept_object(objspace, obj) ? "done" : "not yet");
}
else {
fprintf(stderr, "lazy sweeping?: false\n");
}
}
static VALUE
gcdebug_sential(VALUE obj, VALUE name)
{
fprintf(stderr, "WARNING: object %s(%p) is inadvertently collected\n", (char *)name, (void *)obj);
return Qnil;
}
void
rb_gcdebug_sentinel(VALUE obj, const char *name)
{
rb_define_final(obj, rb_proc_new(gcdebug_sential, (VALUE)name));
}
#endif /* GC_DEBUG */
/*
* Document-class: ObjectSpace
*
* The <code>ObjectSpace</code> module contains a number of routines
* that interact with the garbage collection facility and allow you to
* traverse all living objects with an iterator.
*
* <code>ObjectSpace</code> also provides support for object
* finalizers, procs that will be called when a specific object is
* about to be destroyed by garbage collection.
*
* include ObjectSpace
*
*
* a = "A"
* b = "B"
* c = "C"
*
*
* define_finalizer(a, proc {|id| puts "Finalizer one on #{id}" })
* define_finalizer(a, proc {|id| puts "Finalizer two on #{id}" })
* define_finalizer(b, proc {|id| puts "Finalizer three on #{id}" })
*
* <em>produces:</em>
*
* Finalizer three on 537763470
* Finalizer one on 537763480
* Finalizer two on 537763480
*
*/
/*
* Document-class: ObjectSpace::WeakMap
*
* An <code>ObjectSpace::WeakMap</code> object holds references to
* any objects, but those objects can get disposed by GC.
*/
/* 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'
*
* puts GC::Profiler.result
*
* GC::Profiler.disable
*
* See also GC.count, GC.malloc_allocated_size and GC.malloc_allocations
*/
/*
* The <code>GC</code> module provides an interface to Ruby's mark and
* sweep garbage collection mechanism. Some of the underlying methods
* are also available via the ObjectSpace module.
*
* You may obtain information about the operation of the GC through
* GC::Profiler.
*/
void
Init_GC(void)
{
VALUE rb_mObSpace;
VALUE rb_mProfiler;
rb_mGC = rb_define_module("GC");
rb_define_singleton_method(rb_mGC, "start", rb_gc_start, 0);
rb_define_singleton_method(rb_mGC, "enable", rb_gc_enable, 0);
rb_define_singleton_method(rb_mGC, "disable", rb_gc_disable, 0);
rb_define_singleton_method(rb_mGC, "stress", gc_stress_get, 0);
rb_define_singleton_method(rb_mGC, "stress=", gc_stress_set, 1);
rb_define_singleton_method(rb_mGC, "count", gc_count, 0);
rb_define_singleton_method(rb_mGC, "stat", gc_stat, -1);
rb_define_method(rb_mGC, "garbage_collect", rb_gc_start, 0);
rb_mProfiler = rb_define_module_under(rb_mGC, "Profiler");
rb_define_singleton_method(rb_mProfiler, "enabled?", gc_profile_enable_get, 0);
rb_define_singleton_method(rb_mProfiler, "enable", gc_profile_enable, 0);
rb_define_singleton_method(rb_mProfiler, "raw_data", gc_profile_record_get, 0);
rb_define_singleton_method(rb_mProfiler, "disable", gc_profile_disable, 0);
rb_define_singleton_method(rb_mProfiler, "clear", gc_profile_clear, 0);
rb_define_singleton_method(rb_mProfiler, "result", gc_profile_result, 0);
rb_define_singleton_method(rb_mProfiler, "report", gc_profile_report, -1);
rb_define_singleton_method(rb_mProfiler, "total_time", gc_profile_total_time, 0);
rb_mObSpace = rb_define_module("ObjectSpace");
rb_define_module_function(rb_mObSpace, "each_object", os_each_obj, -1);
rb_define_module_function(rb_mObSpace, "garbage_collect", rb_gc_start, 0);
rb_define_module_function(rb_mObSpace, "define_finalizer", define_final, -1);
rb_define_module_function(rb_mObSpace, "undefine_finalizer", undefine_final, 1);
rb_define_module_function(rb_mObSpace, "_id2ref", id2ref, 1);
nomem_error = rb_exc_new3(rb_eNoMemError,
rb_obj_freeze(rb_str_new2("failed to allocate memory")));
OBJ_TAINT(nomem_error);
OBJ_FREEZE(nomem_error);
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_mObSpace, "count_objects", count_objects, -1);
{
VALUE rb_cWeakMap = rb_define_class_under(rb_mObSpace, "WeakMap", rb_cObject);
rb_define_alloc_func(rb_cWeakMap, wmap_allocate);
rb_define_method(rb_cWeakMap, "[]=", wmap_aset, 2);
rb_define_method(rb_cWeakMap, "[]", wmap_aref, 1);
rb_define_private_method(rb_cWeakMap, "finalize", wmap_finalize, 1);
}
#if CALC_EXACT_MALLOC_SIZE
rb_define_singleton_method(rb_mGC, "malloc_allocated_size", gc_malloc_allocated_size, 0);
rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0);
#endif
}