diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index fb12eb80b335db..28b448f3e40c70 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -86,6 +86,7 @@ jobs: YJIT_BENCH_OPTS: ${{ matrix.yjit_bench_opts }} RUBY_DEBUG: ci BUNDLE_JOBS: 8 # for yjit-bench + RUST_BACKTRACE: 1 runs-on: ubuntu-20.04 if: ${{ !contains(github.event.head_commit.message, '[DOC]') && !contains(github.event.pull_request.labels.*.name, 'Documentation') }} steps: diff --git a/NEWS.md b/NEWS.md index a2973362527cae..b7e5ea4305dc3f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -29,6 +29,7 @@ The following default gems are updated. * csv 3.2.7 * fiddle 1.1.2 * optparse 0.4.0.pre.1 +* psych 5.0.2 * stringio 3.0.5 * strscan 3.0.6 diff --git a/common.mk b/common.mk index 9dd35eebc252c3..86f93c4c479393 100644 --- a/common.mk +++ b/common.mk @@ -50,13 +50,7 @@ GEM_PATH = GEM_VENDOR = BENCHMARK_DRIVER_GIT_URL = https://github.com/benchmark-driver/benchmark-driver -BENCHMARK_DRIVER_GIT_REF = v0.16.0 -SIMPLECOV_GIT_URL = https://github.com/colszowka/simplecov.git -SIMPLECOV_GIT_REF = v0.17.0 -SIMPLECOV_HTML_GIT_URL = https://github.com/colszowka/simplecov-html.git -SIMPLECOV_HTML_GIT_REF = v0.10.2 -DOCLIE_GIT_URL = https://github.com/ms-ati/docile.git -DOCLIE_GIT_REF = v1.3.2 +BENCHMARK_DRIVER_GIT_REF = v0.16.3 STATIC_RUBY = static-ruby @@ -1378,6 +1372,10 @@ update-config_files: PHONY $(Q) $(BASERUBY) -C "$(srcdir)" tool/downloader.rb -d tool --cache-dir=$(CACHE_DIR) -e gnu \ config.guess config.sub +update-coverage: PHONY + $(XRUBY) -C "$(srcdir)" bin/gem install --no-document \ + --install-dir .bundle --conservative "simplecov" -v "0.20.0" + refresh-gems: update-bundled_gems prepare-gems prepare-gems: $(HAVE_BASERUBY:yes=update-gems) $(HAVE_BASERUBY:yes=extract-gems) extract-gems: $(HAVE_BASERUBY:yes=update-gems) @@ -1754,12 +1752,14 @@ help: PHONY " runruby: runs test.rb by ruby you just built" \ " gdb: runs test.rb by miniruby under gdb" \ " gdb-ruby: runs test.rb by ruby under gdb" \ - " check: equals make test test-tool test-all test-spec" \ + " exam: equals make check test-bundler-parallel test-bundled-gems" \ + " check: equals make test test-tool test-all test-spec test-syntax-suggest" \ " test: ruby core tests [BTESTS=]" \ " test-all: all ruby tests [TESTOPTS=-j4 TESTS=]" \ " test-spec: run the Ruby spec suite [SPECOPTS=]" \ " test-bundler: run the Bundler spec" \ " test-bundler-parallel: run the Bundler spec with parallel" \ + " test-syntax-suggest: run the SyntaxSuggest spec" \ " test-bundled-gems: run the test suite of bundled gems" \ " test-tool: tests under the tool/test" \ " update-gems: download files of the bundled gems" \ diff --git a/compar.c b/compar.c index 040f77975ea477..dc157fd2410227 100644 --- a/compar.c +++ b/compar.c @@ -229,7 +229,7 @@ cmp_clamp(int argc, VALUE *argv, VALUE x) } } if (!NIL_P(min) && !NIL_P(max) && cmpint(min, max) > 0) { - rb_raise(rb_eArgError, "min argument must be smaller than max argument"); + rb_raise(rb_eArgError, "min argument must be less than or equal to max argument"); } if (!NIL_P(min)) { diff --git a/compile.c b/compile.c index f252339ee5709c..c20ef48c43316d 100644 --- a/compile.c +++ b/compile.c @@ -12311,7 +12311,7 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) verify_call_cache(iseq); RB_GC_GUARD(dummy_frame); - rb_vm_pop_frame(ec); + rb_vm_pop_frame_no_int(ec); } struct ibf_dump_iseq_list_arg diff --git a/dir.c b/dir.c index 3f73f83fc552b6..40107c7b818cdd 100644 --- a/dir.c +++ b/dir.c @@ -465,12 +465,12 @@ struct dir_data { rb_encoding *enc; }; -static void -dir_mark(void *ptr) -{ - struct dir_data *dir = ptr; - rb_gc_mark(dir->path); -} +/* static void */ +/* dir_mark(void *ptr) */ +/* { */ +/* struct dir_data *dir = ptr; */ +/* rb_gc_mark(dir->path); */ +/* } */ static void dir_free(void *ptr) @@ -487,10 +487,14 @@ dir_memsize(const void *ptr) return sizeof(struct dir_data); } +#define END_REFS UINTPTR_MAX + +static VALUE references[] = {offsetof(struct dir_data, path), END_REFS}; + static const rb_data_type_t dir_data_type = { "dir", - {dir_mark, dir_free, dir_memsize,}, - 0, 0, RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY + {NULL, dir_free, dir_memsize,}, + 0, references, RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_DECL_MARKING }; static VALUE dir_close(VALUE); diff --git a/doc/yjit/yjit.md b/doc/yjit/yjit.md index f68b36b65b83fb..01dd77410bb794 100644 --- a/doc/yjit/yjit.md +++ b/doc/yjit/yjit.md @@ -172,18 +172,19 @@ We have collected a set of benchmarks and implemented a simple benchmarking harn ### Performance Tips -This section contains tips on writing Ruby code that will run as fast as possible on YJIT. Some of this advice is based on current limitations of YJIT, while other advice is broadly applicable. It probably won't be practical to apply these tips everywhere in your codebase, but you can profile your code using a tool such as [stackprof](https://github.com/tmm1/stackprof) and refactor the specific methods that make up the largest fractions of the execution time. +This section contains tips on writing Ruby code that will run as fast as possible on YJIT. Some of this advice is based on current limitations of YJIT, while other advice is broadly applicable. It probably won't be practical to apply these tips everywhere in your codebase. You should ideally start by profiling your application using a tool such as [stackprof](https://github.com/tmm1/stackprof) so that you can determine which methods make up most of the execution time. You can then refactor the specific methods that make up the largest fractions of the execution time. We do not recommend modifying your entire codebase based on the current limitations of YJIT. - Use exceptions for error recovery only, not as part of normal control-flow - Avoid redefining basic integer operations (i.e. +, -, <, >, etc.) - Avoid redefining the meaning of `nil`, equality, etc. - Avoid allocating objects in the hot parts of your code -- Use while loops if you can, instead of `integer.times` - Minimize layers of indirection - Avoid classes that wrap objects if you can - Avoid methods that just call another method, trivial one liner methods -- CRuby method calls are costly. Favor larger methods over smaller methods. - Try to write code so that the same variables always have the same type +- Use while loops if you can, instead of `integer.times` + - This is not idiomatic Ruby, but could help in hot methods +- CRuby method calls are costly. Avoid things such as methods that only return a value from a hash or return a constant. You can also use the `--yjit-stats` command-line option to see which bytecodes cause YJIT to exit, and refactor your code to avoid using these instructions in the hottest methods of your code. diff --git a/ext/.document b/ext/.document index aeb40c60fb6071..f2fa5b5ba2d6ef 100644 --- a/ext/.document +++ b/ext/.document @@ -11,7 +11,6 @@ date/date_parse.c date/date_strftime.c date/date_strptime.c date/lib -dbm/dbm.c digest/bubblebabble/bubblebabble.c digest/digest.c digest/lib @@ -22,7 +21,6 @@ digest/sha2/sha2init.c digest/sha2/lib etc/etc.c fcntl/fcntl.c -fiber/fiber.c fiddle/closure.c fiddle/conversions.c fiddle/fiddle.c @@ -31,7 +29,6 @@ fiddle/pinned.c fiddle/pointer.c fiddle/handle.c fiddle/lib -gdbm/gdbm.c io/console/console.c io/console/lib io/nonblock/nonblock.c diff --git a/ext/Setup b/ext/Setup index 0376e2fc6c742d..9c3e2f48fe60e6 100644 --- a/ext/Setup +++ b/ext/Setup @@ -5,7 +5,6 @@ #continuation #coverage #date -#dbm #digest/bubblebabble #digest #digest/md5 @@ -14,9 +13,7 @@ #digest/sha2 #etc #fcntl -#fiber #fiddle -#gdbm #io/console #io/nonblock #io/wait diff --git a/ext/Setup.atheos b/ext/Setup.atheos index 3f6263b1e2276d..6a8b9903afe826 100644 --- a/ext/Setup.atheos +++ b/ext/Setup.atheos @@ -1,23 +1,19 @@ option nodynamic -#Win32API bigdecimal cgi/escape -dbm digest digest/md5 digest/rmd160 digest/sha1 digest/sha2 -enumerator etc fcntl -gdbm io/wait nkf #openssl pty -racc/parse +racc/cparse readline ripper socket diff --git a/ext/Setup.nt b/ext/Setup.nt index dc36aa8688c16a..067f153d36cd08 100644 --- a/ext/Setup.nt +++ b/ext/Setup.nt @@ -1,19 +1,15 @@ #option platform cygwin|mingw|mswin #option nodynamic -Win32API bigdecimal cgi/escape -#dbm digest digest/md5 digest/rmd160 digest/sha1 digest/sha2 -enumerator etc fcntl -#gdbm #io/wait nkf #openssl diff --git a/ext/psych/extconf.rb b/ext/psych/extconf.rb index 41daf8c2387369..e7dd0bb60a16fc 100644 --- a/ext/psych/extconf.rb +++ b/ext/psych/extconf.rb @@ -22,7 +22,7 @@ args = [ yaml_configure, "--enable-#{shared ? 'shared' : 'static'}", - "--host=#{RbConfig::CONFIG['host'].sub(/-unknown-/, '-')}", + "--host=#{RbConfig::CONFIG['host'].sub(/-unknown-/, '-').sub(/arm64/, 'arm')}", "CC=#{RbConfig::CONFIG['CC']}", *(["CFLAGS=-w"] if RbConfig::CONFIG["GCC"] == "yes"), ] diff --git a/ext/psych/lib/psych/versions.rb b/ext/psych/lib/psych/versions.rb index a592a6916c73b4..22b909430cfebd 100644 --- a/ext/psych/lib/psych/versions.rb +++ b/ext/psych/lib/psych/versions.rb @@ -2,7 +2,7 @@ module Psych # The version of Psych you are using - VERSION = '5.0.1' + VERSION = '5.0.2' if RUBY_ENGINE == 'jruby' DEFAULT_SNAKEYAML_VERSION = '1.33'.freeze diff --git a/gc.c b/gc.c index f5f510c9796b68..f5928f48d5c2c6 100644 --- a/gc.c +++ b/gc.c @@ -7208,6 +7208,19 @@ gc_mark_imemo(rb_objspace_t *objspace, VALUE obj) } } +static bool +gc_declarative_marking_p(const rb_data_type_t *type) +{ + return (type->flags & RUBY_TYPED_DECL_MARKING) != 0; +} + +static void +gc_mark_from_offset(rb_objspace_t *objspace, VALUE *data_struct, VALUE offset) +{ + VALUE markable = *(data_struct + (offset / sizeof(VALUE))); + gc_mark_and_pin(objspace, markable); +} + static void gc_mark_children(rb_objspace_t *objspace, VALUE obj) { @@ -7310,16 +7323,29 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj) break; case T_DATA: - { + { void *const ptr = DATA_PTR(obj); if (ptr) { - RUBY_DATA_FUNC mark_func = RTYPEDDATA_P(obj) ? - any->as.typeddata.type->function.dmark : - any->as.data.dmark; - if (mark_func) (*mark_func)(ptr); + if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(any->as.typeddata.type)) { + VALUE *offset_list = any->as.typeddata.type->data; + VALUE *data_struct = (VALUE *)any->as.typeddata.data; + VALUE offset = *offset_list; + + int n = 0; + while (offset != UINTPTR_MAX) { + gc_mark_from_offset(objspace, data_struct, offset); + n++; + offset = *(offset_list + n); + } + } else { + RUBY_DATA_FUNC mark_func = RTYPEDDATA_P(obj) ? + any->as.typeddata.type->function.dmark : + any->as.data.dmark; + if (mark_func) (*mark_func)(ptr); + } } } - break; + break; case T_OBJECT: { diff --git a/gems/bundled_gems b/gems/bundled_gems index 2cb6d14c93eb85..60b6d6598c78b8 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -17,6 +17,6 @@ net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.3.3 https://github.com/ruby/net-smtp matrix 0.4.2 https://github.com/ruby/matrix prime 0.1.2 https://github.com/ruby/prime -rbs 2.8.3 https://github.com/ruby/rbs eab5367add3469b3f614e663ba43a3debc62420a +rbs 2.8.3 https://github.com/ruby/rbs c822808a08ef86f3ab460d7aa7506da7783f0bef typeprof 0.21.4 https://github.com/ruby/typeprof debug 1.7.1 https://github.com/ruby/debug diff --git a/include/ruby/internal/core/rhash.h b/include/ruby/internal/core/rhash.h index 61d2c15d87a41e..897c5707945895 100644 --- a/include/ruby/internal/core/rhash.h +++ b/include/ruby/internal/core/rhash.h @@ -45,19 +45,6 @@ */ #define RHASH_TBL(h) rb_hash_tbl(h, __FILE__, __LINE__) -/** - * @private - * - * @deprecated This macro once was a thing in the old days, but makes no sense - * any longer today. Exists here for backwards compatibility - * only. You can safely forget about it. - * - * @internal - * - * Declaration of rb_hash_iter_lev() is at include/ruby/backward.h. - */ -#define RHASH_ITER_LEV(h) rb_hash_iter_lev(h) - /** * @private * diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index bbf208867df87a..9d91a3e3f18dd4 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -176,7 +176,14 @@ rbimpl_typeddata_flags { * This flag is mysterious. It seems nobody is currently using it. The * intention of this flag is also unclear. We need further investigations. */ - RUBY_TYPED_PROMOTED1 = RUBY_FL_PROMOTED1 /* THIS FLAG DEPENDS ON Ruby version */ + RUBY_TYPED_PROMOTED1 = RUBY_FL_PROMOTED1, /* THIS FLAG DEPENDS ON Ruby version */ + + /** + * This flag determines whether marking and compaction should be carried out + * using the dmark/dcompact callback functions or whether we should mark + * declaratively using a list of references defined inside the data struct we're wrapping + */ + RUBY_TYPED_DECL_MARKING = RUBY_FL_USER2 }; /** diff --git a/internal.h b/internal.h index b63af50616fd70..c66e057f60a3a5 100644 --- a/internal.h +++ b/internal.h @@ -43,7 +43,6 @@ /* internal/gc.h */ #undef NEWOBJ_OF #undef RB_NEWOBJ_OF -#undef RB_OBJ_WRITE /* internal/hash.h */ #undef RHASH_IFNONE diff --git a/internal/class.h b/internal/class.h index 92e018f75998fa..bdc3bdd23692e8 100644 --- a/internal/class.h +++ b/internal/class.h @@ -9,7 +9,6 @@ * @brief Internal header for Class. */ #include "id_table.h" /* for struct rb_id_table */ -#include "internal/gc.h" /* for RB_OBJ_WRITE */ #include "internal/serial.h" /* for rb_serial_t */ #include "internal/static_assert.h" #include "ruby/internal/stdbool.h" /* for bool */ diff --git a/internal/gc.h b/internal/gc.h index e54a5dce9dc915..c9123b3c7f1ed5 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -21,7 +21,6 @@ struct rb_objspace; /* in vm_core.h */ #ifdef NEWOBJ_OF # undef NEWOBJ_OF # undef RB_NEWOBJ_OF -# undef RB_OBJ_WRITE #endif #define RVALUE_SIZE (sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX])) @@ -63,9 +62,6 @@ struct rb_objspace; /* in vm_core.h */ #endif #define UNALIGNED_MEMBER_PTR(ptr, mem) UNALIGNED_MEMBER_ACCESS(&(ptr)->mem) -#define RB_OBJ_WRITE(a, slot, b) \ - rb_obj_write((VALUE)(a), UNALIGNED_MEMBER_ACCESS((VALUE *)(slot)), \ - (VALUE)(b), __FILE__, __LINE__) // We use SIZE_POOL_COUNT number of shape IDs for transitions out of different size pools // The next available shapd ID will be the SPECIAL_CONST_SHAPE_ID diff --git a/internal/imemo.h b/internal/imemo.h index 91b524e0a6ae77..ef07333a582170 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -11,7 +11,6 @@ #include "ruby/internal/config.h" #include /* for size_t */ #include "internal/array.h" /* for rb_ary_hidden_new_fill */ -#include "internal/gc.h" /* for RB_OBJ_WRITE */ #include "ruby/internal/stdbool.h" /* for bool */ #include "ruby/ruby.h" /* for rb_block_call_func_t */ diff --git a/internal/process.h b/internal/process.h index ceadfdcbbb7600..b9c52aebaae2a3 100644 --- a/internal/process.h +++ b/internal/process.h @@ -20,6 +20,7 @@ #endif #include "ruby/ruby.h" /* for VALUE */ +#include "internal/compilers.h" /* for __has_warning */ #include "internal/imemo.h" /* for RB_IMEMO_TMPBUF_PTR */ #include "internal/warnings.h" /* for COMPILER_WARNING_PUSH */ diff --git a/internal/rational.h b/internal/rational.h index 61ddbf089ae497..f11fab4583b32e 100644 --- a/internal/rational.h +++ b/internal/rational.h @@ -10,7 +10,6 @@ */ #include "ruby/internal/config.h" /* for HAVE_LIBGMP */ #include "ruby/ruby.h" /* for struct RBasic */ -#include "internal/gc.h" /* for RB_OBJ_WRITE */ #include "internal/numeric.h" /* for INT_POSITIVE_P */ #include "ruby_assert.h" /* for assert */ diff --git a/internal/struct.h b/internal/struct.h index 8acc00ec3ca5a1..5b00e522622adc 100644 --- a/internal/struct.h +++ b/internal/struct.h @@ -9,7 +9,6 @@ * @brief Internal header for Struct. */ #include "ruby/internal/stdbool.h" /* for bool */ -#include "internal/gc.h" /* for RB_OBJ_WRITE */ #include "ruby/ruby.h" /* for struct RBasic */ enum { diff --git a/lib/net/http/header.rb b/lib/net/http/header.rb index f2292ecdc7f594..4f3d3fa0986b30 100644 --- a/lib/net/http/header.rb +++ b/lib/net/http/header.rb @@ -806,56 +806,108 @@ def set_form_data(params, sep = '&') alias form_data= set_form_data - # Set an HTML form data set. - # +params+ :: The form data to set, which should be an enumerable. - # See below for more details. - # +enctype+ :: The content type to use to encode the form submission, - # which should be application/x-www-form-urlencoded or - # multipart/form-data. - # +formopt+ :: An options hash, supporting the following options: - # :boundary :: The boundary of the multipart message. If - # not given, a random boundary will be used. - # :charset :: The charset of the form submission. All - # field names and values of non-file fields - # should be encoded with this charset. - # - # Each item of params should respond to +each+ and yield 2-3 arguments, - # or an array of 2-3 elements. The arguments yielded should be: - # - # - The name of the field. - # - The value of the field, it should be a String or a File or IO-like. - # - An options hash, supporting the following options - # (used only for file uploads); entries: - # - # - +:filename+: The name of the file to use. - # - +:content_type+: The content type of the uploaded file. - # - # Each item is a file field or a normal field. - # If +value+ is a File object or the +opt+ hash has a :filename key, - # the item is treated as a file field. - # - # If Transfer-Encoding is set as chunked, this sends the request using - # chunked encoding. Because chunked encoding is HTTP/1.1 feature, - # you should confirm that the server supports HTTP/1.1 before using - # chunked encoding. - # - # Example: - # - # req.set_form([["q", "ruby"], ["lang", "en"]]) - # - # req.set_form({"f"=>File.open('/path/to/filename')}, - # "multipart/form-data", - # charset: "UTF-8", - # ) - # - # req.set_form([["f", - # File.open('/path/to/filename.bar'), - # {filename: "other-filename.foo"} - # ]], - # "multipart/form-data", - # ) - # - # See also RFC 2388, RFC 2616, HTML 4.01, and HTML5 + # Stores form data to be used in a +POST+ or +PUT+ request. + # + # The form data given in +params+ consists of zero or more fields; + # each field is: + # + # - A scalar value. + # - A name/value pair. + # - An IO stream opened for reading. + # + # Argument +params+ should be an + # {Enumerable}[rdoc-ref:Enumerable@Enumerable+in+Ruby+Classes] + # (method params.map will be called), + # and is often an array or hash. + # + # First, we set up a request: + # + # _uri = uri.dup + # _uri.path ='/posts' + # req = Net::HTTP::Post.new(_uri) + # + # Argument +params+ As an Array + # + # When +params+ is an array, + # each of its elements is a subarray that defines a field; + # the subarray may contain: + # + # - One string: + # + # req.set_form([['foo'], ['bar'], ['baz']]) + # + # - Two strings: + # + # req.set_form([%w[foo 0], %w[bar 1], %w[baz 2]]) + # + # - When argument +enctype+ (see below) is given as + # 'multipart/form-data': + # + # - A string name and an IO stream opened for reading: + # + # require 'stringio' + # req.set_form([['file', StringIO.new('Ruby is cool.')]]) + # + # - A string name, an IO stream opened for reading, + # and an options hash, which may contain these entries: + # + # - +:filename+: The name of the file to use. + # - +:content_type+: The content type of the uploaded file. + # + # Example: + # + # req.set_form([['file', file, {filename: "other-filename.foo"}]] + # + # The various forms may be mixed: + # + # req.set_form(['foo', %w[bar 1], ['file', file]]) + # + # Argument +params+ As a Hash + # + # When +params+ is a hash, + # each of its entries is a name/value pair that defines a field: + # + # - The name is a string. + # - The value may be: + # + # - +nil+. + # - Another string. + # - An IO stream opened for reading + # (only when argument +enctype+ -- see below -- is given as + # 'multipart/form-data'). + # + # Examples: + # + # # Nil-valued fields. + # req.set_form({'foo' => nil, 'bar' => nil, 'baz' => nil}) + # + # # String-valued fields. + # req.set_form({'foo' => 0, 'bar' => 1, 'baz' => 2}) + # + # # IO-valued field. + # require 'stringio' + # req.set_form({'file' => StringIO.new('Ruby is cool.')}) + # + # # Mixture of fields. + # req.set_form({'foo' => nil, 'bar' => 1, 'file' => file}) + # + # Optional argument +enctype+ specifies the value to be given + # to field 'Content-Type', and must be one of: + # + # - 'application/x-www-form-urlencoded' (the default). + # - 'multipart/form-data'; + # see {RFC 7578}[https://www.rfc-editor.org/rfc/rfc7578]. + # + # Optional argument +formopt+ is a hash of options + # (applicable only when argument +enctype+ + # is 'multipart/form-data') + # that may include the following entries: + # + # - +:boundary+: The value is the boundary string for the multipart message. + # If not given, the boundary is a random string. + # See {Boundary}[https://www.rfc-editor.org/rfc/rfc7578#section-4.1]. + # - +:charset+: Value is the character set for the form submission. + # Field names and values of non-file fields should be encoded with this charset. # def set_form(params, enctype='application/x-www-form-urlencoded', formopt={}) @body_data = params diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 28daae8884a936..a1402a0f9ed57b 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -52,6 +52,7 @@ module CompletionState MenuInfo = Struct.new('MenuInfo', :target, :list) PROMPT_LIST_CACHE_TIMEOUT = 0.5 + MINIMUM_SCROLLBAR_HEIGHT = 1 def initialize(config, encoding) @config = config @@ -705,17 +706,17 @@ def add_dialog_proc(name, p, context = nil) dialog.scroll_top = dialog.pointer end pointer = dialog.pointer - dialog.scroll_top + else + dialog.scroll_top = 0 end dialog.contents = dialog.contents[dialog.scroll_top, height] end - if dialog.contents and dialog.scroll_top >= dialog.contents.size - dialog.scroll_top = dialog.contents.size - height - end if dialog_render_info.scrollbar and dialog_render_info.contents.size > height bar_max_height = height * 2 moving_distance = (dialog_render_info.contents.size - height) * 2 position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance) bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i + bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT dialog.scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i else dialog.scrollbar_pos = nil @@ -757,7 +758,7 @@ def add_dialog_proc(name, p, context = nil) str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width) str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width) @output.write "\e[#{bg_color}m\e[#{fg_color}m#{str}" - if dialog.scrollbar_pos and (dialog.scrollbar_pos != old_dialog.scrollbar_pos or dialog.column != old_dialog.column) + if dialog.scrollbar_pos @output.write "\e[37m" if dialog.scrollbar_pos <= (i * 2) and (i * 2 + 1) < (dialog.scrollbar_pos + bar_height) @output.write @full_block diff --git a/spec/ruby/optional/capi/ext/rubyspec.h b/spec/ruby/optional/capi/ext/rubyspec.h index 245669d2007b1c..80deca24c6f129 100644 --- a/spec/ruby/optional/capi/ext/rubyspec.h +++ b/spec/ruby/optional/capi/ext/rubyspec.h @@ -21,16 +21,21 @@ ((RUBY_VERSION_MAJOR < (major)) || \ (RUBY_VERSION_MAJOR == (major) && RUBY_VERSION_MINOR < (minor)) || \ (RUBY_VERSION_MAJOR == (major) && RUBY_VERSION_MINOR == (minor) && RUBY_VERSION_TEENY < (teeny))) +#define RUBY_VERSION_SINCE(major,minor,teeny) (!RUBY_VERSION_BEFORE(major, minor, teeny)) -#if RUBY_VERSION_MAJOR > 3 || (RUBY_VERSION_MAJOR == 3 && RUBY_VERSION_MINOR >= 2) +#if RUBY_VERSION_SINCE(3, 3, 0) +#define RUBY_VERSION_IS_3_3 +#endif + +#if RUBY_VERSION_SINCE(3, 2, 0) #define RUBY_VERSION_IS_3_2 #endif -#if RUBY_VERSION_MAJOR > 3 || (RUBY_VERSION_MAJOR == 3 && RUBY_VERSION_MINOR >= 1) +#if RUBY_VERSION_SINCE(3, 1, 0) #define RUBY_VERSION_IS_3_1 #endif -#if RUBY_VERSION_MAJOR > 3 || (RUBY_VERSION_MAJOR == 3 && RUBY_VERSION_MINOR >= 0) +#if RUBY_VERSION_SINCE(3, 0, 0) #define RUBY_VERSION_IS_3_0 #endif diff --git a/string.c b/string.c index de96796269eacb..af5721ecc5678b 100644 --- a/string.c +++ b/string.c @@ -6268,7 +6268,7 @@ rb_str_byteslice(int argc, VALUE *argv, VALUE str) * bytesplice(index, length, str) -> string * bytesplice(range, str) -> string * - * Replaces some or all of the content of +self+ with +str+, and returns +str+. + * Replaces some or all of the content of +self+ with +str+, and returns +self+. * The portion of the string affected is determined using * the same criteria as String#byteslice, except that +length+ cannot be omitted. * If the replacement string is not the same length as the text it is replacing, @@ -6330,7 +6330,7 @@ rb_str_bytesplice(int argc, VALUE *argv, VALUE str) cr = ENC_CODERANGE_AND(ENC_CODERANGE(str), ENC_CODERANGE(val)); if (cr != ENC_CODERANGE_BROKEN) ENC_CODERANGE_SET(str, cr); - return val; + return str; } /* diff --git a/template/Makefile.in b/template/Makefile.in index c68f2942ee9317..d638a31af57ac0 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -602,23 +602,6 @@ update-benchmark-driver: --branch $(BENCHMARK_DRIVER_GIT_REF) \ $(BENCHMARK_DRIVER_GIT_URL) benchmark-driver $(GIT_OPTS) -update-doclie: - $(Q) $(tooldir)/git-refresh -C $(srcdir)/coverage $(Q1:0=-q) \ - --branch $(DOCLIE_GIT_REF) \ - $(DOCLIE_GIT_URL) doclie $(GIT_OPTS) - -update-simplecov-html: - $(Q) $(tooldir)/git-refresh -C $(srcdir)/coverage $(Q1:0=-q) \ - --branch $(SIMPLECOV_HTML_GIT_REF) \ - $(SIMPLECOV_HTML_GIT_URL) simplecov-html $(GIT_OPTS) - -update-simplecov: - $(Q) $(tooldir)/git-refresh -C $(srcdir)/coverage $(Q1:0=-q) \ - --branch $(SIMPLECOV_GIT_REF) \ - $(SIMPLECOV_GIT_URL) simplecov $(GIT_OPTS) - -update-coverage: update-simplecov update-simplecov-html update-doclie - update-known-errors: errno --list | cut -d' ' -f1 | sort -u - $(srcdir)/defs/known_errors.def | \ $(IFCHANGE) $(srcdir)/defs/known_errors.def - diff --git a/test/coverage/test_coverage.rb b/test/coverage/test_coverage.rb index d5334d07a5b99d..134b41ea6ee076 100644 --- a/test/coverage/test_coverage.rb +++ b/test/coverage/test_coverage.rb @@ -158,14 +158,16 @@ def test_eval end def test_eval_coverage - assert_in_out_err(%w[-rcoverage], <<-"end;", ["[1, nil, 1, nil]"], []) + assert_in_out_err(%w[-rcoverage], <<-"end;", ["[1, 1, 1, nil, 0, nil]"], []) Coverage.start(eval: true, lines: true) eval(<<-RUBY, TOPLEVEL_BINDING, "test.rb") - s = String.new - begin - s << "foo - bar".freeze; end + _out = String.new + if _out.empty? + _out << 'Hello World' + else + _out << 'Goodbye World' + end RUBY p Coverage.result["test.rb"][:lines] diff --git a/test/reline/yamatanooroti/multiline_repl b/test/reline/yamatanooroti/multiline_repl index c429494442cbcd..7e58b602eb990b 100755 --- a/test/reline/yamatanooroti/multiline_repl +++ b/test/reline/yamatanooroti/multiline_repl @@ -53,7 +53,9 @@ opt.on('--color-bold') { } } opt.on('--auto-indent') { - AutoIndent.new + Reline.auto_indent_proc = lambda do |lines, line_index, byte_pointer, is_newline| + AutoIndent.calculate_indent(lines, line_index, byte_pointer, is_newline) + end } opt.on('--dialog VAL') { |v| Reline.add_dialog_proc(:simple_dialog, lambda { @@ -194,8 +196,7 @@ end begin prompt = ENV['RELINE_TEST_PROMPT'] || 'prompt> ' puts 'Multiline REPL.' - checker = TerminationChecker.new - while code = Reline.readmultiline(prompt, true) { |code| checker.terminated?(code) } + while code = Reline.readmultiline(prompt, true) { |code| TerminationChecker.terminated?(code) } case code.chomp when 'exit', 'quit', 'q' exit 0 diff --git a/test/reline/yamatanooroti/termination_checker.rb b/test/reline/yamatanooroti/termination_checker.rb index 15ec7d5913240a..b97c798c59318b 100644 --- a/test/reline/yamatanooroti/termination_checker.rb +++ b/test/reline/yamatanooroti/termination_checker.rb @@ -1,28 +1,26 @@ require 'ripper' -require 'irb/ruby-lex' -class TerminationChecker < RubyLex - def terminated?(code) - code.gsub!(/\n*$/, '').concat("\n") - tokens = self.class.ripper_lex_without_warning(code) - continue = process_continue(tokens) - code_block_open = check_code_block(code, tokens) - indent = process_nesting_level(tokens) - ltype = process_literal_type(tokens) - if code_block_open or ltype or continue or indent > 0 - false - else - true - end +module TerminationChecker + def self.terminated?(code) + Ripper.sexp(code) ? true : false end end -class AutoIndent < RubyLex - def initialize - @context = Struct.new("MockIRBContext", :auto_indent_mode, :workspace, :local_variables).new(true, nil, []) +module AutoIndent + def self.calculate_indent(lines, line_index, byte_pointer, is_newline) + if is_newline + 2 * nesting_level(lines[0..line_index - 1]) + else + lines = lines.dup + lines[line_index] = lines[line_index]&.byteslice(0, byte_pointer) + prev_level = nesting_level(lines[0..line_index - 1]) + level = nesting_level(lines[0..line_index]) + 2 * level if level < prev_level + end end - def auto_indent(&block) - Reline.auto_indent_proc = block + def self.nesting_level(lines) + code = lines.join("\n") + code.scan(/if|def|\(|\[|\{/).size - code.scan(/end|\)|\]|\}/).size end end diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb index 42d2b173d51f26..940758d3fd44d8 100644 --- a/test/reline/yamatanooroti/test_rendering.rb +++ b/test/reline/yamatanooroti/test_rendering.rb @@ -661,6 +661,35 @@ def test_update_cursor_correctly_when_just_cursor_moving EOC end + def test_auto_indent + start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.') + "def hoge\nputs(\n1,\n2\n)\nend".lines do |line| + write line + end + close + assert_screen(<<~EOC) + Multiline REPL. + prompt> def hoge + prompt> puts( + prompt> 1, + prompt> 2 + prompt> ) + prompt> end + EOC + end + + def test_auto_indent_when_inserting_line + start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.') + write 'aa(bb(cc(dd(ee(' + write "\C-b" * 5 + "\n" + close + assert_screen(<<~EOC) + Multiline REPL. + prompt> aa(bb(cc(d + prompt> d(ee( + EOC + end + def test_suppress_auto_indent_just_after_pasted start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.') write("def hoge\n [[\n 3]]\ned") @@ -1093,6 +1122,32 @@ def test_autocomplete_long_with_scrollbar_scroll EOC end + def test_autocomplete_super_long_scroll_to_bottom + start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-super-long}, startup_message: 'Multiline REPL.') + shift_tab = [27, 91, 90] + write('S' + shift_tab.map(&:chr).join) + close + assert_screen(<<~'EOC') + Multiline REPL. + prompt> Str_BXX + Str_BXJ + Str_BXK + Str_BXL + Str_BXM + Str_BXN + Str_BXO + Str_BXP + Str_BXQ + Str_BXR + Str_BXS + Str_BXT + Str_BXU + Str_BXV + Str_BXW + Str_BXX▄ + EOC + end + def test_autocomplete_super_long_and_backspace start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-super-long}, startup_message: 'Multiline REPL.') shift_tab = [27, 91, 90] @@ -1102,15 +1157,15 @@ def test_autocomplete_super_long_and_backspace assert_screen(<<~'EOC') Multiline REPL. prompt> Str_BX - Str_BX - Str_BXA - Str_BXB - Str_BXC - Str_BXD - Str_BXE - Str_BXF - Str_BXG - Str_BXH + Str_BX █ + Str_BXA█ + Str_BXB█ + Str_BXC█ + Str_BXD█ + Str_BXE█ + Str_BXF█ + Str_BXG█ + Str_BXH█ Str_BXI Str_BXJ Str_BXK @@ -1154,7 +1209,7 @@ def test_dialog_narrower_than_screen_with_scrollbar Multiline R EPL. prompt> Sym - String + String █ Struct █ Symbol █ StopIterat█ diff --git a/test/ruby/test_comparable.rb b/test/ruby/test_comparable.rb index b849217b7d4216..4a90d443bf9cc9 100644 --- a/test/ruby/test_comparable.rb +++ b/test/ruby/test_comparable.rb @@ -85,7 +85,7 @@ def test_clamp assert_equal(1, @o.clamp(1, 1)) assert_equal(@o, @o.clamp(0, 0)) - assert_raise_with_message(ArgumentError, 'min argument must be smaller than max argument') { + assert_raise_with_message(ArgumentError, 'min argument must be less than or equal to max argument') { @o.clamp(2, 1) } end @@ -115,7 +115,7 @@ def test_clamp_with_range assert_raise_with_message(*exc) {@o.clamp(-1...0)} assert_raise_with_message(*exc) {@o.clamp(...2)} - assert_raise_with_message(ArgumentError, 'min argument must be smaller than max argument') { + assert_raise_with_message(ArgumentError, 'min argument must be less than or equal to max argument') { @o.clamp(2..1) } end diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index e34fd116c98b3d..2e66521df7232d 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3489,7 +3489,7 @@ def test_bytesplice private def assert_bytesplice_result(expected, s, *args) - assert_equal(args.last, s.send(:bytesplice, *args)) + assert_equal(expected, s.send(:bytesplice, *args)) assert_equal(expected, s) end diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb index f8a7c68fd380fb..9acba39f403e85 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -261,6 +261,12 @@ def test_special_constant_ivars v.instance_variable_set(:@foo, :bar) end + assert_raise_with_message(FrozenError, msg, "[Bug #19339]") do + v.instance_eval do + @a = 1 + end + end + assert_nil EnvUtil.suppress_warning {v.instance_variable_get(:@foo)} assert_not_send([v, :instance_variable_defined?, :@foo]) diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock index e597d1981eddfe..ac6f2c90f54251 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock @@ -151,18 +151,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.54" +version = "0.9.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3277448b8eee18de8bedb18883ae02dcd60d47922ddfc6ab408def77da0a9b4" +checksum = "ef82428221475c6f9e7893fe30b88d45ac86bdb12e58e7c92055ba4bceb78a69" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.54" +version = "0.9.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9baae802c93180af02cccb21819589d109070f8e28e14e7070a9ffdeca9b464" +checksum = "950bfc239d2e7704576abe4d37b008876bbfd70a99196a188c5caeae2ba7344a" dependencies = [ "bindgen", "regex", diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml index 53f0879012772d..316857a360492c 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.54" +rb-sys = "0.9.56" diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock index 6162043fa15dd7..626ad100ca5fe0 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock @@ -144,18 +144,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.54" +version = "0.9.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3277448b8eee18de8bedb18883ae02dcd60d47922ddfc6ab408def77da0a9b4" +checksum = "ef82428221475c6f9e7893fe30b88d45ac86bdb12e58e7c92055ba4bceb78a69" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.54" +version = "0.9.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9baae802c93180af02cccb21819589d109070f8e28e14e7070a9ffdeca9b464" +checksum = "950bfc239d2e7704576abe4d37b008876bbfd70a99196a188c5caeae2ba7344a" dependencies = [ "bindgen", "regex", diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml index b2f56a91977ef4..c1dfc490489148 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.54" +rb-sys = "0.9.56" diff --git a/thread_sync.c b/thread_sync.c index 2f43896cfb2ac0..09d8226dd9836b 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -905,7 +905,7 @@ rb_queue_initialize(int argc, VALUE *argv, VALUE self) if ((argc = rb_scan_args(argc, argv, "01", &initial)) == 1) { initial = rb_to_array(initial); } - RB_OBJ_WRITE(self, &q->que, ary_buf_new()); + RB_OBJ_WRITE(self, UNALIGNED_MEMBER_ACCESS(&q->que), ary_buf_new()); ccan_list_head_init(queue_waitq(q)); if (argc == 1) { rb_ary_concat(q->que, initial); @@ -1178,7 +1178,7 @@ rb_szqueue_initialize(VALUE self, VALUE vmax) rb_raise(rb_eArgError, "queue size must be positive"); } - RB_OBJ_WRITE(self, &sq->q.que, ary_buf_new()); + RB_OBJ_WRITE(self, UNALIGNED_MEMBER_ACCESS(&sq->q.que), ary_buf_new()); ccan_list_head_init(szqueue_waitq(sq)); ccan_list_head_init(szqueue_pushq(sq)); sq->max = max; diff --git a/tool/lib/test/unit/parallel.rb b/tool/lib/test/unit/parallel.rb index be0936bd76f0ba..b3a8957f26e21b 100644 --- a/tool/lib/test/unit/parallel.rb +++ b/tool/lib/test/unit/parallel.rb @@ -87,8 +87,6 @@ def _run_suite(suite, type) # :nodoc: $stdout = orig_stdout if orig_stdout o.close if o && !o.closed? i.close if i && !i.closed? - - Gem.clear_paths if defined?(Gem) end def run(args = []) # :nodoc: diff --git a/tool/rbinstall.rb b/tool/rbinstall.rb index 02ce7faaa74f5b..85d05eff2518d0 100755 --- a/tool/rbinstall.rb +++ b/tool/rbinstall.rb @@ -999,6 +999,7 @@ def install_default_gem(dir, srcdir, bindir) end installed_gems = {} + skipped = {} options = { :install_dir => install_dir, :bin_dir => with_destdir(bindir), @@ -1029,11 +1030,20 @@ def install_default_gem(dir, srcdir, bindir) path = "#{srcdir}/.bundle/specifications/#{gem_name}.gemspec" unless File.exist?(path) path = "#{srcdir}/.bundle/gems/#{gem_name}/#{gem_name}.gemspec" - next unless File.exist?(path) + unless File.exist?(path) + skipped[gem_name] = "gemspec not found" + next + end end spec = load_gemspec(path, "#{srcdir}/.bundle/gems/#{gem_name}") - next unless spec.platform == Gem::Platform::RUBY - next unless spec.full_name == gem_name + unless spec.platform == Gem::Platform::RUBY + skipped[gem_name] = "not ruby platform (#{spec.platform})" + next + end + unless spec.full_name == gem_name + skipped[gem_name] = "full name unmatch #{spec.full_name}" + next + end spec.extension_dir = "#{extensions_dir}/#{spec.full_name}" package = RbInstall::DirPackage.new spec ins = RbInstall::UnpackedInstaller.new(package, options) @@ -1051,7 +1061,11 @@ def install_default_gem(dir, srcdir, bindir) install installed_gems, gem_dir+"/cache" end unless gems.empty? - puts "skipped bundled gems: #{gems.join(' ')}" + skipped.default = "not found in bundled_gems" + puts "skipped bundled gems:" + gems.each do |gem| + printf " %-32s%s\n", File.basename(gem), skipped[gem] + end end end @@ -1071,6 +1085,7 @@ def install_default_gem(dir, srcdir, bindir) end installs.flatten! installs -= $exclude.map {|exc| $install_procs[exc]}.flatten +puts "Installing to #$destdir" unless installs.empty? installs.each do |block| dir = Dir.pwd begin diff --git a/tool/test-coverage.rb b/tool/test-coverage.rb index 4950bc65de4b3d..c4ffa66a4f3be0 100644 --- a/tool/test-coverage.rb +++ b/tool/test-coverage.rb @@ -62,8 +62,11 @@ def save_coverage_data(res1) end def invoke_simplecov_formatter - %w[doclie simplecov-html simplecov].each do |f| - $LOAD_PATH.unshift "#{__dir__}/../coverage/#{f}/lib" + # XXX docile-x.y.z and simplecov-x.y.z, simplecov-html-x.y.z, simplecov_json_formatter-x.y.z + %w[simplecov simplecov-html simplecov_json_formatter docile].each do |f| + Dir.glob("#{__dir__}/../.bundle/gems/#{f}-*/lib").each do |d| + $LOAD_PATH.unshift d + end end require "simplecov" diff --git a/vm_core.h b/vm_core.h index 1f5c56aa17136a..c67b3a5f4bc3ea 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1747,6 +1747,7 @@ const VALUE *rb_binding_add_dynavars(VALUE bindval, rb_binding_t *bind, int dync void rb_vm_inc_const_missing_count(void); VALUE rb_vm_call_kw(rb_execution_context_t *ec, VALUE recv, VALUE id, int argc, const VALUE *argv, const rb_callable_method_entry_t *me, int kw_splat); +void rb_vm_pop_frame_no_int(rb_execution_context_t *ec); MJIT_STATIC void rb_vm_pop_frame(rb_execution_context_t *ec); void rb_thread_start_timer_thread(void); diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 9c2947523c60d3..0ce7fb6cc7ff27 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -405,6 +405,17 @@ vm_push_frame(rb_execution_context_t *ec, vm_push_frame_debug_counter_inc(ec, cfp, type); } +void +rb_vm_pop_frame_no_int(rb_execution_context_t *ec) +{ + rb_control_frame_t *cfp = ec->cfp; + + if (VM_CHECK_MODE >= 4) rb_gc_verify_internal_consistency(); + if (VMDEBUG == 2) SDR(); + + ec->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); +} + /* return TRUE if the frame is finished */ static inline int vm_pop_frame(rb_execution_context_t *ec, rb_control_frame_t *cfp, const VALUE *ep) @@ -1562,6 +1573,11 @@ vm_getinstancevariable(const rb_iseq_t *iseq, VALUE obj, ID id, IVC ic) static inline void vm_setinstancevariable(const rb_iseq_t *iseq, VALUE obj, ID id, VALUE val, IVC ic) { + if (RB_SPECIAL_CONST_P(obj)) { + rb_error_frozen_object(obj); + return; + } + shape_id_t dest_shape_id; attr_index_t index; vm_ic_atomic_shape_and_index(ic, &dest_shape_id, &index); diff --git a/yjit.c b/yjit.c index db0a5f5978a265..a1710f9c0586b3 100644 --- a/yjit.c +++ b/yjit.c @@ -37,6 +37,12 @@ #include +// Field offsets for the RString struct +enum rstring_offsets { + RUBY_OFFSET_RSTRING_AS_HEAP_LEN = offsetof(struct RString, as.heap.len), + RUBY_OFFSET_RSTRING_EMBED_LEN = offsetof(struct RString, as.embed.len), +}; + // We need size_t to have a known size to simplify code generation and FFI. // TODO(alan): check this in configure.ac to fail fast on 32 bit platforms. STATIC_ASSERT(64b_size_t, SIZE_MAX == UINT64_MAX); diff --git a/yjit.rb b/yjit.rb index 062784f5642d43..0079d8d89539ab 100644 --- a/yjit.rb +++ b/yjit.rb @@ -253,6 +253,11 @@ def _print_stats # :nodoc: # Number of failed compiler invocations compilation_failure = stats[:compilation_failure] + if stats[:x86_call_rel32] != 0 || stats[:x86_call_reg] != 0 + $stderr.puts "x86_call_rel32: " + ("%10d" % stats[:x86_call_rel32]) + $stderr.puts "x86_call_reg: " + ("%10d" % stats[:x86_call_reg]) + end + $stderr.puts "bindings_allocations: " + ("%10d" % stats[:binding_allocations]) $stderr.puts "bindings_set: " + ("%10d" % stats[:binding_set]) $stderr.puts "compilation_failure: " + ("%10d" % compilation_failure) if compilation_failure != 0 diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 23b32afbd0bc00..98d91618667f55 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -81,6 +81,9 @@ fn main() { // From include/ruby/internal/core/rbasic.h .allowlist_type("RBasic") + .allowlist_type("rstring_offsets") + .allowlist_type("ruby_rstring_flags") + // From internal.h // This function prints info about a value and is useful for debugging .allowlist_function("rb_obj_info_dump") diff --git a/yjit/src/asm/x86_64/mod.rs b/yjit/src/asm/x86_64/mod.rs index 1a0cb9cae79349..67bb5d1ffb43e0 100644 --- a/yjit/src/asm/x86_64/mod.rs +++ b/yjit/src/asm/x86_64/mod.rs @@ -690,6 +690,8 @@ pub fn call_rel32(cb: &mut CodeBlock, rel32: i32) { /// call - Call a pointer, encode with a 32-bit offset if possible pub fn call_ptr(cb: &mut CodeBlock, scratch_opnd: X86Opnd, dst_ptr: *const u8) { if let X86Opnd::Reg(_scratch_reg) = scratch_opnd { + use crate::stats::{incr_counter}; + // Pointer to the end of this call instruction let end_ptr = cb.get_ptr(cb.write_pos + 5); @@ -698,11 +700,13 @@ pub fn call_ptr(cb: &mut CodeBlock, scratch_opnd: X86Opnd, dst_ptr: *const u8) { // If the offset fits in 32-bit if rel64 >= i32::MIN.into() && rel64 <= i32::MAX.into() { + incr_counter!(x86_call_rel32); call_rel32(cb, rel64.try_into().unwrap()); return; } // Move the pointer into the scratch register and call + incr_counter!(x86_call_reg); mov(cb, scratch_opnd, const_ptr_opnd(dst_ptr)); call(cb, scratch_opnd); } else { diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 00b39006b60536..d16d3fd9adead6 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -1420,7 +1420,7 @@ fn gen_expandarray( // Only handle the case where the number of values in the array is greater // than or equal to the number of values requested. asm.cmp(array_len_opnd, num.into()); - asm.jl(counted_exit!(ocb, side_exit, expandarray_rhs_too_small).into()); + asm.jl(counted_exit!(ocb, side_exit, expandarray_rhs_too_small).as_side_exit()); // Load the address of the embedded array into REG1. // (struct RArray *)(obj)->as.ary @@ -2854,7 +2854,7 @@ fn gen_opt_aref( // Bail if idx is not a FIXNUM let idx_reg = asm.load(idx_opnd); asm.test(idx_reg, (RUBY_FIXNUM_FLAG as u64).into()); - asm.jz(counted_exit!(ocb, side_exit, oaref_arg_not_fixnum).into()); + asm.jz(counted_exit!(ocb, side_exit, oaref_arg_not_fixnum).as_side_exit()); // Call VALUE rb_ary_entry_internal(VALUE ary, long offset). // It never raises or allocates, so we don't need to write to cfp->pc. @@ -3908,7 +3908,7 @@ fn jit_protected_callee_ancestry_guard( ], ); asm.test(val, val); - asm.jz(counted_exit!(ocb, side_exit, send_se_protected_check_failed).into()) + asm.jz(counted_exit!(ocb, side_exit, send_se_protected_check_failed).as_side_exit()) } // Codegen for rb_obj_not(). @@ -4123,6 +4123,39 @@ fn jit_rb_str_to_s( false } +// Codegen for rb_str_empty() +fn jit_rb_str_empty( + _jit: &mut JITState, + ctx: &mut Context, + asm: &mut Assembler, + _ocb: &mut OutlinedCb, + _ci: *const rb_callinfo, + _cme: *const rb_callable_method_entry_t, + _block: Option, + _argc: i32, + _known_recv_class: *const VALUE, +) -> bool { + const _: () = assert!( + RUBY_OFFSET_RSTRING_AS_HEAP_LEN == RUBY_OFFSET_RSTRING_EMBED_LEN, + "same offset to len embedded or not so we can use one code path to read the length", + ); + + let recv_opnd = ctx.stack_pop(1); + let out_opnd = ctx.stack_push(Type::UnknownImm); + + let str_len_opnd = Opnd::mem( + (8 * size_of::()) as u8, + asm.load(recv_opnd), + RUBY_OFFSET_RSTRING_AS_HEAP_LEN as i32, + ); + + asm.cmp(str_len_opnd, Opnd::UImm(0)); + let string_empty = asm.csel_e(Qtrue.into(), Qfalse.into()); + asm.mov(out_opnd, string_empty); + + return true; +} + // Codegen for rb_str_concat() -- *not* String#concat // Frequently strings are concatenated using "out_str << next_str". // This is common in Erb and similar templating languages. @@ -4816,7 +4849,7 @@ fn push_splat_args(required_args: i32, ctx: &mut Context, asm: &mut Assembler, o // Only handle the case where the number of values in the array is equal to the number requested asm.cmp(array_len_opnd, required_args.into()); - asm.jne(counted_exit!(ocb, side_exit, send_splatarray_length_not_equal).into()); + asm.jne(counted_exit!(ocb, side_exit, send_splatarray_length_not_equal).as_side_exit()); let array_opnd = ctx.stack_pop(1); @@ -6107,7 +6140,7 @@ fn gen_invokeblock( let side_exit = get_side_exit(jit, ocb, ctx); let tag_opnd = asm.and(block_handler_opnd, 0x3.into()); // block_handler is a tagged pointer asm.cmp(tag_opnd, 0x1.into()); // VM_BH_ISEQ_BLOCK_P - asm.jne(counted_exit!(ocb, side_exit, invokeblock_iseq_tag_changed).into()); + asm.jne(counted_exit!(ocb, side_exit, invokeblock_iseq_tag_changed).as_side_exit()); // Not supporting vm_callee_setup_block_arg_arg0_splat for now let comptime_captured = unsafe { ((comptime_handler.0 & !0x3) as *const rb_captured_block).as_ref().unwrap() }; @@ -6265,7 +6298,7 @@ fn gen_invokesuper( SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_ME_CREF, ); asm.cmp(ep_me_opnd, me_as_value.into()); - asm.jne(counted_exit!(ocb, side_exit, invokesuper_me_changed).into()); + asm.jne(counted_exit!(ocb, side_exit, invokesuper_me_changed).as_side_exit()); if block.is_none() { // Guard no block passed @@ -6283,7 +6316,7 @@ fn gen_invokesuper( SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL, ); asm.cmp(ep_specval_opnd, VM_BLOCK_HANDLER_NONE.into()); - asm.jne(counted_exit!(ocb, side_exit, invokesuper_block).into()); + asm.jne(counted_exit!(ocb, side_exit, invokesuper_block).as_side_exit()); } // We need to assume that both our current method entry and the super @@ -6721,7 +6754,7 @@ fn gen_opt_getconstant_path( // Check the result. SysV only specifies one byte for _Bool return values, // so it's important we only check one bit to ignore the higher bits in the register. asm.test(ret_val, 1.into()); - asm.jz(counted_exit!(ocb, side_exit, opt_getinlinecache_miss).into()); + asm.jz(counted_exit!(ocb, side_exit, opt_getinlinecache_miss).as_side_exit()); let inline_cache = asm.load(Opnd::const_ptr(ic as *const u8)); @@ -6800,7 +6833,7 @@ fn gen_getblockparamproxy( SIZEOF_VALUE_I32 * (VM_ENV_DATA_INDEX_FLAGS as i32), ); asm.test(flag_check, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM.into()); - asm.jnz(counted_exit!(ocb, side_exit, gbpp_block_param_modified).into()); + asm.jnz(counted_exit!(ocb, side_exit, gbpp_block_param_modified).as_side_exit()); // Load the block handler for the current frame // note, VM_ASSERT(VM_ENV_LOCAL_P(ep)) @@ -7330,6 +7363,7 @@ impl CodegenGlobals { self.yjit_reg_method(rb_cInteger, "===", jit_rb_int_equal); // rb_str_to_s() methods in string.c + self.yjit_reg_method(rb_cString, "empty?", jit_rb_str_empty); self.yjit_reg_method(rb_cString, "to_s", jit_rb_str_to_s); self.yjit_reg_method(rb_cString, "to_str", jit_rb_str_to_s); self.yjit_reg_method(rb_cString, "bytesize", jit_rb_str_bytesize); diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index c28518ea381d7b..aef65277b6467f 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -225,6 +225,9 @@ pub const RUBY_FL_USER19: ruby_fl_type = -2147483648; pub const RUBY_ELTS_SHARED: ruby_fl_type = 16384; pub const RUBY_FL_SINGLETON: ruby_fl_type = 4096; pub type ruby_fl_type = i32; +pub const RSTRING_NOEMBED: ruby_rstring_flags = 8192; +pub const RSTRING_FSTR: ruby_rstring_flags = 536870912; +pub type ruby_rstring_flags = u32; pub type st_data_t = ::std::os::raw::c_ulong; pub type st_index_t = st_data_t; pub const ST_CONTINUE: st_retval = 0; @@ -1052,6 +1055,9 @@ pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), >; +pub const RUBY_OFFSET_RSTRING_AS_HEAP_LEN: rstring_offsets = 16; +pub const RUBY_OFFSET_RSTRING_EMBED_LEN: rstring_offsets = 16; +pub type rstring_offsets = u32; pub type rb_seq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; extern "C" { pub fn rb_singleton_class(obj: VALUE) -> VALUE; diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 8fc92453646be4..2145cdf923b22f 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -299,6 +299,9 @@ make_counters! { exec_mem_non_bump_alloc, num_gc_obj_refs, + + x86_call_rel32, + x86_call_reg, } //===========================================================================