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
74 changes: 74 additions & 0 deletions benchmark/marshal_load_partial_objects.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
prelude: |
MarshalLoadPartialObjectsObject = Class.new do
def initialize(a, b, c, d)
@a = a
@b = b
@c = c
@d = d
end
end

MarshalLoadPartialObjectsStruct = Struct.new(:a, :b, :c, :d)
MarshalLoadPartialObjectsData = Data.define(:a, :b, :c, :d)

integer_array_10 = 10.times.to_a
integer_array_1000 = 1000.times.to_a
unique_strings = 1000.times.map { |i| "string #{i}" }
shared_string = +"shared string"
shared_strings = Array.new(1000, shared_string)
integer_hash = 1000.times.to_h { |i| [i, i] }
symbol_string_hash = 1000.times.to_h { |i| ["key#{i}".to_sym, "value #{i}"] }
objects = 1000.times.map { |i| MarshalLoadPartialObjectsObject.new(i, i + 1, i + 2, i + 3) }
structs = 1000.times.map { |i| MarshalLoadPartialObjectsStruct.new(i, i + 1, i + 2, i + 3) }
data_objects = 1000.times.map { |i| MarshalLoadPartialObjectsData.new(i, i + 1, i + 2, i + 3) }

integer_array_10_dump = Marshal.dump(integer_array_10)
integer_array_1000_dump = Marshal.dump(integer_array_1000)
unique_strings_dump = Marshal.dump(unique_strings)
shared_strings_dump = Marshal.dump(shared_strings)
integer_hash_dump = Marshal.dump(integer_hash)
symbol_string_hash_dump = Marshal.dump(symbol_string_hash)
objects_dump = Marshal.dump(objects)
structs_dump = Marshal.dump(structs)
data_objects_dump = Marshal.dump(data_objects)

load_proc = ->(object) { object }

benchmark:
marshal_load_integer_array_10: 'Marshal.load(integer_array_10_dump)'
marshal_load_integer_array_10_with_proc: 'Marshal.load(integer_array_10_dump, load_proc)'
marshal_load_integer_array_10_freeze: 'Marshal.load(integer_array_10_dump, freeze: true)'

marshal_load_integer_array_1000: 'Marshal.load(integer_array_1000_dump)'
marshal_load_integer_array_1000_with_proc: 'Marshal.load(integer_array_1000_dump, load_proc)'
marshal_load_integer_array_1000_freeze: 'Marshal.load(integer_array_1000_dump, freeze: true)'

marshal_load_unique_strings: 'Marshal.load(unique_strings_dump)'
marshal_load_unique_strings_with_proc: 'Marshal.load(unique_strings_dump, load_proc)'
marshal_load_unique_strings_freeze: 'Marshal.load(unique_strings_dump, freeze: true)'

marshal_load_shared_strings: 'Marshal.load(shared_strings_dump)'
marshal_load_shared_strings_with_proc: 'Marshal.load(shared_strings_dump, load_proc)'
marshal_load_shared_strings_freeze: 'Marshal.load(shared_strings_dump, freeze: true)'

marshal_load_integer_hash: 'Marshal.load(integer_hash_dump)'
marshal_load_integer_hash_with_proc: 'Marshal.load(integer_hash_dump, load_proc)'
marshal_load_integer_hash_freeze: 'Marshal.load(integer_hash_dump, freeze: true)'

marshal_load_symbol_string_hash: 'Marshal.load(symbol_string_hash_dump)'
marshal_load_symbol_string_hash_with_proc: 'Marshal.load(symbol_string_hash_dump, load_proc)'
marshal_load_symbol_string_hash_freeze: 'Marshal.load(symbol_string_hash_dump, freeze: true)'

marshal_load_objects: 'Marshal.load(objects_dump)'
marshal_load_objects_with_proc: 'Marshal.load(objects_dump, load_proc)'
marshal_load_objects_freeze: 'Marshal.load(objects_dump, freeze: true)'

marshal_load_structs: 'Marshal.load(structs_dump)'
marshal_load_structs_with_proc: 'Marshal.load(structs_dump, load_proc)'
marshal_load_structs_freeze: 'Marshal.load(structs_dump, freeze: true)'

marshal_load_data_objects: 'Marshal.load(data_objects_dump)'
marshal_load_data_objects_with_proc: 'Marshal.load(data_objects_dump, load_proc)'
marshal_load_data_objects_freeze: 'Marshal.load(data_objects_dump, freeze: true)'

loop_count: 1000
25 changes: 16 additions & 9 deletions marshal.c
Original file line number Diff line number Diff line change
Expand Up @@ -1275,7 +1275,7 @@ mark_load_arg(void *ptr)
return;
rb_mark_tbl(p->symbols);
rb_mark_tbl(p->data);
rb_mark_tbl(p->partial_objects);
if (p->partial_objects) rb_mark_tbl(p->partial_objects);
rb_mark_hash(p->compat_tbl);
}

Expand Down Expand Up @@ -1658,7 +1658,9 @@ r_entry0(VALUE v, st_index_t num, struct load_arg *arg)
st_lookup(arg->compat_tbl, v, &real_obj);
}
st_insert(arg->data, num, real_obj);
st_insert(arg->partial_objects, (st_data_t)real_obj, Qtrue);
if (arg->partial_objects) {
st_insert(arg->partial_objects, (st_data_t)real_obj, Qtrue);
}
return v;
}

Expand Down Expand Up @@ -1693,9 +1695,11 @@ r_leave(VALUE v, struct load_arg *arg, bool partial)
{
v = r_fixup_compat(v, arg);
if (!partial) {
st_data_t data;
st_data_t key = (st_data_t)v;
st_delete(arg->partial_objects, &key, &data);
if (arg->partial_objects) {
st_data_t data;
st_data_t key = (st_data_t)v;
st_delete(arg->partial_objects, &key, &data);
}
if (arg->freeze) {
if (RB_TYPE_P(v, T_MODULE) || RB_TYPE_P(v, T_CLASS)) {
// noop
Expand Down Expand Up @@ -1890,7 +1894,8 @@ r_object_for(struct load_arg *arg, bool partial, int *ivp, VALUE klass, VALUE ex
rb_raise(rb_eArgError, "dump format error (unlinked)");
}
v = (VALUE)link;
if (!st_lookup(arg->partial_objects, (st_data_t)v, &link)) {
if (arg->partial_objects &&
!st_lookup(arg->partial_objects, (st_data_t)v, &link)) {
if (arg->freeze && RB_TYPE_P(v, T_STRING)) {
v = rb_str_to_interned_str(v);
}
Expand Down Expand Up @@ -2382,8 +2387,10 @@ clear_load_arg(struct load_arg *arg)
arg->symbols = 0;
st_free_table(arg->data);
arg->data = 0;
st_free_table(arg->partial_objects);
arg->partial_objects = 0;
if (arg->partial_objects) {
st_free_table(arg->partial_objects);
arg->partial_objects = 0;
}
if (arg->compat_tbl) {
st_free_table(arg->compat_tbl);
arg->compat_tbl = 0;
Expand Down Expand Up @@ -2413,7 +2420,7 @@ rb_marshal_load_with_proc(VALUE port, VALUE proc, bool freeze)
arg->offset = 0;
arg->symbols = st_init_numtable();
arg->data = rb_init_identtable();
arg->partial_objects = rb_init_identtable();
arg->partial_objects = (RTEST(proc) || freeze) ? rb_init_identtable() : NULL;
arg->compat_tbl = 0;
arg->proc = 0;
arg->readable = 0;
Expand Down
8 changes: 8 additions & 0 deletions test/ruby/test_marshal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,14 @@ def test_marshal_proc_freeze
assert_equal object, Marshal.load(Marshal.dump(object), :freeze.to_proc)
end

def test_marshal_false_proc
object = []
object << object

loaded = Marshal.load(Marshal.dump(object), false)
assert_same loaded, loaded.first
end

def test_marshal_load_extended_class_crash
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
Expand Down
7 changes: 6 additions & 1 deletion thread_pthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,12 @@ static const void *const condattr_monotonic = NULL;
#define USE_MN_THREADS 0
#elif HAVE_SYS_EPOLL_H
#include <sys/epoll.h>
#define USE_MN_THREADS 1
#ifdef EPOLLONESHOT
#define USE_MN_THREADS 1
#else
// the scheduler arms io fds with EPOLLONESHOT (Linux 2.6.2)
#define USE_MN_THREADS 0
#endif
#elif HAVE_SYS_EVENT_H
#include <sys/event.h>
#define USE_MN_THREADS 1
Expand Down
3 changes: 3 additions & 0 deletions thread_sched.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ struct rb_fd_waiters {
// what its waiters asked for.
uint32_t armed_flags;

// epoll only: the fd is in the interest set (kept across oneshot disarms).
bool registered;

// Bumped on full disarm. Events carry the generation they were armed with,
// so one queued before the fd was disarmed (and reused) is recognised.
uint32_t generation;
Expand Down
47 changes: 30 additions & 17 deletions thread_sched_mn.c
Original file line number Diff line number Diff line change
Expand Up @@ -1228,11 +1228,14 @@ fd_event_tag(int fd, uint32_t generation)

// Make the backend match `want`. Returns false if the fd cannot be registered
// at all (closed, or unsupported by the backend), leaving the entry untouched.
// The fd's shard lock must be held.
// The fd's shard lock must be held. `consumed` says an epoll event for the
// current arming was just delivered, so EPOLLONESHOT has already disarmed it.
static bool
fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want)
fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want, bool consumed)
{
if (want == e->armed_flags) return true;
// After a delivery the kernel side is disarmed even when the flags agree,
// so a consumed call must fall through to re-arm.
if (want == e->armed_flags && !consumed) return true;

#if HAVE_SYS_EVENT_H
struct kevent ke[2];
Expand Down Expand Up @@ -1268,15 +1271,22 @@ fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want)
}
#elif HAVE_SYS_EPOLL_H
if (want == 0) {
if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
switch (errno) {
case EBADF:
case ENOENT:
// the fd is already closed or gone from the set
break;
default:
perror("epoll_ctl");
rb_bug("fd_waiters_arm/epoll_ctl del failed (fd:%d errno:%d)", fd, errno);
// A delivered oneshot event has already disarmed the fd; otherwise
// disarm by MOD to no events. Either way the registration stays, so
// the next wait is one MOD instead of DEL + ADD.
if (!consumed && e->registered) {
struct epoll_event off = { .events = 0, .data = { .u64 = 0 } };
if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_MOD, fd, &off) == -1) {
switch (errno) {
case EBADF:
case ENOENT:
// the fd is already closed or gone from the set
e->registered = false;
break;
default:
perror("epoll_ctl");
rb_bug("fd_waiters_arm/epoll_ctl disarm failed (fd:%d errno:%d)", fd, errno);
}
}
}
// Anything epoll_wait already queued for the old arming is stale now.
Expand All @@ -1285,7 +1295,7 @@ fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want)
return true;
}

uint32_t epoll_events = 0;
uint32_t epoll_events = EPOLLONESHOT;
if (want & thread_sched_waiting_io_read) epoll_events |= EPOLLIN;
if (want & thread_sched_waiting_io_write) epoll_events |= EPOLLOUT;

Expand All @@ -1294,7 +1304,7 @@ fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want)
.data = { .u64 = fd_event_tag(fd, e->generation) },
};

int op = e->armed_flags ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;
int op = e->registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;

if (epoll_ctl(timer_th.event_fd, op, fd, &event) == -1) {
switch (errno) {
Expand All @@ -1304,6 +1314,7 @@ fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want)
epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == 0) {
break;
}
e->registered = false;
return false;
case EEXIST:
// Likewise in the other direction.
Expand All @@ -1315,12 +1326,14 @@ fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want)
case EBADF:
case EPERM:
// closed, or the fd does not support epoll
e->registered = false;
return false;
default:
perror("epoll_ctl");
rb_bug("fd_waiters_arm/epoll_ctl failed (fd:%d op:%d errno:%d)", fd, op, errno);
}
}
e->registered = true;
#else
# error "neither kqueue nor epoll"
#endif
Expand Down Expand Up @@ -1493,7 +1506,7 @@ timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting

// Arm the union of what this fd's waiters want, so a second waiter
// on the same fd extends the arming instead of colliding with it.
if (!fd_waiters_arm(fd, e, fd_waiters_union(e) | (uint32_t)(flags & FD_WAIT_IO_MASK))) {
if (!fd_waiters_arm(fd, e, fd_waiters_union(e) | (uint32_t)(flags & FD_WAIT_IO_MASK), false)) {
fd_shard_unlock(fd);
return timer_thread_unavailable;
}
Expand Down Expand Up @@ -1573,7 +1586,7 @@ timer_thread_unregister_waiting(rb_thread_t *th, int fd, enum thread_sched_waiti

struct rb_fd_waiters *e = fd_waiters_lookup(fd, false);
if (e) {
fd_waiters_arm(fd, e, fd_waiters_union(e));
fd_waiters_arm(fd, e, fd_waiters_union(e), false);
}
}

Expand Down Expand Up @@ -1689,7 +1702,7 @@ timer_thread_wake_fd_waiters(int fd, uint32_t generation, uint32_t wake_flags, i

// Re-arm for whoever is still waiting on this fd (nothing, if
// they all just woke up).
fd_waiters_arm(fd, e, fd_waiters_union(e));
fd_waiters_arm(fd, e, fd_waiters_union(e), true);
}
}
fd_shard_unlock(fd);
Expand Down
1 change: 1 addition & 0 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
assert_eq!(SHAPE_ID_NUM_BITS, 32);
gen_const_uint32(val.0)
}
&Insn::Const { val: Const::CBool(val) } => Opnd::UImm(val.into()),
Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"),
Insn::NewArray { elements, state } => gen_new_array(jit, asm, function, opnds!(elements), &function.frame_state(*state)),
Insn::NewHash { elements, state } => {
Expand Down
15 changes: 15 additions & 0 deletions zjit/src/cruby_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ pub fn init() -> Annotations {
annotate!(rb_cFloat, "nan?", types::BoolExact, no_gc, leaf, elidable);
annotate!(rb_cFloat, "finite?", types::BoolExact, no_gc, leaf, elidable);
annotate!(rb_cFloat, "infinite?", types::Fixnum.union(types::NilClass), no_gc, leaf, elidable);
annotate!(rb_cFalseClass, "&", inline_falseclass_and);
annotate!(rb_cTrueClass, "&", inline_trueclass_and);
let thread_singleton = unsafe { rb_singleton_class(rb_cThread) };
annotate!(thread_singleton, "current", inline_thread_current, types::BasicObject, no_gc, leaf);

Expand Down Expand Up @@ -319,6 +321,19 @@ fn inline_string_to_s(fun: &mut hir::Function, block: hir::BlockId, recv: hir::I
None
}

fn inline_falseclass_and(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option<hir::InsnId> {
// FalseClass#& just returns Qfalse and ignores its argument.
let &[_] = args else { return None; };
Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qfalse) }))
}

fn inline_trueclass_and(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option<hir::InsnId> {
// TrueClass#& does RBOOL(RTEST(arg))
let &[val] = args else { return None; };
let test = fun.push_insn(block, hir::Insn::Test { val });
Some(fun.push_insn(block, hir::Insn::BoxBool { val: test }))
}

fn inline_thread_current(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option<hir::InsnId> {
let &[] = args else { return None; };
let ec = fun.push_insn(block, hir::Insn::LoadEC);
Expand Down
18 changes: 18 additions & 0 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6447,6 +6447,10 @@ impl Function {
_ => insn_id,
}
}
&Insn::WriteBarrier { val, .. } if self.is_a(val, types::Immediate) => {
// The write barrier does nothing for immediates.
continue;
}
&Insn::ArrayLength { array } => {
match self.type_of(array).ruby_object() {
Some(array_obj) if array_obj.is_frozen() => {
Expand Down Expand Up @@ -6714,6 +6718,20 @@ impl Function {
insn_id
}
}
&Insn::BoxBool { val: bool_val } => {
if let &Insn::Test { val: test_val } = self.resolve(bool_val).insn(self) {
// If the thing being Test'd is already a BoolExact
// (TrueClass|FalseClass), then we don't need to Test+BoxBool and can
// just return the test_val.
if self.is_a(test_val, types::BoolExact) {
self.make_equal_to(insn_id, test_val);
continue;
}
insn_id
} else {
insn_id
}
}
&Insn::CondBranch { val, ref if_true, .. } if self.is_a(val, Type::from_cbool(true)) => {
self.new_insn(Insn::Jump(if_true.clone()))
}
Expand Down
Loading