diff --git a/NEWS.md b/NEWS.md index a7a94d94287f2c..2435498da9af12 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,7 +12,7 @@ Note that each entry is kept to a minimum, see links for details. * Logical binary operators (`||`, `&&`, `and` and `or`) at the beginning of a line continue the previous line, like fluent dot. - The following two code are equal: + The following two code examples are equal: ```ruby if condition1 @@ -33,6 +33,21 @@ Note that each entry is kept to a minimum, see links for details. Note: We're only listing outstanding class updates. +* Array + + * `Array#rfind` has been added as a more efficient alternative to `array.reverse_each.find` [[Feature #21678]] + * `Array#find` has been added as a more efficient override of `Enumerable#find` [[Feature #21678]] +* Binding + + * `Binding#local_variables` does no longer include numbered parameters. + Also, `Binding#local_variable_get`, `Binding#local_variable_set`, and + `Binding#local_variable_defined?` reject to handle numbered parameters. + [[Bug #21049]] + + * `Binding#implicit_parameters`, `Binding#implicit_parameter_get`, and + `Binding#implicit_parameter_defined?` have been added to access + numbered parameters and "it" parameter. [[Bug #21049]] + * Enumerator * `Enumerator.produce` now accepts an optional `size` keyword argument @@ -57,47 +72,6 @@ Note: We're only listing outstanding class updates. [[Feature #21701]] -* Kernel - - * `Kernel#inspect` now checks for the existence of a `#instance_variables_to_inspect` method, - allowing control over which instance variables are displayed in the `#inspect` string: - - ```ruby - class DatabaseConfig - def initialize(host, user, password) - @host = host - @user = user - @password = password - end - - private def instance_variables_to_inspect = [:@host, :@user] - end - - conf = DatabaseConfig.new("localhost", "root", "hunter2") - conf.inspect #=> # - ``` - - [[Feature #21219]] - - * A deprecated behavior, process creation by `Kernel#open` with a - leading `|`, was removed. [[Feature #19630]] - -* Array - - * `Array#rfind` has been added as a more efficient alternative to `array.reverse_each.find` [[Feature #21678]] - * `Array#find` has been added as a more efficient override of `Enumerable#find` [[Feature #21678]] - -* Binding - - * `Binding#local_variables` does no longer include numbered parameters. - Also, `Binding#local_variable_get`, `Binding#local_variable_set`, and - `Binding#local_variable_defined?` reject to handle numbered parameters. - [[Bug #21049]] - - * `Binding#implicit_parameters`, `Binding#implicit_parameter_get`, and - `Binding#implicit_parameter_defined?` have been added to access - numbered parameters and "it" parameter. [[Bug #21049]] - * ErrorHighlight * When an ArgumentError is raised, it now displays code snippets for @@ -116,6 +90,27 @@ Note: We're only listing outstanding class updates. from test.rb:3:in '
' ``` +* Fiber + + * Introduce support for `Fiber#raise(cause:)` argument similar to + `Kernel#raise`. [[Feature #21360]] + +* Fiber::Scheduler + + * Introduce `Fiber::Scheduler#fiber_interrupt` to interrupt a fiber with a + given exception. The initial use case is to interrupt a fiber that is + waiting on a blocking IO operation when the IO operation is closed. + [[Feature #21166]] + + * Introduce `Fiber::Scheduler#yield` to allow the fiber scheduler to + continue processing when signal exceptions are disabled. + [[Bug #21633]] + + * Reintroduce the `Fiber::Scheduler#io_close` hook for asynchronous `IO#close`. + + * Invoke `Fiber::Scheduler#io_write` when flushing the IO write buffer. + [[Bug #21789]] + * File * `File::Stat#birthtime` is now available on Linux via the statx @@ -130,10 +125,40 @@ Note: We're only listing outstanding class updates. * A deprecated behavior, process creation by `IO` class methods with a leading `|`, was removed. [[Feature #19630]] +* Kernel + + * `Kernel#inspect` now checks for the existence of a `#instance_variables_to_inspect` method, + allowing control over which instance variables are displayed in the `#inspect` string: + + ```ruby + class DatabaseConfig + def initialize(host, user, password) + @host = host + @user = user + @password = password + end + + private def instance_variables_to_inspect = [:@host, :@user] + end + + conf = DatabaseConfig.new("localhost", "root", "hunter2") + conf.inspect #=> # + ``` + + [[Feature #21219]] + + * A deprecated behavior, process creation by `Kernel#open` with a + leading `|`, was removed. [[Feature #19630]] + * Math * `Math.log1p` and `Math.expm1` are added. [[Feature #21527]] +* Pathname + + * Pathname has been promoted from a default gem to a core class of Ruby. + [[Feature #17473]] + * Proc * `Proc#parameters` now shows anonymous optional parameters as `[:opt]` @@ -165,7 +190,7 @@ Note: We're only listing outstanding class updates. * `Ractor::Port#close` * `Ractor::Port#closed?` - As result, `Ractor.yield` and `Ractor#take` were removed. + As a result, `Ractor.yield` and `Ractor#take` were removed. * `Ractor#join` and `Ractor#value` were added to wait for the termination of a Ractor. These are similar to `Thread#join` @@ -182,7 +207,7 @@ Note: We're only listing outstanding class updates. * `Ractor#close_incoming` and `Ractor#close_outgoing` were removed. - * `Ractor.shareable_proc` and `Ractor.shareable_lambda` is introduced + * `Ractor.shareable_proc` and `Ractor.shareable_lambda` are introduced to make shareable Proc or lambda. [[Feature #21550]], [[Feature #21557]] @@ -214,7 +239,7 @@ Note: We're only listing outstanding class updates. * `Set` is now a core class, instead of an autoloaded stdlib class. [[Feature #21216]] - * `Set#inspect` now uses a simpler displays, similar to literal arrays. + * `Set#inspect` now uses a simpler display, similar to literal arrays. (e.g., `Set[1, 2, 3]` instead of `#`). [[Feature #21389]] * Passing arguments to `Set#to_set` and `Enumerable#to_set` is now deprecated. @@ -244,18 +269,6 @@ Note: We're only listing outstanding class updates. * Introduce support for `Thread#raise(cause:)` argument similar to `Kernel#raise`. [[Feature #21360]] -* Fiber - - * Introduce support for `Fiber#raise(cause:)` argument similar to - `Kernel#raise`. [[Feature #21360]] - -* Fiber::Scheduler - - * Introduce `Fiber::Scheduler#fiber_interrupt` to interrupt a fiber with a - given exception. The initial use case is to interrupt a fiber that is - waiting on a blocking IO operation when the IO operation is closed. - [[Feature #21166]] - * Pathname * Pathname has been promoted from a default gem to a core class of Ruby. @@ -276,10 +289,13 @@ The following bundled gems are promoted from default gems. * readline 0.0.4 * fiddle 1.1.8 +The following bundled gems are added. + + We only list stdlib changes that are notable feature changes. Other changes are listed in the following sections. We also listed release -history from the previous bundled version that is Ruby 3.3.0 if it has GitHub +history from the previous bundled version that is Ruby 3.4.0 if it has GitHub releases. The following default gem is added. @@ -308,7 +324,7 @@ The following default gems are updated. * openssl 4.0.0 * optparse 0.8.1 * pp 0.6.3 -* prism 1.6.0 +* prism 1.7.0 * psych 5.3.1 * resolv 0.7.0 * stringio 3.2.0 @@ -319,24 +335,22 @@ The following default gems are updated. * weakref 0.1.4 * zlib 3.2.2 -The following bundled gems are added. - - The following bundled gems are updated. -* minitest 5.27.0 +* minitest 6.0.0 * power_assert 3.0.1 * rake 13.3.1 * test-unit 3.7.3 * rexml 3.4.4 +* rss 0.3.2 * net-ftp 0.3.9 -* net-imap 0.6.1 +* net-imap 0.6.2 * net-smtp 0.5.1 * matrix 0.4.3 * prime 0.1.4 * rbs 3.10.0.pre.2 * typeprof 0.31.0 -* debug 1.11.0 +* debug 1.11.1 * base64 0.3.0 * bigdecimal 4.0.1 * drb 2.2.3 @@ -344,6 +358,15 @@ The following bundled gems are updated. * csv 3.3.5 * repl_type_completor 0.1.12 +### RubyGems and Bundler + +see the following links for details. + +* [Upgrading to RubyGems/Bundler 4 - RubyGems Blog](https://blog.rubygems.org/2025/12/03/upgrade-to-rubygems-bundler-4.html) +* [4.0.0 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/03/4.0.0-released.html) +* [4.0.1 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/09/4.0.1-released.html) +* [4.0.2 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/17/4.0.2-released.html) + ## Supported platforms * Windows @@ -358,7 +381,7 @@ The following bundled gems are updated. * `Ractor.yield` * `Ractor#take` * `Ractor#close_incoming` - * `Ractor#close_outgoging` + * `Ractor#close_outgoing` [[Feature #21262]] @@ -369,7 +392,7 @@ The following bundled gems are updated. * `rb_path_check` has been removed. This function was used for `$SAFE` path checking which was removed in Ruby 2.7, - and was already deprecated,. + and was already deprecated. [[Feature #20971]] * A backtrace for `ArgumentError` of "wrong number of arguments" now @@ -449,15 +472,27 @@ The following bundled gems are updated. ## Implementation improvements +* `Class#new` (ex. `Object.new`) is faster in all cases, but especially when passing keyword arguments. This has also been integrated into YJIT and ZJIT. [[Feature #21254]] +* GC heaps of different size pools now grow independently, reducing memory usage when only some pools contain long-lived objects +* GC sweeping is faster on pages of large objects +* "Generic ivar" objects (String, Array, `TypedData`, etc.) now use a new internal "fields" object for faster instance variable access +* The GC avoids maintaining an internal `id2ref` table until it is first used, making `object_id` allocation and GC sweeping faster +* `object_id` and `hash` are faster on Class and Module objects +* Larger bignum Integers can remain embedded using variable width allocation +* `Random`, `Enumerator::Product`, `Enumerator::Chain`, `Addrinfo`, + `StringScanner`, and some internal objects are now write-barrier protected, + which reduces GC overhead. + ### Ractor -A lot of work has gone into making Ractors more stable, performant, and usable. These improvements bring Ractors implementation closer to leaving experimental status. +A lot of work has gone into making Ractors more stable, performant, and usable. These improvements bring Ractor implementation closer to leaving experimental status. * Performance improvements - * Frozen strings and the symbol table internally use a lock-free hash set + * Frozen strings and the symbol table internally use a lock-free hash set [[Feature #21268]] * Method cache lookups avoid locking in most cases - * Class (and geniv) instance variable access is faster and avoids locking - * Cache contention is avoided during object allocation + * Class (and generic ivar) instance variable access is faster and avoids locking + * CPU cache contention is avoided in object allocation by using a per-ractor counter + * CPU cache contention is avoided in xmalloc/xfree by using a thread-local counter * `object_id` avoids locking in most cases * Bug fixes and stability * Fixed possible deadlocks when combining Ractors and Threads @@ -465,6 +500,8 @@ A lot of work has gone into making Ractors more stable, performant, and usable. * Fixed encoding/transcoding issues across Ractors * Fixed race conditions in GC operations and method invalidation * Fixed issues with processes forking after starting a Ractor + * GC allocation counts are now accurate under Ractors + * Fixed TracePoints not working after GC [[Bug #19112]] ## JIT @@ -488,6 +525,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #15408]: https://bugs.ruby-lang.org/issues/15408 [Feature #17473]: https://bugs.ruby-lang.org/issues/17473 [Feature #18455]: https://bugs.ruby-lang.org/issues/18455 +[Bug #19112]: https://bugs.ruby-lang.org/issues/19112 [Feature #19630]: https://bugs.ruby-lang.org/issues/19630 [Bug #19868]: https://bugs.ruby-lang.org/issues/19868 [Feature #19908]: https://bugs.ruby-lang.org/issues/19908 @@ -510,6 +548,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21219]: https://bugs.ruby-lang.org/issues/21219 [Feature #21254]: https://bugs.ruby-lang.org/issues/21254 [Feature #21258]: https://bugs.ruby-lang.org/issues/21258 +[Feature #21268]: https://bugs.ruby-lang.org/issues/21268 [Feature #21262]: https://bugs.ruby-lang.org/issues/21262 [Feature #21275]: https://bugs.ruby-lang.org/issues/21275 [Feature #21287]: https://bugs.ruby-lang.org/issues/21287 @@ -523,8 +562,11 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21527]: https://bugs.ruby-lang.org/issues/21527 [Feature #21543]: https://bugs.ruby-lang.org/issues/21543 [Feature #21550]: https://bugs.ruby-lang.org/issues/21550 +[Feature #21552]: https://bugs.ruby-lang.org/issues/21552 [Feature #21557]: https://bugs.ruby-lang.org/issues/21557 +[Bug #21633]: https://bugs.ruby-lang.org/issues/21633 [Bug #21654]: https://bugs.ruby-lang.org/issues/21654 [Feature #21678]: https://bugs.ruby-lang.org/issues/21678 [Bug #21698]: https://bugs.ruby-lang.org/issues/21698 [Feature #21701]: https://bugs.ruby-lang.org/issues/21701 +[Bug #21789]: https://bugs.ruby-lang.org/issues/21789 diff --git a/array.c b/array.c index 27b84249bf44fe..e13239ad3daaa1 100644 --- a/array.c +++ b/array.c @@ -2102,9 +2102,7 @@ rb_ary_fetch(int argc, VALUE *argv, VALUE ary) * * If no such element is found, calls +if_none_proc+ and returns its return value. * - * [1, 3, 5].find(proc {false}) {|element| element > 12} # => false - * [[:foo, 0], [:bar, 1], [:baz, 2]].find {|key, value| key.start_with?('b') } # => [:bar, 1] - * [[:foo, 0], [:bar, 1], [:baz, 2]].find(proc {[]}) {|key, value| key.start_with?('c') } # => [] + * [1, 3, 5].find(proc {-1}) {|element| element > 12} # => -1 * * With no block given, returns an Enumerator. * @@ -2140,16 +2138,14 @@ rb_ary_find(int argc, VALUE *argv, VALUE ary) * Returns the last element for which the block returns a truthy value. * * With a block given, calls the block with successive elements of the array in - * reverse order; returns the last element for which the block returns a truthy + * reverse order; returns the first element for which the block returns a truthy * value: * - * (0..9).rfind {|element| element < 5} # => 4 + * [1, 2, 3, 4, 5, 6].rfind {|element| element < 5} # => 4 * * If no such element is found, calls +if_none_proc+ and returns its return value. * - * (0..9).rfind(proc {false}) {|element| element < -2} # => false - * {foo: 0, bar: 1, baz: 2}.rfind {|key, value| key.start_with?('b') } # => [:baz, 2] - * {foo: 0, bar: 1, baz: 2}.rfind(proc {[]}) {|key, value| key.start_with?('c') } # => [] + * [1, 2, 3, 4].rfind(proc {0}) {|element| element < -2} # => 0 * * With no block given, returns an Enumerator. * diff --git a/ast.c b/ast.c index 449b21aa2d1023..5357aa38a5ae09 100644 --- a/ast.c +++ b/ast.c @@ -32,9 +32,13 @@ static size_t node_memsize(const void *ptr) { struct ASTNodeData *data = (struct ASTNodeData *)ptr; - rb_ast_t *ast = rb_ruby_ast_data_get(data->ast_value); + size_t size = sizeof(struct ASTNodeData); + if (data->ast_value) { + rb_ast_t *ast = rb_ruby_ast_data_get(data->ast_value); + size += rb_ast_memsize(ast); + } - return sizeof(struct ASTNodeData) + rb_ast_memsize(ast); + return size; } static const rb_data_type_t rb_node_type = { diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb index 8988ac20ce41f6..04de0c93b90cb0 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -16,6 +16,7 @@ $:.unshift File.join(File.dirname(__FILE__), '../lib') retry end +require_relative '../tool/lib/test/jobserver' if !Dir.respond_to?(:mktmpdir) # copied from lib/tmpdir.rb @@ -110,35 +111,7 @@ def putc(c) def wn=(wn) unless wn == 1 - if /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ ENV.delete("MAKEFLAGS") - begin - if fifo = $3 - fifo.gsub!(/\\(?=.)/, '') - r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY) - w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY) - else - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - end - rescue - r.close if r - else - r.close_on_exec = true - w.close_on_exec = true - tokens = r.read_nonblock(wn > 0 ? wn : 1024, exception: false) - r.close - if String === tokens - tokens.freeze - auth = w - w = nil - at_exit {auth << tokens; auth.close} - wn = tokens.size + 1 - else - w.close - wn = 1 - end - end - end + wn = Test::JobServer.max_jobs(wn > 0 ? wn : 1024, ENV.delete("MAKEFLAGS")) || wn if wn <= 0 require 'etc' wn = [Etc.nprocessors / 2, 1].max diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index a1169f9d29c54f..e2a3e8dd5beff1 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -316,7 +316,7 @@ def test n } unless (ENV.key?('TRAVIS') && ENV['TRAVIS_CPU_ARCH'] == 'arm64') # https://bugs.ruby-lang.org/issues/17878 # Exception for empty select -assert_match /specify at least one ractor/, %q{ +assert_match /specify at least one Ractor::Port or Ractor/, %q{ begin Ractor.select rescue ArgumentError => e diff --git a/box.c b/box.c index 14f6acdd8267b5..54bd5c6258343f 100644 --- a/box.c +++ b/box.c @@ -9,6 +9,7 @@ #include "internal/file.h" #include "internal/gc.h" #include "internal/hash.h" +#include "internal/io.h" #include "internal/load.h" #include "internal/st.h" #include "internal/variable.h" @@ -276,10 +277,15 @@ box_entry_free(void *ptr) static size_t box_entry_memsize(const void *ptr) { + size_t size = sizeof(rb_box_t); const rb_box_t *box = (const rb_box_t *)ptr; - return sizeof(rb_box_t) + \ - rb_st_memsize(box->loaded_features_index) + \ - rb_st_memsize(box->loading_table); + if (box->loaded_features_index) { + size += rb_st_memsize(box->loaded_features_index); + } + if (box->loading_table) { + size += rb_st_memsize(box->loading_table); + } + return size; } static const rb_data_type_t rb_box_data_type = { @@ -622,8 +628,14 @@ copy_ext_file(const char *src_path, const char *dst_path) # else const int bin = 0; # endif - const int src_fd = open(src_path, O_RDONLY|bin); +# ifdef O_CLOEXEC + const int cloexec = O_CLOEXEC; +# else + const int cloexec = 0; +# endif + const int src_fd = open(src_path, O_RDONLY|cloexec|bin); if (src_fd < 0) return COPY_ERROR_SRC_OPEN; + if (!cloexec) rb_maygvl_fd_fix_cloexec(src_fd); struct stat src_st; if (fstat(src_fd, &src_st)) { @@ -631,11 +643,12 @@ copy_ext_file(const char *src_path, const char *dst_path) return COPY_ERROR_SRC_STAT; } - const int dst_fd = open(dst_path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|bin, S_IRWXU); + const int dst_fd = open(dst_path, O_WRONLY|O_CREAT|O_EXCL|cloexec|bin, S_IRWXU); if (dst_fd < 0) { close(src_fd); return COPY_ERROR_DST_OPEN; } + if (!cloexec) rb_maygvl_fd_fix_cloexec(dst_fd); enum copy_error_type ret = COPY_ERROR_NONE; diff --git a/class.c b/class.c index 9716ba07dae2e2..840bdeb0c2f9e6 100644 --- a/class.c +++ b/class.c @@ -30,6 +30,7 @@ #include "internal/variable.h" #include "ruby/st.h" #include "vm_core.h" +#include "ruby/ractor.h" #include "yjit.h" #include "zjit.h" @@ -2823,7 +2824,7 @@ rb_special_singleton_class(VALUE obj) * consistency of the metaclass hierarchy. */ static VALUE -singleton_class_of(VALUE obj) +singleton_class_of(VALUE obj, bool ensure_eigenclass) { VALUE klass; @@ -2851,13 +2852,26 @@ singleton_class_of(VALUE obj) } } - klass = METACLASS_OF(obj); - if (!(RCLASS_SINGLETON_P(klass) && - RCLASS_ATTACHED_OBJECT(klass) == obj)) { - klass = rb_make_metaclass(obj, klass); + bool needs_lock = rb_multi_ractor_p() && rb_ractor_shareable_p(obj); + unsigned int lev; + if (needs_lock) { + RB_VM_LOCK_ENTER_LEV(&lev); + } + { + klass = METACLASS_OF(obj); + if (!(RCLASS_SINGLETON_P(klass) && + RCLASS_ATTACHED_OBJECT(klass) == obj)) { + klass = rb_make_metaclass(obj, klass); + } + RB_FL_SET_RAW(klass, RB_OBJ_FROZEN_RAW(obj)); + if (ensure_eigenclass && RB_TYPE_P(obj, T_CLASS)) { + /* ensures an exposed class belongs to its own eigenclass */ + (void)ENSURE_EIGENCLASS(klass); + } + } + if (needs_lock) { + RB_VM_LOCK_LEAVE_LEV(&lev); } - - RB_FL_SET_RAW(klass, RB_OBJ_FROZEN_RAW(obj)); return klass; } @@ -2900,12 +2914,7 @@ rb_singleton_class_get(VALUE obj) VALUE rb_singleton_class(VALUE obj) { - VALUE klass = singleton_class_of(obj); - - /* ensures an exposed class belongs to its own eigenclass */ - if (RB_TYPE_P(obj, T_CLASS)) (void)ENSURE_EIGENCLASS(klass); - - return klass; + return singleton_class_of(obj, true); } /*! @@ -2923,7 +2932,7 @@ rb_singleton_class(VALUE obj) void rb_define_singleton_method(VALUE obj, const char *name, VALUE (*func)(ANYARGS), int argc) { - rb_define_method(singleton_class_of(obj), name, func, argc); + rb_define_method(singleton_class_of(obj, false), name, func, argc); } #ifdef rb_define_module_function diff --git a/common.mk b/common.mk index 50abfeab8a4df6..08fee9119a1ef9 100644 --- a/common.mk +++ b/common.mk @@ -804,6 +804,7 @@ clean-platform distclean-platform realclean-platform: RUBYSPEC_CAPIEXT = spec/ruby/optional/capi/ext RUBYSPEC_CAPIEXT_SRCDIR = $(srcdir)/$(RUBYSPEC_CAPIEXT) RUBYSPEC_CAPIEXT_DEPS = $(RUBYSPEC_CAPIEXT_SRCDIR)/rubyspec.h $(RUBY_H_INCLUDES) $(LIBRUBY) +RUBYSPEC_CAPIEXT_BUILD = $(enable_shared:yes=rubyspec-capiext) rubyspec-capiext: build-ext $(DOT_WAIT) # make-dependent rules should be included after this and built after build-ext. @@ -905,7 +906,7 @@ test: test-short # Separate to skip updating encs and exts by `make -o test-precheck` # for GNU make. -test-precheck: $(ENCSTATIC:static=lib)encs exts PHONY $(DOT_WAIT) +test-precheck: $(ENCSTATIC:static=lib)encs $(RUBYSPEC_CAPIEXT_BUILD) exts PHONY $(DOT_WAIT) yes-test-all-precheck: programs $(DOT_WAIT) test-precheck PRECHECK_TEST_ALL = yes-test-all-precheck @@ -1566,7 +1567,7 @@ yes-install-for-test-bundled-gems: yes-update-default-gemspecs $(XRUBY) -C "$(srcdir)" -r./tool/lib/gem_env.rb bin/gem \ install --no-document --conservative \ "hoe" "json-schema:5.1.0" "test-unit-rr" "simplecov" "simplecov-html" "simplecov-json" "rspec" "zeitwerk" \ - "sinatra" "rack" "tilt" "mustermann" "base64" "compact_index" "rack-test" "logger" "kpeg" "tracer" + "sinatra" "rack" "tilt" "mustermann" "base64" "compact_index" "rack-test" "logger" "kpeg" "tracer" "minitest-mock" test-bundled-gems-fetch: yes-test-bundled-gems-fetch yes-test-bundled-gems-fetch: clone-bundled-gems-src diff --git a/compar.c b/compar.c index f5da6178cf4358..eda7e83824a4e0 100644 --- a/compar.c +++ b/compar.c @@ -123,10 +123,14 @@ cmp_ge(VALUE x, VALUE y) /* * call-seq: - * obj < other -> true or false + * self < other -> true or false + * + * Returns whether +self+ is "less than" +other+; + * equivalent to (self <=> other) < 0: + * + * 'foo' < 'foo' # => false + * 'foo' < 'food' # => true * - * Compares two objects based on the receiver's <=> - * method, returning true if it returns a value less than 0. */ static VALUE @@ -137,10 +141,15 @@ cmp_lt(VALUE x, VALUE y) /* * call-seq: - * obj <= other -> true or false + * self <= other -> true or false + * + * Returns whether +self+ is "less than or equal to" +other+; + * equivalent to (self <=> other) <= 0: + * + * 'foo' <= 'foo' # => true + * 'foo' <= 'food' # => true + * 'food' <= 'foo' # => false * - * Compares two objects based on the receiver's <=> - * method, returning true if it returns a value less than or equal to 0. */ static VALUE diff --git a/complex.c b/complex.c index 72e13b4e2ecde2..bb54d4f61f31ca 100644 --- a/complex.c +++ b/complex.c @@ -1260,14 +1260,16 @@ nucomp_real_p(VALUE self) /* * call-seq: - * complex <=> object -> -1, 0, 1, or nil + * self <=> other -> -1, 0, 1, or nil + * + * Compares +self+ and +other+. * * Returns: * - * - self.real <=> object.real if both of the following are true: + * - self.real <=> other.real if both of the following are true: * * - self.imag == 0. - * - object.imag == 0. # Always true if object is numeric but not complex. + * - other.imag == 0 (always true if +other+ is numeric but not complex). * * - +nil+ otherwise. * @@ -1280,6 +1282,8 @@ nucomp_real_p(VALUE self) * Complex.rect(1) <=> Complex.rect(1, 1) # => nil # object.imag not zero. * Complex.rect(1) <=> 'Foo' # => nil # object.imag not defined. * + * \Class \Complex includes module Comparable, + * each of whose methods uses Complex#<=> for comparison. */ static VALUE nucomp_cmp(VALUE self, VALUE other) diff --git a/cont.c b/cont.c index 0087994730419e..b33c1462bf3ea1 100644 --- a/cont.c +++ b/cont.c @@ -3246,28 +3246,37 @@ rb_fiber_raise(VALUE fiber, int argc, VALUE *argv) /* * call-seq: - * fiber.raise -> obj - * fiber.raise(string) -> obj - * fiber.raise(exception [, string [, array]]) -> obj + * raise(exception, message = exception.to_s, backtrace = nil, cause: $!) + * raise(message = nil, cause: $!) * * Raises an exception in the fiber at the point at which the last - * +Fiber.yield+ was called. If the fiber has not been started or has + * +Fiber.yield+ was called. + * + * f = Fiber.new { + * puts "Before the yield" + * Fiber.yield 1 # -- exception will be raised here + * puts "After the yield" + * } + * + * p f.resume + * f.raise "Gotcha" + * + * Output + * + * Before the first yield + * 1 + * t.rb:8:in 'Fiber.yield': Gotcha (RuntimeError) + * from t.rb:8:in 'block in
' + * + * If the fiber has not been started or has * already run to completion, raises +FiberError+. If the fiber is * yielding, it is resumed. If it is transferring, it is transferred into. * But if it is resuming, raises +FiberError+. * - * With no arguments, raises a +RuntimeError+. With a single +String+ - * argument, raises a +RuntimeError+ with the string as a message. Otherwise, - * the first parameter should be the name of an +Exception+ class (or an - * object that returns an +Exception+ object when sent an +exception+ - * message). The optional second parameter sets the message associated with - * the exception, and the third parameter is an array of callback information. - * Exceptions are caught by the +rescue+ clause of begin...end - * blocks. - * * Raises +FiberError+ if called on a Fiber belonging to another +Thread+. * - * See Kernel#raise for more information. + * See Kernel#raise for more information on arguments. + * */ static VALUE rb_fiber_m_raise(int argc, VALUE *argv, VALUE self) diff --git a/defs/gmake.mk b/defs/gmake.mk index 2422151009599c..e6f553fa8c1b5d 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -529,11 +529,7 @@ RUBYSPEC_CAPIEXT_SO := $(patsubst %.c,$(RUBYSPEC_CAPIEXT)/%.$(DLEXT),$(notdir $( rubyspec-capiext: $(RUBYSPEC_CAPIEXT_SO) @ $(NULLCMD) -ifeq ($(ENABLE_SHARED),yes) -exts: rubyspec-capiext -endif - -spec/%/ spec/%_spec.rb: programs exts PHONY +spec/%/ spec/%_spec.rb: programs exts $(RUBYSPEC_CAPIEXT_BUILD) PHONY +$(RUNRUBY) -r./$(arch)-fake $(srcdir)/spec/mspec/bin/mspec-run -B $(srcdir)/spec/default.mspec $(SPECOPTS) $(patsubst %,$(srcdir)/%,$@) ruby.pc: $(filter-out ruby.pc,$(ruby_pc)) diff --git a/depend b/depend index 69ce9e63bd9000..11397bf647adf1 100644 --- a/depend +++ b/depend @@ -745,6 +745,7 @@ box.$(OBJEXT): $(top_srcdir)/internal/file.h box.$(OBJEXT): $(top_srcdir)/internal/gc.h box.$(OBJEXT): $(top_srcdir)/internal/hash.h box.$(OBJEXT): $(top_srcdir)/internal/imemo.h +box.$(OBJEXT): $(top_srcdir)/internal/io.h box.$(OBJEXT): $(top_srcdir)/internal/load.h box.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h box.$(OBJEXT): $(top_srcdir)/internal/serial.h @@ -951,6 +952,7 @@ box.$(OBJEXT): {$(VPATH)}internal/value_type.h box.$(OBJEXT): {$(VPATH)}internal/variable.h box.$(OBJEXT): {$(VPATH)}internal/warning_push.h box.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +box.$(OBJEXT): {$(VPATH)}io.h box.$(OBJEXT): {$(VPATH)}iseq.h box.$(OBJEXT): {$(VPATH)}method.h box.$(OBJEXT): {$(VPATH)}missing.h @@ -1403,6 +1405,7 @@ class.$(OBJEXT): {$(VPATH)}missing.h class.$(OBJEXT): {$(VPATH)}node.h class.$(OBJEXT): {$(VPATH)}onigmo.h class.$(OBJEXT): {$(VPATH)}oniguruma.h +class.$(OBJEXT): {$(VPATH)}ractor.h class.$(OBJEXT): {$(VPATH)}ruby_assert.h class.$(OBJEXT): {$(VPATH)}ruby_atomic.h class.$(OBJEXT): {$(VPATH)}rubyparser.h diff --git a/dir.rb b/dir.rb index a05bd18630b923..0088bb39fa5399 100644 --- a/dir.rb +++ b/dir.rb @@ -178,7 +178,7 @@ class Dir # if +nil+ (the default), the file system's encoding is used: # # Dir.open('.').read.encoding # => # - # Dir.open('.', encoding: 'US-ASCII').read.encoding # => # + # Dir.open('.', encoding: Encoding::US_ASCII).read.encoding # => # # def self.open(name, encoding: nil, &block) dir = Primitive.dir_s_open(name, encoding) @@ -206,7 +206,7 @@ def self.open(name, encoding: nil, &block) # if +nil+ (the default), the file system's encoding is used: # # Dir.new('.').read.encoding # => # - # Dir.new('.', encoding: 'US-ASCII').read.encoding # => # + # Dir.new('.', encoding: Encoding::US_ASCI).read.encoding # => # # def initialize(name, encoding: nil) Primitive.dir_initialize(name, encoding) diff --git a/doc/jit/zjit.md b/doc/jit/zjit.md index b5a2f05604983f..c94270554fd349 100644 --- a/doc/jit/zjit.md +++ b/doc/jit/zjit.md @@ -47,7 +47,7 @@ ZJIT. 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. +builds require `cargo` and may download dependencies. GNU Make is required. ### For normal use @@ -352,7 +352,7 @@ Ruby execution involves three distinct stacks and understanding them will help y The Ruby VM uses a single contiguous memory region (`ec->vm_stack`) containing two sub-stacks that grow toward each other. When they meet, stack overflow occurs. -See [doc/contributing/vm_stack_and_frames.md](contributing/vm_stack_and_frames.md) for detailed architecture and frame layout. +See [doc/contributing/vm_stack_and_frames.md](rdoc-ref:contributing/vm_stack_and_frames.md) for detailed architecture and frame layout. **Control Frame Stack:** diff --git a/doc/language/box.md b/doc/language/box.md index aebce7188b3179..abc6af868fc470 100644 --- a/doc/language/box.md +++ b/doc/language/box.md @@ -11,7 +11,7 @@ Ruby Box is designed to provide separated spaces in a Ruby process, to isolate a ## TODOs -* Add the loaded namespace on iseq to check if another namespace tries running the iseq (add a field only when VM_CHECK_MODE?) +* Add the loaded box on iseq to check if another box tries running the iseq (add a field only when VM_CHECK_MODE?) * Assign its own TOPLEVEL_BINDING in boxes * Fix calling `warn` in boxes to refer `$VERBOSE` and `Warning.warn` in the box * Make an internal data container class `Ruby::Box::Entry` invisible @@ -22,7 +22,7 @@ Ruby Box is designed to provide separated spaces in a Ruby process, to isolate a ### Enabling Ruby Box First, an environment variable should be set at the ruby process bootup: `RUBY_BOX=1`. -The only valid value is `1` to enable namespace. Other values (or unset `RUBY_BOX`) means disabling namespace. And setting the value after Ruby program starts doesn't work. +The only valid value is `1` to enable Ruby Box. Other values (or unset `RUBY_BOX`) means disabling Ruby Box. And setting the value after Ruby program starts doesn't work. ### Using Ruby Box @@ -75,7 +75,7 @@ There are two box types: There is the root box, just a single box in a Ruby process. Ruby bootstrap runs in the root box, and all builtin classes/modules are defined in the root box. (See "Builtin classes and modules".) -User boxes are to run user-written programs and libraries loaded from user programs. The user's main program (specified by the `ruby` command line argument) is executed in the "main" box, which is a user namespace automatically created at the end of Ruby's bootstrap, copied from the root box. +User boxes are to run user-written programs and libraries loaded from user programs. The user's main program (specified by the `ruby` command line argument) is executed in the "main" box, which is a user box automatically created at the end of Ruby's bootstrap, copied from the root box. When `Ruby::Box.new` is called, an "optional" box (a user, non-main box) is created, copied from the root box. All user boxes are flat, copied from the root box. diff --git a/doc/language/character_selectors.rdoc b/doc/language/character_selectors.rdoc index 47cf242be7382b..20685b8392b655 100644 --- a/doc/language/character_selectors.rdoc +++ b/doc/language/character_selectors.rdoc @@ -14,6 +14,8 @@ Each of these instance methods accepts one or more character selectors: - String#delete!(*selectors): returns +self+ or +nil+. - String#squeeze(*selectors): returns a new string. - String#squeeze!(*selectors): returns +self+ or +nil+. +- String#strip(*selectors): returns a new string. +- String#strip!(*selectors): returns +self+ or +nil+. A character selector identifies zero or more characters in +self+ that are to be operands for the method. @@ -79,6 +81,8 @@ These instance methods accept multiple character selectors: - String#delete!(*selectors): returns +self+ or +nil+. - String#squeeze(*selectors): returns a new string. - String#squeeze!(*selectors): returns +self+ or +nil+. +- String#strip(*selectors): returns a new string. +- String#strip!(*selectors): returns +self+ or +nil+. In effect, the given selectors are formed into a single selector consisting of only those characters common to _all_ of the given selectors. diff --git a/doc/language/globals.md b/doc/language/globals.md index 716e62a7df87d5..4d42798b8ca154 100644 --- a/doc/language/globals.md +++ b/doc/language/globals.md @@ -10,76 +10,76 @@ To use the module: require 'English' ``` -## Summary +## In Brief ### Exceptions -| Variable | English | Contains | -|:--------:|:-----------------:|----------------------------------------------------| -| `$!` | `$ERROR_INFO` | Exception object; set by Kernel#raise. | -| `$@` | `$ERROR_POSITION` | Array of backtrace positions; set by Kernel#raise. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:--------:|:-----------------:|-----------------------------------------|:---------:|:---------:|---------------| +| `$!` | `$ERROR_INFO` | \Exception object or `nil` | `nil` | Yes | Kernel#raise | +| `$@` | `$ERROR_POSITION` | \Array of backtrace positions or `nil` | `nil` | Yes | Kernel#raise | ### Pattern Matching -| Variable | English | Contains | -|:-------------:|:-------------------:|--------------------------------------------------| -| `$~` | `$LAST_MATCH_INFO` | MatchData object; set by matcher method. | -| `$&` | `$MATCH` | Matched substring; set by matcher method. | -| `` $` `` | `$PRE_MATCH` | Substring left of match; set by matcher method. | -| `$'` | `$POST_MATCH` | Substring right of match; set by matcher method. | -| `$+` | `$LAST_PAREN_MATCH` | Last group matched; set by matcher method. | -| `$1` | | First group matched; set by matcher method. | -| `$2` | | Second group matched; set by matcher method. | -| $_n_ | | nth group matched; set by matcher method. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:-------------:|:-------------------:|-----------------------------------|:---------:|:---------:|-----------------| +| `$~` | `$LAST_MATCH_INFO` | \MatchData object or `nil` | `nil` | No | Matcher methods | +| `$&` | `$MATCH` | Matched substring or `nil` | `nil` | No | Matcher methods | +| `` $` `` | `$PRE_MATCH` | Substring left of match or `nil` | `nil` | No | Matcher methods | +| `$'` | `$POST_MATCH` | Substring right of match or `nil` | `nil` | No | Matcher methods | +| `$+` | `$LAST_PAREN_MATCH` | Last group matched or `nil` | `nil` | No | Matcher methods | +| `$1` | | First group matched or `nil` | `nil` | Yes | Matcher methods | +| `$2` | | Second group matched or `nil` | `nil` | Yes | Matcher methods | +| $_n_ | | nth group matched or `nil` | `nil` | Yes | Matcher methods | ### Separators -| Variable | English | Contains | -|:-----------:|:--------------------------:|--------------------------------------------| -| `$/`, `$-0` | `$INPUT_RECORD_SEPARATOR` | Input record separator; initially newline. | -| `$\` | `$OUTPUT_RECORD_SEPARATOR` | Output record separator; initially `nil`. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:-----------:|:---------------------------:|-------------------------|:---------:|:---------:|----------| +| `$/`, `$-0` | `$INPUT_RECORD_SEPARATOR` | Input record separator | Newline | No | | +| `$\` | `$OUTPUT_RECORD_SEPARATOR` | Output record separator | `nil` | No | | ### Streams -| Variable | English | Contains | -|:---------:|:---------------------------:|-----------------------------------------------| -| `$stdin` | | Standard input stream; initially `STDIN`. | -| `$stdout` | | Standard input stream; initially `STDIOUT`. | -| `$stderr` | | Standard input stream; initially `STDERR`. | -| `$<` | `$DEFAULT_INPUT` | Default standard input; `ARGF` or `$stdin`. | -| `$>` | `$DEFAULT_OUTPUT` | Default standard output; initially `$stdout`. | -| `$.` | `$INPUT_LINE_NUMBER`, `$NR` | Input position of most recently read stream. | -| `$_` | `$LAST_READ_LINE` | String from most recently read stream. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:---------:|:----------------------------:|:-------------------------------------------:|:---------:|:---------:|----------------------| +| `$stdin` | | Standard input stream | `STDIN` | No | | +| `$stdout` | | Standard output stream | `STDOUT` | No | | +| `$stderr` | | Standard error stream | `STDERR` | No | | +| `$<` | `$DEFAULT_INPUT` | Default standard input | `ARGF` | Yes | | +| `$>` | `$DEFAULT_OUTPUT` | Default standard output | `STDOUT` | No | | +| `$.` | `$INPUT_LINE_NUMBER`, `$NR` | Input position of most recently read stream | 0 | No | Certain read methods | +| `$_` | `$LAST_READ_LINE` | String from most recently read stream | `nil` | No | Certain read methods | ### Processes -| Variable | English | Contains | -|:-------------------------:|:---------------------:|--------------------------------------------------------| -| `$0` | | Initially, the name of the executing program. | -| `$*` | `$ARGV` | Points to the `ARGV` array. | -| `$$` | `$PROCESS_ID`, `$PID` | Process ID of the current process. | -| `$?` | `$CHILD_STATUS` | Process::Status of most recently exited child process. | -| `$LOAD_PATH`, `$:`, `$-I` | | Array of paths to be searched. | -| `$LOADED_FEATURES`, `$"` | | Array of paths to loaded files. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:-------------------------:|:----------------------:|---------------------------------|:-------------:|:---------:|----------| +| `$0`, `$PROGRAM_NAME` | | Program name | Program name | No | | +| `$*` | `$ARGV` | \ARGV array | `ARGV` | Yes | | +| `$$` | `$PROCESS_ID`, `$PID` | Process id | Process PID | Yes | | +| `$?` | `$CHILD_STATUS` | Status of recently exited child | `nil` | Yes | | +| `$LOAD_PATH`, `$:`, `$-I` | | \Array of search paths | Ruby defaults | Yes | | +| `$LOADED_FEATURES`, `$"` | | \Array of load paths | Ruby defaults | Yes | | ### Debugging -| Variable | English | Contains | -|:-----------:|:-------:|--------------------------------------------------------| -| `$FILENAME` | | The value returned by method ARGF.filename. | -| `$DEBUG` | | Initially, whether option `-d` or `--debug` was given. | -| `$VERBOSE` | | Initially, whether option `-V` or `-W` was given. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:-----------:|:--------:|--------------------------------------------|:----------------------------:|:---------:|----------| +| `$FILENAME` | | Value returned by method `ARGF.filename` | Command-line argument or '-' | Yes | | +| `$DEBUG` | | Whether option `-d` or `--debug` was given | Command-line option | No | | +| `$VERBOSE` | | Whether option `-V` or `-W` was given | Command-line option | No | | ### Other Variables -| Variable | English | Contains | -|:-----------:|:-------:|------------------------------------------------| -| `$-F`, `$;` | | Separator given with command-line option `-F`. | -| `$-a` | | Whether option `-a` was given. | -| `$-i` | | Extension given with command-line option `-i`. | -| `$-l` | | Whether option `-l` was given. | -| `$-p` | | Whether option `-p` was given. | -| `$F` | | Array of `$_` split by `$-F`. | +| Variable | \English | Contains | Initially | Read-Only | Reset By | +|:-----------:|:--------:|-----------------------------------------------|:---------:|:---------:|----------| +| `$-F`, `$;` | | Separator given with command-line option `-F` | | | | +| `$-a` | | Whether option `-a` was given | | Yes | | +| `$-i` | | Extension given with command-line option `-i` | | No | | +| `$-l` | | Whether option `-l` was given | | Yes | | +| `$-p` | | Whether option `-p` was given | | Yes | | +| `$F` | | \Array of `$_` split by `$-F` | | | | ## Exceptions @@ -352,6 +352,10 @@ Aliased as `$-v` and `$-w`. ## Other Variables +### `$-a` + +Whether command-line option `-a` was given; read-only. + ### `$-F` The default field separator in String#split; must be a String or a @@ -407,27 +411,27 @@ obtained by splitting `$_` by `$-F` is assigned at the start of each ### Environment -| Constant | Contains | -|:---------------------:|-------------------------------------------------------------------------------| -| `ENV` | Hash of current environment variable names and values. | -| `ARGF` | String concatenation of files given on the command line, or `$stdin` if none. | -| `ARGV` | Array of the given command-line arguments. | -| `TOPLEVEL_BINDING` | Binding of the top level scope. | -| `RUBY_VERSION` | String Ruby version. | -| `RUBY_RELEASE_DATE` | String Ruby release date. | -| `RUBY_PLATFORM` | String Ruby platform. | -| `RUBY_PATCH_LEVEL` | String Ruby patch level. | -| `RUBY_REVISION` | String Ruby revision. | -| `RUBY_COPYRIGHT` | String Ruby copyright. | -| `RUBY_ENGINE` | String Ruby engine. | +| Constant | Contains | +|-----------------------|-------------------------------------------------------------------------------| +| `ENV` | Hash of current environment variable names and values. | +| `ARGF` | String concatenation of files given on the command line, or `$stdin` if none. | +| `ARGV` | Array of the given command-line arguments. | +| `TOPLEVEL_BINDING` | Binding of the top level scope. | +| `RUBY_VERSION` | String Ruby version. | +| `RUBY_RELEASE_DATE` | String Ruby release date. | +| `RUBY_PLATFORM` | String Ruby platform. | +| `RUBY_PATCH_LEVEL` | String Ruby patch level. | +| `RUBY_REVISION` | String Ruby revision. | +| `RUBY_COPYRIGHT` | String Ruby copyright. | +| `RUBY_ENGINE` | String Ruby engine. | | `RUBY_ENGINE_VERSION` | String Ruby engine version. | -| `RUBY_DESCRIPTION` | String Ruby description. | +| `RUBY_DESCRIPTION` | String Ruby description. | -### Embedded Data +### Embedded \Data -| Constant | Contains | -|:--------:|--------------------------------------------------------------------| -| `DATA` | File containing embedded data (lines following `__END__`, if any). | +| Constant | Contains | +|:---------------------:|-------------------------------------------------------------------------------| +| `DATA` | File containing embedded data (lines following `__END__`, if any). | ## Streams @@ -607,3 +611,4 @@ Output: [command-line option `-p`]: rdoc-ref:language/options.md@p-3A+-n-2C+with+Printing [command-line option `-v`]: rdoc-ref:language/options.md@v-3A+Print+Version-3B+Set+-24VERBOSE [command-line option `-w`]: rdoc-ref:language/options.md@w-3A+Synonym+for+-W1 + diff --git a/doc/language/packed_data.rdoc b/doc/language/packed_data.rdoc index 3a762c03829a74..f7cb4dbf74d99b 100644 --- a/doc/language/packed_data.rdoc +++ b/doc/language/packed_data.rdoc @@ -340,6 +340,22 @@ for one element in the input or output array. s.unpack('U*') # => [4194304] +- 'r' - Signed LEB128-encoded integer + (see {Signed LEB128}[https://en.wikipedia.org/wiki/LEB128#Signed_LEB128]) + + s = [1, 127, -128, 16383, -16384].pack("r*") + # => "\x01\xFF\x00\x80\x7F\xFF\xFF\x00\x80\x80\x7F" + s.unpack('r*') + # => [1, 127, -128, 16383, -16384] + +- 'R' - Unsigned LEB128-encoded integer + (see {Unsigned LEB128}[https://en.wikipedia.org/wiki/LEB128#Unsigned_LEB128]) + + s = [1, 127, 128, 16383, 16384].pack("R*") + # => "\x01\x7F\x80\x01\xFF\x7F\x80\x80\x01" + s.unpack('R*') + # => [1, 127, 128, 16383, 16384] + - 'w' - BER-encoded integer (see {BER encoding}[https://en.wikipedia.org/wiki/X.690#BER_encoding]): diff --git a/doc/language/ractor.md b/doc/language/ractor.md index 224e36934b51d2..24b75c1c5b552e 100644 --- a/doc/language/ractor.md +++ b/doc/language/ractor.md @@ -1,39 +1,39 @@ -# Ractor - Ruby's Actor-like concurrent abstraction +# Ractor - Ruby's Actor-like concurrency abstraction -Ractor is designed to provide a parallel execution feature of Ruby without thread-safety concerns. +Ractors are designed to provide parallel execution of Ruby code without thread-safety concerns. ## Summary ### Multiple Ractors in an interpreter process -You can make multiple Ractors and they run in parallel. +You can create multiple Ractors which can run ruby code in parallel. -* `Ractor.new{ expr }` creates a new Ractor and `expr` is run in parallel on a parallel computer. -* Interpreter invokes with the first Ractor (called *main Ractor*). -* If the main Ractor terminates, all other Ractors receive termination requests, similar to how threads behave. (if main thread (first invoked Thread), Ruby interpreter sends all running threads to terminate execution). +* `Ractor.new{ expr }` creates a new Ractor and `expr` can run in parallel with other ractors on a multi-core computer. +* Ruby processes start with one Ractor (called the *main Ractor*). +* If the main Ractor terminates, all other Ractors receive termination requests, similar to how threads behave. * Each Ractor contains one or more Threads. - * Threads within the same Ractor share a Ractor-wide global lock like GIL (GVL in MRI terminology), so they can't run in parallel (without releasing GVL explicitly in C-level). Threads in different ractors run in parallel. - * The overhead of creating a Ractor is similar to overhead of one Thread creation. + * Threads within the same Ractor share a Ractor-wide global lock (GVL in MRI terminology), so they can't run in parallel wich each other (without releasing the GVL explicitly in C extensions). Threads in different ractors can run in parallel. + * The overhead of creating a Ractor is slightly above the overhead of creating a Thread. -### Limited sharing between multiple ractors +### Limited sharing between Ractors -Ractors don't share everything, unlike threads. +Ractors don't share all objects, unlike Threads which can access any object other than objects stored in another Thread's thread-locals. -* Most objects are *Unshareable objects*, so you don't need to care about thread-safety problems which are caused by sharing. -* Some objects are *Shareable objects*. - * Immutable objects: frozen objects which don't refer to unshareable-objects. +* Most objects are *Unshareable objects*. Unshareable objects can only be used by the ractor that instantiated them, so you don't need to worry about thread-safety issues resulting from using the object concurrently across Ractors. +* Some objects are *Shareable objects*. Here is an incomplete list to give you an idea: + * Immutable objects: these are frozen objects which don't refer to unshareable-objects. * `i = 123`: `i` is an immutable object. * `s = "str".freeze`: `s` is an immutable object. - * `a = [1, [2], 3].freeze`: `a` is not an immutable object because `a` refers unshareable-object `[2]` (which is not frozen). - * `h = {c: Object}.freeze`: `h` is an immutable object because `h` refers Symbol `:c` and shareable `Object` class object which is not frozen. - * Class/Module objects + * `a = [1, [2], 3].freeze`: `a` is not an immutable object because `a` refers to the unshareable-object `[2]` (which is not frozen). + * `h = {c: Object}.freeze`: `h` is an immutable object because `h` refers to the Symbol `:c` and the shareable `Object` class. + * Class/Module objects are always shareable, even if they refer to unshareable objects. * Special shareable objects - * Ractor object itself. + * Ractor objects themselves are shareable. * And more... ### Communication between Ractors with `Ractor::Port` -Ractors communicate with each other and synchronize the execution by message exchanging between Ractors. `Ractor::Port` is provided for this communication. +Ractors communicate with each other and synchronize their execution by exchanging messages. The `Ractor::Port` class provides this communication mechanism. ```ruby port = Ractor::Port.new @@ -43,72 +43,65 @@ Ractor.new port do |port| port << 42 end -port.receive # get a message to the port. Only the creator Ractor can receive from the port +port.receive # get a message from the port. Only the ractor that created the Port can receive from it. #=> 42 ``` -Ractors have its own default port and `Ractor#send`, `Ractor.receive` will use it. +All Ractors have a default port, which `Ractor#send`, `Ractor.receive` (etc) will use. -### Copy & Move semantics to send messages +### Copy & Move semantics when sending objects -To send unshareable objects as messages, objects are copied or moved. +To send unshareable objects to another ractor, objects are either copied or moved. -* Copy: use deep-copy. -* Move: move membership. - * Sender can not access the moved object after moving the object. - * Guarantee that at least only 1 Ractor can access the object. +* Copy: deep-copies the object to the other ractor. +* Move: moves membership to another ractor. + * The sending Ractor can not access the moved object after it moves. + * There is a guarantee that only one Ractor can access an unshareable object at once. ### Thread-safety -Ractor helps to write a thread-safe concurrent program, but we can make thread-unsafe programs with Ractors. +Ractors help to write thread-safe, concurrent programs. They allow sharing of data only through explicit message passing for +unshareable objects. Shareable objects are guaranteed to work correctly across ractors, even if the ractors are running in parallel. +This guarantee, however, only applies across ractors. You still need to use Mutexes and other thread-safety tools within a ractor if +you're using multiple ruby Threads. -* GOOD: Sharing limitation - * Most objects are unshareable, so we can't make data-racy and race-conditional programs. - * Shareable objects are protected by an interpreter or locking mechanism. -* BAD: Class/Module can violate this assumption - * To make it compatible with old behavior, classes and modules can introduce data-race and so on. - * Ruby programmers should take care if they modify class/module objects on multi Ractor programs. -* BAD: Ractor can't solve all thread-safety problems - * There are several blocking operations (waiting send) so you can make a program which has dead-lock and live-lock issues. - * Some kind of shareable objects can introduce transactions (STM, for example). However, misusing transactions will generate inconsistent state. - -Without Ractor, we need to trace all state-mutations to debug thread-safety issues. -With Ractor, you can concentrate on suspicious code which are shared with Ractors. + * Most objects are unshareable. You can't create data-races across ractors due to the inability to use these objects across ractors. + * Shareable objects are protected by locks (or otherwise don't need to be) so they can be used by more than one ractor at once. ## Creation and termination ### `Ractor.new` -* `Ractor.new{ expr }` generates another Ractor. +* `Ractor.new{ expr }` creates a Ractor. ```ruby -# Ractor.new with a block creates new Ractor +# Ractor.new with a block creates a new Ractor r = Ractor.new do - # This block will be run in parallel with other ractors + # This block can run in parallel with other ractors end -# You can name a Ractor with `name:` argument. -r = Ractor.new name: 'test-name' do +# You can name a Ractor with a `name:` argument. +r = Ractor.new name: 'my-first-ractor' do end # and Ractor#name returns its name. -r.name #=> 'test-name' +r.name #=> 'my-first-ractor' ``` -### Given block isolation +### Block isolation -The Ractor executes given `expr` in a given block. -Given block will be isolated from outer scope by the `Proc#isolate` method (not exposed yet for Ruby users). To prevent sharing unshareable objects between ractors, block outer-variables, `self` and other information are isolated. +The Ractor executes `expr` in the given block. +The given block will be isolated from its outer scope. To prevent sharing objects between ractors, outer variables, `self` and other information is isolated from the block. -`Proc#isolate` is called at Ractor creation time (when `Ractor.new` is called). If given Proc object is not able to isolate because of outer variables and so on, an error will be raised. +This isolation occurs at Ractor creation time (when `Ractor.new` is called). If the given block is not able to be isolated because of outer variables or `self`, an error will be raised. ```ruby begin a = true r = Ractor.new do - a #=> ArgumentError because this block accesses `a`. + a #=> ArgumentError because this block accesses outer variable `a`. end - r.join # see later + r.join # wait for ractor to finish rescue ArgumentError end ``` @@ -123,7 +116,7 @@ end r.value == self.object_id #=> false ``` -Passed arguments to `Ractor.new()` becomes block parameters for the given block. However, an interpreter does not pass the parameter object references, but send them as messages (see below for details). +Arguments passed to `Ractor.new()` become block parameters for the given block. However, Ruby does not pass the objects themselves, but sends them as messages (see below for details). ```ruby r = Ractor.new 'ok' do |msg| @@ -133,7 +126,7 @@ r.value #=> 'ok' ``` ```ruby -# almost similar to the last example +# similar to the last example r = Ractor.new do msg = Ractor.receive msg @@ -142,9 +135,9 @@ r.send 'ok' r.value #=> 'ok' ``` -### An execution result of given block +### The execution result of the given block -Return value of the given block becomes an outgoing message (see below for details). +The return value of the given block becomes an outgoing message (see below for details). ```ruby r = Ractor.new do @@ -153,11 +146,11 @@ end r.value #=> `ok` ``` -Error in the given block will be propagated to the receiver of an outgoing message. +An error in the given block will be propagated to the consumer of the outgoing message. ```ruby r = Ractor.new do - raise 'ok' # exception will be transferred to the receiver + raise 'ok' # exception will be transferred to the consumer end begin @@ -171,36 +164,39 @@ end ## Communication between Ractors -Communication between Ractors is achieved by sending and receiving messages. There are two ways to communicate with each other. +Communication between Ractors is achieved by sending and receiving messages. There are two ways to communicate: -* (1) Message sending/receiving via `Ractor::Port` +* (1) Sending and receiving messages via `Ractor::Port` * (2) Using shareable container objects * Ractor::TVar gem ([ko1/ractor-tvar](https://github.com/ko1/ractor-tvar)) - * more? -Users can control program execution timing with (1), but should not control with (2) (only manage as critical section). +Users can control program execution timing with (1), but should not control with (2) (only perform critical sections). + +For sending and receiving messages, these are the fundamental APIs: + +* send/receive via `Ractor::Port`. + * `Ractor::Port#send(obj)` (`Ractor::Port#<<(obj)` is an alias) sends a message to the port. Ports are connected to an infinite size incoming queue so it will never block the caller. + * `Ractor::Port#receive` dequeues a message from its own incoming queue. If the incoming queue is empty, `Ractor::Port#receive` will block the execution of the current Thread. + * `Ractor#send` and `Ractor.receive` use ports (their default port) internally, so are conceptually similar to the above. +* You can close a `Ractor::Port` by `Ractor::Port#close`. A port can only be closed by the ractor that created it. + * If a port is closed, you can't `send` to it. Doing so raises an exception. + * When a Ractor is terminated, the Ractor's ports are automatically closed. +* You can wait for a ractor's termination and receive its return value with `Ractor#value`. This is similar to `Thread#value`. -For message sending and receiving, there are two types of APIs: push type and pull type. +There are 3 ways to send an object as a message: -* (1) send/receive via `Ractor::Port`. - * `Ractor::Port#send(obj)` (`Ractor::Port#<<(obj)` is an alias) send a message to the port. Ports are connected to the infinite size incoming queue so `Ractor::Port#send` will never block. - * `Ractor::Port#receive` dequeue a message from its own incoming queue. If the incoming queue is empty, `Ractor::Port#receive` calling will block the execution of a thread. -* `Ractor.select()` can wait for the success of `Ractor::Port#receive`. -* You can close `Ractor::Port` by `Ractor::Port#close` only by the creator Ractor of the port. - * If the port is closed, you can't `send` to the port. If `Ractor::Port#receive` is blocked for the closed port, then it will raise an exception. - * When a Ractor is terminated, the Ractor's ports are closed. -* There are 3 ways to send an object as a message - * (1) Send a reference: Sending a shareable object, send only a reference to the object (fast) - * (2) Copy an object: Sending an unshareable object by copying an object deeply (slow). Note that you can not send an object which does not support deep copy. Some `T_DATA` objects (objects whose class is defined in a C extension, such as `StringIO`) are not supported. - * (3) Move an object: Sending an unshareable object reference with a membership. Sender Ractor can not access moved objects anymore (raise an exception) after moving it. Current implementation makes new object as a moved object for receiver Ractor and copies references of sending object to moved object. `T_DATA` objects are not supported. - * You can choose "Copy" and "Move" by the `move:` keyword, `Ractor#send(obj, move: true/false)` and `Ractor.yield(obj, move: true/false)` (default is `false` (COPY)). +1) Send a reference: sending a shareable object sends only a reference to the object (fast). +2) Copy an object: sending an unshareable object through copying it deeply (can be slow). Note that you can not send an object this way which does not support deep copy. Some `T_DATA` objects (objects whose class is defined in a C extension, such as `StringIO`) are not supported. +3) Move an object: sending an unshareable object across ractors with a membership change. The sending Ractor can not access the moved object after moving it, otherwise an exception will be raised. Implementation note: `T_DATA` objects are not supported. + +You can choose between "Copy" and "Move" by the `move:` keyword, `Ractor#send(obj, move: true/false)`. The default is `false` ("Copy"). However, if the object is shareable it will automatically use `move`. ### Wait for multiple Ractors with `Ractor.select` -You can wait multiple Ractor port's receiving. +You can wait for messages on multiple ports at once. The return value of `Ractor.select()` is `[port, msg]` where `port` is a ready port and `msg` is received message. -To make convenient, `Ractor.select` can also accept Ractors to wait the termination of Ractors. +To make it convenient, `Ractor.select` can also accept Ractors. In this case, it waits for their termination. The return value of `Ractor.select()` is `[r, msg]` where `r` is a terminated Ractor and `msg` is the value of Ractor's block. Wait for a single ractor (same as `Ractor#value`): @@ -218,23 +214,21 @@ Waiting for two ractors: r1 = Ractor.new{'r1'} r2 = Ractor.new{'r2'} rs = [r1, r2] -as = [] +values = [] -# Wait for r1 or r2's Ractor.yield +# Wait for r1 or r2's termination r, obj = Ractor.select(*rs) rs.delete(r) -as << obj +values << obj # Second try (rs only contain not-closed ractors) r, obj = Ractor.select(*rs) rs.delete(r) -as << obj -as.sort == ['r1', 'r2'] #=> true +values << obj +values.sort == ['r1', 'r2'] #=> true ``` -TODO: Current `Ractor.select()` has the same issue of `select(2)`, so this interface should be refined. - -TODO: `select` syntax of go-language uses round-robin technique to make fair scheduling. Now `Ractor.select()` doesn't use it. +NOTE: Using `Ractor.select()` on a very large number of ractors has the same issue as `select(2)` currently. ### Closing Ractor's ports @@ -252,7 +246,7 @@ end r.join # success (wait for the termination) r.value # success (will return 'finish') -# the first Ractor which success the `Ractor#value` can get the result +# The ractor's termination value has already been given to another ractor Ractor.new r do |r| r.value #=> Ractor::Error end @@ -264,7 +258,7 @@ Example (try to send to closed (terminated) Ractor): r = Ractor.new do end -r.join # wait terminate +r.join # wait for termination begin r.send(1) @@ -289,7 +283,7 @@ end obj.object_id == r.value #=> false ``` -Some objects are not supported to copy the value, and raise an exception. +Some objects do not support copying, and raise an exception. ```ruby obj = Thread.new{} @@ -299,15 +293,13 @@ begin end rescue TypeError => e e.message #=> # -else - 'ng' # unreachable here end ``` ### Send a message by moving `Ractor::Port#send(obj, move: true)` moves `obj` to the destination Ractor. -If the source Ractor touches the moved object (for example, call the method like `obj.foo()`), it will be an error. +If the source Ractor touches the moved object (for example, calls a method like `obj.foo()`), it will raise an error. ```ruby # move with Ractor#send @@ -316,23 +308,21 @@ r = Ractor.new do obj << ' world' end -str = 'hello' +str = 'hello'.dup r.send str, move: true +# str is now moved, and accessing str from this Ractor is prohibited modified = r.value #=> 'hello world' -# str is moved, and accessing str from this Ractor is prohibited begin # Error because it touches moved str. str << ' exception' # raise Ractor::MovedError rescue Ractor::MovedError modified #=> 'hello world' -else - raise 'unreachable' end ``` -Some objects are not supported to move, and an exception will be raised. +Some objects do not support moving, and an exception will be raised. ```ruby r = Ractor.new do @@ -342,34 +332,29 @@ end r.send(Thread.new{}, move: true) #=> allocator undefined for Thread (TypeError) ``` -To achieve the access prohibition for moved objects, _class replacement_ technique is used to implement it. +Once an object has been moved, the source object's class is changed to `Ractor::MovedObject`. ### Shareable objects -The following objects are shareable. - -* Immutable objects - * Small integers, some symbols, `true`, `false`, `nil` (a.k.a. `SPECIAL_CONST_P()` objects in internal) - * Frozen native objects - * Numeric objects: `Float`, `Complex`, `Rational`, big integers (`T_BIGNUM` in internal) - * All Symbols. - * Frozen `String` and `Regexp` objects (their instance variables should refer only shareable objects) -* Class, Module objects (`T_CLASS`, `T_MODULE` and `T_ICLASS` in internal) -* `Ractor` and other special objects which care about synchronization. +The following is an inexhaustive list of shareable objects: -Implementation: Now shareable objects (`RVALUE`) have `FL_SHAREABLE` flag. This flag can be added lazily. +* Small integers, big integers, `Float`, `Complex`, `Rational` +* All symbols, frozen Strings, `true`, `false`, `nil` +* `Regexp` objects, if they have no instance variables or their instance variables refer only to shareables +* Class and Module objects +* `Ractor` and other special objects which care about synchronization -To make shareable objects, `Ractor.make_shareable(obj)` method is provided. In this case, try to make shareable by freezing `obj` and recursively traversable objects. This method accepts `copy:` keyword (default value is false).`Ractor.make_shareable(obj, copy: true)` tries to make a deep copy of `obj` and make the copied object shareable. +To make objects shareable, `Ractor.make_shareable(obj)` is provided. It tries to make the object shareable by freezing `obj` and recursively traversing its references to freeze them all. This method accepts the `copy:` keyword (default value is false). `Ractor.make_shareable(obj, copy: true)` tries to make a deep copy of `obj` and make the copied object shareable. `Ractor.make_shareable(copy: false)` has no effect on an already shareable object. If the object cannot be made shareable, a `Ractor::Error` exception will be raised. ## Language changes to isolate unshareable objects between Ractors To isolate unshareable objects between Ractors, we introduced additional language semantics on multi-Ractor Ruby programs. -Note that without using Ractors, these additional semantics is not needed (100% compatible with Ruby 2). +Note that without using Ractors, these additional semantics are not needed (100% compatible with Ruby 2). ### Global variables -Only the main Ractor (a Ractor created at starting of interpreter) can access global variables. +Only the main Ractor can access global variables. ```ruby $gv = 1 @@ -388,7 +373,7 @@ Note that some special global variables, such as `$stdin`, `$stdout` and `$stder ### Instance variables of shareable objects -Instance variables of classes/modules can be get from non-main Ractors if the referring values are shareable objects. +Instance variables of classes/modules can be accessed from non-main Ractors only if their values are shareable objects. ```ruby class C @@ -428,8 +413,6 @@ Ractor.new do end.join ``` - - ```ruby shared = Ractor.new{} shared.instance_variable_set(:@iv, 'str') @@ -445,8 +428,6 @@ rescue Ractor::RemoteError => e end ``` -Note that instance variables for class/module objects are also prohibited on Ractors. - ### Class variables Only the main Ractor can access class variables. @@ -472,11 +453,11 @@ end ### Constants -Only the main Ractor can read constants which refer to the unshareable object. +Only the main Ractor can read constants which refer to an unshareable object. ```ruby class C - CONST = 'str' + CONST = 'str'.dup end r = Ractor.new do C::CONST @@ -488,13 +469,13 @@ rescue => e end ``` -Only the main Ractor can define constants which refer to the unshareable object. +Only the main Ractor can define constants which refer to an unshareable object. ```ruby class C end r = Ractor.new do - C::CONST = 'str' + C::CONST = 'str'.dup end begin r.join @@ -503,19 +484,19 @@ rescue => e end ``` -To make multi-ractor supported library, the constants should only refer shareable objects. +When creating/updating a library to support ractors, constants should only refer to shareable objects if they are to be used by non-main ractors. ```ruby TABLE = {a: 'ko1', b: 'ko2', c: 'ko3'} ``` -In this case, `TABLE` references an unshareable Hash object. So that other ractors can not refer `TABLE` constant. To make it shareable, we can use `Ractor.make_shareable()` like that. +In this case, `TABLE` refers to an unshareable Hash object. In order for other ractors to use `TABLE`, we need to make it shareable. We can use `Ractor.make_shareable()` like so: ```ruby TABLE = Ractor.make_shareable( {a: 'ko1', b: 'ko2', c: 'ko3'} ) ``` -To make it easy, Ruby 3.0 introduced new `shareable_constant_value` Directive. +To make it easy, Ruby 3.0 introduced a new `shareable_constant_value` file directive. ```ruby # shareable_constant_value: literal @@ -524,7 +505,7 @@ TABLE = {a: 'ko1', b: 'ko2', c: 'ko3'} #=> Same as: TABLE = Ractor.make_shareable( {a: 'ko1', b: 'ko2', c: 'ko3'} ) ``` -`shareable_constant_value` directive accepts the following modes (descriptions use the example: `CONST = expr`): +The `shareable_constant_value` directive accepts the following modes (descriptions use the example: `CONST = expr`): * none: Do nothing. Same as: `CONST = expr` * literal: @@ -533,15 +514,71 @@ TABLE = {a: 'ko1', b: 'ko2', c: 'ko3'} * experimental_everything: replaced to `CONST = Ractor.make_shareable(expr)`. * experimental_copy: replaced to `CONST = Ractor.make_shareable(expr, copy: true)`. -Except the `none` mode (default), it is guaranteed that the assigned constants refer to only shareable objects. +Except for the `none` mode (default), it is guaranteed that the constants in the file refer only to shareable objects. See [doc/syntax/comments.rdoc](syntax/comments.rdoc) for more details. -## Implementation note +### Shareable procs + +Procs and lambdas are unshareable objects, even when they are frozen. To create an unshareable Proc, you must use `Ractor.shareable_proc { expr }`. Much like during Ractor creation, the Proc's block is isolated +from its outer environment, so it cannot access locals from the outside scope. `self` is also changed within the Proc to be `nil` by default, although a `self:` keyword can be provided if you want to customize +the value to a different shareable object. + +```ruby +p = Ractor.shareable_proc { p self } +p.call #=> nil +``` + +```ruby +begin + a = 1 + pr = Ractor.shareable_proc { p a } + pr.call # never gets here +rescue Ractor::IsolationError +end +``` -* Each Ractor has its own thread, it means each Ractor has at least 1 native thread. -* Each Ractor has its own ID (`rb_ractor_t::pub::id`). - * On debug mode, all unshareable objects are labeled with current Ractor's id, and it is checked to detect unshareable object leak (access an object from different Ractor) in VM. +In order to dynamically define a method with `define_method` that can be used from different ractors, you must +define it with a shareable proc. Alternatively, you can use `class_eval` or `module_eval` with a String. Even though +the shareable proc's `self` is initially bound to `nil`, `define_method` will bind `self` to the correct value in the +method. + +```ruby +class A + define_method :testing, &Ractor.shareable_proc do + p self + end +end +Ractor.new do + a = A.new + a.testing #=> # +end.join +``` + +This isolation must be done to prevent the method from accessing captured outer variables across Ractors. + +### Ractor-local storage + +You can store any object (even unshareables) in ractor-local storage. + +```ruby +r = Ractor.new do + values = [] + Ractor[:threads] = [] + 3.times do |i| + Ractor[:threads] << Thread.new do + values << [Ractor.receive, i+1] # Ractor.receive blocks the current thread in the current ractor until it receives a message + end + end + Ractor[:threads].each(&:join) + values +end + +r << 1 +r << 2 +r << 3 +r.value #=> [[1,1],[2,2],[3,3]] (the order can change with each run) +``` ## Examples diff --git a/doc/string/aset.rdoc b/doc/string/aset.rdoc index cac3b67ef580dd..db9079ebfb8188 100644 --- a/doc/string/aset.rdoc +++ b/doc/string/aset.rdoc @@ -170,9 +170,9 @@ With string argument +substring+ given: s['ll'] = 'foo' # => "foo" s # => "hefooo" - s = 'тест' - s['ес'] = 'foo' # => "foo" - s # => "тfooт" + s = 'Привет' + s['ив'] = 'foo' # => "foo" + s # => "Прfooет" s = 'こんにちは' s['んにち'] = 'foo' # => "foo" diff --git a/doc/string/bytes.rdoc b/doc/string/bytes.rdoc index f4b071f6306394..3815f13276fa11 100644 --- a/doc/string/bytes.rdoc +++ b/doc/string/bytes.rdoc @@ -1,7 +1,7 @@ Returns an array of the bytes in +self+: - 'hello'.bytes # => [104, 101, 108, 108, 111] - 'тест'.bytes # => [209, 130, 208, 181, 209, 129, 209, 130] + 'hello'.bytes # => [104, 101, 108, 108, 111] + 'Привет'.bytes # => [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] 'こんにちは'.bytes # => [227, 129, 147, 227, 130, 147, 227, 129, 171, 227, 129, 161, 227, 129, 175] diff --git a/doc/string/bytesize.rdoc b/doc/string/bytesize.rdoc index 5166dd7dc614ac..cbb7f439fcb448 100644 --- a/doc/string/bytesize.rdoc +++ b/doc/string/bytesize.rdoc @@ -5,9 +5,9 @@ Note that the byte count may be different from the character count (returned by s = 'foo' s.bytesize # => 3 s.size # => 3 - s = 'тест' - s.bytesize # => 8 - s.size # => 4 + s = 'Привет' + s.bytesize # => 12 + s.size # => 6 s = 'こんにちは' s.bytesize # => 15 s.size # => 5 diff --git a/doc/string/center.rdoc b/doc/string/center.rdoc index 343f6ba263acae..3116d211174286 100644 --- a/doc/string/center.rdoc +++ b/doc/string/center.rdoc @@ -9,7 +9,7 @@ centered and padded on one or both ends with +pad_string+: 'hello'.center(20, '-|') # => "-|-|-|-hello-|-|-|-|" # Some padding repeated. 'hello'.center(10, 'abcdefg') # => "abhelloabc" # Some padding not used. ' hello '.center(13) # => " hello " - 'тест'.center(10) # => " тест " + 'Привет'.center(10) # => " Привет " 'こんにちは'.center(10) # => " こんにちは " # Multi-byte characters. If +size+ is less than or equal to the size of +self+, returns an unpadded copy of +self+: diff --git a/doc/string/chars.rdoc b/doc/string/chars.rdoc index 094384271b1a77..97ea07331f4c46 100644 --- a/doc/string/chars.rdoc +++ b/doc/string/chars.rdoc @@ -1,7 +1,7 @@ Returns an array of the characters in +self+: 'hello'.chars # => ["h", "e", "l", "l", "o"] - 'тест'.chars # => ["т", "е", "с", "т"] + 'Привет'.chars # => ["П", "р", "и", "в", "е", "т"] 'こんにちは'.chars # => ["こ", "ん", "に", "ち", "は"] ''.chars # => [] diff --git a/doc/syntax/calling_methods.rdoc b/doc/syntax/calling_methods.rdoc index bf5916e99aa071..76babcc3dcd969 100644 --- a/doc/syntax/calling_methods.rdoc +++ b/doc/syntax/calling_methods.rdoc @@ -355,9 +355,8 @@ as one argument: # Prints the object itself: # # -This allows to handle one or many arguments polymorphically. Note also that +nil+ -has NilClass#to_a defined to return an empty array, so conditional unpacking is -possible: +This allows to handle one or many arguments polymorphically. Note also that *nil +is unpacked to an empty list of arguments, so conditional unpacking is possible: my_method(*(some_arguments if some_condition?)) diff --git a/enumerator.c b/enumerator.c index 5514d76dace3d4..89ec5035305753 100644 --- a/enumerator.c +++ b/enumerator.c @@ -1230,6 +1230,24 @@ enumerator_inspect(VALUE obj) * (1..100).to_a.permutation(4).size # => 94109400 * loop.size # => Float::INFINITY * (1..100).drop_while.size # => nil + * + * Note that enumerator size might be inaccurate, and should be rather treated as a hint. + * For example, there is no check that the size provided to ::new is accurate: + * + * e = Enumerator.new(5) { |y| 2.times { y << it} } + * e.size # => 5 + * e.to_a.size # => 2 + * + * Another example is an enumerator created by ::produce without a +size+ argument. + * Such enumerators return +Infinity+ for size, but this is inaccurate if the passed + * block raises StopIteration: + * + * e = Enumerator.produce(1) { it + 1 } + * e.size # => Infinity + * + * e = Enumerator.produce(1) { it > 3 ? raise(StopIteration) : it + 1 } + * e.size # => Infinity + * e.to_a.size # => 4 */ static VALUE @@ -3051,6 +3069,12 @@ producer_size(VALUE obj, VALUE args, VALUE eobj) * File.dirname(it) * } * traverser.size # => 4 + * + * # Finite enumerator with unknown size + * calendar = Enumerator.produce(Date.today, size: nil) { + * it.monday? ? raise(StopIteration) : it + 1 + * } + * calendar.size # => nil */ static VALUE enumerator_s_produce(int argc, VALUE *argv, VALUE klass) diff --git a/eval.c b/eval.c index 0c80872bee76e5..deadd5dd6414fb 100644 --- a/eval.c +++ b/eval.c @@ -922,6 +922,9 @@ rb_f_raise(int argc, VALUE *argv) * With argument +exception+ not given, * argument +message+ and keyword argument +cause+ may be given, * but argument +backtrace+ may not be given. + * + * +cause+ can not be given as an only argument. + * */ static VALUE diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index 88225f76e5c882..e952b7871b3f6f 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -83,15 +83,13 @@ init_inetsock_internal(VALUE v) VALUE open_timeout = arg->open_timeout; VALUE timeout; VALUE starts_at; - unsigned int timeout_msec; timeout = NIL_P(open_timeout) ? resolv_timeout : open_timeout; - timeout_msec = NIL_P(timeout) ? 0 : rsock_value_timeout_to_msec(timeout); starts_at = current_clocktime(); arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv, family, SOCK_STREAM, - (type == INET_SERVER) ? AI_PASSIVE : 0, timeout_msec); + (type == INET_SERVER) ? AI_PASSIVE : 0, timeout); /* * Maybe also accept a local address @@ -99,7 +97,7 @@ init_inetsock_internal(VALUE v) if (type != INET_SERVER && (!NIL_P(arg->local.host) || !NIL_P(arg->local.serv))) { arg->local.res = rsock_addrinfo(arg->local.host, arg->local.serv, - family, SOCK_STREAM, 0, 0); + family, SOCK_STREAM, 0, timeout); } VALUE io = Qnil; @@ -630,14 +628,7 @@ init_fast_fallback_inetsock_internal(VALUE v) arg->getaddrinfo_shared = NULL; int family = arg->families[0]; - unsigned int t; - if (!NIL_P(open_timeout)) { - t = rsock_value_timeout_to_msec(open_timeout); - } else if (!NIL_P(open_timeout)) { - t = rsock_value_timeout_to_msec(resolv_timeout); - } else { - t = 0; - } + VALUE t = NIL_P(open_timeout) ? resolv_timeout : open_timeout; arg->remote.res = rsock_addrinfo( arg->remote.host, @@ -1337,15 +1328,7 @@ rsock_init_inetsock( * Maybe also accept a local address */ if (!NIL_P(local_host) || !NIL_P(local_serv)) { - unsigned int t; - if (!NIL_P(open_timeout)) { - t = rsock_value_timeout_to_msec(open_timeout); - } else if (!NIL_P(open_timeout)) { - t = rsock_value_timeout_to_msec(resolv_timeout); - } else { - t = 0; - } - + VALUE t = NIL_P(open_timeout) ? resolv_timeout : open_timeout; local_res = rsock_addrinfo( local_host, local_serv, @@ -1609,7 +1592,7 @@ static VALUE ip_s_getaddress(VALUE obj, VALUE host) { union_sockaddr addr; - struct rb_addrinfo *res = rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, 0, 0); + struct rb_addrinfo *res = rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, 0, Qnil); socklen_t len = res->ai->ai_addrlen; /* just take the first one */ diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 3dcbe7717a0ca2..6cdf5c6abc40e7 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -293,7 +293,7 @@ rb_freeaddrinfo(struct rb_addrinfo *ai) xfree(ai); } -unsigned int +static int rsock_value_timeout_to_msec(VALUE timeout) { double seconds = NUM2DBL(timeout); @@ -308,7 +308,7 @@ rsock_value_timeout_to_msec(VALUE timeout) #if GETADDRINFO_IMPL == 0 static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, unsigned int _timeout) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int _timeout) { return getaddrinfo(hostp, portp, hints, ai); } @@ -346,7 +346,7 @@ fork_safe_getaddrinfo(void *arg) } static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, unsigned int _timeout) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int _timeout) { struct getaddrinfo_arg arg; MEMZERO(&arg, struct getaddrinfo_arg, 1); @@ -367,11 +367,11 @@ struct getaddrinfo_arg int err, gai_errno, refcount, done, cancelled, timedout; rb_nativethread_lock_t lock; rb_nativethread_cond_t cond; - unsigned int timeout; + int timeout; }; static struct getaddrinfo_arg * -allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addrinfo *hints, unsigned int timeout) +allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addrinfo *hints, int timeout) { size_t hostp_offset = sizeof(struct getaddrinfo_arg); size_t portp_offset = hostp_offset + (hostp ? strlen(hostp) + 1 : 0); @@ -465,8 +465,11 @@ wait_getaddrinfo(void *ptr) struct getaddrinfo_arg *arg = (struct getaddrinfo_arg *)ptr; rb_nativethread_lock_lock(&arg->lock); while (!arg->done && !arg->cancelled) { - unsigned long msec = arg->timeout; - if (msec > 0) { + long msec = arg->timeout; + if (msec == 0) { + arg->cancelled = 1; + arg->timedout = 1; + } else if (msec > 0) { rb_native_cond_timedwait(&arg->cond, &arg->lock, msec); if (!arg->done) { arg->cancelled = 1; @@ -549,7 +552,7 @@ fork_safe_do_getaddrinfo(void *ptr) } static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, unsigned int timeout) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int timeout) { int retry; struct getaddrinfo_arg *arg; @@ -1021,7 +1024,7 @@ rb_scheduler_getaddrinfo(VALUE scheduler, VALUE host, const char *service, } struct rb_addrinfo* -rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, unsigned int timeout) +rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, VALUE timeout) { struct rb_addrinfo* res = NULL; struct addrinfo *ai; @@ -1056,7 +1059,8 @@ rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_h } if (!resolved) { - error = rb_getaddrinfo(hostp, portp, hints, &ai, timeout); + int t = NIL_P(timeout) ? -1 : rsock_value_timeout_to_msec(timeout); + error = rb_getaddrinfo(hostp, portp, hints, &ai, t); if (error == 0) { res = (struct rb_addrinfo *)xmalloc(sizeof(struct rb_addrinfo)); res->allocated_by_malloc = 0; @@ -1089,7 +1093,7 @@ rsock_fd_family(int fd) } struct rb_addrinfo* -rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, unsigned int timeout) +rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, VALUE timeout) { struct addrinfo hints; @@ -1380,8 +1384,7 @@ call_getaddrinfo(VALUE node, VALUE service, hints.ai_flags = NUM2INT(flags); } - unsigned int t = NIL_P(timeout) ? 0 : rsock_value_timeout_to_msec(timeout); - res = rsock_getaddrinfo(node, service, &hints, socktype_hack, t); + res = rsock_getaddrinfo(node, service, &hints, socktype_hack, timeout); if (res == NULL) rb_raise(rb_eSocket, "host not found"); diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 638b7ede6ec72e..2ec3ab335aef86 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -327,8 +327,8 @@ void rb_freeaddrinfo(struct rb_addrinfo *ai); VALUE rsock_freeaddrinfo(VALUE arg); int rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags); int rsock_fd_family(int fd); -struct rb_addrinfo *rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, unsigned int timeout); -struct rb_addrinfo *rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, unsigned int timeout); +struct rb_addrinfo *rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, VALUE timeout); +struct rb_addrinfo *rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, VALUE timeout); VALUE rsock_fd_socket_addrinfo(int fd, struct sockaddr *addr, socklen_t len); VALUE rsock_io_socket_addrinfo(VALUE io, struct sockaddr *addr, socklen_t len); @@ -453,7 +453,6 @@ void free_fast_fallback_getaddrinfo_shared(struct fast_fallback_getaddrinfo_shar # endif #endif -unsigned int rsock_value_timeout_to_msec(VALUE); NORETURN(void rsock_raise_user_specified_timeout(struct addrinfo *ai, VALUE host, VALUE port)); void rsock_init_basicsocket(void); diff --git a/ext/socket/socket.c b/ext/socket/socket.c index 26bf0bae8c046d..a8e5ae81190c21 100644 --- a/ext/socket/socket.c +++ b/ext/socket/socket.c @@ -965,7 +965,7 @@ sock_s_gethostbyname(VALUE obj, VALUE host) { rb_warn("Socket.gethostbyname is deprecated; use Addrinfo.getaddrinfo instead."); struct rb_addrinfo *res = - rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, 0); + rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, Qnil); return rsock_make_hostent(host, res, sock_sockaddr); } @@ -1183,7 +1183,7 @@ sock_s_getaddrinfo(int argc, VALUE *argv, VALUE _) norevlookup = rsock_do_not_reverse_lookup; } - res = rsock_getaddrinfo(host, port, &hints, 0, 0); + res = rsock_getaddrinfo(host, port, &hints, 0, Qnil); ret = make_addrinfo(res, norevlookup); rb_freeaddrinfo(res); @@ -1279,7 +1279,7 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _) hints.ai_socktype = (fl & NI_DGRAM) ? SOCK_DGRAM : SOCK_STREAM; /* af */ hints.ai_family = NIL_P(af) ? PF_UNSPEC : rsock_family_arg(af); - res = rsock_getaddrinfo(host, port, &hints, 0, 0); + res = rsock_getaddrinfo(host, port, &hints, 0, Qnil); sap = res->ai->ai_addr; salen = res->ai->ai_addrlen; } @@ -1335,7 +1335,7 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _) static VALUE sock_s_pack_sockaddr_in(VALUE self, VALUE port, VALUE host) { - struct rb_addrinfo *res = rsock_addrinfo(host, port, AF_UNSPEC, 0, 0, 0); + struct rb_addrinfo *res = rsock_addrinfo(host, port, AF_UNSPEC, 0, 0, Qnil); VALUE addr = rb_str_new((char*)res->ai->ai_addr, res->ai->ai_addrlen); rb_freeaddrinfo(res); diff --git a/ext/socket/tcpsocket.c b/ext/socket/tcpsocket.c index 300a426eda8471..7ce536e0af9ca3 100644 --- a/ext/socket/tcpsocket.c +++ b/ext/socket/tcpsocket.c @@ -113,7 +113,7 @@ tcp_s_gethostbyname(VALUE obj, VALUE host) { rb_warn("TCPSocket.gethostbyname is deprecated; use Addrinfo.getaddrinfo instead."); struct rb_addrinfo *res = - rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, 0); + rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, Qnil); return rsock_make_hostent(host, res, tcp_sockaddr); } diff --git a/ext/socket/udpsocket.c b/ext/socket/udpsocket.c index 5538f24523fc09..b2bc92553886aa 100644 --- a/ext/socket/udpsocket.c +++ b/ext/socket/udpsocket.c @@ -84,7 +84,7 @@ udp_connect(VALUE self, VALUE host, VALUE port) { struct udp_arg arg = {.io = self}; - arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, Qnil); int result = (int)rb_ensure(udp_connect_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!result) { @@ -129,7 +129,7 @@ udp_bind(VALUE self, VALUE host, VALUE port) { struct udp_arg arg = {.io = self}; - arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, Qnil); VALUE result = rb_ensure(udp_bind_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!result) { @@ -212,7 +212,7 @@ udp_send(int argc, VALUE *argv, VALUE sock) GetOpenFile(sock, arg.fptr); arg.sarg.fd = arg.fptr->fd; arg.sarg.flags = NUM2INT(flags); - arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0, Qnil); ret = rb_ensure(udp_send_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!ret) rsock_sys_fail_host_port("sendto(2)", host, port); diff --git a/gc.c b/gc.c index d229c1f5aba254..104b027cca788f 100644 --- a/gc.c +++ b/gc.c @@ -1001,7 +1001,10 @@ newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, shape_id_t shape_id, bool w if (UNLIKELY(rb_gc_event_hook_required_p(RUBY_INTERNAL_EVENT_NEWOBJ))) { int lev = RB_GC_VM_LOCK_NO_BARRIER(); { - memset((char *)obj + RVALUE_SIZE, 0, rb_gc_obj_slot_size(obj) - RVALUE_SIZE); + size_t slot_size = rb_gc_obj_slot_size(obj); + if (slot_size > RVALUE_SIZE) { + memset((char *)obj + RVALUE_SIZE, 0, slot_size - RVALUE_SIZE); + } /* We must disable GC here because the callback could call xmalloc * which could potentially trigger a GC, and a lot of code is unsafe diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index e1678dcf6ab0b4..c0c1bf30ea34c9 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -355,6 +355,12 @@ rb_mmtk_special_const_p(MMTk_ObjectReference object) return RB_SPECIAL_CONST_P(obj); } +static void +rb_mmtk_mutator_thread_panic_handler(void) +{ + rb_bug("Ruby mutator thread panicked"); +} + // Bootup MMTk_RubyUpcalls ruby_upcalls = { rb_mmtk_init_gc_worker_thread, @@ -374,6 +380,7 @@ MMTk_RubyUpcalls ruby_upcalls = { rb_mmtk_global_tables_count, rb_mmtk_update_finalizer_table, rb_mmtk_special_const_p, + rb_mmtk_mutator_thread_panic_handler, }; // Use max 80% of the available memory by default for MMTk @@ -462,6 +469,13 @@ void rb_gc_impl_set_params(void *objspace_ptr) { } static VALUE gc_verify_internal_consistency(VALUE self) { return Qnil; } +#define MMTK_HEAP_COUNT 5 +#define MMTK_MAX_OBJ_SIZE 640 + +static size_t heap_sizes[MMTK_HEAP_COUNT + 1] = { + 40, 80, 160, 320, MMTK_MAX_OBJ_SIZE, 0 +}; + void rb_gc_impl_init(void) { @@ -469,7 +483,7 @@ rb_gc_impl_init(void) rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(sizeof(VALUE) * 5)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic))); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), INT2NUM(0)); - rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), LONG2FIX(640)); + rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), LONG2FIX(MMTK_MAX_OBJ_SIZE)); // Pretend we have 5 size pools rb_hash_aset(gc_constants, ID2SYM(rb_intern("SIZE_POOL_COUNT")), LONG2FIX(5)); OBJ_FREEZE(gc_constants); @@ -485,10 +499,6 @@ rb_gc_impl_init(void) rb_define_singleton_method(rb_mGC, "verify_compaction_references", rb_f_notimplement, -1); } -static size_t heap_sizes[6] = { - 40, 80, 160, 320, 640, 0 -}; - size_t * rb_gc_impl_heap_sizes(void *objspace_ptr) { @@ -609,8 +619,8 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags struct objspace *objspace = objspace_ptr; struct MMTk_ractor_cache *ractor_cache = cache_ptr; - if (alloc_size > 640) rb_bug("too big"); - for (int i = 0; i < 5; i++) { + if (alloc_size > MMTK_MAX_OBJ_SIZE) rb_bug("too big"); + for (int i = 0; i < MMTK_HEAP_COUNT; i++) { if (alloc_size == heap_sizes[i]) break; if (alloc_size < heap_sizes[i]) { alloc_size = heap_sizes[i]; @@ -647,7 +657,7 @@ rb_gc_impl_obj_slot_size(VALUE obj) size_t rb_gc_impl_heap_id_for_size(void *objspace_ptr, size_t size) { - for (int i = 0; i < 5; i++) { + for (int i = 0; i < MMTK_HEAP_COUNT; i++) { if (size == heap_sizes[i]) return i; if (size < heap_sizes[i]) return i; } @@ -658,7 +668,7 @@ rb_gc_impl_heap_id_for_size(void *objspace_ptr, size_t size) bool rb_gc_impl_size_allocatable_p(size_t size) { - return size <= 640; + return size <= MMTK_MAX_OBJ_SIZE; } // Malloc diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index b00133a820c546..18466c61108b40 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -69,6 +69,7 @@ typedef struct MMTk_RubyUpcalls { int (*global_tables_count)(void); void (*update_finalizer_table)(void); bool (*special_const_p)(MMTk_ObjectReference object); + void (*mutator_thread_panic_handler)(void); } MMTk_RubyUpcalls; typedef struct MMTk_RawVecOfObjRef { diff --git a/gc/mmtk/src/abi.rs b/gc/mmtk/src/abi.rs index 2214441c9d6db7..1bd19fbe7efd1d 100644 --- a/gc/mmtk/src/abi.rs +++ b/gc/mmtk/src/abi.rs @@ -314,6 +314,7 @@ pub struct RubyUpcalls { pub global_tables_count: extern "C" fn() -> c_int, pub update_finalizer_table: extern "C" fn(), pub special_const_p: extern "C" fn(object: ObjectReference) -> bool, + pub mutator_thread_panic_handler: extern "C" fn(), } unsafe impl Sync for RubyUpcalls {} diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index 5217eb4a758da1..006e987ea9d060 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -138,6 +138,10 @@ pub unsafe extern "C" fn mmtk_init_binding( upcalls: *const RubyUpcalls, weak_reference_dead_value: ObjectReference, ) { + crate::MUTATOR_THREAD_PANIC_HANDLER + .set((unsafe { (*upcalls).clone() }).mutator_thread_panic_handler) + .unwrap_or_else(|_| panic!("MUTATOR_THREAD_PANIC_HANDLER is already initialized")); + crate::set_panic_hook(); let builder = unsafe { Box::from_raw(builder) }; diff --git a/gc/mmtk/src/lib.rs b/gc/mmtk/src/lib.rs index d16a5bf42f2ec5..4bcafb597a2bb6 100644 --- a/gc/mmtk/src/lib.rs +++ b/gc/mmtk/src/lib.rs @@ -55,6 +55,11 @@ impl VMBinding for Ruby { type VMMemorySlice = RubyMemorySlice; } +/// The callback for mutator thread panic handler (which calls rb_bug to output +/// debugging information such as the Ruby backtrace and memory maps). +/// This is set before BINDING is set because mmtk_init could panic. +pub static MUTATOR_THREAD_PANIC_HANDLER: OnceCell = OnceCell::new(); + /// The singleton object for the Ruby binding itself. pub static BINDING: OnceCell = OnceCell::new(); @@ -132,6 +137,9 @@ pub(crate) fn set_panic_hook() { handle_gc_thread_panic(panic_info); } else { old_hook(panic_info); + (crate::MUTATOR_THREAD_PANIC_HANDLER + .get() + .expect("MUTATOR_THREAD_PANIC_HANDLER is not set"))(); } })); } diff --git a/gems/bundled_gems b/gems/bundled_gems index 6717d7e1924180..dd28b2bdfe2e5f 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,21 +6,21 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 5.27.0 https://github.com/minitest/minitest +minitest 6.0.0 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.3 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml -rss 0.3.1 https://github.com/ruby/rss +rss 0.3.2 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp -net-imap 0.6.1 https://github.com/ruby/net-imap +net-imap 0.6.2 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix prime 0.1.4 https://github.com/ruby/prime -rbs 3.10.0.pre.2 https://github.com/ruby/rbs +rbs 3.10.0.pre.2 https://github.com/ruby/rbs badcb9165b52c1b7ccaa6251e4d5bbd78329c5a7 typeprof 0.31.0 https://github.com/ruby/typeprof -debug 1.11.0 https://github.com/ruby/debug +debug 1.11.1 https://github.com/ruby/debug racc 1.8.1 https://github.com/ruby/racc mutex_m 0.3.0 https://github.com/ruby/mutex_m getoptlong 0.2.1 https://github.com/ruby/getoptlong diff --git a/hash.c b/hash.c index ac9a71794c8430..3669f55d5024d0 100644 --- a/hash.c +++ b/hash.c @@ -4888,10 +4888,9 @@ hash_le(VALUE hash1, VALUE hash2) /* * call-seq: - * self <= other_hash -> true or false + * self <= other -> true or false * - * Returns +true+ if the entries of +self+ are a subset of the entries of +other_hash+, - * +false+ otherwise: + * Returns whether the entries of +self+ are a subset of the entries of +other+: * * h0 = {foo: 0, bar: 1} * h1 = {foo: 0, bar: 1, baz: 2} @@ -4915,10 +4914,9 @@ rb_hash_le(VALUE hash, VALUE other) /* * call-seq: - * self < other_hash -> true or false + * self < other -> true or false * - * Returns +true+ if the entries of +self+ are a proper subset of the entries of +other_hash+, - * +false+ otherwise: + * Returns whether the entries of +self+ are a proper subset of the entries of +other+: * * h = {foo: 0, bar: 1} * h < {foo: 0, bar: 1, baz: 2} # => true # Proper subset. diff --git a/id_table.c b/id_table.c index 4629af553505b7..cece14c3891153 100644 --- a/id_table.c +++ b/id_table.c @@ -374,7 +374,7 @@ rb_managed_id_table_create(const rb_data_type_t *type, size_t capa) struct rb_id_table *tbl; VALUE obj = TypedData_Make_Struct(0, struct rb_id_table, type, tbl); RB_OBJ_SET_SHAREABLE(obj); - rb_id_table_init(tbl, capa); + rb_id_table_init(tbl, capa); // NOTE: this can cause GC, so dmark and dsize need to check tbl->items return obj; } diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index aed4bd89b893f6..72044562df8e3b 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -615,13 +615,24 @@ RBIMPL_ATTR_ARTIFICIAL() * directly. */ static inline void * -rbimpl_check_typeddata(VALUE obj, const rb_data_type_t *type) +rbimpl_check_typeddata(VALUE obj, const rb_data_type_t *expected_type) { - if (RB_LIKELY(RB_TYPE_P(obj, T_DATA) && RTYPEDDATA_P(obj) && RTYPEDDATA_TYPE(obj) == type)) { - return RTYPEDDATA_GET_DATA(obj); + if (RB_LIKELY(RB_TYPE_P(obj, T_DATA) && RTYPEDDATA_P(obj))) { + const rb_data_type_t *actual_type = RTYPEDDATA_TYPE(obj); + void *data = RTYPEDDATA_GET_DATA(obj); + if (RB_LIKELY(actual_type == expected_type)) { + return data; + } + + while (actual_type) { + actual_type = actual_type->parent; + if (actual_type == expected_type) { + return data; + } + } } - return rb_check_typeddata(obj, type); + return rb_check_typeddata(obj, expected_type); } diff --git a/io.c b/io.c index e739cee3795eec..42017b1c253035 100644 --- a/io.c +++ b/io.c @@ -1418,10 +1418,34 @@ io_flush_buffer_sync(void *arg) return (VALUE)-1; } +static inline VALUE +io_flush_buffer_fiber_scheduler(VALUE scheduler, rb_io_t *fptr) +{ + VALUE ret = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, fptr->wbuf.ptr+fptr->wbuf.off, fptr->wbuf.len, 0); + if (!UNDEF_P(ret)) { + ssize_t result = rb_fiber_scheduler_io_result_apply(ret); + if (result > 0) { + fptr->wbuf.off += result; + fptr->wbuf.len -= result; + } + return result >= 0 ? (VALUE)0 : (VALUE)-1; + } + return ret; +} + static VALUE io_flush_buffer_async(VALUE arg) { rb_io_t *fptr = (rb_io_t *)arg; + + VALUE scheduler = rb_fiber_scheduler_current(); + if (scheduler != Qnil) { + VALUE result = io_flush_buffer_fiber_scheduler(scheduler, fptr); + if (!UNDEF_P(result)) { + return result; + } + } + return rb_io_blocking_region_wait(fptr, io_flush_buffer_sync, fptr, RUBY_IO_WRITABLE); } @@ -2635,9 +2659,6 @@ io_fillbuf(rb_io_t *fptr) fptr->rbuf.len = 0; fptr->rbuf.capa = IO_RBUF_CAPA_FOR(fptr); fptr->rbuf.ptr = ALLOC_N(char, fptr->rbuf.capa); -#ifdef _WIN32 - fptr->rbuf.capa--; -#endif } if (fptr->rbuf.len == 0) { retry: @@ -3305,10 +3326,6 @@ io_shift_cbuf(rb_io_t *fptr, int len, VALUE *strp) static int io_setstrbuf(VALUE *str, long len) { -#ifdef _WIN32 - if (len > 0) - len = (len + 1) & ~1L; /* round up for wide char */ -#endif if (NIL_P(*str)) { *str = rb_str_new(0, len); return TRUE; diff --git a/iseq.c b/iseq.c index 7457118e07ce96..2e13928e920ed0 100644 --- a/iseq.c +++ b/iseq.c @@ -3936,7 +3936,6 @@ rb_vm_insn_decode(const VALUE encoded) static inline int encoded_iseq_trace_instrument(VALUE *iseq_encoded_insn, rb_event_flag_t turnon, bool remain_traced) { - ASSERT_vm_locking(); st_data_t key = (st_data_t)*iseq_encoded_insn; st_data_t val; diff --git a/jit.c b/jit.c index ff44ac5b2e565d..fb7a5bd47d4919 100644 --- a/jit.c +++ b/jit.c @@ -23,7 +23,14 @@ enum jit_bindgen_constants { ROBJECT_OFFSET_AS_ARY = offsetof(struct RObject, as.ary), // Field offsets for the RString struct - RUBY_OFFSET_RSTRING_LEN = offsetof(struct RString, len) + RUBY_OFFSET_RSTRING_LEN = offsetof(struct RString, len), + + // Field offsets for rb_execution_context_t + RUBY_OFFSET_EC_CFP = offsetof(rb_execution_context_t, cfp), + RUBY_OFFSET_EC_INTERRUPT_FLAG = offsetof(rb_execution_context_t, interrupt_flag), + RUBY_OFFSET_EC_INTERRUPT_MASK = offsetof(rb_execution_context_t, interrupt_mask), + RUBY_OFFSET_EC_THREAD_PTR = offsetof(rb_execution_context_t, thread_ptr), + RUBY_OFFSET_EC_RACTOR_ID = offsetof(rb_execution_context_t, ractor_id), }; // Manually bound in rust since this is out-of-range of `int`, diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 7e6103fde775bf..d4c9d60c9aa2a4 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -434,6 +434,8 @@ def dump_options_version(version) 2 when /\A3\.5(\.\d+)?\z/, /\A4\.0(\.\d+)?\z/ 3 + when /\A4\.1(\.\d+)?\z/ + 4 else if current raise CurrentVersionError, RUBY_VERSION diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 10c2eaad209ed4..2fb5d1d0b308e4 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "prism" - spec.version = "1.6.0" + spec.version = "1.7.0" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] @@ -102,6 +102,7 @@ Gem::Specification.new do |spec| "lib/prism/translation/parser34.rb", "lib/prism/translation/parser35.rb", "lib/prism/translation/parser40.rb", + "lib/prism/translation/parser41.rb", "lib/prism/translation/parser/builder.rb", "lib/prism/translation/parser/compiler.rb", "lib/prism/translation/parser/lexer.rb", @@ -125,6 +126,7 @@ Gem::Specification.new do |spec| "rbi/prism/translation/parser34.rbi", "rbi/prism/translation/parser35.rbi", "rbi/prism/translation/parser40.rbi", + "rbi/prism/translation/parser41.rbi", "rbi/prism/translation/ripper.rbi", "rbi/prism/visitor.rbi", "sig/prism.rbs", diff --git a/lib/prism/translation.rb b/lib/prism/translation.rb index 7933b4a7222e93..89c70ee420130f 100644 --- a/lib/prism/translation.rb +++ b/lib/prism/translation.rb @@ -11,6 +11,7 @@ module Translation # steep:ignore autoload :Parser34, "prism/translation/parser34" autoload :Parser35, "prism/translation/parser35" autoload :Parser40, "prism/translation/parser40" + autoload :Parser41, "prism/translation/parser41" autoload :Ripper, "prism/translation/ripper" autoload :RubyParser, "prism/translation/ruby_parser" end diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index 23245dc383e9e9..fed4ac4cd1f0fd 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -84,7 +84,7 @@ def initialize(builder = Prism::Translation::Parser::Builder.new, parser: Prism) end def version # :nodoc: - 40 + 41 end # The default encoding for Ruby files is UTF-8. @@ -358,6 +358,8 @@ def convert_for_prism(version) "3.4.0" when 35, 40 "4.0.0" + when 41 + "4.1.0" else "latest" end diff --git a/lib/prism/translation/parser41.rb b/lib/prism/translation/parser41.rb new file mode 100644 index 00000000000000..ed819064004606 --- /dev/null +++ b/lib/prism/translation/parser41.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +# :markup: markdown + +module Prism + module Translation + # This class is the entry-point for Ruby 4.1 of `Prism::Translation::Parser`. + class Parser41 < Parser + def version # :nodoc: + 41 + end + end + end +end diff --git a/lib/prism/translation/parser_current.rb b/lib/prism/translation/parser_current.rb index 76d71e940926cc..ac6daf7082e416 100644 --- a/lib/prism/translation/parser_current.rb +++ b/lib/prism/translation/parser_current.rb @@ -12,6 +12,8 @@ module Translation ParserCurrent = Parser34 when /^3\.5\./, /^4\.0\./ ParserCurrent = Parser40 + when /^4\.1\./ + ParserCurrent = Parser41 else # Keep this in sync with released Ruby. parser = Parser34 diff --git a/numeric.c b/numeric.c index 63e10fbe9f9141..87d50ae4a1b9e8 100644 --- a/numeric.c +++ b/numeric.c @@ -1468,10 +1468,17 @@ num_eql(VALUE x, VALUE y) * call-seq: * self <=> other -> zero or nil * - * Returns zero if +self+ is the same as +other+, +nil+ otherwise. + * Compares +self+ and +other+. * - * No subclass in the Ruby Core or Standard Library uses this implementation. + * Returns: + * + * - Zero, if +self+ is the same as +other+. + * - +nil+, otherwise. + * + * \Class \Numeric includes module Comparable, + * each of whose methods uses Numeric#<=> for comparison. * + * No subclass in the Ruby Core or Standard Library uses this implementation. */ static VALUE @@ -1561,30 +1568,32 @@ rb_dbl_cmp(double a, double b) /* * call-seq: - * self <=> other -> -1, 0, +1, or nil + * self <=> other -> -1, 0, 1, or nil * - * Returns a value that depends on the numeric relation - * between +self+ and +other+: + * Compares +self+ and +other+. * - * - -1, if +self+ is less than +other+. - * - 0, if +self+ is equal to +other+. - * - 1, if +self+ is greater than +other+. + * Returns: + * + * - +-1+, if +self+ is less than +other+. + * - +0+, if +self+ is equal to +other+. + * - +1+, if +self+ is greater than +other+. * - +nil+, if the two values are incommensurate. * * Examples: * + * 2.0 <=> 2.1 # => -1 * 2.0 <=> 2 # => 0 * 2.0 <=> 2.0 # => 0 * 2.0 <=> Rational(2, 1) # => 0 * 2.0 <=> Complex(2, 0) # => 0 * 2.0 <=> 1.9 # => 1 - * 2.0 <=> 2.1 # => -1 * 2.0 <=> 'foo' # => nil * - * This is the basis for the tests in the Comparable module. - * * Float::NAN <=> Float::NAN returns an implementation-dependent value. * + * \Class \Float includes module Comparable, + * each of whose methods uses Float#<=> for comparison. + * */ static VALUE @@ -1702,7 +1711,8 @@ flo_ge(VALUE x, VALUE y) * call-seq: * self < other -> true or false * - * Returns +true+ if +self+ is numerically less than +other+: + * Returns whether the value of +self+ is less than the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 2.0 < 3 # => true * 2.0 < 3.0 # => true @@ -1710,7 +1720,6 @@ flo_ge(VALUE x, VALUE y) * 2.0 < 2.0 # => false * * Float::NAN < Float::NAN returns an implementation-dependent value. - * */ static VALUE @@ -1738,7 +1747,8 @@ flo_lt(VALUE x, VALUE y) * call-seq: * self <= other -> true or false * - * Returns +true+ if +self+ is numerically less than or equal to +other+: + * Returns whether the value of +self+ is less than or equal to the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 2.0 <= 3 # => true * 2.0 <= 3.0 # => true @@ -4887,28 +4897,29 @@ fix_cmp(VALUE x, VALUE y) /* * call-seq: - * self <=> other -> -1, 0, +1, or nil + * self <=> other -> -1, 0, 1, or nil + * + * Compares +self+ and +other+. * * Returns: * - * - -1, if +self+ is less than +other+. - * - 0, if +self+ is equal to +other+. - * - 1, if +self+ is greater then +other+. + * - +-1+, if +self+ is less than +other+. + * - +0+, if +self+ is equal to +other+. + * - +1+, if +self+ is greater then +other+. * - +nil+, if +self+ and +other+ are incomparable. * * Examples: * * 1 <=> 2 # => -1 * 1 <=> 1 # => 0 - * 1 <=> 0 # => 1 - * 1 <=> 'foo' # => nil - * * 1 <=> 1.0 # => 0 * 1 <=> Rational(1, 1) # => 0 * 1 <=> Complex(1, 0) # => 0 + * 1 <=> 0 # => 1 + * 1 <=> 'foo' # => nil * - * This method is the basis for comparisons in module Comparable. - * + * \Class \Integer includes module Comparable, + * each of whose methods uses Integer#<=> for comparison. */ VALUE @@ -5038,7 +5049,8 @@ fix_lt(VALUE x, VALUE y) * call-seq: * self < other -> true or false * - * Returns +true+ if the value of +self+ is less than that of +other+: + * Returns whether the value of +self+ is less than the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 1 < 0 # => false * 1 < 1 # => false @@ -5046,8 +5058,6 @@ fix_lt(VALUE x, VALUE y) * 1 < 0.5 # => false * 1 < Rational(1, 2) # => false * - * Raises an exception if the comparison cannot be made. - * */ static VALUE @@ -5082,10 +5092,10 @@ fix_le(VALUE x, VALUE y) /* * call-seq: - * self <= real -> true or false + * self <= other -> true or false * - * Returns +true+ if the value of +self+ is less than or equal to - * that of +other+: + * Returns whether the value of +self+ is less than or equal to the value of +other+; + * +other+ must be numeric, but may not be Complex: * * 1 <= 0 # => false * 1 <= 1 # => true diff --git a/object.c b/object.c index 158bb0b219256c..93c8a483cfe26c 100644 --- a/object.c +++ b/object.c @@ -1948,14 +1948,15 @@ rb_class_inherited_p(VALUE mod, VALUE arg) /* * call-seq: - * mod < other -> true, false, or nil + * self < other -> true, false, or nil * - * Returns true if mod is a subclass of other. Returns - * false if mod is the same as other - * or mod is an ancestor of other. - * Returns nil if there's no relationship between the two. - * (Think of the relationship in terms of the class definition: - * "class A < B" implies "A < B".) + * Returns whether +self+ is a subclass of +other+, + * or +nil+ if there is no relationship between the two: + * + * Float < Numeric # => true + * Numeric < Float # => false + * Float < Float # => false + * Float < Hash # => nil * */ @@ -2011,13 +2012,15 @@ rb_mod_gt(VALUE mod, VALUE arg) /* * call-seq: - * self <=> object -> -1, 0, +1, or nil + * self <=> other -> -1, 0, 1, or nil + * + * Compares +self+ and +other+. * * Returns: * - * - +-1+, if +self+ includes +object+, if or +self+ is a subclass of +object+. - * - +0+, if +self+ and +object+ are the same. - * - +1+, if +object+ includes +self+, or if +object+ is a subclass of +self+. + * - +-1+, if +self+ includes +other+, if or +self+ is a subclass of +other+. + * - +0+, if +self+ and +other+ are the same. + * - +1+, if +other+ includes +self+, or if +other+ is a subclass of +self+. * - +nil+, if none of the above is true. * * Examples: @@ -2028,8 +2031,10 @@ rb_mod_gt(VALUE mod, VALUE arg) * Enumerable <=> Array # => 1 * # Class File is a subclass of class IO. * File <=> IO # => -1 - * IO <=> File # => 1 * File <=> File # => 0 + * IO <=> File # => 1 + * # Class File has no relationship to class String. + * File <=> String # => nil * */ diff --git a/parse.y b/parse.y index 4754435fab0d21..067f45e6d6044a 100644 --- a/parse.y +++ b/parse.y @@ -2943,6 +2943,7 @@ rb_parser_ary_free(rb_parser_t *p, rb_parser_ary_t *ary) $$ = new_args_tail(p, 0, 0, $1, &@1); /*% ripper: [Qnil, Qnil, $:1] %*/ } + ; %rule def_endless_method(bodystmt) : defn_head[head] f_opt_paren_args[args] '=' bodystmt diff --git a/prism/extension.h b/prism/extension.h index 70017a4ae39ca2..6c3de31adb94d6 100644 --- a/prism/extension.h +++ b/prism/extension.h @@ -1,7 +1,7 @@ #ifndef PRISM_EXT_NODE_H #define PRISM_EXT_NODE_H -#define EXPECTED_PRISM_VERSION "1.6.0" +#define EXPECTED_PRISM_VERSION "1.7.0" #include #include diff --git a/prism/options.c b/prism/options.c index 4a8953da7d2aac..09d2a65a6cbcf6 100644 --- a/prism/options.c +++ b/prism/options.c @@ -93,6 +93,11 @@ pm_options_version_set(pm_options_t *options, const char *version, size_t length return true; } + if (strncmp(version, "4.1", 3) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_4_1; + return true; + } + return false; } @@ -111,6 +116,11 @@ pm_options_version_set(pm_options_t *options, const char *version, size_t length options->version = PM_OPTIONS_VERSION_CRUBY_4_0; return true; } + + if (strncmp(version, "4.1.", 4) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_4_1; + return true; + } } if (length >= 6) { diff --git a/prism/options.h b/prism/options.h index a663c9767e994e..c00c7bf7553a4f 100644 --- a/prism/options.h +++ b/prism/options.h @@ -97,8 +97,11 @@ typedef enum { /** The vendored version of prism in CRuby 4.0.x. */ PM_OPTIONS_VERSION_CRUBY_4_0 = 3, + /** The vendored version of prism in CRuby 4.1.x. */ + PM_OPTIONS_VERSION_CRUBY_4_1 = 4, + /** The current version of prism. */ - PM_OPTIONS_VERSION_LATEST = PM_OPTIONS_VERSION_CRUBY_4_0 + PM_OPTIONS_VERSION_LATEST = PM_OPTIONS_VERSION_CRUBY_4_1 } pm_options_version_t; /** diff --git a/prism/prism.c b/prism/prism.c index f98032cd73b0b1..4c8ab91f0ef4e6 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -13412,6 +13412,30 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod return contains_keyword_splat; } +static inline bool +argument_allowed_for_bare_hash(pm_parser_t *parser, pm_node_t *argument) { + if (pm_symbol_node_label_p(argument)) { + return true; + } + + switch (PM_NODE_TYPE(argument)) { + case PM_CALL_NODE: { + pm_call_node_t *cast = (pm_call_node_t *) argument; + if (cast->opening_loc.start == NULL && cast->arguments != NULL) { + if (PM_NODE_FLAG_P(cast->arguments, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORDS | PM_ARGUMENTS_NODE_FLAGS_CONTAINS_SPLAT)) { + return false; + } + if (cast->block != NULL) { + return false; + } + } + break; + } + default: break; + } + return accept1(parser, PM_TOKEN_EQUAL_GREATER); +} + /** * Append an argument to a list of arguments. */ @@ -13569,7 +13593,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for bool contains_keywords = false; bool contains_keyword_splat = false; - if (pm_symbol_node_label_p(argument) || accept1(parser, PM_TOKEN_EQUAL_GREATER)) { + if (argument_allowed_for_bare_hash(parser, argument)){ if (parsed_bare_hash) { pm_parser_err_previous(parser, PM_ERR_ARGUMENT_BARE_HASH); } diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 526be67431f0f0..63f97dddd790b1 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -10,7 +10,7 @@ module Prism # The minor version of prism that we are expecting to find in the serialized # strings. - MINOR_VERSION = 6 + MINOR_VERSION = 7 # The patch version of prism that we are expecting to find in the serialized # strings. diff --git a/prism/version.h b/prism/version.h index f202b0f4d72c3a..0b64a70dfff5c0 100644 --- a/prism/version.h +++ b/prism/version.h @@ -14,7 +14,7 @@ /** * The minor version of the Prism library as an int. */ -#define PRISM_VERSION_MINOR 6 +#define PRISM_VERSION_MINOR 7 /** * The patch version of the Prism library as an int. @@ -24,6 +24,6 @@ /** * The version of the Prism library as a constant string. */ -#define PRISM_VERSION "1.6.0" +#define PRISM_VERSION "1.7.0" #endif diff --git a/ractor.c b/ractor.c index a95468880708d0..f4c2e243be02e3 100644 --- a/ractor.c +++ b/ractor.c @@ -953,36 +953,16 @@ ractor_moved_missing(int argc, VALUE *argv, VALUE self) * * Raised when an attempt is made to send a message to a closed port, * or to retrieve a message from a closed and empty port. - * Ports may be closed explicitly with Ractor#close_outgoing/close_incoming + * Ports may be closed explicitly with Ractor::Port#close * and are closed implicitly when a Ractor terminates. * - * r = Ractor.new { sleep(500) } - * r.close_outgoing - * r.take # Ractor::ClosedError + * port = Ractor::Port.new + * port.close + * port << "test" # Ractor::ClosedError + * port.receive # Ractor::ClosedError * - * ClosedError is a descendant of StopIteration, so the closing of the ractor will break - * the loops without propagating the error: - * - * r = Ractor.new do - * loop do - * msg = receive # raises ClosedError and loop traps it - * puts "Received: #{msg}" - * end - * puts "loop exited" - * end - * - * 3.times{|i| r << i} - * r.close_incoming - * r.take - * puts "Continue successfully" - * - * This will print: - * - * Received: 0 - * Received: 1 - * Received: 2 - * loop exited - * Continue successfully + * ClosedError is a descendant of StopIteration, so the closing of a port will break + * out of loops without propagating the error. */ /* @@ -995,14 +975,14 @@ ractor_moved_missing(int argc, VALUE *argv, VALUE self) /* * Document-class: Ractor::RemoteError * - * Raised on attempt to Ractor#take if there was an uncaught exception in the Ractor. + * Raised on Ractor#join or Ractor#value if there was an uncaught exception in the Ractor. * Its +cause+ will contain the original exception, and +ractor+ is the original ractor * it was raised in. * * r = Ractor.new { raise "Something weird happened" } * * begin - * r.take + * r.value * rescue => e * p e # => # * p e.ractor == r # => true @@ -1014,7 +994,7 @@ ractor_moved_missing(int argc, VALUE *argv, VALUE self) /* * Document-class: Ractor::MovedError * - * Raised on an attempt to access an object which was moved in Ractor#send or Ractor.yield. + * Raised on an attempt to access an object which was moved in Ractor#send or Ractor::Port#send. * * r = Ractor.new { sleep } * @@ -1029,7 +1009,7 @@ ractor_moved_missing(int argc, VALUE *argv, VALUE self) * Document-class: Ractor::MovedObject * * A special object which replaces any value that was moved to another ractor in Ractor#send - * or Ractor.yield. Any attempt to access the object results in Ractor::MovedError. + * or Ractor::Port#send. Any attempt to access the object results in Ractor::MovedError. * * r = Ractor.new { receive } * diff --git a/ractor.rb b/ractor.rb index 70ce1a9fffecb9..0002eece2ce700 100644 --- a/ractor.rb +++ b/ractor.rb @@ -47,7 +47,7 @@ # frozen objects can be unshareable if they contain (through their instance variables) unfrozen # objects. # -# Shareable objects are those which can be used by several threads without compromising +# Shareable objects are those which can be used by several ractors at once without compromising # thread-safety, for example numbers, +true+ and +false+. Ractor.shareable? allows you to check this, # and Ractor.make_shareable tries to make the object shareable if it's not already, and gives an error # if it can't do it. @@ -65,12 +65,12 @@ # ary[0].frozen? #=> true # ary[1].frozen? #=> true # -# When a shareable object is sent (via #send or Ractor.yield), no additional processing occurs -# on it. It just becomes usable by both ractors. When an unshareable object is sent, it can be +# When a shareable object is sent via #send, no additional processing occurs +# on it and it becomes usable by both ractors. When an unshareable object is sent, it can be # either _copied_ or _moved_. The first is the default, and it copies the object fully by # deep cloning (Object#clone) the non-shareable parts of its structure. # -# data = ['foo', 'bar'.freeze] +# data = ['foo'.dup, 'bar'.freeze] # r = Ractor.new do # data2 = Ractor.receive # puts "In ractor: #{data2.object_id}, #{data2[0].object_id}, #{data2[1].object_id}" @@ -81,8 +81,8 @@ # # This will output something like: # -# In ractor: 340, 360, 320 -# Outside : 380, 400, 320 +# In ractor: 8, 16, 24 +# Outside : 32, 40, 24 # # Note that the object ids of the array and the non-frozen string inside the array have changed in # the ractor because they are different objects. The second array's element, which is a @@ -267,7 +267,7 @@ def self.count # # TBD def self.select(*ports) - raise ArgumentError, 'specify at least one ractor or `yield_value`' if ports.empty? + raise ArgumentError, 'specify at least one Ractor::Port or Ractor' if ports.empty? monitors = {} # Ractor::Port => Ractor @@ -371,7 +371,7 @@ def close # # Checks if the object is shareable by ractors. # - # Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are frozen + # Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are shareable # Ractor.shareable?('foo') #=> false, unless the string is frozen due to # frozen_string_literal: true # Ractor.shareable?('foo'.freeze) #=> true # @@ -460,9 +460,9 @@ def self.[]=(sym, val) # call-seq: # Ractor.store_if_absent(key){ init_block } # - # If the corresponding value is not set, yield a value with - # init_block and store the value in thread-safe manner. - # This method returns corresponding stored value. + # If the corresponding ractor-local value is not set, yield a value with + # init_block and store the value in a thread-safe manner. + # This method returns the stored value. # # (1..10).map{ # Thread.new(it){|i| @@ -578,10 +578,10 @@ def value # call-seq: # ractor.monitor(port) -> self # - # Register port as a monitoring port. If the ractor terminated, - # the port received a Symbol object. + # Add another ractor's port to the monitored list of the receiver. If +self+ terminates, + # the port is sent a Symbol object. # :exited will be sent if the ractor terminated without an exception. - # :aborted will be sent if the ractor terminated with a exception. + # :aborted will be sent if the ractor terminated with an exception. # # r = Ractor.new{ some_task() } # r.monitor(port = Ractor::Port.new) @@ -589,7 +589,7 @@ def value # # r = Ractor.new{ raise "foo" } # r.monitor(port = Ractor::Port.new) - # port.receive #=> :terminated and r is terminated with an exception "foo" + # port.receive #=> :aborted and r is terminated with an exception "foo" # def monitor port __builtin_ractor_monitor(port) @@ -599,7 +599,7 @@ def monitor port # call-seq: # ractor.unmonitor(port) -> self # - # Unregister port from the monitoring ports. + # Remove the given port from the ractor's monitored list. # def unmonitor port __builtin_ractor_unmonitor(port) @@ -609,11 +609,11 @@ def unmonitor port # call-seq: # Ractor.shareable_proc(self: nil){} -> shareable proc # - # It returns shareable Proc object. The Proc object is - # shareable and the self in a block will be replaced with - # the value passed via `self:` keyword. + # Returns a shareable copy of the given block's Proc. The value of +self+ + # in the Proc will be replaced with the value passed via the `self:` keyword, + # or +nil+ if not given. # - # In a shareable Proc, you can not access to the outer variables. + # In a shareable Proc, you can not access any outer variables. # # a = 42 # Ractor.shareable_proc{ p a } @@ -634,9 +634,9 @@ def self.shareable_proc self: nil # # call-seq: - # Ractor.shareable_proc{} -> shareable proc + # Ractor.shareable_lambda{} -> shareable lambda # - # Same as Ractor.shareable_proc, but returns lambda proc. + # Same as Ractor.shareable_proc, but returns a lambda. # def self.shareable_lambda self: nil Primitive.attr! :use_block @@ -652,7 +652,7 @@ class Port # call-seq: # port.receive -> msg # - # Receive a message to the port (which was sent there by Port#send). + # Receive a message from the port (which was sent there by Port#send). # # port = Ractor::Port.new # r = Ractor.new port do |port| @@ -662,7 +662,7 @@ class Port # v1 = port.receive # puts "Received: #{v1}" # r.join - # # Here will be printed: "Received: message1" + # # This will print: "Received: message1" # # The method blocks if the message queue is empty. # @@ -690,7 +690,7 @@ class Port # Still received only one # Received: message2 # - # If close_incoming was called on the ractor, the method raises Ractor::ClosedError + # If the port is closed, the method raises Ractor::ClosedError # if there are no more messages in the message queue: # # port = Ractor::Port.new @@ -710,8 +710,8 @@ def receive # Send a message to a port to be accepted by port.receive. # # port = Ractor::Port.new - # r = Ractor.new do - # r.send 'message' + # r = Ractor.new(port) do |port| + # port.send 'message' # end # value = port.receive # puts "Received #{value}" @@ -722,7 +722,7 @@ def receive # # port = Ractor::Port.new # r = Ractor.new(port) do |port| - # port.send 'test'} + # port.send 'test' # puts "Sent successfully" # # Prints: "Sent successfully" immediately # end @@ -752,7 +752,7 @@ def send obj, move: false # call-seq: # port.close # - # Close the port. On the closed port, sending is not prohibited. + # Close the port. On the closed port, sending is prohibited. # Receiving is also not allowed if there is no sent messages arrived before closing. # # port = Ractor::Port.new diff --git a/ractor_sync.c b/ractor_sync.c index 8c7c144c3fda97..a63df7c407b194 100644 --- a/ractor_sync.c +++ b/ractor_sync.c @@ -1267,7 +1267,11 @@ static size_t ractor_selector_memsize(const void *ptr) { const struct ractor_selector *s = ptr; - return sizeof(struct ractor_selector) + st_memsize(s->ports); + size_t size = sizeof(struct ractor_selector); + if (s->ports) { + size += st_memsize(s->ports); + } + return size; } static const rb_data_type_t ractor_selector_data_type = { diff --git a/range.c b/range.c index f82d1a316ddd7d..82c252e3ef50cd 100644 --- a/range.c +++ b/range.c @@ -1018,6 +1018,20 @@ range_to_a(VALUE range) return rb_call_super(0, 0); } +/* + * call-seq: + * to_set -> set + * + * Returns a set containing the elements in +self+, if a finite collection; + * raises an exception otherwise. + * + * (1..4).to_set # => Set[1, 2, 3, 4] + * (1...4).to_set # => Set[1, 2, 3] + * + * (1..).to_set + * # in 'Range#to_set': cannot convert endless range to a set (RangeError) + * + */ static VALUE range_to_set(int argc, VALUE *argv, VALUE range) { diff --git a/signal.c b/signal.c index 12af9058a25439..5110ea4401a4be 100644 --- a/signal.c +++ b/signal.c @@ -1346,31 +1346,36 @@ reserved_signal_p(int signo) /* * call-seq: - * Signal.trap( signal, command ) -> obj - * Signal.trap( signal ) {| | block } -> obj + * Signal.trap(signal, command) -> obj + * Signal.trap(signal) { ... } -> obj * - * Specifies the handling of signals. The first parameter is a signal - * name (a string such as ``SIGALRM'', ``SIGUSR1'', and so on) or a - * signal number. The characters ``SIG'' may be omitted from the - * signal name. The command or block specifies code to be run when the + * Specifies the handling of signals. Returns the previous handler for + * the given signal. + * + * Argument +signal+ is a signal name (a string or symbol such + * as +SIGALRM+ or +SIGUSR1+) or an integer signal number. When +signal+ + * is a string or symbol, the leading characters +SIG+ may be omitted. + * + * Argument +command+ or block provided specifies code to be run when the * signal is raised. - * If the command is the string ``IGNORE'' or ``SIG_IGN'', the signal - * will be ignored. - * If the command is ``DEFAULT'' or ``SIG_DFL'', the Ruby's default handler - * will be invoked. - * If the command is ``EXIT'', the script will be terminated by the signal. - * If the command is ``SYSTEM_DEFAULT'', the operating system's default - * handler will be invoked. - * Otherwise, the given command or block will be run. - * The special signal name ``EXIT'' or signal number zero will be - * invoked just prior to program termination. - * trap returns the previous handler for the given signal. + * + * Argument +command+ may also be a string or symbol with the following special + * values: + * + * - +IGNORE+, +SIG_IGN+: the signal will be ignored. + * - +DEFAULT+, +SIG_DFL+: Ruby's default handler will be invoked. + * - +EXIT+: the process will be terminated by the signal. + * - +SYSTEM_DEFAULT+: the operating system's default handler will be invoked. + * + * The special signal name +EXIT+ or signal number zero will be + * invoked just prior to program termination: * * Signal.trap(0, proc { puts "Terminating: #{$$}" }) * Signal.trap("CLD") { puts "Child died" } * fork && Process.wait * - * produces: + * Outputs: + * * Terminating: 27461 * Child died * Terminating: 27460 diff --git a/spec/default.mspec b/spec/default.mspec index 058835cd10d91c..d756dc31ffd566 100644 --- a/spec/default.mspec +++ b/spec/default.mspec @@ -9,6 +9,7 @@ ENV["CHECK_CONSTANT_LEAKS"] ||= "true" require "./rbconfig" unless defined?(RbConfig) require_relative "../tool/test-coverage" if ENV.key?("COVERAGE") +require_relative "../tool/lib/test/jobserver" load File.dirname(__FILE__) + '/ruby/default.mspec' OBJDIR = File.expand_path("spec/ruby/optional/capi/ext") unless defined?(OBJDIR) class MSpecScript @@ -50,34 +51,10 @@ end module MSpecScript::JobServer def cores(max = 1) - if max > 1 and /(?:\A|\s)--jobserver-(?:auth|fds)=(\d+),(\d+)/ =~ ENV["MAKEFLAGS"] - cores = 1 - begin - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - jobtokens = r.read_nonblock(max - 1) - cores = jobtokens.size - if cores > 0 - cores += 1 - jobserver = w - w = nil - at_exit { - jobserver.print(jobtokens) - jobserver.close - } - MSpecScript::JobServer.module_eval do - remove_method :cores - define_method(:cores) do - cores - end - end - return cores - end - rescue Errno::EBADF - ensure - r&.close - w&.close - end + MSpecScript::JobServer.remove_method :cores + if cores = Test::JobServer.max_jobs(max) + MSpecScript::JobServer.define_method(:cores) { cores } + return cores end super end diff --git a/spec/ruby/optional/capi/spec_helper.rb b/spec/ruby/optional/capi/spec_helper.rb index 1076b206ecbdb6..e7abf46e6ccf65 100644 --- a/spec/ruby/optional/capi/spec_helper.rb +++ b/spec/ruby/optional/capi/spec_helper.rb @@ -122,13 +122,9 @@ def setup_make opts = {} if /(?:\A|\s)--jobserver-(?:auth|fds)=(\d+),(\d+)/ =~ make_flags - begin - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - rescue Errno::EBADF - else - opts[r] = r - opts[w] = w + [$1, $2].each do |fd| + fd = fd.to_i(10) + opts[fd] = fd end end diff --git a/st.c b/st.c index ef9ffbec5e1ea3..8937f7935f6b22 100644 --- a/st.c +++ b/st.c @@ -674,6 +674,7 @@ st_free_table(st_table *tab) size_t st_memsize(const st_table *tab) { + RUBY_ASSERT(tab != NULL); return(sizeof(st_table) + (tab->bins == NULL ? 0 : bins_size(tab)) + get_allocated_entries(tab) * sizeof(st_table_entry)); diff --git a/string.c b/string.c index 9819a5910fa76e..8c7c82c10f4fa8 100644 --- a/string.c +++ b/string.c @@ -4294,23 +4294,29 @@ rb_str_eql(VALUE str1, VALUE str2) /* * call-seq: - * self <=> other_string -> -1, 0, 1, or nil + * self <=> other -> -1, 0, 1, or nil * - * Compares +self+ and +other_string+, returning: + * Compares +self+ and +other+, + * evaluating their _contents_, not their _lengths_. * - * - -1 if +other_string+ is larger. - * - 0 if the two are equal. - * - 1 if +other_string+ is smaller. - * - +nil+ if the two are incomparable. + * Returns: + * + * - +-1+, if +self+ is smaller. + * - +0+, if the two are equal. + * - +1+, if +self+ is larger. + * - +nil+, if the two are incomparable. * * Examples: * - * 'foo' <=> 'foo' # => 0 - * 'foo' <=> 'food' # => -1 - * 'food' <=> 'foo' # => 1 - * 'FOO' <=> 'foo' # => -1 - * 'foo' <=> 'FOO' # => 1 - * 'foo' <=> 1 # => nil + * 'a' <=> 'b' # => -1 + * 'a' <=> 'ab' # => -1 + * 'a' <=> 'a' # => 0 + * 'b' <=> 'a' # => 1 + * 'ab' <=> 'a' # => 1 + * 'a' <=> :a # => nil + * + * \Class \String includes module Comparable, + * each of whose methods uses String#<=> for comparison. * * Related: see {Comparing}[rdoc-ref:String@Comparing]. */ @@ -12371,18 +12377,24 @@ sym_succ(VALUE sym) /* * call-seq: - * symbol <=> object -> -1, 0, +1, or nil + * self <=> other -> -1, 0, 1, or nil * - * If +object+ is a symbol, - * returns the equivalent of symbol.to_s <=> object.to_s: + * Compares +self+ and +other+, using String#<=>. * - * :bar <=> :foo # => -1 - * :foo <=> :foo # => 0 - * :foo <=> :bar # => 1 + * Returns: + * + * - symbol.to_s <=> other.to_s, if +other+ is a symbol. + * - +nil+, otherwise. + * + * Examples: * - * Otherwise, returns +nil+: + * :bar <=> :foo # => -1 + * :foo <=> :foo # => 0 + * :foo <=> :bar # => 1 + * :foo <=> 'bar' # => nil * - * :foo <=> 'bar' # => nil + * \Class \Symbol includes module Comparable, + * each of whose methods uses Symbol#<=> for comparison. * * Related: String#<=>. */ diff --git a/test/.excludes-mmtk/TestObjSpace.rb b/test/.excludes-mmtk/TestObjSpace.rb index 05666e46f07fc8..94eb2c436d4435 100644 --- a/test/.excludes-mmtk/TestObjSpace.rb +++ b/test/.excludes-mmtk/TestObjSpace.rb @@ -1,3 +1,4 @@ exclude(:test_dump_all_full, "testing behaviour specific to default GC") exclude(:test_dump_flag_age, "testing behaviour specific to default GC") exclude(:test_dump_flags, "testing behaviour specific to default GC") +exclude(:test_dump_objects_dumps_page_slot_sizes, "testing behaviour specific to default GC") diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb index 029c5043dc1b14..07b15c5ce4b86a 100644 --- a/test/fiber/scheduler.rb +++ b/test/fiber/scheduler.rb @@ -488,6 +488,29 @@ def blocking(&block) end end +class IOScheduler < Scheduler + def __io_ops__ + @__io_ops__ ||= [] + end + + def io_write(io, buffer, length, offset) + fd = io.fileno + str = buffer.get_string + __io_ops__ << [:io_write, fd, str] + Fiber.blocking { buffer.write(IO.for_fd(fd), 0, offset) } + end +end + +class IOErrorScheduler < Scheduler + def io_read(io, buffer, length, offset) + return -Errno::EBADF::Errno + end + + def io_write(io, buffer, length, offset) + return -Errno::EINVAL::Errno + end +end + # This scheduler has a broken implementation of `unblock`` in the sense that it # raises an exception. This is used to test the behavior of the scheduler when # unblock raises an exception. diff --git a/test/fiber/test_scheduler.rb b/test/fiber/test_scheduler.rb index 97ce94bd270b69..c20fe86ff4531d 100644 --- a/test/fiber/test_scheduler.rb +++ b/test/fiber/test_scheduler.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true require 'test/unit' +require 'securerandom' +require 'fileutils' require_relative 'scheduler' class TestFiberScheduler < Test::Unit::TestCase @@ -283,4 +285,94 @@ def test_post_fork_fiber_blocking ensure thread.kill rescue nil end + + def test_io_write_on_flush + omit "skip this test because it makes CI fragile" + begin + fn = File.join(Dir.tmpdir, "ruby_test_io_write_on_flush_#{SecureRandom.hex}") + write_fd = nil + io_ops = nil + thread = Thread.new do + scheduler = IOScheduler.new + Fiber.set_scheduler scheduler + + Fiber.schedule do + File.open(fn, 'w+') do |f| + write_fd = f.fileno + f << 'foo' + f.flush + f << 'bar' + end + end + io_ops = scheduler.__io_ops__ + end + thread.join + assert_equal [ + [:io_write, write_fd, 'foo'], + [:io_write, write_fd, 'bar'] + ], io_ops + + assert_equal 'foobar', IO.read(fn) + ensure + thread.kill rescue nil + FileUtils.rm_f(fn) + end + end + + def test_io_read_error + fn = File.join(Dir.tmpdir, "ruby_test_io_read_error_#{SecureRandom.hex}") + exception = nil + thread = Thread.new do + scheduler = IOErrorScheduler.new + Fiber.set_scheduler scheduler + Fiber.schedule do + File.open(fn, 'w+') { it.read } + rescue => e + exception = e + end + end + thread.join + assert_kind_of Errno::EBADF, exception + ensure + thread.kill rescue nil + FileUtils.rm_f(fn) + end + + def test_io_write_error + fn = File.join(Dir.tmpdir, "ruby_test_io_write_error_#{SecureRandom.hex}") + exception = nil + thread = Thread.new do + scheduler = IOErrorScheduler.new + Fiber.set_scheduler scheduler + Fiber.schedule do + File.open(fn, 'w+') { it.sync = true; it << 'foo' } + rescue => e + exception = e + end + end + thread.join + assert_kind_of Errno::EINVAL, exception + ensure + thread.kill rescue nil + FileUtils.rm_f(fn) + end + + def test_io_write_flush_error + fn = File.join(Dir.tmpdir, "ruby_test_io_write_flush_error_#{SecureRandom.hex}") + exception = nil + thread = Thread.new do + scheduler = IOErrorScheduler.new + Fiber.set_scheduler scheduler + Fiber.schedule do + File.open(fn, 'w+') { it << 'foo' } + rescue => e + exception = e + end + end + thread.join + assert_kind_of Errno::EINVAL, exception + ensure + thread.kill rescue nil + FileUtils.rm_f(fn) + end end diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb index bb1761109fadbb..bbf28201ffe2b4 100644 --- a/test/prism/api/parse_test.rb +++ b/test/prism/api/parse_test.rb @@ -122,6 +122,9 @@ def test_version assert Prism.parse_success?("1 + 1", version: "4.0") assert Prism.parse_success?("1 + 1", version: "4.0.0") + assert Prism.parse_success?("1 + 1", version: "4.1") + assert Prism.parse_success?("1 + 1", version: "4.1.0") + assert Prism.parse_success?("1 + 1", version: "latest") # Test edge case diff --git a/test/prism/errors/command_calls_35.txt b/test/prism/errors/command_calls_35.txt new file mode 100644 index 00000000000000..45f569b117c6c5 --- /dev/null +++ b/test/prism/errors/command_calls_35.txt @@ -0,0 +1,46 @@ +p(p a, x: b => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, x: => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, &block => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a do end => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, *args => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p(p a, **kwargs => value) + ^~ unexpected '=>'; expected a `)` to close the arguments + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +p p 1, &block => 2, &block + ^~ unexpected '=>', expecting end-of-input + ^~ unexpected '=>', ignoring it + ^ unexpected ',', expecting end-of-input + ^ unexpected ',', ignoring it + ^ unexpected '&', ignoring it + +p p p 1 => 2 => 3 => 4 + ^~ unexpected '=>', expecting end-of-input + ^~ unexpected '=>', ignoring it + +p[p a, x: b => value] + ^ expected a matching `]` + ^ unexpected ']', expecting end-of-input + ^ unexpected ']', ignoring it + diff --git a/test/prism/fixtures/command_method_call_3.txt b/test/prism/fixtures/command_method_call_3.txt new file mode 100644 index 00000000000000..6de0446aa9be74 --- /dev/null +++ b/test/prism/fixtures/command_method_call_3.txt @@ -0,0 +1,19 @@ +foo(bar 1, key => '2') + +foo(bar 1, KEY => '2') + +foo(bar 1, :key => '2') + +foo(bar 1, { baz: :bat } => '2') + +foo bar - %i[baz] => '2' + +foo(bar {} => '2') + +foo(bar baz {} => '2') + +foo(bar do end => '2') + +foo(1, bar {} => '2') + +foo(1, bar do end => '2') diff --git a/test/prism/ruby/location_test.rb b/test/prism/ruby/location_test.rb index 33f844243c0547..5e2ab63802b1f5 100644 --- a/test/prism/ruby/location_test.rb +++ b/test/prism/ruby/location_test.rb @@ -13,19 +13,22 @@ def test_join assert_equal 0, joined.start_offset assert_equal 10, joined.length - assert_raise(RuntimeError, "Incompatible locations") do + e = assert_raise(RuntimeError) do argument.location.join(receiver.location) end + assert_equal "Incompatible locations", e.message other_argument = Prism.parse_statement("1234 + 567").arguments.arguments.first - assert_raise(RuntimeError, "Incompatible sources") do + e = assert_raise(RuntimeError) do other_argument.location.join(receiver.location) end + assert_equal "Incompatible sources", e.message - assert_raise(RuntimeError, "Incompatible sources") do + e = assert_raise(RuntimeError) do receiver.location.join(other_argument.location) end + assert_equal "Incompatible sources", e.message end def test_character_offsets diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb index f78e68e87c107a..43771110b4284f 100644 --- a/test/prism/test_helper.rb +++ b/test/prism/test_helper.rb @@ -241,7 +241,7 @@ def self.windows? end # All versions that prism can parse - SYNTAX_VERSIONS = %w[3.3 3.4 4.0] + SYNTAX_VERSIONS = %w[3.3 3.4 4.0 4.1] # `RUBY_VERSION` with the patch version excluded CURRENT_MAJOR_MINOR = RUBY_VERSION.split(".")[0, 2].join(".") diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index 22078514ad5cd3..8f12e06685bb1f 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -930,4 +930,19 @@ def test_safe_multi_ractor_subclasses_list_mutation end.each(&:join) end; end + + def test_safe_multi_ractor_singleton_class_access + assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + class A; end + 4.times.map do + Ractor.new do + a = A + 100.times do + a = a.singleton_class + end + end + end.each(&:join) + end; + end end diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb index 3f905aa1d8caa2..83d4fb0c7b525e 100644 --- a/test/ruby/test_io_m17n.rb +++ b/test/ruby/test_io_m17n.rb @@ -2724,8 +2724,8 @@ def test_pos_dont_move_cursor_position def test_pos_with_buffer_end_cr bug6401 = '[ruby-core:44874]' with_tmpdir { - # Read buffer size is 8191. This generates '\r' at 8191. - lines = ["X" * 8187, "X"] + # Read buffer size is 8192. This generates '\r' at 8192. + lines = ["X" * 8188, "X"] generate_file("tmp", lines.join("\r\n") + "\r\n") open("tmp", "r") do |f| diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index cccd7359e1108e..f4dfe2251b884f 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -356,38 +356,41 @@ def test_remove_instance_variable end def test_remove_instance_variable_re_embed - require "objspace" + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + c = Class.new do + attr_reader :a, :b, :c - c = Class.new do - def a = @a - - def b = @b - - def c = @c - end + def initialize + @a = nil + @b = nil + @c = nil + end + end - o1 = c.new - o2 = c.new - - o1.instance_variable_set(:@foo, 5) - o1.instance_variable_set(:@a, 0) - o1.instance_variable_set(:@b, 1) - o1.instance_variable_set(:@c, 2) - refute_includes ObjectSpace.dump(o1), '"embedded":true' - o1.remove_instance_variable(:@foo) - assert_includes ObjectSpace.dump(o1), '"embedded":true' - - o2.instance_variable_set(:@a, 0) - o2.instance_variable_set(:@b, 1) - o2.instance_variable_set(:@c, 2) - assert_includes ObjectSpace.dump(o2), '"embedded":true' - - assert_equal(0, o1.a) - assert_equal(1, o1.b) - assert_equal(2, o1.c) - assert_equal(0, o2.a) - assert_equal(1, o2.b) - assert_equal(2, o2.c) + o1 = c.new + o2 = c.new + + o1.instance_variable_set(:@foo, 5) + o1.instance_variable_set(:@a, 0) + o1.instance_variable_set(:@b, 1) + o1.instance_variable_set(:@c, 2) + refute_includes ObjectSpace.dump(o1), '"embedded":true' + o1.remove_instance_variable(:@foo) + assert_includes ObjectSpace.dump(o1), '"embedded":true' + + o2.instance_variable_set(:@a, 0) + o2.instance_variable_set(:@b, 1) + o2.instance_variable_set(:@c, 2) + assert_includes ObjectSpace.dump(o2), '"embedded":true' + + assert_equal(0, o1.a) + assert_equal(1, o1.b) + assert_equal(2, o1.c) + assert_equal(0, o2.a) + assert_equal(1, o2.b) + assert_equal(2, o2.c) + end; end def test_convert_string diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 72ca9d8262583a..d2ca933a632c7d 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -1610,7 +1610,7 @@ def test_pass_down_the_job_option_to_make gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out")) if vc_windows? && nmake_found? - refute_includes(gem_make_out, "-j4") + refute_includes(gem_make_out, " -j4") else assert_includes(gem_make_out, "make -j4") end diff --git a/test/socket/test_socket.rb b/test/socket/test_socket.rb index 686114f05c1418..c42527f3703173 100644 --- a/test/socket/test_socket.rb +++ b/test/socket/test_socket.rb @@ -1011,7 +1011,7 @@ def test_tcp_socket_all_hostname_resolution_failed Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| case family when Socket::AF_INET6 then raise SocketError - when Socket::AF_INET then sleep(0.001); raise SocketError, "Last hostname resolution error" + when Socket::AF_INET then sleep(0.01); raise SocketError, "Last hostname resolution error" end end diff --git a/thread.c b/thread.c index 3e1bb1dbe7ad17..e66352c03f6c4a 100644 --- a/thread.c +++ b/thread.c @@ -860,6 +860,7 @@ thread_create_core(VALUE thval, struct thread_create_params *params) #endif th->invoke_type = thread_invoke_type_ractor_proc; th->ractor = params->g; + th->ec->ractor_id = rb_ractor_id(th->ractor); th->ractor->threads.main = th; th->invoke_arg.proc.proc = rb_proc_isolate_bang(params->proc, Qnil); th->invoke_arg.proc.args = INT2FIX(RARRAY_LENINT(params->args)); @@ -2907,12 +2908,11 @@ rb_thread_fd_close(int fd) /* * call-seq: - * thr.raise - * thr.raise(string) - * thr.raise(exception [, string [, array]]) + * raise(exception, message = exception.to_s, backtrace = nil, cause: $!) + * raise(message = nil, cause: $!) * * Raises an exception from the given thread. The caller does not have to be - * +thr+. See Kernel#raise for more information. + * +thr+. See Kernel#raise for more information on arguments. * * Thread.abort_on_exception = true * a = Thread.new { sleep(200) } diff --git a/thread_sync.c b/thread_sync.c index 6bff982f31d78b..a93888fad02ae6 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -8,7 +8,7 @@ static VALUE rb_eClosedQueueError; /* Mutex */ typedef struct rb_mutex_struct { rb_serial_t ec_serial; - VALUE thread; // even if the fiber is collected, we might need access to the thread in mutex_free + rb_thread_t *th; // even if the fiber is collected, we might need access to the thread in mutex_free struct rb_mutex_struct *next_mutex; struct ccan_list_head waitq; /* protected by GVL */ } rb_mutex_t; @@ -133,7 +133,7 @@ mutex_free(void *ptr) { rb_mutex_t *mutex = ptr; if (mutex_locked_p(mutex)) { - const char *err = rb_mutex_unlock_th(mutex, rb_thread_ptr(mutex->thread), 0); + const char *err = rb_mutex_unlock_th(mutex, mutex->th, 0); if (err) rb_bug("%s", err); } ruby_xfree(ptr); @@ -223,7 +223,7 @@ thread_mutex_remove(rb_thread_t *thread, rb_mutex_t *mutex) static void mutex_set_owner(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) { - mutex->thread = th->self; + mutex->th = th; mutex->ec_serial = ec_serial; } @@ -235,7 +235,7 @@ mutex_locked(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) } static inline bool -mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) +do_mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) { if (mutex->ec_serial == 0) { RUBY_DEBUG_LOG("%p ok", mutex); @@ -252,7 +252,7 @@ mutex_trylock(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) static VALUE rb_mut_trylock(rb_execution_context_t *ec, VALUE self) { - return RBOOL(mutex_trylock(mutex_ptr(self), ec->thread_ptr, rb_ec_serial(ec))); + return RBOOL(do_mutex_trylock(mutex_ptr(self), ec->thread_ptr, rb_ec_serial(ec))); } VALUE @@ -315,7 +315,7 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) rb_raise(rb_eThreadError, "can't be called from trap context"); } - if (!mutex_trylock(mutex, th, ec_serial)) { + if (!do_mutex_trylock(mutex, th, ec_serial)) { if (mutex->ec_serial == ec_serial) { rb_raise(rb_eThreadError, "deadlock; recursive locking"); } @@ -340,7 +340,7 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) } } else { - if (!th->vm->thread_ignore_deadlock && rb_thread_ptr(mutex->thread) == th) { + if (!th->vm->thread_ignore_deadlock && mutex->th == th) { rb_raise(rb_eThreadError, "deadlock; lock already owned by another fiber belonging to the same thread"); } @@ -391,7 +391,7 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) /* release mutex before checking for interrupts...as interrupt checking * code might call rb_raise() */ if (mutex->ec_serial == ec_serial) { - mutex->thread = Qfalse; + mutex->th = NULL; mutex->ec_serial = 0; } RUBY_VM_CHECK_INTS_BLOCKING(th->ec); /* may release mutex */ @@ -728,9 +728,14 @@ queue_memsize(const void *ptr) } static const rb_data_type_t queue_data_type = { - "queue", - {queue_mark_and_move, RUBY_TYPED_DEFAULT_FREE, queue_memsize, queue_mark_and_move}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED + .wrap_struct_name = "Thread::Queue", + .function = { + .dmark = queue_mark_and_move, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = queue_memsize, + .dcompact = queue_mark_and_move, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE @@ -803,9 +808,15 @@ szqueue_memsize(const void *ptr) } static const rb_data_type_t szqueue_data_type = { - "sized_queue", - {szqueue_mark_and_move, RUBY_TYPED_DEFAULT_FREE, szqueue_memsize, szqueue_mark_and_move}, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED + .wrap_struct_name = "Thread::SizedQueue", + .function = { + .dmark = szqueue_mark_and_move, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = szqueue_memsize, + .dcompact = szqueue_mark_and_move, + }, + .parent = &queue_data_type, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE @@ -839,13 +850,13 @@ ary_buf_new(void) return rb_ary_hidden_new(1); } -static VALUE +static inline VALUE check_array(VALUE obj, VALUE ary) { - if (!RB_TYPE_P(ary, T_ARRAY)) { - rb_raise(rb_eTypeError, "%+"PRIsVALUE" not initialized", obj); + if (RB_LIKELY(ary)) { + return ary; } - return ary; + rb_raise(rb_eTypeError, "%+"PRIsVALUE" not initialized", obj); } static long @@ -1083,12 +1094,12 @@ szqueue_sleep_done(VALUE p) return Qfalse; } -static VALUE -queue_do_pop(VALUE self, struct rb_queue *q, int should_block, VALUE timeout) +static inline VALUE +queue_do_pop(rb_execution_context_t *ec, VALUE self, struct rb_queue *q, VALUE non_block, VALUE timeout) { check_array(self, q->que); if (RARRAY_LEN(q->que) == 0) { - if (!should_block) { + if (RTEST(non_block)) { rb_raise(rb_eThreadError, "queue empty"); } @@ -1103,8 +1114,6 @@ queue_do_pop(VALUE self, struct rb_queue *q, int should_block, VALUE timeout) return queue_closed_result(self, q); } else { - rb_execution_context_t *ec = GET_EC(); - RUBY_ASSERT(RARRAY_LEN(q->que) == 0); RUBY_ASSERT(queue_closed_p(self) == 0); @@ -1136,7 +1145,7 @@ queue_do_pop(VALUE self, struct rb_queue *q, int should_block, VALUE timeout) static VALUE rb_queue_pop(rb_execution_context_t *ec, VALUE self, VALUE non_block, VALUE timeout) { - return queue_do_pop(self, queue_ptr(self), !RTEST(non_block), timeout); + return queue_do_pop(ec, self, queue_ptr(self), non_block, timeout); } /* @@ -1330,7 +1339,6 @@ rb_szqueue_push(rb_execution_context_t *ec, VALUE self, VALUE object, VALUE non_ raise_closed_queue_error(self); } else { - rb_execution_context_t *ec = GET_EC(); struct queue_waiter queue_waiter = { .w = {.self = self, .th = ec->thread_ptr, .fiber = nonblocking_fiber(ec->fiber_ptr)}, .as = {.sq = sq} @@ -1357,10 +1365,10 @@ rb_szqueue_push(rb_execution_context_t *ec, VALUE self, VALUE object, VALUE non_ } static VALUE -szqueue_do_pop(VALUE self, int should_block, VALUE timeout) +rb_szqueue_pop(rb_execution_context_t *ec, VALUE self, VALUE non_block, VALUE timeout) { struct rb_szqueue *sq = szqueue_ptr(self); - VALUE retval = queue_do_pop(self, &sq->q, should_block, timeout); + VALUE retval = queue_do_pop(ec, self, &sq->q, non_block, timeout); if (queue_length(self, &sq->q) < sq->max) { wakeup_one(szqueue_pushq(sq)); @@ -1368,11 +1376,6 @@ szqueue_do_pop(VALUE self, int should_block, VALUE timeout) return retval; } -static VALUE -rb_szqueue_pop(rb_execution_context_t *ec, VALUE self, VALUE non_block, VALUE timeout) -{ - return szqueue_do_pop(self, !RTEST(non_block), timeout); -} /* * Document-method: Thread::SizedQueue#clear @@ -1390,23 +1393,6 @@ rb_szqueue_clear(VALUE self) return self; } -/* - * Document-method: Thread::SizedQueue#length - * call-seq: - * length - * size - * - * Returns the length of the queue. - */ - -static VALUE -rb_szqueue_length(VALUE self) -{ - struct rb_szqueue *sq = szqueue_ptr(self); - - return LONG2NUM(queue_length(self, &sq->q)); -} - /* * Document-method: Thread::SizedQueue#num_waiting * @@ -1421,21 +1407,6 @@ rb_szqueue_num_waiting(VALUE self) return INT2NUM(sq->q.num_waiting + sq->num_waiting_push); } -/* - * Document-method: Thread::SizedQueue#empty? - * call-seq: empty? - * - * Returns +true+ if the queue is empty. - */ - -static VALUE -rb_szqueue_empty_p(VALUE self) -{ - struct rb_szqueue *sq = szqueue_ptr(self); - - return RBOOL(queue_length(self, &sq->q) == 0); -} - /* ConditionalVariable */ struct rb_condvar { @@ -1686,11 +1657,8 @@ Init_thread_sync(void) rb_define_method(rb_cSizedQueue, "close", rb_szqueue_close, 0); rb_define_method(rb_cSizedQueue, "max", rb_szqueue_max_get, 0); rb_define_method(rb_cSizedQueue, "max=", rb_szqueue_max_set, 1); - rb_define_method(rb_cSizedQueue, "empty?", rb_szqueue_empty_p, 0); rb_define_method(rb_cSizedQueue, "clear", rb_szqueue_clear, 0); - rb_define_method(rb_cSizedQueue, "length", rb_szqueue_length, 0); rb_define_method(rb_cSizedQueue, "num_waiting", rb_szqueue_num_waiting, 0); - rb_define_alias(rb_cSizedQueue, "size", "length"); /* CVar */ DEFINE_CLASS(ConditionVariable, Object); diff --git a/tool/lib/test/jobserver.rb b/tool/lib/test/jobserver.rb new file mode 100644 index 00000000000000..7b889163b02aa2 --- /dev/null +++ b/tool/lib/test/jobserver.rb @@ -0,0 +1,47 @@ +module Test + module JobServer + end +end + +class << Test::JobServer + def connect(makeflags = ENV["MAKEFLAGS"]) + return unless /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ makeflags + begin + if fifo = $3 + fifo.gsub!(/\\(?=.)/, '') + r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY) + w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY) + else + r = IO.for_fd($1.to_i(10), "rb", autoclose: false) + w = IO.for_fd($2.to_i(10), "wb", autoclose: false) + end + rescue + r&.close + nil + else + return r, w + end + end + + def acquire_possible(r, w, max) + return unless tokens = r.read_nonblock(max - 1, exception: false) + if (jobs = tokens.size) > 0 + jobserver, w = w, nil + at_exit do + jobserver.print(tokens) + jobserver.close + end + end + return jobs + 1 + rescue Errno::EBADF + ensure + r&.close + w&.close + end + + def max_jobs(max = 2, makeflags = ENV["MAKEFLAGS"]) + if max > 1 and (r, w = connect(makeflags)) + acquire_possible(r, w, max) + end + end +end diff --git a/tool/lib/test/unit.rb b/tool/lib/test/unit.rb index 7d43e825e179eb..2663b7b76a10bf 100644 --- a/tool/lib/test/unit.rb +++ b/tool/lib/test/unit.rb @@ -19,6 +19,7 @@ def warn(message, category: nil, **kwargs) require_relative '../colorize' require_relative '../leakchecker' require_relative '../test/unit/testcase' +require_relative '../test/jobserver' require 'optparse' # See Test::Unit @@ -262,27 +263,8 @@ def process_args(args = []) def non_options(files, options) @jobserver = nil - makeflags = ENV.delete("MAKEFLAGS") - if !options[:parallel] and - /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ makeflags - begin - if fifo = $3 - fifo.gsub!(/\\(?=.)/, '') - r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY) - w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY) - else - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - end - rescue - r.close if r - nil - else - r.close_on_exec = true - w.close_on_exec = true - @jobserver = [r, w] - options[:parallel] ||= 256 # number of tokens to acquire first - end + if !options[:parallel] and @jobserver = Test::JobServer.connect(ENV.delete("MAKEFLAGS")) + options[:parallel] ||= 256 # number of tokens to acquire first end @worker_timeout = EnvUtil.apply_timeout_scale(options[:worker_timeout] || 1200) super diff --git a/tool/rbs_skip_tests_windows b/tool/rbs_skip_tests_windows index bdf3dddfd7a594..43888c98a71324 100644 --- a/tool/rbs_skip_tests_windows +++ b/tool/rbs_skip_tests_windows @@ -1,5 +1,8 @@ ARGFTest Failing on Windows +RactorSingletonTest Hangs up on Windows +RactorInstanceTest Hangs up on Windows + # NotImplementedError: fileno() function is unimplemented on this machine test_fileno(DirInstanceTest) test_fchdir(DirSingletonTest) diff --git a/vm.c b/vm.c index 27cb5ae25825e0..f78a779c3f3655 100644 --- a/vm.c +++ b/vm.c @@ -1122,6 +1122,14 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co local_size += VM_ENV_DATA_SIZE; } + // Invalidate JIT code that assumes cfp->ep == vm_base_ptr(cfp). + // This is done before creating the imemo_env because VM_STACK_ENV_WRITE + // below leaves the on-stack ep in a state that is unsafe to GC. + if (VM_FRAME_RUBYFRAME_P(cfp)) { + rb_yjit_invalidate_ep_is_bp(cfp->iseq); + rb_zjit_invalidate_no_ep_escape(cfp->iseq); + } + /* * # local variables on a stack frame (N == local_size) * [lvar1, lvar2, ..., lvarN, SPECVAL] @@ -1165,12 +1173,6 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co } #endif - // Invalidate JIT code that assumes cfp->ep == vm_base_ptr(cfp). - if (env->iseq) { - rb_yjit_invalidate_ep_is_bp(env->iseq); - rb_zjit_invalidate_no_ep_escape(env->iseq); - } - return (VALUE)env; } @@ -3953,6 +3955,7 @@ th_init(rb_thread_t *th, VALUE self, rb_vm_t *vm) th->ec->local_storage_recursive_hash_for_trace = Qnil; th->ec->storage = Qnil; + th->ec->ractor_id = rb_ractor_id(th->ractor); #if OPT_CALL_THREADED_CODE th->retval = Qundef; diff --git a/vm_core.h b/vm_core.h index 839c054ab399f5..68adc5eac16f32 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1048,6 +1048,7 @@ struct rb_execution_context_struct { rb_fiber_t *fiber_ptr; struct rb_thread_struct *thread_ptr; rb_serial_t serial; + rb_serial_t ractor_id; /* storage (ec (fiber) local) */ struct rb_id_table *local_storage; @@ -2070,6 +2071,14 @@ rb_ec_ractor_ptr(const rb_execution_context_t *ec) } } +static inline rb_serial_t +rb_ec_ractor_id(const rb_execution_context_t *ec) +{ + rb_serial_t ractor_id = ec->ractor_id; + RUBY_ASSERT(ractor_id); + return ractor_id; +} + static inline rb_vm_t * rb_ec_vm_ptr(const rb_execution_context_t *ec) { diff --git a/vm_insnhelper.c b/vm_insnhelper.c index aa67e54d0ac44b..2ad67461bb7694 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -4120,7 +4120,7 @@ vm_call_bmethod_body(rb_execution_context_t *ec, struct rb_calling_info *calling VALUE procv = cme->def->body.bmethod.proc; if (!RB_OBJ_SHAREABLE_P(procv) && - cme->def->body.bmethod.defined_ractor_id != rb_ractor_id(rb_ec_ractor_ptr(ec))) { + cme->def->body.bmethod.defined_ractor_id != rb_ec_ractor_id(ec)) { rb_raise(rb_eRuntimeError, "defined with an un-shareable Proc in a different Ractor"); } @@ -4143,7 +4143,7 @@ vm_call_iseq_bmethod(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct VALUE procv = cme->def->body.bmethod.proc; if (!RB_OBJ_SHAREABLE_P(procv) && - cme->def->body.bmethod.defined_ractor_id != rb_ractor_id(rb_ec_ractor_ptr(ec))) { + cme->def->body.bmethod.defined_ractor_id != rb_ec_ractor_id(ec)) { rb_raise(rb_eRuntimeError, "defined with an un-shareable Proc in a different Ractor"); } diff --git a/vm_method.c b/vm_method.c index 9f569df7fa6e51..2b3ac74d573434 100644 --- a/vm_method.c +++ b/vm_method.c @@ -149,10 +149,21 @@ vm_cc_table_dup_i(ID key, VALUE old_ccs_ptr, void *data) { VALUE new_table = (VALUE)data; struct rb_class_cc_entries *old_ccs = (struct rb_class_cc_entries *)old_ccs_ptr; + + if (METHOD_ENTRY_INVALIDATED(old_ccs->cme)) { + // Invalidated CME. This entry will be removed from the old table on + // the next GC mark, so it's unsafe (and undesirable) to copy + return ID_TABLE_CONTINUE; + } + size_t memsize = vm_ccs_alloc_size(old_ccs->capa); struct rb_class_cc_entries *new_ccs = ruby_xcalloc(1, memsize); rb_managed_id_table_insert(new_table, key, (VALUE)new_ccs); + // We hold the VM lock, so invalidation should not have happened between + // our earlier invalidation check and now. + VM_ASSERT(!METHOD_ENTRY_INVALIDATED(old_ccs->cme)); + memcpy(new_ccs, old_ccs, memsize); #if VM_CHECK_MODE > 0 @@ -169,6 +180,7 @@ vm_cc_table_dup_i(ID key, VALUE old_ccs_ptr, void *data) VALUE rb_vm_cc_table_dup(VALUE old_table) { + ASSERT_vm_locking(); VALUE new_table = rb_vm_cc_table_create(rb_managed_id_table_size(old_table)); rb_managed_id_table_foreach(old_table, vm_cc_table_dup_i, (void *)new_table); return new_table; @@ -1030,7 +1042,7 @@ rb_method_definition_set(const rb_method_entry_t *me, rb_method_definition_t *de } case VM_METHOD_TYPE_BMETHOD: RB_OBJ_WRITE(me, &def->body.bmethod.proc, (VALUE)opts); - def->body.bmethod.defined_ractor_id = rb_ractor_id(rb_ec_ractor_ptr(GET_EC())); + def->body.bmethod.defined_ractor_id = rb_ec_ractor_id(GET_EC()); return; case VM_METHOD_TYPE_NOTIMPLEMENTED: setup_method_cfunc_struct(UNALIGNED_MEMBER_PTR(def, body.cfunc), (VALUE(*)(ANYARGS))rb_f_notimplement_internal, -1); diff --git a/weakmap.c b/weakmap.c index 2ebf7d204f7195..ecdd219f62a21e 100644 --- a/weakmap.c +++ b/weakmap.c @@ -139,9 +139,11 @@ wmap_memsize(const void *ptr) const struct weakmap *w = ptr; size_t size = 0; - size += st_memsize(w->table); - /* The key and value of the table each take sizeof(VALUE) in size. */ - size += st_table_size(w->table) * (2 * sizeof(VALUE)); + if (w->table) { + size += st_memsize(w->table); + /* The key and value of the table each take sizeof(VALUE) in size. */ + size += st_table_size(w->table) * (2 * sizeof(VALUE)); + } return size; } @@ -689,9 +691,11 @@ wkmap_memsize(const void *ptr) const struct weakkeymap *w = ptr; size_t size = 0; - size += st_memsize(w->table); - /* Each key of the table takes sizeof(VALUE) in size. */ - size += st_table_size(w->table) * sizeof(VALUE); + if (w->table) { + size += st_memsize(w->table); + /* Each key of the table takes sizeof(VALUE) in size. */ + size += st_table_size(w->table) * sizeof(VALUE); + } return size; } diff --git a/win32/Makefile.sub b/win32/Makefile.sub index c3db450abc30a0..80f8f5c2bf88a4 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -1425,8 +1425,6 @@ rubyspec-capiext: $(RUBYSPEC_CAPIEXT_EXTS) $(Q)$(LDSHARED) -Fe$(@) -Fo$(*).obj $(INCFLAGS) $(CFLAGS) $(CPPFLAGS) $< $(LIBRUBYARG) -link $(DLDFLAGS) $(XLDFLAGS) $(LIBS) $(LOCAL_LIBS) -implib:$*.lib -pdb:$*.pdb -def:$*.def $(Q)$(RM) $*.def $*.exp $*.lib $*.obj $*.pdb -exts: rubyspec-capiext - yesterday: (set TZ=UTC-9) && \ for /f "usebackq" %H in \ diff --git a/yjit.c b/yjit.c index 6c3c9cd00161ce..1cd934c42eee63 100644 --- a/yjit.c +++ b/yjit.c @@ -473,6 +473,12 @@ rb_yjit_invokeblock_sp_pops(const struct rb_callinfo *ci) return 1 - sp_inc_of_invokeblock(ci); // + 1 to ignore return value push } +rb_serial_t +rb_yjit_cme_ractor_serial(const rb_callable_method_entry_t *cme) +{ + return cme->def->body.bmethod.defined_ractor_id; +} + // Setup jit_return to avoid returning a non-Qundef value on a non-FINISH frame. // See [jit_compile_exception] for details. void diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 06c475f3c8b493..fd99d529041077 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -272,6 +272,7 @@ fn main() { .allowlist_function("rb_optimized_call") .allowlist_function("rb_yjit_sendish_sp_pops") .allowlist_function("rb_yjit_invokeblock_sp_pops") + .allowlist_function("rb_yjit_cme_ractor_serial") .allowlist_function("rb_yjit_set_exception_return") .allowlist_function("rb_jit_str_concat_codepoint") .allowlist_type("rstring_offsets") diff --git a/yjit/src/backend/tests.rs b/yjit/src/backend/tests.rs index ac2f35b3d9c9e4..bfeea5163a2654 100644 --- a/yjit/src/backend/tests.rs +++ b/yjit/src/backend/tests.rs @@ -232,9 +232,9 @@ fn test_jcc_ptr() let (mut asm, mut cb) = setup_asm(); let side_exit = Target::CodePtr(cb.get_write_ptr().add_bytes(4)); - let not_mask = asm.not(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_MASK)); + let not_mask = asm.not(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_MASK as i32)); asm.test( - Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG), + Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG as i32), not_mask, ); asm.jnz(side_exit); diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 620bdb82800a78..61de6a32dabe8a 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -1208,7 +1208,7 @@ fn gen_check_ints( // Not checking interrupt_mask since it's zero outside finalize_deferred_heap_pages, // signal_exec, or rb_postponed_job_flush. - let interrupt_flag = asm.load(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG)); + let interrupt_flag = asm.load(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG as i32)); asm.test(interrupt_flag, interrupt_flag); asm.jnz(Target::side_exit(counter)); @@ -6659,7 +6659,7 @@ fn jit_thread_s_current( asm.stack_pop(1); // ec->thread_ptr - let ec_thread_opnd = asm.load(Opnd::mem(64, EC, RUBY_OFFSET_EC_THREAD_PTR)); + let ec_thread_opnd = asm.load(Opnd::mem(64, EC, RUBY_OFFSET_EC_THREAD_PTR as i32)); // thread->self let thread_self = Opnd::mem(64, ec_thread_opnd, RUBY_OFFSET_THREAD_SELF); @@ -7124,7 +7124,7 @@ fn gen_send_cfunc( asm_comment!(asm, "set ec->cfp"); let new_cfp = asm.lea(Opnd::mem(64, CFP, -(RUBY_SIZEOF_CONTROL_FRAME as i32))); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), new_cfp); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), new_cfp); if !kw_arg.is_null() { // Build a hash from all kwargs passed @@ -7220,7 +7220,7 @@ fn gen_send_cfunc( // Pop the stack frame (ec->cfp++) // Instead of recalculating, we can reuse the previous CFP, which is stored in a callee-saved // register - let ec_cfp_opnd = Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP); + let ec_cfp_opnd = Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32); asm.store(ec_cfp_opnd, CFP); // cfunc calls may corrupt types @@ -7396,11 +7396,12 @@ fn gen_send_bmethod( let capture = unsafe { proc_block.as_.captured.as_ref() }; let iseq = unsafe { *capture.code.iseq.as_ref() }; - // Optimize for single ractor mode and avoid runtime check for - // "defined with an un-shareable Proc in a different Ractor" - if !assume_single_ractor_mode(jit, asm) { - gen_counter_incr(jit, asm, Counter::send_bmethod_ractor); - return None; + if !procv.shareable_p() { + let ractor_serial = unsafe { rb_yjit_cme_ractor_serial(cme) }; + asm_comment!(asm, "guard current ractor == {}", ractor_serial); + let current_ractor_serial = asm.load(Opnd::mem(64, EC, RUBY_OFFSET_EC_RACTOR_ID as i32)); + asm.cmp(current_ractor_serial, ractor_serial.into()); + asm.jne(Target::side_exit(Counter::send_bmethod_ractor)); } // Passing a block to a block needs logic different from passing @@ -8358,7 +8359,7 @@ fn gen_send_iseq( asm_comment!(asm, "switch to new CFP"); let new_cfp = asm.sub(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, new_cfp); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); // Directly jump to the entry point of the callee gen_direct_jump( @@ -9936,7 +9937,7 @@ fn gen_leave( asm_comment!(asm, "pop stack frame"); let incr_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, incr_cfp); - asm.mov(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.mov(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); // Load the return value let retval_opnd = asm.stack_pop(1); diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 6e6a1810c67dda..d34b049a453625 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -361,6 +361,11 @@ impl VALUE { !self.special_const_p() } + /// Shareability between ractors. `RB_OBJ_SHAREABLE_P()`. + pub fn shareable_p(self) -> bool { + (self.builtin_flags() & RUBY_FL_SHAREABLE as usize) != 0 + } + /// Return true if the value is a Ruby Fixnum (immediate-size integer) pub fn fixnum_p(self) -> bool { let VALUE(cval) = self; @@ -767,12 +772,6 @@ mod manual_defs { pub const RUBY_OFFSET_CFP_JIT_RETURN: i32 = 48; pub const RUBY_SIZEOF_CONTROL_FRAME: usize = 56; - // Constants from rb_execution_context_t vm_core.h - pub const RUBY_OFFSET_EC_CFP: i32 = 16; - pub const RUBY_OFFSET_EC_INTERRUPT_FLAG: i32 = 32; // rb_atomic_t (u32) - pub const RUBY_OFFSET_EC_INTERRUPT_MASK: i32 = 36; // rb_atomic_t (u32) - pub const RUBY_OFFSET_EC_THREAD_PTR: i32 = 48; - // Constants from rb_thread_t in vm_core.h pub const RUBY_OFFSET_THREAD_SELF: i32 = 16; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index d2347671bbacb8..952cf88c205115 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -978,6 +978,11 @@ pub type rb_seq_param_keyword_struct = pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: jit_bindgen_constants = 16; pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; +pub const RUBY_OFFSET_EC_CFP: jit_bindgen_constants = 16; +pub const RUBY_OFFSET_EC_INTERRUPT_FLAG: jit_bindgen_constants = 32; +pub const RUBY_OFFSET_EC_INTERRUPT_MASK: jit_bindgen_constants = 36; +pub const RUBY_OFFSET_EC_THREAD_PTR: jit_bindgen_constants = 48; +pub const RUBY_OFFSET_EC_RACTOR_ID: jit_bindgen_constants = 64; pub type jit_bindgen_constants = u32; pub type rb_iseq_param_keyword_struct = rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword; @@ -1198,6 +1203,7 @@ extern "C" { pub fn rb_yjit_shape_index(shape_id: shape_id_t) -> attr_index_t; pub fn rb_yjit_sendish_sp_pops(ci: *const rb_callinfo) -> usize; pub fn rb_yjit_invokeblock_sp_pops(ci: *const rb_callinfo) -> usize; + pub fn rb_yjit_cme_ractor_serial(cme: *const rb_callable_method_entry_t) -> rb_serial_t; pub fn rb_yjit_set_exception_return( cfp: *mut rb_control_frame_t, leave_exit: *mut ::std::os::raw::c_void, diff --git a/zjit/src/backend/tests.rs b/zjit/src/backend/tests.rs index 6e62b3068d5a29..ece6f8605f1540 100644 --- a/zjit/src/backend/tests.rs +++ b/zjit/src/backend/tests.rs @@ -229,9 +229,9 @@ fn test_jcc_ptr() let (mut asm, mut cb) = setup_asm(); let side_exit = Target::CodePtr(cb.get_write_ptr().add_bytes(4)); - let not_mask = asm.not(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_MASK)); + let not_mask = asm.not(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_MASK as i32)); asm.test( - Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG), + Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG as i32), not_mask, ); asm.jnz(side_exit); diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index e81732f3d54ff7..b05c0110909e62 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -841,7 +841,7 @@ fn gen_ccall_with_frame( asm_comment!(asm, "switch to new CFP"); let new_cfp = asm.sub(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, new_cfp); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); let mut cfunc_args = vec![recv]; cfunc_args.extend(args); @@ -851,7 +851,7 @@ fn gen_ccall_with_frame( asm_comment!(asm, "pop C frame"); let new_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, new_cfp); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); asm_comment!(asm, "restore SP register for the caller"); let new_sp = asm.sub(SP, sp_offset.into()); @@ -926,7 +926,7 @@ fn gen_ccall_variadic( asm_comment!(asm, "switch to new CFP"); let new_cfp = asm.sub(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, new_cfp); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); let argv_ptr = gen_push_opnds(asm, &args); asm.count_call_to(&name.contents_lossy()); @@ -936,7 +936,7 @@ fn gen_ccall_variadic( asm_comment!(asm, "pop C frame"); let new_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, new_cfp); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); asm_comment!(asm, "restore SP register for the caller"); let new_sp = asm.sub(SP, sp_offset.into()); @@ -1051,7 +1051,7 @@ fn gen_check_interrupts(jit: &mut JITState, asm: &mut Assembler, state: &FrameSt asm_comment!(asm, "RUBY_VM_CHECK_INTS(ec)"); // Not checking interrupt_mask since it's zero outside finalize_deferred_heap_pages, // signal_exec, or rb_postponed_job_flush. - let interrupt_flag = asm.load(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG)); + let interrupt_flag = asm.load(Opnd::mem(32, EC, RUBY_OFFSET_EC_INTERRUPT_FLAG as i32)); asm.test(interrupt_flag, interrupt_flag); asm.jnz(side_exit(jit, state, SideExitReason::Interrupt)); } @@ -1382,7 +1382,7 @@ fn gen_send_without_block_direct( asm_comment!(asm, "switch to new CFP"); let new_cfp = asm.sub(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, new_cfp); - asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); // Set up arguments let mut c_args = vec![recv]; @@ -1741,7 +1741,7 @@ fn gen_return(asm: &mut Assembler, val: lir::Opnd) { asm_comment!(asm, "pop stack frame"); let incr_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into()); asm.mov(CFP, incr_cfp); - asm.mov(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + asm.mov(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP as i32), CFP); // Order here is important. Because we're about to tear down the frame, // we need to load the return value, which might be part of the frame. diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 68b6810125ea25..57a3bee7e01d8c 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -1073,12 +1073,6 @@ mod manual_defs { pub const RUBY_OFFSET_CFP_JIT_RETURN: i32 = 48; pub const RUBY_SIZEOF_CONTROL_FRAME: usize = 56; - // Constants from rb_execution_context_t vm_core.h - pub const RUBY_OFFSET_EC_CFP: i32 = 16; - pub const RUBY_OFFSET_EC_INTERRUPT_FLAG: i32 = 32; // rb_atomic_t (u32) - pub const RUBY_OFFSET_EC_INTERRUPT_MASK: i32 = 36; // rb_atomic_t (u32) - pub const RUBY_OFFSET_EC_THREAD_PTR: i32 = 48; - // Constants from rb_thread_t in vm_core.h pub const RUBY_OFFSET_THREAD_SELF: i32 = 16; diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 56ec724dd9b29c..2689ec30cf8b2d 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1828,6 +1828,11 @@ pub type zjit_struct_offsets = u32; pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: jit_bindgen_constants = 16; pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; +pub const RUBY_OFFSET_EC_CFP: jit_bindgen_constants = 16; +pub const RUBY_OFFSET_EC_INTERRUPT_FLAG: jit_bindgen_constants = 32; +pub const RUBY_OFFSET_EC_INTERRUPT_MASK: jit_bindgen_constants = 36; +pub const RUBY_OFFSET_EC_THREAD_PTR: jit_bindgen_constants = 48; +pub const RUBY_OFFSET_EC_RACTOR_ID: jit_bindgen_constants = 64; pub type jit_bindgen_constants = u32; pub const rb_invalid_shape_id: shape_id_t = 4294967295; pub type rb_iseq_param_keyword_struct =