diff --git a/file.c b/file.c index 41f3c372cef995..41275eeebc0c84 100644 --- a/file.c +++ b/file.c @@ -1945,13 +1945,19 @@ rb_file_socket_p(VALUE obj, VALUE fname) /* * call-seq: - * File.blockdev?(filepath) -> true or false + * File.blockdev?(object) -> true or false * - * Returns +true+ if +filepath+ points to a block device, +false+ otherwise: + * Returns whether +object+ (a path or IO object) + * represents a block device (i.e., a direct-access device): * - * File.blockdev?('/dev/sda1') # => true - * File.blockdev?(File.new('t.tmp')) # => false + * File.blockdev?('/dev/nvme0n1') # => true + * File.blockdev?('/dev/loop0') # => true + * File.blockdev?('/dev/tty') # => false + * File.blockdev?('/dev/null') # => false + * File.blockdev?('nosuch') # => false + * File.blockdev?($stdin) # => false * + * The returned value is filesystem-dependent; on Windows, always +false+. */ static VALUE @@ -1977,13 +1983,20 @@ rb_file_blockdev_p(VALUE obj, VALUE fname) /* * call-seq: - * File.chardev?(filepath) -> true or false + * File.chardev?(object) -> true or false * - * Returns +true+ if +filepath+ points to a character device, +false+ otherwise. + * Returns whether +object+ (a path or IO object) + * represents a character device (i.e., a sequential-access device): * - * File.chardev?($stdin) # => true - * File.chardev?('t.txt') # => false + * File.chardev?('/dev/tty') # => true + * File.chardev?('/dev/null') # => true + * File.chardev?($stdin) # => true + * File.chardev?('/dev/nvme0n1') # => false + * File.chardev?('/dev/loop0') # => false + * File.chardev?('nosuch') # => false * + * + * The returned value is filesystem-dependent; on Windows, always +false+. */ static VALUE rb_file_chardev_p(VALUE obj, VALUE fname) diff --git a/io_buffer.c b/io_buffer.c index 6b8d834f9b28d6..f9fa1cf23d5e16 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -345,13 +345,19 @@ get_io_buffer(VALUE self) return buffer; } +static bool +io_buffer_slice_p(struct rb_io_buffer *buffer) +{ + return rb_typeddata_is_kind_of(buffer->source, &rb_io_buffer_type); +} + // Return the buffer which owns the lock count. A slice backed by another // buffer shares that source buffer's lock count. Other external sources, such // as strings, manage their own lifetime and do not share buffer lock state. static struct rb_io_buffer * io_buffer_lock_owner(struct rb_io_buffer *buffer) { - if (rb_typeddata_is_kind_of(buffer->source, &rb_io_buffer_type)) { + if (io_buffer_slice_p(buffer)) { return get_io_buffer(buffer->source); } @@ -1775,7 +1781,7 @@ rb_io_buffer_slice(struct rb_io_buffer *buffer, VALUE self, size_t offset, size_ // Slices retain their root buffer. If this buffer is already a slice, // retain its root directly rather than building a chain of slices: - if (rb_typeddata_is_kind_of(buffer->source, &rb_io_buffer_type)) { + if (io_buffer_slice_p(buffer)) { RB_OBJ_WRITE(instance, &slice->source, buffer->source); } else { @@ -1911,11 +1917,52 @@ io_buffer_resize_copy(VALUE self, struct rb_io_buffer *buffer, size_t size) *buffer = resized; } +static void +io_buffer_resize_slice(struct rb_io_buffer *slice, size_t size) +{ + struct rb_io_buffer *source = get_io_buffer(slice->source); + + if (!io_buffer_validate(source)) { + rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!"); + } + + if (source->base == NULL || slice->base == NULL) { + rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!"); + } + + uintptr_t source_address = (uintptr_t)source->base; + uintptr_t slice_address = (uintptr_t)slice->base; + + if (slice_address < source_address) { + rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!"); + } + + uintptr_t offset = slice_address - source_address; + + if (offset > source->size) { + rb_raise(rb_eIOBufferInvalidatedError, "Buffer is invalid!"); + } + + if (size > source->size - (size_t)offset) { + rb_raise(rb_eArgError, "Resized slice exceeds its source buffer!"); + } + + // Validate the requested range rather than the current range so that + // shrinking a slice can restore its validity after the source shrinks. + slice->size = size; +} + void rb_io_buffer_resize(VALUE self, size_t size) { struct rb_io_buffer *buffer = get_io_buffer(self); + if (io_buffer_slice_p(buffer)) { + // Resizing a slice only changes the view, not the locked allocation. + io_buffer_resize_slice(buffer, size); + return; + } + io_buffer_validate_for_reading(buffer); if (io_buffer_locked(buffer)) { @@ -1986,8 +2033,14 @@ rb_io_buffer_resize(VALUE self, size_t size) * # # * # 0x00000000 74 65 73 74 00 00 00 00 test.... * - * External buffer (created with ::for), and locked buffer - * can not be resized. + * When the buffer is a slice, resizing changes the size of the view without + * modifying the source buffer or allocating new storage. The resized view + * must remain within the source buffer. Growing the view exposes the existing + * bytes in the source; they are not cleared. Because the source allocation + * does not change, a slice can be resized while its source is locked. + * + * External owning buffers (created with ::for), and locked owning buffers + * cannot be resized. */ static VALUE io_buffer_resize(VALUE self, VALUE size) diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb index 044562137cb856..c90f3a93301ddc 100644 --- a/lib/rubygems/security/signer.rb +++ b/lib/rubygems/security/signer.rb @@ -109,9 +109,7 @@ def extract_name(cert) # :nodoc: subject_alt_name = cert.extensions.find {|e| e.oid == "subjectAltName" } if subject_alt_name - /\Aemail:/ =~ subject_alt_name.value # rubocop:disable Performance/StartWith - - $' || subject_alt_name.value + subject_alt_name.value.delete_prefix("email:") else cert.subject end diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 9deb0c860ef2d6..d2300ab56bebb3 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -2220,7 +2220,7 @@ class Pathname # * FileTest * # Pathname($stdin).blockdev? # => false # ``` # - # The returned value is OS-dependent; on Windows, almost always `false`. + # The returned value is filesystem-dependent; on Windows, always `false`. def blockdev?() FileTest.blockdev?(@path) end # :markup: markdown @@ -2240,7 +2240,7 @@ def blockdev?() FileTest.blockdev?(@path) end # Pathname('nosuch').chardev? # => false # ``` # - # The returned value is OS-dependent; on Windows, almost always `false`. + # The returned value is filesystem-dependent; on Windows, always `false`. def chardev?() FileTest.chardev?(@path) end # :markup: markdown diff --git a/proc.c b/proc.c index aeaf6de448027f..6206f2c5da668e 100644 --- a/proc.c +++ b/proc.c @@ -309,7 +309,7 @@ rb_proc_refinements_recipe(VALUE procval) { rb_proc_t *proc; GetProcPtr(procval, proc); - if (!proc->is_refined) return Qnil; + if (!proc->header.is_refined) return Qnil; return rb_ivar_get(procval, id_refinements_recipe); } @@ -319,7 +319,7 @@ rb_proc_set_refinements_recipe(VALUE procval, VALUE recipe) rb_proc_t *proc; GetProcPtr(procval, proc); rb_ivar_set(procval, id_refinements_recipe, recipe); - proc->is_refined = 1; + proc->header.is_refined = 1; } typedef struct { @@ -331,9 +331,19 @@ static size_t proc_memsize(const void *ptr) { const rb_proc_t *proc = ptr; - if (proc->block.as.captured.ep == ((const cfunc_proc_t *)ptr)->env+1) - return sizeof(cfunc_proc_t); - return sizeof(rb_proc_t); + switch (proc->block.type) { + case block_type_iseq: + case block_type_ifunc: + if (proc->block.as.captured.ep == ((const cfunc_proc_t *)ptr)->env+1) + return sizeof(cfunc_proc_t); + return sizeof(rb_proc_captured_t); + case block_type_symbol: + return sizeof(rb_proc_symbol_t); + case block_type_proc: + return sizeof(rb_proc_proc_t); + } + VM_UNREACHABLE(proc_memsize); + return 0; } const rb_data_type_t ruby_proc_data_type = { @@ -350,10 +360,26 @@ const rb_data_type_t ruby_proc_data_type = { #define proc_data_type ruby_proc_data_type VALUE -rb_proc_alloc(VALUE klass) +rb_proc_alloc(VALUE klass, enum rb_block_type block_type) { - rb_proc_t *proc; - return TypedData_Make_Struct(klass, rb_proc_t, &proc_data_type, proc); + size_t size; + switch (block_type) { + case block_type_symbol: + size = sizeof(rb_proc_symbol_t); + break; + case block_type_proc: + size = sizeof(rb_proc_proc_t); + break; + case block_type_iseq: + case block_type_ifunc: + size = sizeof(rb_proc_captured_t); + break; + default: + VM_UNREACHABLE(rb_proc_alloc); + return Qundef; + } + + return rb_data_typed_object_zalloc(klass, size, &proc_data_type); } VALUE @@ -559,7 +585,7 @@ rb_proc_refinements_cref_for_call(VALUE procval) { rb_proc_t *proc; GetProcPtr(procval, proc); - if (!proc->is_refined) return NULL; + if (!proc->header.is_refined) return NULL; refinement_iseq_ensure(procval, proc); VALUE recipe = rb_ivar_get(procval, id_refinements_recipe); @@ -629,7 +655,7 @@ proc_refined(int argc, VALUE *argv, VALUE self) return self; } - if (vm_block_type(&src->block) != block_type_iseq || src->is_from_method) { + if (vm_block_type(&src->block) != block_type_iseq || src->header.is_from_method) { rb_raise(rb_eArgError, "can't apply refinements to a Proc without a Ruby block"); } @@ -798,7 +824,7 @@ rb_proc_lambda_p(VALUE procval) rb_proc_t *proc; GetProcPtr(procval, proc); - return RBOOL(proc->is_lambda); + return RBOOL(proc->header.is_lambda); } /* Binding */ @@ -1396,7 +1422,7 @@ cfunc_proc_new(VALUE klass, VALUE ifunc) /* self? */ RB_OBJ_WRITE(procval, &proc->block.as.captured.code.ifunc, ifunc); - proc->is_lambda = TRUE; + proc->header.is_lambda = TRUE; return procval; } @@ -1429,13 +1455,13 @@ rb_func_proc_dup(VALUE src_obj) static VALUE sym_proc_new(VALUE klass, VALUE sym) { - VALUE procval = rb_proc_alloc(klass); + VALUE procval = rb_proc_alloc(klass, block_type_symbol); rb_proc_t *proc; GetProcPtr(procval, proc); vm_block_type_set(&proc->block, block_type_symbol); - proc->is_lambda = TRUE; - RB_OBJ_WRITE(procval, &proc->block.as.symbol, sym); + proc->header.is_lambda = TRUE; + RB_OBJ_WRITE(procval, &proc->symbol.symbol, sym); return procval; } @@ -1847,7 +1873,7 @@ rb_proc_arity(VALUE self) int max, min; GetProcPtr(self, proc); min = rb_vm_block_min_max_arity(&proc->block, &max); - return (proc->is_lambda ? min == max : max != UNLIMITED_ARGUMENTS) ? min : -min-1; + return (proc->header.is_lambda ? min == max : max != UNLIMITED_ARGUMENTS) ? min : -min-1; } static void @@ -1897,7 +1923,7 @@ rb_block_pair_yield_optimizable(void) VALUE procval = block_handler; rb_proc_t *proc; GetProcPtr(procval, proc); - if (proc->is_lambda) return 0; + if (proc->header.is_lambda) return 0; if (min != max) return 0; return min > 1; } @@ -1965,7 +1991,7 @@ rb_proc_get_iseq(VALUE self, int *is_proc) GetProcPtr(self, proc); block = &proc->block; - if (is_proc) *is_proc = !proc->is_lambda; + if (is_proc) *is_proc = !proc->header.is_lambda; switch (vm_block_type(block)) { case block_type_iseq: @@ -2031,9 +2057,9 @@ proc_eq(VALUE self, VALUE other) GetProcPtr(self, self_proc); GetProcPtr(other, other_proc); - if (self_proc->is_from_method != other_proc->is_from_method || - self_proc->is_lambda != other_proc->is_lambda || - self_proc->is_refined != other_proc->is_refined) { + if (self_proc->header.is_from_method != other_proc->header.is_from_method || + self_proc->header.is_lambda != other_proc->header.is_lambda || + self_proc->header.is_refined != other_proc->header.is_refined) { return Qfalse; } @@ -2052,7 +2078,7 @@ proc_eq(VALUE self, VALUE other) } /* a refined Proc's block iseq flips from the source to the copy on * the first call; compare what the Procs were built from instead */ - if (self_proc->is_refined) { + if (self_proc->header.is_refined) { if (!refinement_recipe_eq(rb_proc_refinements_recipe(self), rb_proc_refinements_recipe(other))) { return Qfalse; @@ -2232,7 +2258,7 @@ rb_hash_proc(st_index_t hash, VALUE prc) switch (vm_block_type(&proc->block)) { case block_type_iseq: - if (proc->is_refined) { + if (proc->header.is_refined) { /* from the recipe, not the block iseq: the latter flips from the * source to the copy on the first call, and the hash must not */ VALUE recipe = rb_proc_refinements_recipe(prc); @@ -2263,8 +2289,9 @@ rb_hash_proc(st_index_t hash, VALUE prc) /* ifunc procs have their own allocated ep. If an ifunc is duplicated, they * will point to different ep but they should return the same hash code, so - * we cannot include the ep in the hash. */ - if (vm_block_type(&proc->block) != block_type_ifunc) { + * we cannot include the ep in the hash. Symbol and proc type blocks are + * smaller and do not have an ep at all. */ + if (vm_block_type(&proc->block) == block_type_iseq) { hash = rb_hash_uint(hash, (st_index_t)proc->block.as.captured.ep); } @@ -2384,7 +2411,7 @@ proc_to_s(VALUE self) { const rb_proc_t *proc; GetProcPtr(self, proc); - return rb_block_to_s(self, &proc->block, proc->is_lambda ? " (lambda)" : NULL); + return rb_block_to_s(self, &proc->block, proc->header.is_lambda ? " (lambda)" : NULL); } /* @@ -3098,7 +3125,7 @@ rb_mod_define_method_with_visibility(int argc, VALUE *argv, VALUE mod, const str GetProcPtr(body, body_proc); /* A bmethod never reads the refinement cref carried on the proc; * reject rather than silently drop the refinements. */ - if (body_proc->is_refined) { + if (body_proc->header.is_refined) { rb_raise(rb_eArgError, "can't define a method from a Proc with refinements"); } @@ -3106,8 +3133,8 @@ rb_mod_define_method_with_visibility(int argc, VALUE *argv, VALUE mod, const str if (vm_proc_iseq(procval) != NULL) { rb_proc_t *proc; GetProcPtr(procval, proc); - proc->is_lambda = TRUE; - proc->is_from_method = TRUE; + proc->header.is_lambda = TRUE; + proc->header.is_from_method = TRUE; } rb_add_method(mod, id, VM_METHOD_TYPE_BMETHOD, (void *)procval, scope_visi->method_visi); if (scope_visi->module_func) { @@ -4231,7 +4258,7 @@ method_to_proc(VALUE method) */ procval = rb_block_call(rb_mRubyVMFrozenCore, idLambda, 0, 0, bmcall, method); GetProcPtr(procval, proc); - proc->is_from_method = 1; + proc->header.is_from_method = 1; return procval; } @@ -4421,7 +4448,7 @@ proc_binding(VALUE self) GetProcPtr(self, proc); block = &proc->block; - if (proc->is_isolated) rb_raise(rb_eArgError, "Can't create Binding from isolated Proc"); + if (proc->header.is_isolated) rb_raise(rb_eArgError, "Can't create Binding from isolated Proc"); again: switch (vm_block_type(block)) { @@ -4488,12 +4515,12 @@ make_curry_proc(VALUE proc, VALUE passed, VALUE arity) int is_lambda; GetProcPtr(proc, procp); - is_lambda = procp->is_lambda; + is_lambda = procp->header.is_lambda; rb_ary_freeze(passed); rb_ary_freeze(args); proc = rb_proc_new(curry, args); GetProcPtr(proc, procp); - procp->is_lambda = is_lambda; + procp->header.is_lambda = is_lambda; return proc; } @@ -4698,7 +4725,7 @@ rb_proc_compose_to_left(VALUE self, VALUE g) if (rb_obj_is_proc(g)) { GetProcPtr(g, procp); - is_lambda = procp->is_lambda; + is_lambda = procp->header.is_lambda; } else { VM_ASSERT(rb_obj_is_method(g) || rb_obj_respond_to(g, idCall, TRUE)); @@ -4707,7 +4734,7 @@ rb_proc_compose_to_left(VALUE self, VALUE g) proc = rb_proc_new(compose, args); GetProcPtr(proc, procp); - procp->is_lambda = is_lambda; + procp->header.is_lambda = is_lambda; return proc; } @@ -4756,11 +4783,11 @@ rb_proc_compose_to_right(VALUE self, VALUE g) args = rb_ary_tmp_new_from_values(0, 2, procs); GetProcPtr(self, procp); - is_lambda = procp->is_lambda; + is_lambda = procp->header.is_lambda; proc = rb_proc_new(compose, args); GetProcPtr(proc, procp); - procp->is_lambda = is_lambda; + procp->header.is_lambda = is_lambda; return proc; } @@ -4843,7 +4870,7 @@ proc_ruby2_keywords(VALUE procval) rb_check_frozen(procval); - if (proc->is_from_method) { + if (proc->header.is_from_method) { rb_warn("Skipping set of ruby2_keywords flag for proc (proc created from method)"); return procval; } @@ -4854,7 +4881,7 @@ proc_ruby2_keywords(VALUE procval) !ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_post && !ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_kw && !ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_kwrest) { - if (proc->is_refined) { + if (proc->header.is_refined) { /* on a copy of this Proc's own: the block is shared with the * source Proc until the first call, and the copy installed by * it may be memoized and shared with sibling Procs */ diff --git a/ractor.c b/ractor.c index 5bf4e41b748b63..f774e6acfba61c 100644 --- a/ractor.c +++ b/ractor.c @@ -1006,6 +1006,14 @@ ractor_check_blocking(rb_ractor_t *cr, unsigned int remained_thread_cnt, const c { VM_ASSERT(cr == GET_RACTOR()); +#ifdef RUBY_THREAD_PTHREAD_H + // vm->ractor.blocking_cnt is only consumed by the win32 scheduler; the + // pthread one must not pay a VM lock per blocking region for it. The + // running<->blocking status flips stop with it (all callers), matching + // rb_ractor_blocking_threads_dec skipping the reverse transition. + return; +#endif + RUBY_DEBUG_LOG2(file, line, "cr->threads.cnt:%u cr->threads.blocking_cnt:%u vm->ractor.cnt:%u vm->ractor.blocking_cnt:%u", cr->threads.cnt, cr->threads.blocking_cnt, @@ -1106,6 +1114,8 @@ rb_ractor_blocking_threads_dec(rb_ractor_t *cr, const char *file, int line) VM_ASSERT(cr == GET_RACTOR()); +#ifndef RUBY_THREAD_PTHREAD_H + // see rb_ractor_blocking_threads_inc if (cr->threads.cnt == cr->threads.blocking_cnt) { rb_vm_t *vm = GET_VM(); @@ -1113,6 +1123,7 @@ rb_ractor_blocking_threads_dec(rb_ractor_t *cr, const char *file, int line) rb_vm_ractor_blocking_cnt_dec(vm, cr, __FILE__, __LINE__); } } +#endif cr->threads.blocking_cnt--; } diff --git a/ractor.rb b/ractor.rb index f0c9cc9aa6968b..e826e61b37655a 100644 --- a/ractor.rb +++ b/ractor.rb @@ -382,7 +382,7 @@ def inspect name = __builtin_cexpr! %q{ RACTOR_PTR(self)->name } id = __builtin_cexpr! %q{ UINT2NUM(rb_ractor_id(RACTOR_PTR(self))) } status = __builtin_cexpr! %q{ - rb_str_new2(ractor_status_str(RACTOR_PTR(self)->status_)) + rb_str_new2(RACTOR_PTR(self)->status_ == ractor_terminated ? "terminated" : "running") } "#" end diff --git a/spec/ruby/core/io/buffer/resize_spec.rb b/spec/ruby/core/io/buffer/resize_spec.rb index 6e684475f34067..35ff38145214aa 100644 --- a/spec/ruby/core/io/buffer/resize_spec.rb +++ b/spec/ruby/core/io/buffer/resize_spec.rb @@ -143,9 +143,72 @@ -> { @buffer.resize(10.0) }.should.raise(TypeError, "not an Integer") end - context "with a slice of a buffer" do - # Current behavior of slice resizing seems unintended (it's undocumented, too). - # It either creates a completely new buffer, or breaks the slice on size 0. - it "needs to be reviewed for spec completeness" + ruby_version_is "4.1" do + context "with a slice of a buffer" do + it "changes the size of the view without modifying the source" do + @buffer = IO::Buffer.for("abcdef").dup + slice = @buffer.slice(2, 2) + + slice.resize(4).should.equal?(slice) + slice.get_string.should == "cdef" + @buffer.get_string.should == "abcdef" + + slice.resize(1) + slice.get_string.should == "c" + end + + it "does not allocate when resized to zero" do + @buffer = IO::Buffer.for("abcdef").dup + slice = @buffer.slice(2, 2) + + slice.resize(0) + slice.should_not.null? + slice.should.empty? + slice.should.valid? + + slice.resize(4) + slice.get_string.should == "cdef" + end + + it "raises ArgumentError when the resized view exceeds the source" do + @buffer = IO::Buffer.for("abcdef").dup + slice = @buffer.slice(2, 2) + + -> { slice.resize(5) }.should.raise( + ArgumentError, + "Resized slice exceeds its source buffer!" + ) + + slice.get_string.should == "cd" + end + + it "can be resized while the source allocation is locked" do + @buffer = IO::Buffer.for("abcdef").dup + slice = @buffer.slice(2, 2) + + slice.locked do + slice.resize(4) + slice.get_string.should == "cdef" + end + end + + it "preserves read-only access" do + @buffer = IO::Buffer.for("abcdef") + slice = @buffer.slice(2, 2) + + slice.resize(4) + slice.should.readonly? + slice.get_string.should == "cdef" + end + + it "uses the retained root buffer as the boundary for nested slices" do + @buffer = IO::Buffer.for("abcdef").dup + parent = @buffer.slice(1, 2) + slice = parent.slice(1, 1) + + slice.resize(4) + slice.get_string.should == "cdef" + end + end end end diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index 53c4fd7a2ba6fa..78a34bcc0307cf 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -326,11 +326,64 @@ def test_resize_zero_slice slice = buffer.slice(0, 8) slice.resize(0) - assert_predicate slice, :null? + refute_predicate slice, :null? + assert_predicate slice, :empty? + assert_predicate slice, :valid? assert_equal 64, buffer.size slice.resize(1) assert_equal 1, slice.size + assert_predicate slice, :valid? + end + + def test_resize_slice_changes_view + buffer = IO::Buffer.for("abcdef").dup + slice = buffer.slice(2, 2) + + assert_same slice, slice.resize(4) + assert_equal "cdef", slice.get_string + + slice.set_string("X") + assert_equal "abXdef", buffer.get_string + + slice.resize(1) + assert_equal "X", slice.get_string + end + + def test_resize_slice_beyond_source + buffer = IO::Buffer.for("abcdef").dup + slice = buffer.slice(2, 2) + + error = assert_raise(ArgumentError) do + slice.resize(5) + end + + assert_equal "Resized slice exceeds its source buffer!", error.message + assert_equal 2, slice.size + assert_equal "cd", slice.get_string + end + + def test_resize_locked_slice + buffer = IO::Buffer.for("abcdef").dup + slice = buffer.slice(2, 2) + + slice.locked do + slice.resize(4) + assert_equal "cdef", slice.get_string + + assert_raise(IO::Buffer::LockedError) do + buffer.resize(8) + end + end + end + + def test_resize_nested_slice_uses_root_bounds + buffer = IO::Buffer.for("abcdef").dup + parent = buffer.slice(1, 2) + slice = parent.slice(1, 1) + + slice.resize(4) + assert_equal "cdef", slice.get_string end def test_resize_zero_external diff --git a/thread_pthread.c b/thread_pthread.c index e624555c682e52..789fd1989de7dc 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -3299,18 +3299,26 @@ static struct { } wheel[TIMER_WHEEL_LEVELS]; uint64_t wheel_cursor_tick; // slots for ticks <= this are drained rb_hrtime_t next_expiry; // never later than the earliest deadline - struct ccan_list_head waiting_untimed; - pthread_mutex_t waiting_lock; + pthread_mutex_t waiting_lock; // the wheel and the flags of fd-less timed waits + + /* The fd map entries and the flags of io waits are guarded per fd by a + * shard lock, so unrelated fds register and wake in parallel. Whoever + * clears an io wait's flags under its shard owns the wakeup. Lock order: + * shard -> waiting_lock -> wake_pending_lock, never the other way. */ +#define IO_WAIT_SHARDS 16 + pthread_mutex_t fd_shard_locks[IO_WAIT_SHARDS]; // signaled when wake_pending clears on a thread; see timer_thread_wake_fence + pthread_mutex_t wake_pending_lock; rb_nativethread_cond_t wake_pending_cond; #endif #if (HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H) && USE_MN_THREADS - // fd -> struct rb_fd_waiters, in chunks so entries never move. - // Protected by waiting_lock. - struct rb_fd_waiters **fdmap_chunks; - unsigned int fdmap_nchunks; + // fd -> struct rb_fd_waiters, in chunks so entries never move. The chunk + // table installs slots by CAS (chunks span shards); entry state is guarded + // by the fd's shard lock. +#define FDMAP_MAX_CHUNKS 1024 // fds up to FDMAP_MAX_CHUNKS * FDMAP_CHUNK_SIZE + struct rb_fd_waiters *fdmap_chunks[FDMAP_MAX_CHUNKS]; #endif } timer_th = { .created_fork_gen = 0, @@ -3539,6 +3547,21 @@ rb_thread_create_timer_thread(void) RUBY_DEBUG_LOG("forked child process"); CLOSE_INVALIDATE_PAIR(timer_th.comm_fds); +#if USE_MN_THREADS + // The parent's waiters do not exist in the child, and the armings + // belong to the closed event backend: a stale entry would satisfy + // fd_waiters_arm's want == armed_flags check and never arm the new + // backend (a lost wake the old dynamic map also had). + for (unsigned int ci = 0; ci < FDMAP_MAX_CHUNKS; ci++) { + struct rb_fd_waiters *chunk = timer_th.fdmap_chunks[ci]; + if (chunk == NULL) continue; + for (unsigned int i = 0; i < FDMAP_CHUNK_SIZE; i++) { + ccan_list_head_init(&chunk[i].waiters); + chunk[i].armed_flags = 0; + chunk[i].generation++; + } + } +#endif #if HAVE_SYS_EPOLL_H && USE_MN_THREADS close_invalidate(&timer_th.event_fd, "close event_fd"); #elif HAVE_SYS_EVENT_H && USE_MN_THREADS @@ -3561,8 +3584,11 @@ rb_thread_create_timer_thread(void) } timer_th.wheel_cursor_tick = timer_wheel_tick(rb_hrtime_now()); timer_th.next_expiry = TIMER_WHEEL_NO_EXPIRY; - ccan_list_head_init(&timer_th.waiting_untimed); rb_native_mutex_initialize(&timer_th.waiting_lock); + for (int i = 0; i < IO_WAIT_SHARDS; i++) { + rb_native_mutex_initialize(&timer_th.fd_shard_locks[i]); + } + rb_native_mutex_initialize(&timer_th.wake_pending_lock); rb_native_cond_initialize(&timer_th.wake_pending_cond); #endif diff --git a/thread_pthread.h b/thread_pthread.h index 4d34941647dd13..040ae55be4697e 100644 --- a/thread_pthread.h +++ b/thread_pthread.h @@ -95,8 +95,9 @@ struct rb_thread_sched_item { struct rb_thread_sched_waiting waiting_reason; uint32_t event_serial; - // the timer thread has a wake pending for this thread; under waiting_lock - bool wake_pending; + // wakes pending on this thread (timer thread or an fd shard claim); + // under timer_th.wake_pending_lock + uint32_t wake_pending_cnt; // parked on its own condvar with a deadline; under the sched lock (see // ubf_waiting). Always false for an M:N thread: its deadline lives on the diff --git a/thread_pthread_mn.c b/thread_pthread_mn.c index 4226a39cb045db..f2f0860f8cfdda 100644 --- a/thread_pthread_mn.c +++ b/thread_pthread_mn.c @@ -23,6 +23,23 @@ thread_sched_waiting_thread(struct rb_thread_sched_waiting *w) } } +#define FD_WAIT_IO_MASK (thread_sched_waiting_io_read | thread_sched_waiting_io_write) + +// Guards the fd map entries and the flags of io waits for its fds. Whoever +// clears an io wait's flags under its shard owns that wakeup. +// Lock order: shard -> waiting_lock -> wake_pending_lock. +static void +fd_shard_lock(int fd) +{ + rb_native_mutex_lock(&timer_th.fd_shard_locks[(unsigned int)fd % IO_WAIT_SHARDS]); +} + +static void +fd_shard_unlock(int fd) +{ + rb_native_mutex_unlock(&timer_th.fd_shard_locks[(unsigned int)fd % IO_WAIT_SHARDS]); +} + #define TIMER_WHEEL_NO_EXPIRY RB_HRTIME_MAX #ifndef TIMER_WHEEL_TICK_MS #define TIMER_WHEEL_TICK_MS 1 // L0 slot width; coarser trades sleep accuracy for fewer drains @@ -178,20 +195,13 @@ timer_wheel_drain(rb_hrtime_t now, uint64_t now_tick, struct ccan_list_head *exp struct rb_thread_sched_waiting *w; while ((w = ccan_list_pop(&pending, struct rb_thread_sched_waiting, node)) != NULL) { if (timer_thread_check_exceed(w->data.timeout, now)) { - rb_thread_t *th = thread_sched_waiting_thread(w); + RUBY_DEBUG_LOG("expired th:%u", rb_th_serial(thread_sched_waiting_thread(w))); - RUBY_DEBUG_LOG("wakeup th:%u", rb_th_serial(th)); - -#if HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H - // An fd+timeout waiter is also on its fd's waiter list. - timer_thread_unregister_waiting(th, w->data.fd, w->flags); -#endif - /* flags stay set until the wakeup below takes them under - * the lock: a waiter whose flags are already cleared may + /* flags stay set until the wakeup takes them under the + * owning lock (the fd shard for io waits, this one + * otherwise): a waiter whose flags are already cleared may * run and re-register through this same `w`, which would * relink the node we are still holding on `expired`. */ - w->data.result = 0; - ccan_list_add_tail(expired, &w->node); } else { @@ -263,39 +273,48 @@ timer_thread_wakeup_thread(rb_thread_t *th, uint32_t event_serial) // One thread the timer thread is about to wake, with the serial it was armed at. struct timer_wake { rb_thread_t *th; uint32_t serial; }; -// Mark each thread while a wake is pending for it, so a dying thread can wait -// (timer_thread_wake_fence). Set under waiting_lock before the lock is dropped. +// Count a pending wake against a thread, so a dying thread can wait them out +// (timer_thread_wake_fence). A count, not a flag: an expiry hold and an fd +// event's wake can be pending on one thread at once. Take it while the lock +// that pinned the thread (shard or waiting_lock) is still held. static void -timer_wake_pending_set(struct timer_wake *batch, int n) +timer_wake_pending_inc(rb_thread_t *th) { - for (int i = 0; i < n; i++) { - batch[i].th->sched.wake_pending = true; - } + rb_native_mutex_lock(&timer_th.wake_pending_lock); + th->sched.wake_pending_cnt++; + rb_native_mutex_unlock(&timer_th.wake_pending_lock); +} + +static void +timer_wake_pending_dec(rb_thread_t *th) +{ + rb_native_mutex_lock(&timer_th.wake_pending_lock); + VM_ASSERT(th->sched.wake_pending_cnt > 0); + th->sched.wake_pending_cnt--; + rb_native_cond_broadcast(&timer_th.wake_pending_cond); + rb_native_mutex_unlock(&timer_th.wake_pending_lock); } static void timer_wake_pending_clear(struct timer_wake *batch, int n) { - rb_native_mutex_lock(&timer_th.waiting_lock); for (int i = 0; i < n; i++) { - batch[i].th->sched.wake_pending = false; + timer_wake_pending_dec(batch[i].th); } - rb_native_cond_broadcast(&timer_th.wake_pending_cond); - rb_native_mutex_unlock(&timer_th.waiting_lock); } -// Wait out a pending wake before a thread is freed: it would touch freed memory, -// or wake a reused thread whose first serial matches the stale entry. +// Wait out pending wakes before a thread is freed: they would touch freed +// memory, or wake a reused thread whose first serial matches a stale entry. static void timer_thread_wake_fence(rb_thread_t *th) { if (!TIMER_THREAD_CREATED_P()) return; - rb_native_mutex_lock(&timer_th.waiting_lock); - while (th->sched.wake_pending) { - rb_native_cond_wait(&timer_th.wake_pending_cond, &timer_th.waiting_lock); + rb_native_mutex_lock(&timer_th.wake_pending_lock); + while (th->sched.wake_pending_cnt > 0) { + rb_native_cond_wait(&timer_th.wake_pending_cond, &timer_th.wake_pending_lock); } - rb_native_mutex_unlock(&timer_th.waiting_lock); + rb_native_mutex_unlock(&timer_th.wake_pending_lock); } static void @@ -312,6 +331,8 @@ timer_thread_check_timeout(rb_vm_t *vm) while (more) { int n = 0; + struct { rb_thread_t *th; uint32_t serial; int fd; } io_claims[TIMEOUT_WAKE_BATCH]; + int n_io = 0; rb_native_mutex_lock(&timer_th.waiting_lock); { @@ -321,18 +342,37 @@ timer_thread_check_timeout(rb_vm_t *vm) } struct rb_thread_sched_waiting *w; - while (n < TIMEOUT_WAKE_BATCH && + while (n + n_io < TIMEOUT_WAKE_BATCH && (w = ccan_list_pop(&expired, struct rb_thread_sched_waiting, node)) != NULL) { // Name the thread and its serial here, then release it: once the // flags are clear the thread may run and re-register through `w`, // and a serial read after that would match the new registration. - batch[n].th = thread_sched_waiting_thread(w); - batch[n].serial = w->data.event_serial; - w->flags = thread_sched_waiting_none; - n++; + // the pop leaves the node dangling; a concurrent claimer's + // wheel del (under this lock) must find it self-linked + ccan_list_node_init(&w->node); + + if (w->flags & FD_WAIT_IO_MASK) { + // The fd shard owns these flags; claim below, once this + // lock is dropped. The pending count pins the thread: it + // was parked when we popped its node (a claimed entry + // leaves the wheel before its thread can wake). + io_claims[n_io].th = thread_sched_waiting_thread(w); + io_claims[n_io].serial = w->data.event_serial; + io_claims[n_io].fd = w->data.fd; + timer_wake_pending_inc(io_claims[n_io].th); + n_io++; + } + else { + // pin before the flags clear; see timer_thread_wake_fd_waiters + batch[n].th = thread_sched_waiting_thread(w); + batch[n].serial = w->data.event_serial; + timer_wake_pending_inc(batch[n].th); + w->flags = thread_sched_waiting_none; + w->data.result = 0; + n++; + } } more = !ccan_list_empty(&expired); - timer_wake_pending_set(batch, n); } rb_native_mutex_unlock(&timer_th.waiting_lock); @@ -340,26 +380,77 @@ timer_thread_check_timeout(rb_vm_t *vm) timer_thread_wakeup_thread(batch[i].th, batch[i].serial); } timer_wake_pending_clear(batch, n); + + // The io entries race the fd event and the ubf for their flags. + for (int i = 0; i < n_io; i++) { + rb_thread_t *th = io_claims[i].th; + int fd = io_claims[i].fd; + bool claimed = false; + + fd_shard_lock(fd); + { + struct rb_thread_sched_waiting *w = &th->sched.waiting_reason; + + if (w->flags != thread_sched_waiting_none && + w->data.event_serial == io_claims[i].serial) { + VM_ASSERT(w->data.fd == fd); + timer_thread_unregister_waiting(th, fd, w->flags); + w->flags = thread_sched_waiting_none; + w->data.result = 0; + claimed = true; + } + } + fd_shard_unlock(fd); + + if (claimed) { + timer_thread_wakeup_thread(th, io_claims[i].serial); + } + timer_wake_pending_dec(th); + } } } static bool timer_thread_cancel_waiting(rb_thread_t *th) { - bool canceled = false; + struct rb_thread_sched_waiting *w = &th->sched.waiting_reason; - rb_native_mutex_lock(&timer_th.waiting_lock); - { - if (th->sched.waiting_reason.flags) { - canceled = true; - timer_wheel_del(&th->sched.waiting_reason); - timer_thread_unregister_waiting(th, th->sched.waiting_reason.data.fd, th->sched.waiting_reason.flags); - th->sched.waiting_reason.flags = thread_sched_waiting_none; + while (1) { + // Racy routing read; the claim is re-verified under the owning lock. + enum thread_sched_waiting_flag flags = w->flags; + + if (flags == thread_sched_waiting_none) { + return false; + } + else if (flags & FD_WAIT_IO_MASK) { + int fd = w->data.fd; + + fd_shard_lock(fd); + if ((w->flags & FD_WAIT_IO_MASK) && w->data.fd == fd) { + if (w->flags & thread_sched_waiting_timeout) { + rb_native_mutex_lock(&timer_th.waiting_lock); + timer_wheel_del(w); + rb_native_mutex_unlock(&timer_th.waiting_lock); + } + timer_thread_unregister_waiting(th, fd, w->flags); + w->flags = thread_sched_waiting_none; + fd_shard_unlock(fd); + return true; + } + fd_shard_unlock(fd); + } + else { + rb_native_mutex_lock(&timer_th.waiting_lock); + if (w->flags && !(w->flags & FD_WAIT_IO_MASK)) { + timer_wheel_del(w); + w->flags = thread_sched_waiting_none; + rb_native_mutex_unlock(&timer_th.waiting_lock); + return true; + } + rb_native_mutex_unlock(&timer_th.waiting_lock); } + // lost the routing race; look again } - rb_native_mutex_unlock(&timer_th.waiting_lock); - - return canceled; } static void @@ -1041,52 +1132,45 @@ native_thread_create_shared(rb_thread_t *th) /// -- a reader and a writer on one socket -- so the backend is armed with their /// union, and an event is dispatched to every waiter it concerns. -#define FD_WAIT_IO_MASK (thread_sched_waiting_io_read | thread_sched_waiting_io_write) +// (FD_WAIT_IO_MASK and the fd shard helpers are defined near the top.) #define FDMAP_CHUNK_BITS 10 #define FDMAP_CHUNK_SIZE (1u << FDMAP_CHUNK_BITS) #define FDMAP_CHUNK_MASK (FDMAP_CHUNK_SIZE - 1) -// timer_th.waiting_lock must be held. +// Callable under any fd shard lock: a chunk spans every shard, so the chunk +// table is not guarded by them; slots install by CAS and are never freed. static struct rb_fd_waiters * fd_waiters_lookup(int fd, bool create) { if (fd < 0) return NULL; unsigned int ci = (unsigned int)fd >> FDMAP_CHUNK_BITS; + if (ci >= FDMAP_MAX_CHUNKS) return NULL; // the caller falls back to a blocking wait - if (ci >= timer_th.fdmap_nchunks) { - if (!create) return NULL; - - unsigned int n = timer_th.fdmap_nchunks ? timer_th.fdmap_nchunks : 8; - while (n <= ci) n *= 2; - - // Only the chunk pointers are reallocated; the entries themselves never - // move, so the list heads inside them stay valid. - struct rb_fd_waiters **chunks = realloc(timer_th.fdmap_chunks, sizeof(*chunks) * n); - if (chunks == NULL) rb_bug("fd_waiters_lookup: realloc failed"); + struct rb_fd_waiters *chunk = RUBY_ATOMIC_PTR_LOAD(timer_th.fdmap_chunks[ci]); - for (unsigned int i = timer_th.fdmap_nchunks; i < n; i++) chunks[i] = NULL; - timer_th.fdmap_chunks = chunks; - timer_th.fdmap_nchunks = n; - } - - if (timer_th.fdmap_chunks[ci] == NULL) { + if (chunk == NULL) { if (!create) return NULL; - struct rb_fd_waiters *chunk = calloc(FDMAP_CHUNK_SIZE, sizeof(*chunk)); + chunk = calloc(FDMAP_CHUNK_SIZE, sizeof(*chunk)); if (chunk == NULL) rb_bug("fd_waiters_lookup: calloc failed"); for (unsigned int i = 0; i < FDMAP_CHUNK_SIZE; i++) { ccan_list_head_init(&chunk[i].waiters); } - timer_th.fdmap_chunks[ci] = chunk; + + struct rb_fd_waiters *prev = RUBY_ATOMIC_PTR_CAS(timer_th.fdmap_chunks[ci], NULL, chunk); + if (prev != NULL) { + free(chunk); // another shard installed it first + chunk = prev; + } } - return &timer_th.fdmap_chunks[ci][(unsigned int)fd & FDMAP_CHUNK_MASK]; + return &chunk[(unsigned int)fd & FDMAP_CHUNK_MASK]; } -// timer_th.waiting_lock must be held. +// The fd's shard lock must be held. static uint32_t fd_waiters_union(struct rb_fd_waiters *e) { @@ -1112,7 +1196,7 @@ 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. -// timer_th.waiting_lock must be held. +// The fd's shard lock must be held. static bool fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want) { @@ -1247,18 +1331,16 @@ verify_waiting_list(void) VM_ASSERT(occupied == !ccan_list_empty(&lv->slots[slot])); ccan_list_for_each(&lv->slots[slot], w, node) { - VM_ASSERT(w->flags & thread_sched_waiting_timeout); - VM_ASSERT(w->data.timeout != 0); + // an io entry's flags belong to its fd shard: do not read them here + if (!(w->flags & FD_WAIT_IO_MASK)) { + VM_ASSERT(w->flags & thread_sched_waiting_timeout); + VM_ASSERT(w->data.timeout != 0); + } VM_ASSERT(w->wheel_lvl == lvl); VM_ASSERT(w->wheel_slot == slot); } } } - - ccan_list_for_each(&timer_th.waiting_untimed, w, node) { - VM_ASSERT(!(w->flags & thread_sched_waiting_timeout)); - VM_ASSERT(w->data.timeout == 0); - } #endif } @@ -1359,66 +1441,85 @@ timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting VM_ASSERT(fd >= 0); } - rb_native_mutex_lock(&timer_th.waiting_lock); - { - if (flags & FD_WAIT_IO_MASK) { - VM_ASSERT(th != NULL); - + if (flags & FD_WAIT_IO_MASK) { + fd_shard_lock(fd); + { struct rb_fd_waiters *e = fd_waiters_lookup(fd, true); + if (e == NULL) { // fd beyond the map: fall back to a blocking wait + fd_shard_unlock(fd); + return timer_thread_unavailable; + } + // 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))) { - rb_native_mutex_unlock(&timer_th.waiting_lock); + fd_shard_unlock(fd); return timer_thread_unavailable; } - ccan_list_add_tail(&e->waiters, &th->sched.waiting_reason.fd_node); - RUBY_DEBUG_LOG("armed fd:%d want:%u", fd, e->armed_flags); - } + if (th) { + ccan_list_add_tail(&e->waiters, &th->sched.waiting_reason.fd_node); - if (th) { - VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none); - - // setup waiting information - { + VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none); th->sched.waiting_reason.flags = flags; th->sched.waiting_reason.data.timeout = abs; th->sched.waiting_reason.data.fd = fd; th->sched.waiting_reason.data.result = 0; th->sched.waiting_reason.data.event_serial = event_serial; - } - if (abs == 0) { // no timeout - VM_ASSERT(!(flags & thread_sched_waiting_timeout)); - ccan_list_add_tail(&timer_th.waiting_untimed, &th->sched.waiting_reason.node); - } - else { - RUBY_DEBUG_LOG("abs:%lu", (unsigned long)abs); - VM_ASSERT(flags & thread_sched_waiting_timeout); - - rb_hrtime_t prev_expiry = timer_th.next_expiry; - timer_wheel_insert(&th->sched.waiting_reason); - - verify_waiting_list(); - - if (timer_th.next_expiry < prev_expiry) { - // an earlier deadline than the timer thread is armed for - timer_thread_wakeup_force(); + if (abs != 0) { + RUBY_DEBUG_LOG("abs:%lu", (unsigned long)abs); + VM_ASSERT(flags & thread_sched_waiting_timeout); + + rb_native_mutex_lock(&timer_th.waiting_lock); + { + rb_hrtime_t prev_expiry = timer_th.next_expiry; + timer_wheel_insert(&th->sched.waiting_reason); + verify_waiting_list(); + if (timer_th.next_expiry < prev_expiry) { + // an earlier deadline than the timer thread is armed for + timer_thread_wakeup_force(); + } + } + rb_native_mutex_unlock(&timer_th.waiting_lock); } } + RUBY_DEBUG_LOG("armed fd:%d want:%u", fd, e->armed_flags); } - else { - VM_ASSERT(abs == 0); + fd_shard_unlock(fd); + } + else if (th) { + // fd-less timed wait: the wheel lock owns it end to end + VM_ASSERT(abs != 0 && (flags & thread_sched_waiting_timeout)); + + rb_native_mutex_lock(&timer_th.waiting_lock); + { + VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none); + th->sched.waiting_reason.flags = flags; + th->sched.waiting_reason.data.timeout = abs; + th->sched.waiting_reason.data.fd = fd; + th->sched.waiting_reason.data.result = 0; + th->sched.waiting_reason.data.event_serial = event_serial; + + rb_hrtime_t prev_expiry = timer_th.next_expiry; + timer_wheel_insert(&th->sched.waiting_reason); + verify_waiting_list(); + if (timer_th.next_expiry < prev_expiry) { + timer_thread_wakeup_force(); + } } + rb_native_mutex_unlock(&timer_th.waiting_lock); + } + else { + VM_ASSERT(abs == 0); } - rb_native_mutex_unlock(&timer_th.waiting_lock); return timer_thread_registered; } // Drop `th` from its fd's waiter list and re-arm the backend for whoever is -// left. timer_th.waiting_lock must be held. +// left. The fd's shard lock must be held. static void timer_thread_unregister_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags) { @@ -1502,11 +1603,11 @@ timer_thread_wake_fd_waiters(int fd, uint32_t generation, uint32_t wake_flags, i if (wake_flags == 0) return; - for (;;) { + while (1) { int n = 0; bool more = false; - rb_native_mutex_lock(&timer_th.waiting_lock); + fd_shard_lock(fd); { struct rb_fd_waiters *e = fd_waiters_lookup(fd, false); @@ -1524,25 +1625,34 @@ timer_thread_wake_fd_waiters(int fd, uint32_t generation, uint32_t wake_flags, i } ccan_list_del_init(&w->fd_node); - timer_wheel_del(w); // also leaves the timer wheel - w->flags = thread_sched_waiting_none; - w->data.fd = -1; - w->data.result = result; + if (w->flags & thread_sched_waiting_timeout) { + // also leaves the timer wheel (or the expiry pass's + // batch, whose claim will then find the flags gone) + rb_native_mutex_lock(&timer_th.waiting_lock); + timer_wheel_del(w); + rb_native_mutex_unlock(&timer_th.waiting_lock); + } + // The pin must be visible before the flags clear: a waiter + // that sees the clear may skip parking, finish and die, + // and the fence only waits on the pending count. batch[n].th = thread_sched_waiting_thread(w); batch[n].serial = w->data.event_serial; + timer_wake_pending_inc(batch[n].th); n++; + + w->flags = thread_sched_waiting_none; + w->data.fd = -1; + w->data.result = result; } // 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)); } - - timer_wake_pending_set(batch, n); } - rb_native_mutex_unlock(&timer_th.waiting_lock); + fd_shard_unlock(fd); for (int i = 0; i < n; i++) { timer_thread_wakeup_thread(batch[i].th, batch[i].serial); diff --git a/vm.c b/vm.c index 749fe5cc1a760c..f0bebc00bd7ada 100644 --- a/vm.c +++ b/vm.c @@ -1301,7 +1301,7 @@ vm_proc_create_from_captured(VALUE klass, enum rb_block_type block_type, int8_t is_from_method, int8_t is_lambda) { - VALUE procval = rb_proc_alloc(klass); + VALUE procval = rb_proc_alloc(klass, block_type); rb_proc_t *proc = RTYPEDDATA_DATA(procval); VM_ASSERT(VM_EP_IN_HEAP_P(GET_EC(), captured->ep)); @@ -1312,8 +1312,8 @@ vm_proc_create_from_captured(VALUE klass, rb_vm_block_ep_update(procval, &proc->block, captured->ep); vm_block_type_set(&proc->block, block_type); - proc->is_from_method = is_from_method; - proc->is_lambda = is_lambda; + proc->header.is_from_method = is_from_method; + proc->header.is_lambda = is_lambda; return procval; } @@ -1341,14 +1341,14 @@ rb_vm_block_copy(VALUE obj, const struct rb_block *dst, const struct rb_block *s static VALUE proc_create(VALUE klass, const struct rb_block *block, int8_t is_from_method, int8_t is_lambda) { - VALUE procval = rb_proc_alloc(klass); + VALUE procval = rb_proc_alloc(klass, block->type); rb_proc_t *proc = RTYPEDDATA_DATA(procval); VM_ASSERT(VM_EP_IN_HEAP_P(GET_EC(), vm_block_ep(block))); rb_vm_block_copy(procval, &proc->block, block); vm_block_type_set(&proc->block, block->type); - proc->is_from_method = is_from_method; - proc->is_lambda = is_lambda; + proc->header.is_from_method = is_from_method; + proc->header.is_lambda = is_lambda; return procval; } @@ -1366,14 +1366,14 @@ rb_proc_dup_0(VALUE self) procval = rb_func_proc_dup(self); break; default: - procval = proc_create(rb_obj_class(self), &src->block, src->is_from_method, src->is_lambda); + procval = proc_create(rb_obj_class(self), &src->block, src->header.is_from_method, src->header.is_lambda); break; } - if (src->is_refined) { + if (src->header.is_refined) { rb_proc_t *dst; GetProcPtr(procval, dst); - dst->is_refined = 1; + dst->header.is_refined = 1; } if (RB_OBJ_SHAREABLE_P(self)) RB_OBJ_SET_SHAREABLE(procval); @@ -1403,7 +1403,7 @@ rb_proc_dup_with_iseq_and_recipe(VALUE self, const rb_iseq_t *iseq, VALUE recipe struct rb_block block = src->block; block.as.captured.code.iseq = iseq; - VALUE procval = proc_create(rb_obj_class(self), &block, src->is_from_method, src->is_lambda); + VALUE procval = proc_create(rb_obj_class(self), &block, src->header.is_from_method, src->header.is_lambda); rb_proc_set_refinements_recipe(procval, recipe); RB_GC_GUARD(self); @@ -1587,19 +1587,19 @@ rb_proc_isolate_bang(VALUE self, VALUE replace_self) if (iseq) { rb_proc_t *proc = (rb_proc_t *)RTYPEDDATA_DATA(self); + if (proc->block.type != block_type_iseq) rb_raise(rb_eRuntimeError, "not supported yet"); + if (!UNDEF_P(replace_self)) { VM_ASSERT(rb_ractor_shareable_p(replace_self)); RB_OBJ_WRITE(self, &proc->block.as.captured.self, replace_self); } - if (proc->block.type != block_type_iseq) rb_raise(rb_eRuntimeError, "not supported yet"); - if (ISEQ_BODY(iseq)->outer_variables) { proc_shared_outer_variables(ISEQ_BODY(iseq)->outer_variables, true, "isolate a Proc"); } proc_isolate_env(self, proc, Qfalse); - proc->is_isolated = TRUE; + proc->header.is_isolated = TRUE; RB_OBJ_WRITE(self, &proc->block.as.captured.self, Qnil); } @@ -1623,12 +1623,12 @@ rb_proc_ractor_make_shareable(VALUE self, VALUE replace_self) if (iseq) { rb_proc_t *proc = (rb_proc_t *)RTYPEDDATA_DATA(self); + if (proc->block.type != block_type_iseq) rb_raise(rb_eRuntimeError, "not supported yet"); + if (!UNDEF_P(replace_self)) { RB_OBJ_WRITE(self, &proc->block.as.captured.self, replace_self); } - if (proc->block.type != block_type_iseq) rb_raise(rb_eRuntimeError, "not supported yet"); - if (!rb_ractor_shareable_p(vm_block_self(&proc->block))) { rb_raise(rb_eRactorIsolationError, "Proc's self is not shareable: %" PRIsVALUE, @@ -1643,7 +1643,7 @@ rb_proc_ractor_make_shareable(VALUE self, VALUE replace_self) } proc_isolate_env(self, proc, read_only_variables); - proc->is_isolated = TRUE; + proc->header.is_isolated = TRUE; } else { const struct rb_block *block = vm_proc_block(self); @@ -1896,9 +1896,9 @@ invoke_block_from_c_bh(rb_execution_context_t *ec, VALUE block_handler, VALUE procval = VM_BH_TO_PROC(block_handler); rb_proc_t *po; GetProcPtr(procval, po); - if (po->is_refined) cref = rb_proc_refinements_cref_for_call(procval); + if (po->header.is_refined) cref = rb_proc_refinements_cref_for_call(procval); if (force_blockarg == FALSE) { - is_lambda = po->is_lambda; + is_lambda = po->header.is_lambda; } block_handler = vm_block_to_block_handler(&po->block); goto again; @@ -1999,7 +1999,7 @@ vm_invoke_proc(rb_execution_context_t *ec, rb_proc_t *proc, VALUE self, int argc, const VALUE *argv, int kw_splat, VALUE passed_block_handler, const rb_cref_t *cref) { - return invoke_block_from_c_proc(ec, proc, self, argc, argv, kw_splat, passed_block_handler, proc->is_lambda, cref, NULL); + return invoke_block_from_c_proc(ec, proc, self, argc, argv, kw_splat, passed_block_handler, proc->header.is_lambda, cref, NULL); } static VALUE @@ -2018,7 +2018,7 @@ rb_vm_invoke_proc(rb_execution_context_t *ec, rb_proc_t *proc, VALUE self = vm_block_self(&proc->block); vm_block_handler_verify(passed_block_handler); - if (proc->is_from_method) { + if (proc->header.is_from_method) { return vm_invoke_bmethod(ec, proc, self, argc, argv, kw_splat, passed_block_handler, NULL); } else { @@ -2033,7 +2033,7 @@ rb_vm_invoke_proc_with_self(rb_execution_context_t *ec, rb_proc_t *proc, VALUE s { vm_block_handler_verify(passed_block_handler); - if (proc->is_from_method) { + if (proc->header.is_from_method) { return vm_invoke_bmethod(ec, proc, self, argc, argv, kw_splat, passed_block_handler, NULL); } else { diff --git a/vm_core.h b/vm_core.h index e867230f66d475..3dbfe7442a7f1d 100644 --- a/vm_core.h +++ b/vm_core.h @@ -995,12 +995,12 @@ enum rb_block_type { }; struct rb_block { + enum rb_block_type type : 8; union { struct rb_captured_block captured; VALUE symbol; VALUE proc; } as; - enum rb_block_type type; }; typedef struct rb_control_frame_struct { @@ -1398,13 +1398,44 @@ extern const rb_data_type_t ruby_proc_data_type; GetCoreDataFromValue((obj), rb_proc_t, &ruby_proc_data_type, (ptr)) typedef struct { - const struct rb_block block; + enum rb_block_type type : 8; unsigned int is_from_method: 1; /* bool */ unsigned int is_lambda: 1; /* bool */ unsigned int is_isolated: 1; /* bool */ unsigned int is_refined: 1; /* bool: Proc#refined */ +} rb_proc_header_t; + +typedef struct { + rb_proc_header_t header; + struct rb_captured_block captured; +} rb_proc_captured_t; + +typedef struct { + rb_proc_header_t header; + VALUE symbol; +} rb_proc_symbol_t; + +typedef struct { + rb_proc_header_t header; + VALUE proc; +} rb_proc_proc_t; + +/* A Proc of any block type. */ +typedef union { + const struct rb_block block; + rb_proc_header_t header; + rb_proc_captured_t captured; + rb_proc_symbol_t symbol; + rb_proc_proc_t proc; } rb_proc_t; +STATIC_ASSERT(rb_proc_captured_offset, + offsetof(rb_proc_captured_t, captured) == offsetof(rb_proc_t, block.as.captured)); +STATIC_ASSERT(rb_proc_symbol_offset, + offsetof(rb_proc_symbol_t, symbol) == offsetof(rb_proc_t, block.as.symbol)); +STATIC_ASSERT(rb_proc_proc_offset, + offsetof(rb_proc_proc_t, proc) == offsetof(rb_proc_t, block.as.proc)); + /* A refined proc's refinements recipe (see Proc#refined) lives in a hidden * ivar on the proc object; the accessors return nil/NULL unless is_refined is * set. rb_proc_refinements_cref_for_call also makes the copy of the block @@ -2046,7 +2077,7 @@ VM_BH_FROM_PROC(VALUE procval) /* VM related object allocate functions */ VALUE rb_thread_alloc(VALUE klass); VALUE rb_binding_alloc(VALUE klass); -VALUE rb_proc_alloc(VALUE klass); +VALUE rb_proc_alloc(VALUE klass, enum rb_block_type block_type); VALUE rb_proc_dup(VALUE self); VALUE rb_proc_dup_0(VALUE self); diff --git a/vm_eval.c b/vm_eval.c index 372a9549d58e42..eccf3bf8256909 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -2239,8 +2239,8 @@ yield_under(VALUE self, int singleton, int argc, const VALUE *argv, int kw_splat VALUE procval = VM_BH_TO_PROC(block_handler); rb_proc_t *po; GetProcPtr(procval, po); - is_lambda = po->is_lambda; - if (po->is_refined) proc_cref = rb_proc_refinements_cref_for_call(procval); + is_lambda = po->header.is_lambda; + if (po->header.is_refined) proc_cref = rb_proc_refinements_cref_for_call(procval); block_handler = vm_block_to_block_handler(&po->block); } goto again; diff --git a/vm_insnhelper.c b/vm_insnhelper.c index ce8e21d0fde2ff..15e21792c36a60 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -5205,7 +5205,7 @@ block_proc_is_lambda(const VALUE procval) if (procval) { GetProcPtr(procval, proc); - return proc->is_lambda; + return proc->header.is_lambda; } else { return 0; @@ -5497,8 +5497,8 @@ vm_invoke_proc_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, VALUE procval = VM_BH_TO_PROC(block_handler); rb_proc_t *po; GetProcPtr(procval, po); - if (po->is_refined) refined_procval = procval; - is_lambda = po->is_lambda; + if (po->header.is_refined) refined_procval = procval; + is_lambda = po->header.is_lambda; block_handler = vm_block_to_block_handler(&po->block); } diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 93dc9b4be6a5b4..1b1a833eed9216 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -266,6 +266,7 @@ fn main() { .allowlist_function("rb_RSTRING_LEN") .allowlist_function("rb_ENCODING_GET") .allowlist_function("rb_jit_get_proc_ptr") + .allowlist_type("rb_block_type") .allowlist_function("rb_yjit_exit_locations_dict") .allowlist_function("rb_jit_icache_invalidate") .allowlist_function("rb_optimized_call") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 2fefb0319406bc..9483b715a69671 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -7450,9 +7450,9 @@ fn gen_send_bmethod( let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) }; let proc = unsafe { rb_jit_get_proc_ptr(procv) }; - let proc_block = unsafe { &(*proc).block }; + let proc_block = unsafe { (*proc).block.as_ref() }; - if proc_block.type_ != block_type_iseq { + if proc_block.type_() != block_type_iseq { return None; } diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 75ff2642244b79..e00aec76e277e9 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -517,8 +517,9 @@ pub const block_type_proc: rb_block_type = 3; pub type rb_block_type = u32; #[repr(C)] pub struct rb_block { + pub _bitfield_align_1: [u8; 0], + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>, pub as_: rb_block__bindgen_ty_1, - pub type_: rb_block_type, } #[repr(C)] pub struct rb_block__bindgen_ty_1 { @@ -527,86 +528,149 @@ pub struct rb_block__bindgen_ty_1 { pub proc_: __BindgenUnionField, pub bindgen_union_field: [u64; 3usize], } +impl rb_block { + #[inline] + pub fn type_(&self) -> rb_block_type { + unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 8u8) as u32) } + } + #[inline] + pub fn set_type(&mut self, val: rb_block_type) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(0usize, 8u8, val as u64) + } + } + #[inline] + pub fn new_bitfield_1(type_: rb_block_type) -> __BindgenBitfieldUnit<[u8; 1usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 1usize]> = Default::default(); + __bindgen_bitfield_unit.set(0usize, 8u8, { + let type_: u32 = unsafe { ::std::mem::transmute(type_) }; + type_ as u64 + }); + __bindgen_bitfield_unit + } +} pub type rb_control_frame_t = rb_control_frame_struct; #[repr(C)] -pub struct rb_proc_t { - pub block: rb_block, +#[repr(align(4))] +#[derive(Debug, Copy, Clone)] +pub struct rb_proc_header_t { pub _bitfield_align_1: [u8; 0], - pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>, - pub __bindgen_padding_0: [u8; 7usize], + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 2usize]>, + pub __bindgen_padding_0: u16, } -impl rb_proc_t { +impl rb_proc_header_t { + #[inline] + pub fn type_(&self) -> rb_block_type { + unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 8u8) as u32) } + } + #[inline] + pub fn set_type(&mut self, val: rb_block_type) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(0usize, 8u8, val as u64) + } + } #[inline] pub fn is_from_method(&self) -> ::std::os::raw::c_uint { - unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u32) } + unsafe { ::std::mem::transmute(self._bitfield_1.get(8usize, 1u8) as u32) } } #[inline] pub fn set_is_from_method(&mut self, val: ::std::os::raw::c_uint) { unsafe { let val: u32 = ::std::mem::transmute(val); - self._bitfield_1.set(0usize, 1u8, val as u64) + self._bitfield_1.set(8usize, 1u8, val as u64) } } #[inline] pub fn is_lambda(&self) -> ::std::os::raw::c_uint { - unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 1u8) as u32) } + unsafe { ::std::mem::transmute(self._bitfield_1.get(9usize, 1u8) as u32) } } #[inline] pub fn set_is_lambda(&mut self, val: ::std::os::raw::c_uint) { unsafe { let val: u32 = ::std::mem::transmute(val); - self._bitfield_1.set(1usize, 1u8, val as u64) + self._bitfield_1.set(9usize, 1u8, val as u64) } } #[inline] pub fn is_isolated(&self) -> ::std::os::raw::c_uint { - unsafe { ::std::mem::transmute(self._bitfield_1.get(2usize, 1u8) as u32) } + unsafe { ::std::mem::transmute(self._bitfield_1.get(10usize, 1u8) as u32) } } #[inline] pub fn set_is_isolated(&mut self, val: ::std::os::raw::c_uint) { unsafe { let val: u32 = ::std::mem::transmute(val); - self._bitfield_1.set(2usize, 1u8, val as u64) + self._bitfield_1.set(10usize, 1u8, val as u64) } } #[inline] pub fn is_refined(&self) -> ::std::os::raw::c_uint { - unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u32) } + unsafe { ::std::mem::transmute(self._bitfield_1.get(11usize, 1u8) as u32) } } #[inline] pub fn set_is_refined(&mut self, val: ::std::os::raw::c_uint) { unsafe { let val: u32 = ::std::mem::transmute(val); - self._bitfield_1.set(3usize, 1u8, val as u64) + self._bitfield_1.set(11usize, 1u8, val as u64) } } #[inline] pub fn new_bitfield_1( + type_: rb_block_type, is_from_method: ::std::os::raw::c_uint, is_lambda: ::std::os::raw::c_uint, is_isolated: ::std::os::raw::c_uint, is_refined: ::std::os::raw::c_uint, - ) -> __BindgenBitfieldUnit<[u8; 1usize]> { - let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 1usize]> = Default::default(); - __bindgen_bitfield_unit.set(0usize, 1u8, { + ) -> __BindgenBitfieldUnit<[u8; 2usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 2usize]> = Default::default(); + __bindgen_bitfield_unit.set(0usize, 8u8, { + let type_: u32 = unsafe { ::std::mem::transmute(type_) }; + type_ as u64 + }); + __bindgen_bitfield_unit.set(8usize, 1u8, { let is_from_method: u32 = unsafe { ::std::mem::transmute(is_from_method) }; is_from_method as u64 }); - __bindgen_bitfield_unit.set(1usize, 1u8, { + __bindgen_bitfield_unit.set(9usize, 1u8, { let is_lambda: u32 = unsafe { ::std::mem::transmute(is_lambda) }; is_lambda as u64 }); - __bindgen_bitfield_unit.set(2usize, 1u8, { + __bindgen_bitfield_unit.set(10usize, 1u8, { let is_isolated: u32 = unsafe { ::std::mem::transmute(is_isolated) }; is_isolated as u64 }); - __bindgen_bitfield_unit.set(3usize, 1u8, { + __bindgen_bitfield_unit.set(11usize, 1u8, { let is_refined: u32 = unsafe { ::std::mem::transmute(is_refined) }; is_refined as u64 }); __bindgen_bitfield_unit } } +#[repr(C)] +pub struct rb_proc_captured_t { + pub header: rb_proc_header_t, + pub captured: rb_captured_block, +} +#[repr(C)] +pub struct rb_proc_symbol_t { + pub header: rb_proc_header_t, + pub symbol: VALUE, +} +#[repr(C)] +pub struct rb_proc_proc_t { + pub header: rb_proc_header_t, + pub proc_: VALUE, +} +#[repr(C)] +pub struct rb_proc_t { + pub block: __BindgenUnionField, + pub header: __BindgenUnionField, + pub captured: __BindgenUnionField, + pub symbol: __BindgenUnionField, + pub proc_: __BindgenUnionField, + pub bindgen_union_field: [u64; 4usize], +} pub const VM_CHECKMATCH_TYPE_WHEN: vm_check_match_type = 1; pub const VM_CHECKMATCH_TYPE_CASE: vm_check_match_type = 2; pub const VM_CHECKMATCH_TYPE_RESCUE: vm_check_match_type = 3; diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 773c24aff45d04..ceafd6bbb0ddbf 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -422,6 +422,7 @@ fn main() { .allowlist_function("rb_get_def_iseq_ptr") .allowlist_function("rb_get_def_bmethod_proc") .allowlist_function("rb_jit_get_proc_ptr") + .allowlist_type("rb_block_type") .allowlist_function("rb_iseq_encoded_size") .allowlist_function("rb_get_iseq_body_total_calls") .allowlist_function("rb_get_iseq_body_local_iseq") diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index e39822242b523d..170f597735fbe8 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1656,7 +1656,7 @@ fn gen_push_inline_frame( // Extract EP from the Proc instance let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) }; let proc = unsafe { rb_jit_get_proc_ptr(procv) }; - let proc_block = unsafe { &(*proc).block }; + let proc_block = unsafe { (*proc).block.as_ref() }; let capture = unsafe { proc_block.as_.captured.as_ref() }; let bmethod_frame_type = VM_FRAME_MAGIC_BLOCK | VM_FRAME_FLAG_BMETHOD | VM_FRAME_FLAG_LAMBDA; // Tag the captured EP like VM_GUARDED_PREV_EP() in vm_call_iseq_bmethod() @@ -1796,7 +1796,7 @@ fn gen_send_iseq_direct( // Extract EP from the Proc instance let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) }; let proc = unsafe { rb_jit_get_proc_ptr(procv) }; - let proc_block = unsafe { &(*proc).block }; + let proc_block = unsafe { (*proc).block.as_ref() }; let capture = unsafe { proc_block.as_.captured.as_ref() }; let bmethod_frame_type = VM_FRAME_MAGIC_BLOCK | VM_FRAME_FLAG_BMETHOD | VM_FRAME_FLAG_LAMBDA; // Tag the captured EP like VM_GUARDED_PREV_EP() in vm_call_iseq_bmethod() diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index c2789778fbffac..dfa994c02c5dac 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -102,6 +102,7 @@ pub type RedefinitionFlag = u32; #[allow(unsafe_op_in_unsafe_fn)] #[allow(dead_code)] +#[allow(non_snake_case)] // bindgen names bitfield raw accessors like `type__raw` #[allow(clippy::all)] // warning meant to help with reading; not useful for generated code mod autogened { use super::*; diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 08d4181f77e1f6..437121381c7009 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1326,8 +1326,9 @@ pub const block_type_proc: rb_block_type = 3; pub type rb_block_type = u32; #[repr(C)] pub struct rb_block { + pub _bitfield_align_1: [u8; 0], + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>, pub as_: rb_block__bindgen_ty_1, - pub type_: rb_block_type, } #[repr(C)] pub struct rb_block__bindgen_ty_1 { @@ -1336,6 +1337,50 @@ pub struct rb_block__bindgen_ty_1 { pub proc_: __BindgenUnionField, pub bindgen_union_field: [u64; 3usize], } +impl rb_block { + #[inline] + pub fn type_(&self) -> rb_block_type { + unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 8u8) as u32) } + } + #[inline] + pub fn set_type(&mut self, val: rb_block_type) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(0usize, 8u8, val as u64) + } + } + #[inline] + pub unsafe fn type__raw(this: *const Self) -> rb_block_type { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 1usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 0usize, + 8u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_type_raw(this: *mut Self, val: rb_block_type) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 1usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 0usize, + 8u8, + val as u64, + ) + } + } + #[inline] + pub fn new_bitfield_1(type_: rb_block_type) -> __BindgenBitfieldUnit<[u8; 1usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 1usize]> = Default::default(); + __bindgen_bitfield_unit.set(0usize, 8u8, { + let type_: u32 = unsafe { ::std::mem::transmute(type_) }; + type_ as u64 + }); + __bindgen_bitfield_unit + } +} #[repr(C)] pub struct rb_control_frame_struct { pub pc: *const VALUE, @@ -1348,30 +1393,64 @@ pub struct rb_control_frame_struct { } pub type rb_control_frame_t = rb_control_frame_struct; #[repr(C)] -pub struct rb_proc_t { - pub block: rb_block, +#[repr(align(4))] +#[derive(Debug, Copy, Clone)] +pub struct rb_proc_header_t { pub _bitfield_align_1: [u8; 0], - pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>, - pub __bindgen_padding_0: [u8; 7usize], + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 2usize]>, + pub __bindgen_padding_0: u16, } -impl rb_proc_t { +impl rb_proc_header_t { + #[inline] + pub fn type_(&self) -> rb_block_type { + unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 8u8) as u32) } + } + #[inline] + pub fn set_type(&mut self, val: rb_block_type) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(0usize, 8u8, val as u64) + } + } + #[inline] + pub unsafe fn type__raw(this: *const Self) -> rb_block_type { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 0usize, + 8u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_type_raw(this: *mut Self, val: rb_block_type) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 0usize, + 8u8, + val as u64, + ) + } + } #[inline] pub fn is_from_method(&self) -> ::std::os::raw::c_uint { - unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u32) } + unsafe { ::std::mem::transmute(self._bitfield_1.get(8usize, 1u8) as u32) } } #[inline] pub fn set_is_from_method(&mut self, val: ::std::os::raw::c_uint) { unsafe { let val: u32 = ::std::mem::transmute(val); - self._bitfield_1.set(0usize, 1u8, val as u64) + self._bitfield_1.set(8usize, 1u8, val as u64) } } #[inline] pub unsafe fn is_from_method_raw(this: *const Self) -> ::std::os::raw::c_uint { unsafe { - ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 1usize]>>::raw_get( + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( ::std::ptr::addr_of!((*this)._bitfield_1), - 0usize, + 8usize, 1u8, ) as u32) } @@ -1380,9 +1459,9 @@ impl rb_proc_t { pub unsafe fn set_is_from_method_raw(this: *mut Self, val: ::std::os::raw::c_uint) { unsafe { let val: u32 = ::std::mem::transmute(val); - <__BindgenBitfieldUnit<[u8; 1usize]>>::raw_set( + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( ::std::ptr::addr_of_mut!((*this)._bitfield_1), - 0usize, + 8usize, 1u8, val as u64, ) @@ -1390,21 +1469,21 @@ impl rb_proc_t { } #[inline] pub fn is_lambda(&self) -> ::std::os::raw::c_uint { - unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 1u8) as u32) } + unsafe { ::std::mem::transmute(self._bitfield_1.get(9usize, 1u8) as u32) } } #[inline] pub fn set_is_lambda(&mut self, val: ::std::os::raw::c_uint) { unsafe { let val: u32 = ::std::mem::transmute(val); - self._bitfield_1.set(1usize, 1u8, val as u64) + self._bitfield_1.set(9usize, 1u8, val as u64) } } #[inline] pub unsafe fn is_lambda_raw(this: *const Self) -> ::std::os::raw::c_uint { unsafe { - ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 1usize]>>::raw_get( + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( ::std::ptr::addr_of!((*this)._bitfield_1), - 1usize, + 9usize, 1u8, ) as u32) } @@ -1413,9 +1492,9 @@ impl rb_proc_t { pub unsafe fn set_is_lambda_raw(this: *mut Self, val: ::std::os::raw::c_uint) { unsafe { let val: u32 = ::std::mem::transmute(val); - <__BindgenBitfieldUnit<[u8; 1usize]>>::raw_set( + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( ::std::ptr::addr_of_mut!((*this)._bitfield_1), - 1usize, + 9usize, 1u8, val as u64, ) @@ -1423,21 +1502,21 @@ impl rb_proc_t { } #[inline] pub fn is_isolated(&self) -> ::std::os::raw::c_uint { - unsafe { ::std::mem::transmute(self._bitfield_1.get(2usize, 1u8) as u32) } + unsafe { ::std::mem::transmute(self._bitfield_1.get(10usize, 1u8) as u32) } } #[inline] pub fn set_is_isolated(&mut self, val: ::std::os::raw::c_uint) { unsafe { let val: u32 = ::std::mem::transmute(val); - self._bitfield_1.set(2usize, 1u8, val as u64) + self._bitfield_1.set(10usize, 1u8, val as u64) } } #[inline] pub unsafe fn is_isolated_raw(this: *const Self) -> ::std::os::raw::c_uint { unsafe { - ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 1usize]>>::raw_get( + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( ::std::ptr::addr_of!((*this)._bitfield_1), - 2usize, + 10usize, 1u8, ) as u32) } @@ -1446,9 +1525,9 @@ impl rb_proc_t { pub unsafe fn set_is_isolated_raw(this: *mut Self, val: ::std::os::raw::c_uint) { unsafe { let val: u32 = ::std::mem::transmute(val); - <__BindgenBitfieldUnit<[u8; 1usize]>>::raw_set( + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( ::std::ptr::addr_of_mut!((*this)._bitfield_1), - 2usize, + 10usize, 1u8, val as u64, ) @@ -1456,21 +1535,21 @@ impl rb_proc_t { } #[inline] pub fn is_refined(&self) -> ::std::os::raw::c_uint { - unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u32) } + unsafe { ::std::mem::transmute(self._bitfield_1.get(11usize, 1u8) as u32) } } #[inline] pub fn set_is_refined(&mut self, val: ::std::os::raw::c_uint) { unsafe { let val: u32 = ::std::mem::transmute(val); - self._bitfield_1.set(3usize, 1u8, val as u64) + self._bitfield_1.set(11usize, 1u8, val as u64) } } #[inline] pub unsafe fn is_refined_raw(this: *const Self) -> ::std::os::raw::c_uint { unsafe { - ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 1usize]>>::raw_get( + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( ::std::ptr::addr_of!((*this)._bitfield_1), - 3usize, + 11usize, 1u8, ) as u32) } @@ -1479,9 +1558,9 @@ impl rb_proc_t { pub unsafe fn set_is_refined_raw(this: *mut Self, val: ::std::os::raw::c_uint) { unsafe { let val: u32 = ::std::mem::transmute(val); - <__BindgenBitfieldUnit<[u8; 1usize]>>::raw_set( + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( ::std::ptr::addr_of_mut!((*this)._bitfield_1), - 3usize, + 11usize, 1u8, val as u64, ) @@ -1489,31 +1568,60 @@ impl rb_proc_t { } #[inline] pub fn new_bitfield_1( + type_: rb_block_type, is_from_method: ::std::os::raw::c_uint, is_lambda: ::std::os::raw::c_uint, is_isolated: ::std::os::raw::c_uint, is_refined: ::std::os::raw::c_uint, - ) -> __BindgenBitfieldUnit<[u8; 1usize]> { - let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 1usize]> = Default::default(); - __bindgen_bitfield_unit.set(0usize, 1u8, { + ) -> __BindgenBitfieldUnit<[u8; 2usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 2usize]> = Default::default(); + __bindgen_bitfield_unit.set(0usize, 8u8, { + let type_: u32 = unsafe { ::std::mem::transmute(type_) }; + type_ as u64 + }); + __bindgen_bitfield_unit.set(8usize, 1u8, { let is_from_method: u32 = unsafe { ::std::mem::transmute(is_from_method) }; is_from_method as u64 }); - __bindgen_bitfield_unit.set(1usize, 1u8, { + __bindgen_bitfield_unit.set(9usize, 1u8, { let is_lambda: u32 = unsafe { ::std::mem::transmute(is_lambda) }; is_lambda as u64 }); - __bindgen_bitfield_unit.set(2usize, 1u8, { + __bindgen_bitfield_unit.set(10usize, 1u8, { let is_isolated: u32 = unsafe { ::std::mem::transmute(is_isolated) }; is_isolated as u64 }); - __bindgen_bitfield_unit.set(3usize, 1u8, { + __bindgen_bitfield_unit.set(11usize, 1u8, { let is_refined: u32 = unsafe { ::std::mem::transmute(is_refined) }; is_refined as u64 }); __bindgen_bitfield_unit } } +#[repr(C)] +pub struct rb_proc_captured_t { + pub header: rb_proc_header_t, + pub captured: rb_captured_block, +} +#[repr(C)] +pub struct rb_proc_symbol_t { + pub header: rb_proc_header_t, + pub symbol: VALUE, +} +#[repr(C)] +pub struct rb_proc_proc_t { + pub header: rb_proc_header_t, + pub proc_: VALUE, +} +#[repr(C)] +pub struct rb_proc_t { + pub block: __BindgenUnionField, + pub header: __BindgenUnionField, + pub captured: __BindgenUnionField, + pub symbol: __BindgenUnionField, + pub proc_: __BindgenUnionField, + pub bindgen_union_field: [u64; 4usize], +} pub const VM_CHECKMATCH_TYPE_WHEN: vm_check_match_type = 1; pub const VM_CHECKMATCH_TYPE_CASE: vm_check_match_type = 2; pub const VM_CHECKMATCH_TYPE_RESCUE: vm_check_match_type = 3; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index c9d76288204939..abe17e006691c5 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4603,10 +4603,10 @@ impl Function { } else if !has_block && def_type == VM_METHOD_TYPE_BMETHOD { let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) }; let proc = unsafe { rb_jit_get_proc_ptr(procv) }; - let proc_block = unsafe { &(*proc).block }; + let proc_block = unsafe { (*proc).block.as_ref() }; // Target ISEQ bmethods. Can't handle for example, `define_method(:foo, &:foo)` // which makes a `block_type_symbol` bmethod. - if proc_block.type_ != block_type_iseq { + if proc_block.type_() != block_type_iseq { self.set_dynamic_send_reason(insn_id, BmethodNonIseqProc); self.push_insn_id(block, insn_id); continue; }