Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions bootstraptest/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2620,3 +2620,51 @@ def foo(a:, b:, c:) = super(a: a, b: b, c: c)

:ok
}

# A thread terminating (here: the pipe writer) while another Ractor runs a
# compaction barrier must not corrupt the machine context of a thread blocked
# in IO, whose locked read buffer lives only on that machine stack.
assert_equal 'ok', %q{
Warning[:experimental] = false
can_compact = begin
GC.compact
true
rescue NotImplementedError
false
end
if can_compact
b = Ractor.new do
10.times do
r, w = IO.pipe
t = Thread.new { w.write("x" * 40); w.close }
r.read
r.close
t.join
end
:ok
end
a = Ractor.new { 20.times { GC.compact }; :ok }
[a, b].each(&:value)
end
:ok
}

# Forking while other Ractors are alive takes a VM barrier in the parent; the
# child inherits that scheduler/barrier state and must reset it. Exercises the
# parent-side fork barrier and the child teardown, then checks the surviving
# Ractors still work.
assert_equal 'ok', %q{
begin
rs = 5.times.map { Ractor.new { Ractor.receive } }
10.times do
pid = fork { GC.start }
_, status = Process.waitpid2(pid)
raise "child failed" unless status.success?
end
rs.each { |r| r.send(nil) }
rs.each(&:value)
:ok
rescue NotImplementedError
:ok # platform without fork
end
}
86 changes: 39 additions & 47 deletions gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,17 @@ rb_gc_shutdown_call_finalizer_p(VALUE obj)
void
rb_gc_obj_changed_slot_size(VALUE obj, size_t slot_size)
{
RBASIC_SET_FULL_SHAPE_ID(obj, rb_obj_shape_transition_slot_size(obj, slot_size));
shape_id_t shape_id = rb_obj_shape_transition_capacity(obj, rb_shape_capacity_for_slot_size(slot_size));

if (RB_TYPE_P(obj, T_OBJECT) && FL_TEST_RAW(obj, ROBJECT_HEAP) && !rb_obj_shape_complex_p(obj)) {
VALUE *embedded_fields = ROBJECT_EMBEDDED_FIELDS(obj);
VALUE *extended_fields = ROBJECT_FIELDS(obj);
MEMCPY(embedded_fields, extended_fields, VALUE, RSHAPE_LEN(RBASIC_SHAPE_ID(obj)));
FL_UNSET_RAW(obj, ROBJECT_HEAP);
shape_id = rb_shape_transition_robject(shape_id);
}

RBASIC_SET_FULL_SHAPE_ID(obj, shape_id);
}

void rb_vm_update_references(void *ptr);
Expand Down Expand Up @@ -1584,19 +1594,6 @@ rb_gc_obj_free(void *objspace, VALUE obj)

switch (builtin_type) {
case T_OBJECT:
if (FL_TEST_RAW(obj, ROBJECT_HEAP)) {
if (rb_obj_shape_complex_p(obj)) {
RB_DEBUG_COUNTER_INC(obj_obj_complex);
st_free_table(ROBJECT_FIELDS_HASH(obj));
}
else {
SIZED_FREE_N(ROBJECT(obj)->as.heap.fields, ROBJECT_FIELDS_CAPACITY(obj));
RB_DEBUG_COUNTER_INC(obj_obj_ptr);
}
}
else {
RB_DEBUG_COUNTER_INC(obj_obj_embed);
}
break;
case T_MODULE:
case T_CLASS:
Expand Down Expand Up @@ -2372,14 +2369,6 @@ rb_obj_memsize_of(VALUE obj)

switch (BUILTIN_TYPE(obj)) {
case T_OBJECT:
if (FL_TEST_RAW(obj, ROBJECT_HEAP)) {
if (rb_obj_shape_complex_p(obj)) {
size += rb_st_memsize(ROBJECT_FIELDS_HASH(obj));
}
else {
size += ROBJECT_FIELDS_CAPACITY(obj) * sizeof(VALUE);
}
}
break;
case T_MODULE:
case T_CLASS:
Expand Down Expand Up @@ -3303,12 +3292,13 @@ rb_gc_mark_children(void *objspace, VALUE obj)

case T_OBJECT: {
uint32_t len;
if (rb_obj_shape_complex_p(obj)) {
gc_mark_tbl_no_pin(ROBJECT_FIELDS_HASH(obj));
len = ROBJECT_FIELDS_COUNT_COMPLEX(obj);
if (FL_TEST_RAW(obj, ROBJECT_HEAP)) {
if (!rb_gc_checking_shareable()) {
gc_mark_internal(ROBJECT(obj)->as.extended);
}
}
else {
const VALUE * const ptr = ROBJECT_FIELDS(obj);
const VALUE * const ptr = ROBJECT(obj)->as.ary;

len = ROBJECT_FIELDS_COUNT_NOT_COMPLEX(obj);
for (uint32_t i = 0; i < len; i++) {
Expand Down Expand Up @@ -3678,27 +3668,14 @@ gc_ref_update_array(void *objspace, VALUE v)
static void
gc_ref_update_object(void *objspace, VALUE v)
{
VALUE *ptr = ROBJECT_FIELDS(v);

if (FL_TEST_RAW(v, ROBJECT_HEAP)) {
if (rb_obj_shape_complex_p(v)) {
gc_ref_update_table_values_only(ROBJECT_FIELDS_HASH(v));
return;
}

size_t slot_size = rb_gc_obj_slot_size(v);
size_t embed_size = rb_obj_embedded_size(ROBJECT_FIELDS_CAPACITY(v));
if (slot_size >= embed_size) {
// Object can be re-embedded
memcpy(ROBJECT(v)->as.ary, ptr, sizeof(VALUE) * ROBJECT_FIELDS_COUNT(v));
SIZED_FREE_N(ptr, ROBJECT_FIELDS_CAPACITY(v));
FL_UNSET_RAW(v, ROBJECT_HEAP);
ptr = ROBJECT(v)->as.ary;
}
UPDATE_IF_MOVED(objspace, ROBJECT(v)->as.extended);
}

for (uint32_t i = 0; i < ROBJECT_FIELDS_COUNT(v); i++) {
UPDATE_IF_MOVED(objspace, ptr[i]);
else {
VALUE *ptr = ROBJECT_FIELDS(v);
for (uint32_t i = 0; i < ROBJECT_FIELDS_COUNT(v); i++) {
UPDATE_IF_MOVED(objspace, ptr[i]);
}
}
}

Expand Down Expand Up @@ -4860,11 +4837,11 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU
APPEND_F("(complex) len:%zu", hash_len);
}
else {
APPEND_F("(embed) len:%d capa:%d", RSHAPE_LEN(RBASIC_SHAPE_ID(obj)), ROBJECT_FIELDS_CAPACITY(obj));
APPEND_F("len:%d capa:%d extended:%p", RSHAPE_LEN(RBASIC_SHAPE_ID(obj)), ROBJECT_FIELDS_CAPACITY(obj), (void *)ROBJECT_FIELDS_OBJ(obj));
}
}
else {
APPEND_F("len:%d capa:%d ptr:%p", RSHAPE_LEN(RBASIC_SHAPE_ID(obj)), ROBJECT_FIELDS_CAPACITY(obj), (void *)ROBJECT_FIELDS(obj));
APPEND_F("(embed) len:%d capa:%d", RSHAPE_LEN(RBASIC_SHAPE_ID(obj)), ROBJECT_FIELDS_CAPACITY(obj));
}
}
break;
Expand All @@ -4889,6 +4866,21 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU
APPEND_F("<%s> ", rb_imemo_name(imemo_type(obj)));

switch (imemo_type(obj)) {
case imemo_fields:
{
if (rb_obj_shape_complex_p(obj)) {
size_t hash_len = rb_st_table_size(rb_imemo_fields_complex_tbl(obj));
APPEND_F("(complex) len:%zu", hash_len);
}
else {
APPEND_F("(embed) len:%d capa:%d", RSHAPE_LEN(RBASIC_SHAPE_ID(obj)), ROBJECT_FIELDS_CAPACITY(obj));
}

APPEND_S("owner -> ");
rb_raw_obj_info(BUFF_ARGS, CLASS_OF(obj));

break;
}
case imemo_ment:
{
const rb_method_entry_t *me = (const rb_method_entry_t *)obj;
Expand Down
9 changes: 0 additions & 9 deletions imemo.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ rb_imemo_fields_new(VALUE owner, shape_id_t shape_id, bool shareable)
size_t embedded_size = offsetof(struct rb_fields, as.embed) + capa * sizeof(VALUE);

VALUE fields = imemo_fields_new(owner, 0, shape_id, embedded_size, shareable);

RUBY_ASSERT(IMEMO_TYPE_P(fields, imemo_fields));
RUBY_ASSERT(rb_shape_embedded_capacity(RBASIC_SHAPE_ID(fields)) >= capa);

Expand Down Expand Up @@ -223,14 +222,6 @@ rb_imemo_fields_clone(VALUE fields_obj)
void
rb_imemo_fields_clear(VALUE fields_obj)
{
// When replacing an imemo/fields by another one, we must clear
// its shape so that gc.c:obj_free_object_id won't be called.
if (rb_obj_shape_complex_p(fields_obj)) {
RBASIC_SET_SHAPE_ID(fields_obj, ROOT_COMPLEX_SHAPE_ID);
}
else {
RBASIC_SET_SHAPE_ID(fields_obj, ROOT_SHAPE_ID);
}
// Invalidate the ec->gen_fields_cache.
RBASIC_CLEAR_CLASS(fields_obj);
}
Expand Down
26 changes: 9 additions & 17 deletions include/ruby/internal/core/robject.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,30 +89,22 @@ struct RObject {

/** Object's specific fields. */
union {

/**
* Object that use separated memory region for instance variables use
* this pattern.
*/
struct {
/** Pointer to a C array that holds instance variables. */
VALUE *fields;
} heap;

/* When an object is too complex, it uses a st_table to store instance
* variable name to value mappings.
*/
st_table *hash;

/* Embedded instance variables. When an object is small enough, it
* uses this area to store the instance variables.
/* Embedded instance variables. When an object slot is large enough, it
* uses this area to store the instance variables embedded.
*
* This is a length 1 array because:
* 1. GCC has a bug that does not optimize C flexible array members
* (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102452)
* 2. Zero length arrays are not supported by all compilers
*/
VALUE ary[1];

/**
* When an object slot is too small or too complex to store instance
* variables inline, it references another, larger, object that contains
* the instance variables.
*/
VALUE extended;
} as;
};

Expand Down
25 changes: 13 additions & 12 deletions internal/imemo.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,7 @@ struct rb_fields {
VALUE fields[1];
} embed;
struct {
VALUE *ptr;
} external;
struct {
// Note: the st_table could be embedded, but complex T_CLASS should be rare to
// non-existent, so not really worth the trouble.
// TODO: the st_table should be embedded
st_table *table;
} complex;
} as;
Expand All @@ -260,8 +256,6 @@ struct rb_fields {
#define OBJ_FIELD_HEAP ROBJECT_HEAP
STATIC_ASSERT(imemo_fields_flags, OBJ_FIELD_HEAP == IMEMO_FL_USER0);
STATIC_ASSERT(imemo_fields_embed_offset, offsetof(struct RObject, as.ary) == offsetof(struct rb_fields, as.embed.fields));
STATIC_ASSERT(imemo_fields_external_offset, offsetof(struct RObject, as.heap.fields) == offsetof(struct rb_fields, as.external.ptr));
STATIC_ASSERT(imemo_fields_complex_offset, offsetof(struct RObject, as.heap.fields) == offsetof(struct rb_fields, as.complex.table));

#define IMEMO_OBJ_FIELDS(fields) ((struct rb_fields *)fields)

Expand Down Expand Up @@ -309,11 +303,18 @@ rb_imemo_fields_ptr(VALUE fields_obj)
RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields) || RB_TYPE_P(fields_obj, T_OBJECT));

if (UNLIKELY(FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP))) {
return IMEMO_OBJ_FIELDS(fields_obj)->as.external.ptr;
}
else {
return IMEMO_OBJ_FIELDS(fields_obj)->as.embed.fields;
if (RB_TYPE_P(fields_obj, T_OBJECT)) {
fields_obj = ROBJECT(fields_obj)->as.extended;
}
RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields));

// This will go away once we embed the `st_table` inside `IMEMO/fields`.
if (UNLIKELY(FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP))) {
return (VALUE *)IMEMO_OBJ_FIELDS(fields_obj)->as.complex.table;
}
}

return IMEMO_OBJ_FIELDS(fields_obj)->as.embed.fields;
}

static inline st_table *
Expand All @@ -323,7 +324,7 @@ rb_imemo_fields_complex_tbl(VALUE fields_obj)
return NULL;
}

RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields) || RB_TYPE_P(fields_obj, T_OBJECT));
RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields));
RUBY_ASSERT(FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP));

// Some codepaths unconditionally access the fields_ptr, and assume it can be used as st_table if the
Expand Down
30 changes: 23 additions & 7 deletions internal/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,35 @@ RBASIC_SET_CLASS(VALUE obj, VALUE klass)
RB_OBJ_WRITTEN(obj, oldv, klass);
}

static inline VALUE
ROBJECT_FIELDS_OBJ(VALUE obj)
{
RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);

return FL_TEST_RAW(obj, ROBJECT_HEAP) ? ROBJECT(obj)->as.extended : obj;
}

static inline VALUE *
ROBJECT_EMBEDDED_FIELDS(VALUE obj)
{
return ROBJECT(obj)->as.ary;
}

static inline VALUE *
ROBJECT_FIELDS(VALUE obj)
{
RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT);

struct RObject *const ptr = ROBJECT(obj);
return ROBJECT_EMBEDDED_FIELDS(ROBJECT_FIELDS_OBJ(obj));
}

if (RB_UNLIKELY(RB_FL_ANY_RAW(obj, ROBJECT_HEAP))) {
return ptr->as.heap.fields;
}
else {
return ptr->as.ary;
}
static inline void
ROBJECT_SET_EXTENDED(VALUE obj, VALUE fields_obj)
{
RUBY_ASSERT(RB_TYPE_P(obj, T_OBJECT));
RUBY_ASSERT(RB_TYPE_P(fields_obj, T_IMEMO));
RB_OBJ_WRITE(obj, &ROBJECT(obj)->as.extended, fields_obj);
FL_SET_RAW(obj, ROBJECT_HEAP);
}

static inline size_t
Expand Down
1 change: 0 additions & 1 deletion internal/variable.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,5 @@ VALUE rb_gvar_get(ID);
VALUE rb_gvar_set(ID, VALUE);
VALUE rb_gvar_defined(ID);
void rb_const_warn_if_deprecated(const rb_const_entry_t *, VALUE, ID);
void rb_ensure_iv_list_size(VALUE obj, uint32_t current_len, uint32_t newsize);

#endif /* INTERNAL_VARIABLE_H */
2 changes: 1 addition & 1 deletion jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

enum jit_bindgen_constants {
// Field offsets for the RObject struct
ROBJECT_OFFSET_AS_HEAP_FIELDS = offsetof(struct RObject, as.heap.fields),
ROBJECT_OFFSET_AS_HEAP_FIELDS = offsetof(struct RObject, as.extended),
ROBJECT_OFFSET_AS_ARY = offsetof(struct RObject, as.ary),

// Field offset for prime classext's fields_obj from a class pointer
Expand Down
9 changes: 9 additions & 0 deletions lib/bundler/rubygems_gem_installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ def generate_plugins
end
end

# Reimplemented from RubyGems, since RubyGems older than 4.1 doesn't
# provide it
def remove_stale_plugins
return unless spec.plugins.empty?

ensure_writable_dir @plugins_dir
remove_plugins_for(spec, @plugins_dir)
end

def warn_skipped_extensions
return if spec.extensions.empty?

Expand Down
Loading