diff --git a/.github/auto_request_review.yml b/.github/auto_request_review.yml index 814150c90e1da9..38496d5cebbebe 100644 --- a/.github/auto_request_review.yml +++ b/.github/auto_request_review.yml @@ -2,16 +2,15 @@ files: 'yjit*': [team:jit] 'yjit/**/*': [team:jit] 'yjit/src/cruby_bindings.inc.rs': [] - 'doc/yjit/*': [team:jit] 'bootstraptest/test_yjit*': [team:jit] 'test/ruby/test_yjit*': [team:jit] 'zjit*': [team:jit] 'zjit/**/*': [team:jit] 'zjit/src/cruby_bindings.inc.rs': [] - 'doc/zjit*': [team:jit] 'test/ruby/test_zjit*': [team:jit] 'defs/jit.mk': [team:jit] 'tool/zjit_bisect.rb': [team:jit] + 'doc/jit/*': [team:jit] # Skip github workflow files because the team don't necessarily need to review dependabot updates for GitHub Actions. It's noisy in notifications, and they're auto-merged anyway. options: ignore_draft: true diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 0b7a43272c3ca9..6b35bbb46be896 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1169,7 +1169,8 @@ class C # Inserting into the id2ref table should be Ractor-safe assert_equal 'ok', <<~'RUBY' # Force all calls to Kernel#object_id to insert into the id2ref table - ObjectSpace._id2ref(Object.new.object_id) + obj = Object.new + ObjectSpace._id2ref(obj.object_id) rescue nil 10.times.map do Ractor.new do diff --git a/configure.ac b/configure.ac index f992e4c015332a..5dadf13168a867 100644 --- a/configure.ac +++ b/configure.ac @@ -3957,7 +3957,7 @@ AC_ARG_ENABLE(zjit, [AS_CASE(["$JIT_TARGET_OK"], [yes], [ rb_zjit_build_possible=no - AC_MSG_CHECKING([possible to build ZJIT])dnl only checked when --enable-zjit is not specified + AC_MSG_CHECKING([prerequisites for ZJIT])dnl only checked when --enable-zjit is not specified # Fails in case rustc target doesn't match ruby target. Can happen on Rosetta, for example. # 1.85.0 is the first stable version that supports the 2024 edition. AS_IF([test "$RUSTC" != "no" && echo "#[cfg(target_arch = \"$JIT_TARGET_ARCH\")] fn main() {}" | @@ -4051,44 +4051,44 @@ AS_CASE(["${ZJIT_SUPPORT}"], JIT_RUST_FLAGS='--crate-type=staticlib --cfg feature=\"stats_allocator\"' RLIB_DIR= AS_CASE(["$JIT_CARGO_SUPPORT:$YJIT_SUPPORT:$ZJIT_SUPPORT"], - [no:yes:yes], [ # release build of YJIT+ZJIT - YJIT_LIBS= - ZJIT_LIBS= - JIT_RUST_FLAGS="--crate-type=rlib" - RLIB_DIR="target/release" - RUST_LIB="target/release/libruby.a" - ], - [no:*], [], - [*], [ # JIT_CARGO_SUPPORT not "no" -- cargo required. - AC_CHECK_TOOL(CARGO, [cargo], [no]) - AS_IF([test x"$CARGO" = "xno"], - AC_MSG_ERROR([this build configuration requires cargo. Installation instructions available at https://www.rust-lang.org/tools/install])) - - YJIT_LIBS= - ZJIT_LIBS= - - # There's more processing below to get the feature set for the - # top-level crate, so capture at this point for feature set of - # just the zjit crate. - ZJIT_TEST_FEATURES="${rb_cargo_features}" - - AS_IF([test x"${YJIT_SUPPORT}" != x"no"], [ - rb_cargo_features="$rb_cargo_features,yjit" - ]) - AS_IF([test x"${ZJIT_SUPPORT}" != x"no"], [ - AC_SUBST(ZJIT_TEST_FEATURES) - rb_cargo_features="$rb_cargo_features,zjit" - ]) - # if YJIT and ZJIT release mode - AS_IF([test "${YJIT_SUPPORT}:${ZJIT_SUPPORT}" = "yes:yes"], [ - JIT_CARGO_SUPPORT=release - ]) - CARGO_BUILD_ARGS="--profile ${JIT_CARGO_SUPPORT} --features ${rb_cargo_features}" - AS_IF([test "${JIT_CARGO_SUPPORT}" = "dev"], [ - RUST_LIB="target/debug/libruby.a" - ], [ - RUST_LIB="target/${JIT_CARGO_SUPPORT}/libruby.a" - ]) +[no:yes:yes], [ # release build of YJIT+ZJIT + YJIT_LIBS= + ZJIT_LIBS= + JIT_RUST_FLAGS="--crate-type=rlib" + RLIB_DIR="target/release" + RUST_LIB="target/release/libruby.a" +], +[no:*], [], +[ # JIT_CARGO_SUPPORT not "no" -- cargo required. + AC_CHECK_TOOL(CARGO, [cargo], [no]) + AS_IF([test x"$CARGO" = "xno"], + AC_MSG_ERROR([this build configuration requires cargo. Installation instructions available at https://www.rust-lang.org/tools/install])) + + YJIT_LIBS= + ZJIT_LIBS= + + # There's more processing below to get the feature set for the + # top-level crate, so capture at this point for feature set of + # just the zjit crate. + ZJIT_TEST_FEATURES="${rb_cargo_features}" + + AS_IF([test x"${YJIT_SUPPORT}" != x"no"], [ + rb_cargo_features="$rb_cargo_features,yjit" + ]) + AS_IF([test x"${ZJIT_SUPPORT}" != x"no"], [ + AC_SUBST(ZJIT_TEST_FEATURES) + rb_cargo_features="$rb_cargo_features,zjit" + ]) + # if YJIT and ZJIT release mode + AS_IF([test "${YJIT_SUPPORT}:${ZJIT_SUPPORT}" = "yes:yes"], [ + JIT_CARGO_SUPPORT=release + ]) + CARGO_BUILD_ARGS="--profile ${JIT_CARGO_SUPPORT} --features ${rb_cargo_features}" + AS_IF([test "${JIT_CARGO_SUPPORT}" = "dev"], [ + RUST_LIB="target/debug/libruby.a" + ], [ + RUST_LIB="target/${JIT_CARGO_SUPPORT}/libruby.a" + ]) ]) # In case either we're linking rust code diff --git a/doc/jit/zjit.md b/doc/jit/zjit.md index 1e5a36fd5e6f04..c66235269bd265 100644 --- a/doc/jit/zjit.md +++ b/doc/jit/zjit.md @@ -13,6 +13,10 @@ You can change how much executable memory is allocated using [ZJIT's command-lin ## Build Instructions +Refer to [Building Ruby](rdoc-ref:contributing/building_ruby.md) for general build prerequists. +Additionally, ZJIT requires Rust 1.85.0 or later. Release builds need only `rustc`. Development +builds require `cargo` and may download dependencies. + ### For normal use To build ZJIT on macOS: diff --git a/eval.c b/eval.c index ee5bc43f9cc4c1..0c80872bee76e5 100644 --- a/eval.c +++ b/eval.c @@ -1133,12 +1133,11 @@ rb_protect(VALUE (* proc) (VALUE), VALUE data, int *pstate) } VALUE -rb_ensure(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE data2) +rb_ec_ensure(rb_execution_context_t *ec, VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE data2) { enum ruby_tag_type state; volatile VALUE result = Qnil; VALUE errinfo; - rb_execution_context_t * volatile ec = GET_EC(); EC_PUSH_TAG(ec); if ((state = EC_EXEC_TAG()) == TAG_NONE) { result = (*b_proc) (data1); @@ -1155,6 +1154,12 @@ rb_ensure(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE dat return result; } +VALUE +rb_ensure(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE data2) +{ + return rb_ec_ensure(GET_EC(), b_proc, data1, e_proc, data2); +} + static ID frame_func_id(const rb_control_frame_t *cfp) { diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c index aeb96d7701242d..c43751c4e21f72 100644 --- a/ext/monitor/monitor.c +++ b/ext/monitor/monitor.c @@ -148,7 +148,7 @@ monitor_check_owner(VALUE monitor) static void monitor_exit0(struct monitor_args *args) { - monitor_check_owner(args->monitor); + monitor_check_owner0(args); if (args->mc->count <= 0) rb_bug("monitor_exit: count:%d", (int)args->mc->count); args->mc->count--; diff --git a/internal/eval.h b/internal/eval.h index 4c1c045b4e1042..17ade0a7f1e667 100644 --- a/internal/eval.h +++ b/internal/eval.h @@ -11,6 +11,7 @@ * header (related to this file, but not the same role). */ #include "ruby/ruby.h" /* for ID */ +#include "vm_core.h" /* for ID */ #define id_signo ruby_static_id_signo #define id_status ruby_static_id_status @@ -30,6 +31,7 @@ VALUE rb_exception_setup(int argc, VALUE *argv); void rb_refinement_setup(struct rb_refinements_data *data, VALUE module, VALUE klass); void rb_vm_using_module(VALUE module); VALUE rb_top_main_class(const char *method); +VALUE rb_ec_ensure(rb_execution_context_t *ec, VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE data2); /* eval_error.c */ VALUE rb_get_backtrace(VALUE info); diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index a057e64a4a3596..2ec6478a7fd058 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -1319,4 +1319,10 @@ def test_free_at_exit_env_var def test_toplevel_ruby assert_instance_of Module, ::Ruby end + + def test_ruby_patchlevel + # We stopped bumping RUBY_PATCHLEVEL at Ruby 4.0.0. + # Released versions have RUBY_PATCHLEVEL 0, and un-released versions have -1. + assert_include [-1, 0], RUBY_PATCHLEVEL + end end diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 7421b5ba4174c8..91940085c46ac3 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -6,8 +6,10 @@ class TestTimeout < Test::Unit::TestCase private def kill_timeout_thread thread = Timeout.const_get(:State).instance.instance_variable_get(:@timeout_thread) - thread.kill - thread.join + if thread + thread.kill + thread.join + end end def test_public_methods @@ -425,7 +427,9 @@ def test_timeout_in_trap_handler rd, wr = IO.pipe - trap("SIGUSR1") do + signal = Signal.list["USR1"] ? :USR1 : :TERM + + trap(signal) do begin Timeout.timeout(0.1) do sleep 1 @@ -440,7 +444,7 @@ def test_timeout_in_trap_handler end end - Process.kill :USR1, Process.pid + Process.kill signal, Process.pid assert_equal "OK", rd.read rd.close diff --git a/thread_sync.c b/thread_sync.c index 9fb1639e9b7dd2..9942d08e0a5c8f 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -123,7 +123,7 @@ rb_mutex_num_waiting(rb_mutex_t *mutex) rb_thread_t* rb_fiber_threadptr(const rb_fiber_t *fiber); static bool -locked_p(rb_mutex_t *mutex) +mutex_locked_p(rb_mutex_t *mutex) { return mutex->fiber_serial != 0; } @@ -132,7 +132,7 @@ static void mutex_free(void *ptr) { rb_mutex_t *mutex = ptr; - if (locked_p(mutex)) { + if (mutex_locked_p(mutex)) { const char *err = rb_mutex_unlock_th(mutex, rb_thread_ptr(mutex->thread), NULL); if (err) rb_bug("%s", err); } @@ -179,36 +179,18 @@ mutex_alloc(VALUE klass) return obj; } -/* - * call-seq: - * Thread::Mutex.new -> mutex - * - * Creates a new Mutex - */ -static VALUE -mutex_initialize(VALUE self) -{ - return self; -} - VALUE rb_mutex_new(void) { return mutex_alloc(rb_cMutex); } -/* - * call-seq: - * mutex.locked? -> true or false - * - * Returns +true+ if this lock is currently held by some thread. - */ VALUE rb_mutex_locked_p(VALUE self) { rb_mutex_t *mutex = mutex_ptr(self); - return RBOOL(locked_p(mutex)); + return RBOOL(mutex_locked_p(mutex)); } static void @@ -267,17 +249,16 @@ mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) } } -/* - * call-seq: - * mutex.try_lock -> true or false - * - * Attempts to obtain the lock and returns immediately. Returns +true+ if the - * lock was granted. - */ +static VALUE +rb_mut_trylock(rb_execution_context_t *ec, VALUE self) +{ + return RBOOL(mutex_trylock(mutex_ptr(self), ec->thread_ptr, ec->fiber_ptr)); +} + VALUE rb_mutex_trylock(VALUE self) { - return RBOOL(mutex_trylock(mutex_ptr(self), GET_THREAD(), GET_EC()->fiber_ptr)); + return rb_mut_trylock(GET_EC(), self); } static VALUE @@ -303,13 +284,28 @@ delete_from_waitq(VALUE value) static inline rb_atomic_t threadptr_get_interrupts(rb_thread_t *th); +struct mutex_args { + VALUE self; + rb_mutex_t *mutex; + rb_execution_context_t *ec; +}; + +static inline void +mutex_args_init(struct mutex_args *args, VALUE mutex) +{ + args->self = mutex; + args->mutex = mutex_ptr(mutex); + args->ec = GET_EC(); +} + static VALUE -do_mutex_lock(VALUE self, int interruptible_p) +do_mutex_lock(struct mutex_args *args, int interruptible_p) { - rb_execution_context_t *ec = GET_EC(); + VALUE self = args->self; + rb_execution_context_t *ec = args->ec; rb_thread_t *th = ec->thread_ptr; rb_fiber_t *fiber = ec->fiber_ptr; - rb_mutex_t *mutex = mutex_ptr(self); + rb_mutex_t *mutex = args->mutex; rb_atomic_t saved_ints = 0; /* When running trap handler */ @@ -432,35 +428,40 @@ do_mutex_lock(VALUE self, int interruptible_p) static VALUE mutex_lock_uninterruptible(VALUE self) { - return do_mutex_lock(self, 0); + struct mutex_args args; + mutex_args_init(&args, self); + return do_mutex_lock(&args, 0); +} + +static VALUE +rb_mut_lock(rb_execution_context_t *ec, VALUE self) +{ + struct mutex_args args = { + .self = self, + .mutex = mutex_ptr(self), + .ec = ec, + }; + return do_mutex_lock(&args, 1); } -/* - * call-seq: - * mutex.lock -> self - * - * Attempts to grab the lock and waits if it isn't available. - * Raises +ThreadError+ if +mutex+ was locked by the current thread. - */ VALUE rb_mutex_lock(VALUE self) { - return do_mutex_lock(self, 1); + struct mutex_args args; + mutex_args_init(&args, self); + return do_mutex_lock(&args, 1); +} + +static VALUE +rb_mut_owned_p(rb_execution_context_t *ec, VALUE self) +{ + return mutex_owned_p(ec->fiber_ptr, mutex_ptr(self)); } -/* - * call-seq: - * mutex.owned? -> true or false - * - * Returns +true+ if this lock is currently held by current thread. - */ VALUE rb_mutex_owned_p(VALUE self) { - rb_fiber_t *fiber = GET_EC()->fiber_ptr; - rb_mutex_t *mutex = mutex_ptr(self); - - return mutex_owned_p(fiber, mutex); + return rb_mut_owned_p(GET_EC(), self); } static const char * @@ -508,6 +509,24 @@ rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) return NULL; } +static void +do_mutex_unlock(struct mutex_args *args) +{ + const char *err; + rb_mutex_t *mutex = args->mutex; + rb_thread_t *th = rb_ec_thread_ptr(args->ec); + + err = rb_mutex_unlock_th(mutex, th, args->ec->fiber_ptr); + if (err) rb_raise(rb_eThreadError, "%s", err); +} + +static VALUE +do_mutex_unlock_safe(VALUE args) +{ + do_mutex_unlock((struct mutex_args *)args); + return Qnil; +} + /* * call-seq: * mutex.unlock -> self @@ -518,13 +537,21 @@ rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_fiber_t *fiber) VALUE rb_mutex_unlock(VALUE self) { - const char *err; - rb_mutex_t *mutex = mutex_ptr(self); - rb_thread_t *th = GET_THREAD(); - - err = rb_mutex_unlock_th(mutex, th, GET_EC()->fiber_ptr); - if (err) rb_raise(rb_eThreadError, "%s", err); + struct mutex_args args; + mutex_args_init(&args, self); + do_mutex_unlock(&args); + return self; +} +static VALUE +rb_mut_unlock(rb_execution_context_t *ec, VALUE self) +{ + struct mutex_args args = { + .self = self, + .mutex = mutex_ptr(self), + .ec = ec, + }; + do_mutex_unlock(&args); return self; } @@ -593,17 +620,15 @@ mutex_sleep_begin(VALUE _arguments) return woken; } -VALUE -rb_mutex_sleep(VALUE self, VALUE timeout) +static VALUE +rb_mut_sleep(rb_execution_context_t *ec, VALUE self, VALUE timeout) { - rb_execution_context_t *ec = GET_EC(); - if (!NIL_P(timeout)) { // Validate the argument: rb_time_interval(timeout); } - rb_mutex_unlock(self); + rb_mut_unlock(ec, self); time_t beg = time(0); struct rb_mutex_sleep_arguments arguments = { @@ -611,7 +636,7 @@ rb_mutex_sleep(VALUE self, VALUE timeout) .timeout = timeout, }; - VALUE woken = rb_ensure(mutex_sleep_begin, (VALUE)&arguments, mutex_lock_uninterruptible, self); + VALUE woken = rb_ec_ensure(ec, mutex_sleep_begin, (VALUE)&arguments, mutex_lock_uninterruptible, self); RUBY_VM_CHECK_INTS_BLOCKING(ec); if (!woken) return Qnil; @@ -619,61 +644,32 @@ rb_mutex_sleep(VALUE self, VALUE timeout) return TIMET2NUM(end); } -/* - * call-seq: - * mutex.sleep(timeout = nil) -> number or nil - * - * Releases the lock and sleeps +timeout+ seconds if it is given and - * non-nil or forever. Raises +ThreadError+ if +mutex+ wasn't locked by - * the current thread. - * - * When the thread is next woken up, it will attempt to reacquire - * the lock. - * - * Note that this method can wakeup without explicit Thread#wakeup call. - * For example, receiving signal and so on. - * - * Returns the slept time in seconds if woken up, or +nil+ if timed out. - */ -static VALUE -mutex_sleep(int argc, VALUE *argv, VALUE self) +VALUE +rb_mutex_sleep(VALUE self, VALUE timeout) { - VALUE timeout; - - timeout = rb_check_arity(argc, 0, 1) ? argv[0] : Qnil; - return rb_mutex_sleep(self, timeout); + return rb_mut_sleep(GET_EC(), self, timeout); } -/* - * call-seq: - * mutex.synchronize { ... } -> result of the block - * - * Obtains a lock, runs the block, and releases the lock when the block - * completes. See the example under Thread::Mutex. - */ VALUE -rb_mutex_synchronize(VALUE mutex, VALUE (*func)(VALUE arg), VALUE arg) +rb_mutex_synchronize(VALUE self, VALUE (*func)(VALUE arg), VALUE arg) { - rb_mutex_lock(mutex); - return rb_ensure(func, arg, rb_mutex_unlock, mutex); + struct mutex_args args; + mutex_args_init(&args, self); + do_mutex_lock(&args, 1); + return rb_ec_ensure(args.ec, func, arg, do_mutex_unlock_safe, (VALUE)&args); } -/* - * call-seq: - * mutex.synchronize { ... } -> result of the block - * - * Obtains a lock, runs the block, and releases the lock when the block - * completes. See the example under Thread::Mutex. - */ -static VALUE -rb_mutex_synchronize_m(VALUE self) +VALUE +rb_mut_synchronize(rb_execution_context_t *ec, VALUE self) { - if (!rb_block_given_p()) { - rb_raise(rb_eThreadError, "must be called with a block"); - } - - return rb_mutex_synchronize(self, rb_yield, Qundef); + struct mutex_args args = { + .self = self, + .mutex = mutex_ptr(self), + .ec = ec, + }; + do_mutex_lock(&args, 1); + return rb_ec_ensure(args.ec, rb_yield, Qundef, do_mutex_unlock_safe, (VALUE)&args); } void @@ -1688,14 +1684,6 @@ Init_thread_sync(void) /* Mutex */ DEFINE_CLASS(Mutex, Object); rb_define_alloc_func(rb_cMutex, mutex_alloc); - rb_define_method(rb_cMutex, "initialize", mutex_initialize, 0); - rb_define_method(rb_cMutex, "locked?", rb_mutex_locked_p, 0); - rb_define_method(rb_cMutex, "try_lock", rb_mutex_trylock, 0); - rb_define_method(rb_cMutex, "lock", rb_mutex_lock, 0); - rb_define_method(rb_cMutex, "unlock", rb_mutex_unlock, 0); - rb_define_method(rb_cMutex, "sleep", mutex_sleep, -1); - rb_define_method(rb_cMutex, "synchronize", rb_mutex_synchronize_m, 0); - rb_define_method(rb_cMutex, "owned?", rb_mutex_owned_p, 0); /* Queue */ DEFINE_CLASS(Queue, Object); diff --git a/thread_sync.rb b/thread_sync.rb index f8fa69900b39d0..28c70b1e9ce8fb 100644 --- a/thread_sync.rb +++ b/thread_sync.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Thread class Queue # call-seq: @@ -65,4 +67,85 @@ def push(object, non_block = false, timeout: nil) alias_method :enq, :push alias_method :<<, :push end + + class Mutex + # call-seq: + # Thread::Mutex.new -> mutex + # + # Creates a new Mutex + def initialize + end + + # call-seq: + # mutex.locked? -> true or false + # + # Returns +true+ if this lock is currently held by some thread. + def locked? + Primitive.cexpr! %q{ RBOOL(mutex_locked_p(mutex_ptr(self))) } + end + + # call-seq: + # mutex.owned? -> true or false + # + # Returns +true+ if this lock is currently held by current thread. + def owned? + Primitive.rb_mut_owned_p + end + + # call-seq: + # mutex.lock -> self + # + # Attempts to grab the lock and waits if it isn't available. + # Raises +ThreadError+ if +mutex+ was locked by the current thread. + def lock + Primitive.rb_mut_lock + end + + # call-seq: + # mutex.try_lock -> true or false + # + # Attempts to obtain the lock and returns immediately. Returns +true+ if the + # lock was granted. + def try_lock + Primitive.rb_mut_trylock + end + + # call-seq: + # mutex.lock -> self + # + # Attempts to grab the lock and waits if it isn't available. + # Raises +ThreadError+ if +mutex+ was locked by the current thread. + def unlock + Primitive.rb_mut_unlock + end + + # call-seq: + # mutex.synchronize { ... } -> result of the block + # + # Obtains a lock, runs the block, and releases the lock when the block + # completes. See the example under Thread::Mutex. + def synchronize + raise ThreadError, "must be called with a block" unless defined?(yield) + + Primitive.rb_mut_synchronize + end + + # call-seq: + # mutex.sleep(timeout = nil) -> number or nil + # + # Releases the lock and sleeps +timeout+ seconds if it is given and + # non-nil or forever. Raises +ThreadError+ if +mutex+ wasn't locked by + # the current thread. + # + # When the thread is next woken up, it will attempt to reacquire + # the lock. + # + # Note that this method can wakeup without explicit Thread#wakeup call. + # For example, receiving signal and so on. + # + # Returns the slept time in seconds if woken up, or +nil+ if timed out. + def sleep(timeout = nil) + Primitive.rb_mut_sleep(timeout) + end + end end diff --git a/tool/format-release b/tool/format-release index b7ad74a095c6fd..b38263f9f4fb05 100755 --- a/tool/format-release +++ b/tool/format-release @@ -62,19 +62,18 @@ eom if z != 0 prev_tag = nil elsif y != 0 - prev_tag = "v#{x}_#{y-1}_0" prev_ver = "#{x}.#{y-1}.0" + prev_tag = version_tag(prev_ver) else # y == 0 && z == 0 case x when 3 - prev_tag = "v2_7_0" prev_ver = "2.7.0" when 4 - prev_tag = "v3_4_0" prev_ver = "3.4.0" else raise "it doesn't know what is the previous version of '#{version}'" end + prev_tag = version_tag(prev_ver) end uri = "https://cache.ruby-lang.org/pub/tmp/ruby-info-#{version}-draft.yml" @@ -95,7 +94,7 @@ eom if prev_tag # show diff shortstat - tag = "v#{version.gsub(/[.\-]/, '_')}" + tag = version_tag(version) stat = `git -C #{rubydir} diff -l0 --shortstat #{prev_tag}..#{tag}` files_changed, insertions, deletions = stat.scan(/\d+/) end @@ -189,7 +188,7 @@ eom if /\.0(?:-\w+)?\z/ =~ ver # preview, rc, or first release entry <<= <= 4 + "v#{version}" + else + "v#{version.tr('.-', '_')}" + end + end end def main diff --git a/tool/make-snapshot b/tool/make-snapshot index 8251fa6324f970..041b1a0f2aba6d 100755 --- a/tool/make-snapshot +++ b/tool/make-snapshot @@ -253,7 +253,6 @@ end def package(vcs, rev, destdir, tmp = nil) pwd = Dir.pwd - patchlevel = false prerelease = false if rev and revision = rev[/@(\h+)\z/, 1] rev = $` @@ -270,22 +269,15 @@ def package(vcs, rev, destdir, tmp = nil) when /\Astable\z/ vcs.branch_list("ruby_[0-9]*") {|n| url = n[/\Aruby_\d+_\d+\z/]} url &&= vcs.branch(url) - when /\A(.*)\.(.*)\.(.*)-(preview|rc)(\d+)/ + when /\A(\d+)\.(\d+)\.(\d+)-(preview|rc)(\d+)/ prerelease = true tag = "#{$4}#{$5}" url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}#{$5}") - when /\A(.*)\.(.*)\.(.*)-p(\d+)/ - patchlevel = true - tag = "p#{$4}" - url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}") - when /\A(\d+)\.(\d+)(?:\.(\d+))?\z/ - if $3 && ($1 > "2" || $1 == "2" && $2 >= "1") - patchlevel = true - tag = "" - url = vcs.tag("v#{$1}_#{$2}_#{$3}") - else - url = vcs.branch("ruby_#{rev.tr('.', '_')}") - end + when /\A(\d+)\.(\d+)\.(\d+)\z/ + tag = "" + url = vcs.tag("v#{$1}_#{$2}_#{$3}") + when /\A(\d+)\.(\d+)\z/ + url = vcs.branch("ruby_#{rev.tr('.', '_')}") else warn "#{$0}: unknown version - #{rev}" return @@ -357,13 +349,7 @@ def package(vcs, rev, destdir, tmp = nil) [api_major_version, api_minor_version, version_teeny].join('.') end version or return - if patchlevel - unless tag.empty? - versionhdr ||= File.read("#{v}/version.h") - patchlevel = versionhdr[/^\#define\s+RUBY_PATCHLEVEL\s+(\d+)/, 1] - tag = (patchlevel ? "p#{patchlevel}" : vcs.revision_name(revision)) - end - elsif prerelease + if prerelease versionhdr ||= File.read("#{v}/version.h") versionhdr.sub!(/^\#\s*define\s+RUBY_PATCHLEVEL_STR\s+"\K.+?(?=")/, tag) or raise "no match of RUBY_PATCHLEVEL_STR to replace" File.write("#{v}/version.h", versionhdr) diff --git a/tool/merger.rb b/tool/merger.rb index 795e97a86ee529..4c096087fc9a25 100755 --- a/tool/merger.rb +++ b/tool/merger.rb @@ -65,7 +65,8 @@ def version_up(teeny: false) if teeny v[2].succ! end - if pl != '-1' # trunk does not have patchlevel + # We stopped bumping RUBY_PATCHLEVEL at Ruby 4.0.0. + if Integer(v[0]) <= 3 pl.succ! end @@ -113,7 +114,13 @@ def tag(relname) abort 'no relname is given and not in a release branch even if this is patch release' end end - tagname = "v#{v.join('_')}#{("_#{pl}" if v[0] < "2" || (v[0] == "2" && v[1] < "1") || /^(?:preview|rc)/ =~ pl)}" + if /^(?:preview|rc)/ =~ pl + tagname = "v#{v.join('.')}-#{pl}" + elsif Integer(v[0]) >= 4 + tagname = "v#{v.join('.')}" + else + tagname = "v#{v.join('_')}" + end unless execute('git', 'diff', '--exit-code') abort 'uncommitted changes' @@ -135,10 +142,12 @@ def remove_tag(relname) unless relname raise ArgumentError, 'relname is not specified' end - if /^v/ !~ relname - tagname = "v#{relname.gsub(/[.-]/, '_')}" - else + if relname.start_with?('v') tagname = relname + elsif Integer(relname.split('.', 2).first) >= 4 + tagname = "v#{relname}" + else + tagname = "v#{relname.gsub(/[.-]/, '_')}" end execute('git', 'tag', '-d', tagname) diff --git a/tool/releng/update-www-meta.rb b/tool/releng/update-www-meta.rb index 8a5651dcd05cf1..100f0bee181806 100755 --- a/tool/releng/update-www-meta.rb +++ b/tool/releng/update-www-meta.rb @@ -49,13 +49,18 @@ def self.parse(wwwdir, version) if z != 0 prev_tag = nil elsif y != 0 - prev_tag = "v#{x}_#{y-1}_0" prev_ver = "#{x}.#{y-1}.0" - elsif x == 3 && y == 0 && z == 0 - prev_tag = "v2_7_0" - prev_ver = "2.7.0" - else - raise "unexpected version for prev_ver '#{version}'" + prev_tag = version_tag(prev_ver) + else # y == 0 && z == 0 + case x + when 3 + prev_ver = "2.7.0" + when 4 + prev_ver = "3.4.0" + else + raise "it doesn't know what is the previous version of '#{version}'" + end + prev_tag = version_tag(prev_ver) end uri = "https://cache.ruby-lang.org/pub/tmp/ruby-info-#{version}-draft.yml" @@ -76,7 +81,7 @@ def self.parse(wwwdir, version) if prev_tag # show diff shortstat - tag = "v#{version.gsub(/[.\-]/, '_')}" + tag = version_tag(version) rubydir = File.expand_path(File.join(__FILE__, '../../../')) puts %`git -C #{rubydir} diff --shortstat #{prev_tag}..#{tag}` stat = `git -C #{rubydir} diff --shortstat #{prev_tag}..#{tag}` @@ -155,7 +160,7 @@ def self.update_releases_yml(ver, xy, ary, wwwdir, files_changed, insertions, de date = Time.now.utc # use utc to use previous day in midnight entry = <= 4 + "v#{version}" + else + "v#{version.tr('.-', '_')}" + end + end end # Confirm current directory is www.ruby-lang.org's working directory diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index bc6f1fb5f564fa..4992f177991d47 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -614,6 +614,7 @@ pub enum SendFallbackReason { SendWithoutBlockCfuncArrayVariadic, SendWithoutBlockNotOptimizedMethodType(MethodType), SendWithoutBlockNotOptimizedMethodTypeOptimized(OptimizedMethodType), + SendWithoutBlockNotOptimizedNeedPermission, SendWithoutBlockBopRedefined, SendWithoutBlockOperandsNotFixnum, SendWithoutBlockDirectKeywordMismatch, @@ -626,6 +627,7 @@ pub enum SendFallbackReason { SendCfuncVariadic, SendCfuncArrayVariadic, SendNotOptimizedMethodType(MethodType), + SendNotOptimizedNeedPermission, CCallWithFrameTooManyArgs, ObjToStringNotString, TooManyArgsForLir, @@ -653,6 +655,8 @@ impl Display for SendFallbackReason { SendWithoutBlockCfuncArrayVariadic => write!(f, "SendWithoutBlock: C function expects array variadic"), SendWithoutBlockNotOptimizedMethodType(method_type) => write!(f, "SendWithoutBlock: unsupported method type {:?}", method_type), SendWithoutBlockNotOptimizedMethodTypeOptimized(opt_type) => write!(f, "SendWithoutBlock: unsupported optimized method type {:?}", opt_type), + SendWithoutBlockNotOptimizedNeedPermission => write!(f, "SendWithoutBlock: method private or protected and no FCALL"), + SendNotOptimizedNeedPermission => write!(f, "Send: method private or protected and no FCALL"), SendWithoutBlockBopRedefined => write!(f, "SendWithoutBlock: basic operation was redefined"), SendWithoutBlockOperandsNotFixnum => write!(f, "SendWithoutBlock: operands are not fixnums"), SendWithoutBlockDirectKeywordMismatch => write!(f, "SendWithoutBlockDirect: keyword mismatch"), @@ -2634,6 +2638,16 @@ impl Function { // Load an overloaded cme if applicable. See vm_search_cc(). // It allows you to use a faster ISEQ if possible. cme = unsafe { rb_check_overloaded_cme(cme, ci) }; + let visibility = unsafe { METHOD_ENTRY_VISI(cme) }; + match (visibility, flags & VM_CALL_FCALL != 0) { + (METHOD_VISI_PUBLIC, _) => {} + (METHOD_VISI_PRIVATE, true) => {} + (METHOD_VISI_PROTECTED, true) => {} + _ => { + self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedNeedPermission); + self.push_insn_id(block, insn_id); continue; + } + } let mut def_type = unsafe { get_cme_def_type(cme) }; while def_type == VM_METHOD_TYPE_ALIAS { cme = unsafe { rb_aliased_callable_method_entry(cme) }; @@ -3256,7 +3270,18 @@ impl Function { return Err(()); } + let ci_flags = unsafe { vm_ci_flag(call_info) }; + let visibility = unsafe { METHOD_ENTRY_VISI(cme) }; + match (visibility, ci_flags & VM_CALL_FCALL != 0) { + (METHOD_VISI_PUBLIC, _) => {} + (METHOD_VISI_PRIVATE, true) => {} + (METHOD_VISI_PROTECTED, true) => {} + _ => { + fun.set_dynamic_send_reason(send_insn_id, SendNotOptimizedNeedPermission); + return Err(()); + } + } // When seeing &block argument, fall back to dynamic dispatch for now // TODO: Support block forwarding @@ -3398,6 +3423,18 @@ impl Function { return Err(()); } + let ci_flags = unsafe { vm_ci_flag(call_info) }; + let visibility = unsafe { METHOD_ENTRY_VISI(cme) }; + match (visibility, ci_flags & VM_CALL_FCALL != 0) { + (METHOD_VISI_PUBLIC, _) => {} + (METHOD_VISI_PRIVATE, true) => {} + (METHOD_VISI_PROTECTED, true) => {} + _ => { + fun.set_dynamic_send_reason(send_insn_id, SendWithoutBlockNotOptimizedNeedPermission); + return Err(()); + } + } + // Find the `argc` (arity) of the C method, which describes the parameters it expects let cfunc = unsafe { get_cme_def_body_cfunc(cme) }; let cfunc_argc = unsafe { get_mct_argc(cfunc) }; @@ -3410,8 +3447,6 @@ impl Function { return Err(()); } - let ci_flags = unsafe { vm_ci_flag(call_info) }; - // Filter for simple call sites (i.e. no splats etc.) if ci_flags & VM_CALL_ARGS_SIMPLE == 0 { fun.count_complex_call_features(block, ci_flags); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 62ea7c11b0c672..a602121a0cc51d 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -9932,4 +9932,201 @@ mod hir_opt_tests { Return v25 "); } + + #[test] + fn optimize_call_to_private_method_iseq_with_fcall() { + eval(r#" + class C + def callprivate = secret + private def secret = 42 + end + C.new.callprivate + "#); + assert_snapshot!(hir_string_proc("C.instance_method(:callprivate)"), @r" + fn callprivate@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(C@0x1000, secret@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v18:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] + IncrCounter inline_iseq_optimized_send_count + v21:Fixnum[42] = Const Value(42) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn dont_optimize_call_to_private_method_iseq() { + eval(r#" + class C + private def secret = 42 + end + Obj = C.new + def test = Obj.secret rescue $! + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Obj) + v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v13:BasicObject = SendWithoutBlock v21, :secret # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + CheckInterrupts + Return v13 + "); + } + + #[test] + fn optimize_call_to_private_method_cfunc_with_fcall() { + eval(r#" + class BasicObject + def callprivate = initialize rescue $! + end + Obj = BasicObject.new.callprivate + "#); + assert_snapshot!(hir_string_proc("BasicObject.instance_method(:callprivate)"), @r" + fn callprivate@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(BasicObject@0x1000, initialize@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(BasicObject@0x1000) + v20:BasicObjectExact = GuardType v6, BasicObjectExact + v21:NilClass = Const Value(nil) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v21 + "); + } + + #[test] + fn dont_optimize_call_to_private_method_cfunc() { + eval(r#" + Obj = BasicObject.new + def test = Obj.initialize rescue $! + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Obj) + v21:BasicObjectExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v13:BasicObject = SendWithoutBlock v21, :initialize # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + CheckInterrupts + Return v13 + "); + } + + #[test] + fn dont_optimize_call_to_private_top_level_method() { + eval(r#" + def toplevel_method = :OK + Obj = Object.new + def test = Obj.toplevel_method rescue $! + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Obj) + v21:ObjectExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v13:BasicObject = SendWithoutBlock v21, :toplevel_method # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + CheckInterrupts + Return v13 + "); + } + + #[test] + fn optimize_call_to_protected_method_iseq_with_fcall() { + eval(r#" + class C + def callprotected = secret + protected def secret = 42 + end + C.new.callprotected + "#); + assert_snapshot!(hir_string_proc("C.instance_method(:callprotected)"), @r" + fn callprotected@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(C@0x1000, secret@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v18:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] + IncrCounter inline_iseq_optimized_send_count + v21:Fixnum[42] = Const Value(42) + CheckInterrupts + Return v21 + "); + } + + #[test] + fn dont_optimize_call_to_protected_method_iseq() { + eval(r#" + class C + protected def secret = 42 + end + Obj = C.new + def test = Obj.secret rescue $! + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:6: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Obj) + v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v13:BasicObject = SendWithoutBlock v21, :secret # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + CheckInterrupts + Return v13 + "); + } } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 25f0c638be2998..38e8df170d33fd 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -219,6 +219,7 @@ make_counters! { send_fallback_send_without_block_cfunc_array_variadic, send_fallback_send_without_block_not_optimized_method_type, send_fallback_send_without_block_not_optimized_method_type_optimized, + send_fallback_send_without_block_not_optimized_need_permission, send_fallback_too_many_args_for_lir, send_fallback_send_without_block_bop_redefined, send_fallback_send_without_block_operands_not_fixnum, @@ -230,6 +231,7 @@ make_counters! { send_fallback_send_megamorphic, send_fallback_send_no_profiles, send_fallback_send_not_optimized_method_type, + send_fallback_send_not_optimized_need_permission, send_fallback_ccall_with_frame_too_many_args, send_fallback_argc_param_mismatch, // The call has at least one feature on the caller or callee side @@ -552,6 +554,8 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter SendWithoutBlockNotOptimizedMethodType(_) => send_fallback_send_without_block_not_optimized_method_type, SendWithoutBlockNotOptimizedMethodTypeOptimized(_) => send_fallback_send_without_block_not_optimized_method_type_optimized, + SendWithoutBlockNotOptimizedNeedPermission + => send_fallback_send_without_block_not_optimized_need_permission, TooManyArgsForLir => send_fallback_too_many_args_for_lir, SendWithoutBlockBopRedefined => send_fallback_send_without_block_bop_redefined, SendWithoutBlockOperandsNotFixnum => send_fallback_send_without_block_operands_not_fixnum, @@ -569,6 +573,7 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter ArgcParamMismatch => send_fallback_argc_param_mismatch, BmethodNonIseqProc => send_fallback_bmethod_non_iseq_proc, SendNotOptimizedMethodType(_) => send_fallback_send_not_optimized_method_type, + SendNotOptimizedNeedPermission => send_fallback_send_not_optimized_need_permission, CCallWithFrameTooManyArgs => send_fallback_ccall_with_frame_too_many_args, ObjToStringNotString => send_fallback_obj_to_string_not_string, Uncategorized(_) => send_fallback_uncategorized,