Skip to content

Commit da5e57a

Browse files
committed
Support heap traversal
We use the object enumeration feature in mmtk-core to implement heap traversal. This enables `ObjectSpace.each_object` as well as the `TracePoint` utility which needs heap traversal to enumerate objects and set hooks. Test cases involving each_object and TracePoint are no longer excluded. The TestTracepointObj test case remains excluded because we have not implemented querying GC statistics from TracePoint.
1 parent 3c7e6af commit da5e57a

9 files changed

+111
-12
lines changed

Diff for: gc/default.c

+96
Original file line numberDiff line numberDiff line change
@@ -3207,9 +3207,105 @@ objspace_each_objects(rb_objspace_t *objspace, each_obj_callback *callback, void
32073207
objspace_each_exec(protected, &each_obj_data);
32083208
}
32093209

3210+
#if USE_MMTK
3211+
struct rb_mmtk_build_obj_array_data {
3212+
VALUE **array_ptr;
3213+
size_t len;
3214+
size_t capa;
3215+
};
3216+
3217+
void
3218+
rb_mmtk_build_obj_array_i(MMTk_ObjectReference object, void *data)
3219+
{
3220+
struct rb_mmtk_build_obj_array_data *build_array_data = (struct rb_mmtk_build_obj_array_data*)data;
3221+
VALUE *array = *build_array_data->array_ptr;
3222+
size_t len = build_array_data->len;
3223+
size_t capa = build_array_data->capa;
3224+
if (len == capa) {
3225+
size_t new_capa = capa * 2;
3226+
VALUE *new_array = (VALUE*)realloc(array, sizeof(VALUE) * new_capa);
3227+
*build_array_data->array_ptr = new_array;
3228+
build_array_data->capa = new_capa;
3229+
array = new_array;
3230+
}
3231+
3232+
RUBY_ASSERT(build_array_data->len < build_array_data->capa);
3233+
3234+
array[len] = (VALUE)object;
3235+
build_array_data->len = len + 1;
3236+
}
3237+
3238+
void
3239+
rb_mmtk_each_objects_safe(each_obj_callback *callback, void *data)
3240+
{
3241+
// Allocate a tmpbuf object. It's OK if it triggers GC now.
3242+
volatile VALUE tmpbuf = rb_imemo_tmpbuf_auto_free_pointer();
3243+
3244+
// Build an array of object references.
3245+
const size_t initial_capacity = 512;
3246+
// We must not trigger GC while running `mmtk_enumerate_objects`,
3247+
// so we use `malloc` directly.
3248+
// It will be realloced as we add more objects.
3249+
VALUE *array = (VALUE*)malloc(sizeof(VALUE) * initial_capacity);
3250+
struct rb_mmtk_build_obj_array_data build_array_data = {
3251+
.array_ptr = &array,
3252+
.len = 0,
3253+
.capa = initial_capacity,
3254+
};
3255+
3256+
// No GC from now on.
3257+
mmtk_enumerate_objects(rb_mmtk_build_obj_array_i, &build_array_data);
3258+
3259+
// Root the array.
3260+
rb_imemo_tmpbuf_set_ptr(tmpbuf, array);
3261+
((rb_imemo_tmpbuf_t*)tmpbuf)->cnt = build_array_data.len;
3262+
// GC is OK from now on.
3263+
3264+
// Inform the VM about malloc memory usage.
3265+
// Since elements of `array` are rooted by `tmpbuf`, it is safe to trigger GC.
3266+
// The GC won't free any object because we have just rooted every object.
3267+
// But the GC may adjust the threshold for triggering the next GC.
3268+
rb_gc_adjust_memory_usage(sizeof(VALUE) * build_array_data.capa);
3269+
3270+
RUBY_DEBUG_LOG("Begin enumerating %zu objects\n", build_array_data.len);
3271+
3272+
// Now enumerate objects.
3273+
// If GC is triggered in `callback`, `tmpbuf` will keep elements of `array` alive.
3274+
for (size_t i = 0; i < build_array_data.len; i++) {
3275+
volatile VALUE object = array[i];
3276+
size_t object_size = rb_mmtk_get_object_size(object);
3277+
uintptr_t object_end = object + object_size;
3278+
3279+
RUBY_DEBUG_LOG("Enumerating object: %p\n", (void*)object);
3280+
callback((void*)object, (void*)object_end, object_size, data);
3281+
RB_GC_GUARD(object);
3282+
3283+
// Clear the element so that it no longer pins the object if it dies.
3284+
array[i] = 0;
3285+
}
3286+
3287+
RUBY_DEBUG_LOG("End enumerating %zu objects\n", build_array_data.len);
3288+
3289+
// Explicitly free `array` because we know it is no longer used.
3290+
// Don't wait for GC to free it because `free()` is a bottleneck during GC.
3291+
// Adjust memory usage accordingly.
3292+
rb_imemo_tmpbuf_set_ptr(tmpbuf, NULL);
3293+
((rb_imemo_tmpbuf_t*)tmpbuf)->cnt = 0;
3294+
free(array);
3295+
rb_gc_adjust_memory_usage(-(ssize_t)(sizeof(VALUE) * build_array_data.capa));
3296+
3297+
RB_GC_GUARD(tmpbuf);
3298+
}
3299+
#endif
3300+
32103301
void
32113302
rb_gc_impl_each_objects(void *objspace_ptr, each_obj_callback *callback, void *data)
32123303
{
3304+
WHEN_USING_MMTK({
3305+
rb_mmtk_each_objects_safe(callback, data);
3306+
return;
3307+
})
3308+
32133309
objspace_each_objects(objspace_ptr, callback, data, TRUE);
32143310
}
32153311

Diff for: internal/mmtk.h

+6
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,10 @@ bool mmtk_is_object_wb_unprotected(MMTk_ObjectReference object);
254254

255255
void mmtk_object_reference_write_post(MMTk_Mutator *mutator, MMTk_ObjectReference object);
256256

257+
/**
258+
* Enumerate objects. This function will call `callback(object, data)` for each object. It has
259+
* undefined behavior if allocation or GC happens while this function is running.
260+
*/
261+
void mmtk_enumerate_objects(void (*callback)(MMTk_ObjectReference, void*), void *data);
262+
257263
#endif /* MMTK_H */

Diff for: internal/mmtk_support.h

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ void rb_mmtk_destroy_mutator(MMTk_VMMutatorThread cur_thread, bool at_fork);
4949
// Object layout
5050
size_t rb_mmtk_prefix_size(void);
5151
size_t rb_mmtk_suffix_size(void);
52+
size_t rb_mmtk_get_object_size(VALUE object);
5253

5354
// Allocation
5455
VALUE rb_mmtk_alloc_obj(size_t mmtk_alloc_size, size_t size_pool_size, size_t prefix_size);

Diff for: mmtk_support.c

+7
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,13 @@ rb_mmtk_suffix_size(void)
395395
return ruby_binding_options.suffix_size;
396396
}
397397

398+
size_t
399+
rb_mmtk_get_object_size(VALUE object)
400+
{
401+
return *(size_t*)(object - sizeof(VALUE));
402+
}
403+
404+
398405
////////////////////////////////////////////////////////////////////////////////
399406
// Allocation
400407
////////////////////////////////////////////////////////////////////////////////

Diff for: test/.excludes-mmtk/TestISeq.rb

-6
This file was deleted.

Diff for: test/.excludes-mmtk/TestObjectSpace.rb

-3
This file was deleted.

Diff for: test/.excludes-mmtk/TestRubyOptimization.rb

-1
This file was deleted.

Diff for: test/.excludes-mmtk/TestSetTraceFunc.rb

-1
This file was deleted.

Diff for: test/.excludes-mmtk/TestTracepointObj.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
exclude(/test_/, "tracepoints are not supported")
1+
exclude(/test_/, "tracepoints does not supported GC stats yet")

0 commit comments

Comments
 (0)