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
7 changes: 5 additions & 2 deletions gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1975,14 +1975,17 @@ object_id_to_ref(void *objspace_ptr, VALUE object_id)
// GC Must not trigger while we build the table, otherwise if we end
// up freeing an object that had an ID, we might try to delete it from
// the table even though it wasn't inserted yet.
id2ref_tbl = st_init_table(&object_id_hash_type);
id2ref_value = TypedData_Wrap_Struct(0, &id2ref_tbl_type, id2ref_tbl);
st_table *tmp_id2ref_tbl = st_init_table(&object_id_hash_type);
VALUE tmp_id2ref_value = TypedData_Wrap_Struct(0, &id2ref_tbl_type, tmp_id2ref_tbl);

// build_id2ref_i will most certainly malloc, which could trigger GC and sweep
// objects we just added to the table.
// By calling rb_gc_disable() we also save having to handle potentially garbage objects.
bool gc_disabled = RTEST(rb_gc_disable());
{
id2ref_tbl = tmp_id2ref_tbl;
id2ref_value = tmp_id2ref_value;

rb_gc_impl_each_object(objspace, build_id2ref_i, (void *)id2ref_tbl);
}
if (!gc_disabled) rb_gc_enable();
Expand Down
20 changes: 12 additions & 8 deletions gc/default/default.c
Original file line number Diff line number Diff line change
Expand Up @@ -6110,15 +6110,19 @@ rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj)

gc_report(1, objspace, "rb_gc_writebarrier_remember: %s\n", rb_obj_info(obj));

if (is_incremental_marking(objspace)) {
if (RVALUE_BLACK_P(objspace, obj)) {
gc_grey(objspace, obj);
}
}
else {
if (RVALUE_OLD_P(objspace, obj)) {
rgengc_remember(objspace, obj);
if (is_incremental_marking(objspace) || RVALUE_OLD_P(objspace, obj)) {
int lev = RB_GC_VM_LOCK_NO_BARRIER();
{
if (is_incremental_marking(objspace)) {
if (RVALUE_BLACK_P(objspace, obj)) {
gc_grey(objspace, obj);
}
}
else if (RVALUE_OLD_P(objspace, obj)) {
rgengc_remember(objspace, obj);
}
}
RB_GC_VM_UNLOCK_NO_BARRIER(lev);
}
}

Expand Down
15 changes: 15 additions & 0 deletions test/ruby/test_objectspace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,21 @@ def test_each_object_recursive_key
end;
end

def test_id2ref_table_build
assert_separately([], <<-End)
10.times do
Object.new.object_id
end

GC.start(immediate_mark: false)

obj = Object.new
EnvUtil.suppress_warning do
assert_equal obj, ObjectSpace._id2ref(obj.object_id)
end
End
end

def test_each_object_singleton_class
assert_separately([], <<-End)
class C
Expand Down
39 changes: 39 additions & 0 deletions test/ruby/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,45 @@ def test_require_non_string
RUBY
end

# [Bug #21398]
def test_port_receive_dnt_with_port_send
assert_ractor(<<~'RUBY', timeout: 30)
THREADS = 10
JOBS_PER_THREAD = 50
ARRAY_SIZE = 20_000
def ractor_job(job_count, array_size)
port = Ractor::Port.new
workers = (1..4).map do |i|
Ractor.new(port) do |job_port|
while job = Ractor.receive
result = job.map { |x| x * 2 }.sum
job_port.send result
end
end
end
jobs = Array.new(job_count) { Array.new(array_size) { rand(1000) } }
jobs.each_with_index do |job, i|
w_idx = i % 4
workers[w_idx].send(job)
end
results = []
jobs.size.times do
result = port.receive # dnt receive
results << result
end
results
end
threads = []
# creates 40 ractors (THREADSx4)
THREADS.times do
threads << Thread.new do
ractor_job(JOBS_PER_THREAD, ARRAY_SIZE)
end
end
threads.each(&:join)
RUBY
end

def assert_make_shareable(obj)
refute Ractor.shareable?(obj), "object was already shareable"
Ractor.make_shareable(obj)
Expand Down
9 changes: 9 additions & 0 deletions test/ruby/test_zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ def test_setlocal_on_eval
}
end

def test_call_a_forwardable_method
assert_runs '[]', %q{
def test_root = forwardable
def forwardable(...) = Array.[](...)
test_root
test_root
}, call_threshold: 2
end

def test_setlocal_on_eval_with_spill
assert_compiles '1', %q{
@b = binding
Expand Down
13 changes: 5 additions & 8 deletions thread_pthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -1351,23 +1351,20 @@ rb_ractor_sched_wait(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_fun
}

thread_sched_lock(sched, th);
rb_ractor_unlock_self(cr);
{
// setup sleep
bool can_direct_transfer = !th_has_dedicated_nt(th);
RB_VM_SAVE_MACHINE_CONTEXT(th);
th->status = THREAD_STOPPED_FOREVER;
RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th);
thread_sched_wakeup_next_thread(sched, th, can_direct_transfer);

rb_ractor_unlock_self(cr);
{
// sleep
thread_sched_wait_running_turn(sched, th, can_direct_transfer);
th->status = THREAD_RUNNABLE;
}
rb_ractor_lock_self(cr);
// sleep
thread_sched_wait_running_turn(sched, th, can_direct_transfer);
th->status = THREAD_RUNNABLE;
}
thread_sched_unlock(sched, th);
rb_ractor_lock_self(cr);

ubf_clear(th);

Expand Down
30 changes: 13 additions & 17 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,7 @@ fn can_direct_send(iseq: *const rb_iseq_t) -> bool {
else if unsafe { rb_get_iseq_flags_has_kw(iseq) } { false }
else if unsafe { rb_get_iseq_flags_has_kwrest(iseq) } { false }
else if unsafe { rb_get_iseq_flags_has_block(iseq) } { false }
else if unsafe { rb_get_iseq_flags_forwardable(iseq) } { false }
else { true }
}

Expand Down Expand Up @@ -2581,6 +2582,9 @@ pub enum CallType {
#[derive(Debug, PartialEq)]
pub enum ParameterType {
Optional,
/// For example, `foo(...)`. Interaction of JIT
/// calling convention and side exits currently unsolved.
Forwardable,
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -2650,6 +2654,7 @@ pub const SELF_PARAM_IDX: usize = 0;

fn filter_unknown_parameter_type(iseq: *const rb_iseq_t) -> Result<(), ParseError> {
if unsafe { rb_get_iseq_body_param_opt_num(iseq) } != 0 { return Err(ParseError::UnknownParameterType(ParameterType::Optional)); }
if unsafe { rb_get_iseq_flags_forwardable(iseq) } { return Err(ParseError::UnknownParameterType(ParameterType::Forwardable)); }
Ok(())
}

Expand Down Expand Up @@ -4583,11 +4588,13 @@ mod tests {
eval("
def test(...) = super(...)
");
assert_method_hir("test", expect![[r#"
fn test@<compiled>:2:
bb0(v0:BasicObject, v1:BasicObject):
SideExit UnknownOpcode(invokesuperforward)
"#]]);
assert_compile_fails("test", ParseError::UnknownParameterType(ParameterType::Forwardable));
}

#[test]
fn test_cant_compile_forwardable() {
eval("def forwardable(...) = nil");
assert_compile_fails("forwardable", ParseError::UnknownParameterType(ParameterType::Forwardable));
}

// TODO(max): Figure out how to generate a call with OPT_SEND flag
Expand Down Expand Up @@ -4631,11 +4638,7 @@ mod tests {
eval("
def test(...) = foo(...)
");
assert_method_hir("test", expect![[r#"
fn test@<compiled>:2:
bb0(v0:BasicObject, v1:BasicObject):
SideExit UnknownOpcode(sendforward)
"#]]);
assert_compile_fails("test", ParseError::UnknownParameterType(ParameterType::Forwardable));
}

#[test]
Expand Down Expand Up @@ -5691,7 +5694,6 @@ mod opt_tests {
def kw_rest(**k) = k
def post(*rest, post) = post
def block(&b) = nil
def forwardable(...) = nil
");

assert_optimized_method_hir("rest", expect![[r#"
Expand Down Expand Up @@ -5721,12 +5723,6 @@ mod opt_tests {
bb0(v0:BasicObject, v1:ArrayExact, v2:BasicObject):
Return v2
"#]]);
assert_optimized_method_hir("forwardable", expect![[r#"
fn forwardable@<compiled>:7:
bb0(v0:BasicObject, v1:BasicObject):
v3:NilClass = Const Value(nil)
Return v3
"#]]);
}

#[test]
Expand Down