From 902e459b733a92c3ccdd8762427f71dded997e7c Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Tue, 25 Oct 2022 13:20:25 +0900 Subject: [PATCH 001/148] Prevent buffer overrun in regparse.c A regexp that ends with an escape following an incomplete UTF-8 char might cause buffer overrun. Found by OSS-Fuzz. ``` $ valgrind ./miniruby -e 'Regexp.new("\\u2d73\\0\\0\\0\\0 \\\xE6".b)' ==296213== Memcheck, a memory error detector ==296213== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==296213== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info ==296213== Command: ./miniruby -e Regexp.new("\\\\u2d73\\\\0\\\\0\\\\0\\\\0\ \ \ \ \ \ \ \ \ \ \\\\\\xE6".b) ==296213== ==296213== Warning: client switching stacks? SP change: 0x1ffe8020e0 --> 0x1ffeffff10 ==296213== to suppress, use: --max-stackframe=8379952 or greater ==296213== Invalid read of size 1 ==296213== at 0x484EA10: memmove (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==296213== by 0x339568: memcpy (string_fortified.h:29) ==296213== by 0x339568: onig_strcpy (regparse.c:271) ==296213== by 0x339568: onig_node_str_cat (regparse.c:1413) ==296213== by 0x33CBA0: parse_exp (regparse.c:6198) ==296213== by 0x33EDE4: parse_branch (regparse.c:6511) ==296213== by 0x33EEA2: parse_subexp (regparse.c:6544) ==296213== by 0x34019C: parse_regexp (regparse.c:6593) ==296213== by 0x34019C: onig_parse_make_tree (regparse.c:6638) ==296213== by 0x32782D: onig_compile_ruby (regcomp.c:5779) ==296213== by 0x313EFA: onig_new_with_source (re.c:876) ==296213== by 0x313EFA: make_regexp (re.c:900) ==296213== by 0x313EFA: rb_reg_initialize (re.c:3136) ==296213== by 0x318555: rb_reg_initialize_str (re.c:3170) ==296213== by 0x318555: rb_reg_init_str (re.c:3205) ==296213== by 0x31A669: rb_reg_initialize_m (re.c:3856) ==296213== by 0x3E5165: vm_call0_cfunc_with_frame (vm_eval.c:150) ==296213== by 0x3E5165: vm_call0_cfunc (vm_eval.c:164) ==296213== by 0x3E5165: vm_call0_body (vm_eval.c:210) ==296213== by 0x3E89BD: vm_call0_cc (vm_eval.c:87) ==296213== by 0x3E89BD: rb_call0 (vm_eval.c:551) ==296213== Address 0x9d45b10 is 0 bytes after a block of size 32 alloc'd ==296213== at 0x4844899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==296213== by 0x20FA7B: objspace_xmalloc0 (gc.c:12146) ==296213== by 0x35F8C9: str_buf_cat4.part.0 (string.c:3132) ==296213== by 0x31359D: unescape_escaped_nonascii (re.c:2690) ==296213== by 0x313A9D: unescape_nonascii (re.c:2869) ==296213== by 0x313A9D: rb_reg_preprocess (re.c:2992) ==296213== by 0x313DFC: rb_reg_initialize (re.c:3109) ==296213== by 0x318555: rb_reg_initialize_str (re.c:3170) ==296213== by 0x318555: rb_reg_init_str (re.c:3205) ==296213== by 0x31A669: rb_reg_initialize_m (re.c:3856) ==296213== by 0x3E5165: vm_call0_cfunc_with_frame (vm_eval.c:150) ==296213== by 0x3E5165: vm_call0_cfunc (vm_eval.c:164) ==296213== by 0x3E5165: vm_call0_body (vm_eval.c:210) ==296213== by 0x3E89BD: vm_call0_cc (vm_eval.c:87) ==296213== by 0x3E89BD: rb_call0 (vm_eval.c:551) ==296213== by 0x3E957B: rb_call (vm_eval.c:877) ==296213== by 0x3E957B: rb_funcallv_kw (vm_eval.c:1074) ==296213== by 0x2A4123: rb_class_new_instance_pass_kw (object.c:1991) ==296213== ==296213== ==296213== HEAP SUMMARY: ==296213== in use at exit: 35,476,538 bytes in 9,489 blocks ==296213== total heap usage: 14,893 allocs, 5,404 frees, 37,517,821 bytes allocated ==296213== ==296213== LEAK SUMMARY: ==296213== definitely lost: 316,081 bytes in 2,989 blocks ==296213== indirectly lost: 136,808 bytes in 2,361 blocks ==296213== possibly lost: 1,048,624 bytes in 3 blocks ==296213== still reachable: 33,975,025 bytes in 4,136 blocks ==296213== suppressed: 0 bytes in 0 blocks ==296213== Rerun with --leak-check=full to see details of leaked memory ==296213== ==296213== For lists of detected and suppressed errors, rerun with: -s ==296213== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) ``` --- regparse.c | 1 + 1 file changed, 1 insertion(+) diff --git a/regparse.c b/regparse.c index 4ebd5f1c46053c..513e0a8c7a7a69 100644 --- a/regparse.c +++ b/regparse.c @@ -3799,6 +3799,7 @@ fetch_token(OnigToken* tok, UChar** src, UChar* end, ScanEnv* env) } else { /* string */ p = tok->backp + enclen(enc, tok->backp, end); + if (p > end) return ONIGERR_END_PATTERN_AT_ESCAPE; } } break; From d76284dfb764ac8259b823b5bfc3e886e41a100e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 25 Oct 2022 13:50:31 +0900 Subject: [PATCH 002/148] sync_default_gems.rb: Ignore unmergeable files [ci skip] --- tool/sync_default_gems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 5aa7eb06eb440c..8d33004ffb00a3 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -520,7 +520,7 @@ def sync_default_gems_with_commits(gem, ranges, edit: nil) skipped = true elsif /^CONFLICT/ =~ result result = pipe_readlines(%W"git status --porcelain -z") - result.map! {|line| line[/\A.U (.*)/, 1]} + result.map! {|line| line[/\A(?:.U|AA) (.*)/, 1]} result.compact! ignore, conflict = result.partition {|name| IGNORE_FILE_PATTERN =~ name} unless ignore.empty? From 71a5b1d4573ea16e4691e0847cea7d309116e224 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 25 Oct 2022 13:39:56 +0900 Subject: [PATCH 003/148] [ruby/tmpdir] [DOC] Improve documentation https://github.com/ruby/tmpdir/commit/b9c880f2b6 --- lib/tmpdir.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb index 3b67164039216e..962393f98ae1dd 100644 --- a/lib/tmpdir.rb +++ b/lib/tmpdir.rb @@ -108,6 +108,7 @@ def self.mktmpdir(prefix_suffix=nil, *rest, **options) end end + # Temporary name generator module Tmpname # :nodoc: module_function @@ -115,16 +116,23 @@ def tmpdir Dir.tmpdir end + # Unusable characters as path name UNUSABLE_CHARS = "^,-.0-9A-Z_a-z~" - class << (RANDOM = Random.new) + # Dedicated random number generator + RANDOM = Random.new + class << Random # :nodoc: + # Maximum random number MAX = 36**6 # < 0x100000000 + + # Returns new random string upto 6 bytes def next rand(MAX).to_s(36) end end private_constant :RANDOM + # Generates and yields random names to create a temporary name def create(basename, tmpdir=nil, max_try: nil, **opts) origdir = tmpdir tmpdir ||= tmpdir() From cade3aba610b5b018072c646b28a2c10bc2669bb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 25 Oct 2022 14:02:44 +0900 Subject: [PATCH 004/148] [ruby/tmpdir] Fix typo --- lib/tmpdir.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb index 962393f98ae1dd..6e47ac4fc7fe09 100644 --- a/lib/tmpdir.rb +++ b/lib/tmpdir.rb @@ -121,7 +121,7 @@ def tmpdir # Dedicated random number generator RANDOM = Random.new - class << Random # :nodoc: + class << RANDOM # :nodoc: # Maximum random number MAX = 36**6 # < 0x100000000 From ba15fb709b0d0dd6310f5cce9ef386bb3bc4707d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Oct 2022 23:35:02 +0000 Subject: [PATCH 005/148] [rubygems/rubygems] Bump rb-sys in /test/rubygems/test_gem_ext_cargo_builder/custom_name Bumps [rb-sys](https://github.com/oxidize-rb/rb-sys) from 0.9.31 to 0.9.34. - [Release notes](https://github.com/oxidize-rb/rb-sys/releases) - [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.31...v0.9.34) --- updated-dependencies: - dependency-name: rb-sys dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] https://github.com/rubygems/rubygems/commit/6af714b02c --- .../custom_name/Cargo.lock | 12 ++++++------ .../custom_name/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) 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 cf11ab247b86d0..7bb26c9f7a0e4b 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 @@ -160,21 +160,21 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.31" +version = "0.9.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc41b26ea88da6100f538d31467941e41ab0c002999d687315e67d3b371b796" +checksum = "24ff8b3a4d418f3604ac3781aee54a094f3f9d732fb9a2458f73a3937a9ea918" dependencies = [ - "bindgen", - "linkify", "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.31" +version = "0.9.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79be4233eabd2bf9e19eb8116391aeaf4b89b87a7ab38e0ded44de9158006e46" +checksum = "d2921dcd727615d4af5a6d39cc3c1c8c9dc8e37bf2b42d44b3e101a6da2bce17" dependencies = [ + "bindgen", + "linkify", "regex", "shell-words", ] 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 8dfbaf5799f097..ce05eb6f84080d 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 = { version = "0.9.31", features = ["gem"] } +rb-sys = { version = "0.9.34", features = ["gem"] } From 287c5da4aae6a3c8ae16cde590a9eb6d4dd655bf Mon Sep 17 00:00:00 2001 From: Peter Vandenberk Date: Tue, 20 Sep 2022 08:53:39 +0100 Subject: [PATCH 006/148] [ruby/tmpdir] Make `Dir.tmpdir` more idiomatic and functional Use `Enumerable#find` to iterate over the candidates, not `Enumerable.each`. (this makes the code more functional, and - IMO - slightly more idiomatic, as it avoids setting the "global" (by which I mean: non-local) `tmp` variable from inside the block) https://github.com/ruby/tmpdir/commit/d1f20ad694 --- lib/tmpdir.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb index 6e47ac4fc7fe09..3e76a8f5c31ce7 100644 --- a/lib/tmpdir.rb +++ b/lib/tmpdir.rb @@ -19,8 +19,7 @@ class Dir # Returns the operating system's temporary file path. def self.tmpdir - tmp = nil - ['TMPDIR', 'TMP', 'TEMP', ['system temporary path', @@systmpdir], ['/tmp']*2, ['.']*2].each do |name, dir = ENV[name]| + tmp = ['TMPDIR', 'TMP', 'TEMP', ['system temporary path', @@systmpdir], ['/tmp']*2, ['.']*2].find do |name, dir = ENV[name]| next if !dir dir = File.expand_path(dir) stat = File.stat(dir) rescue next @@ -32,8 +31,7 @@ def self.tmpdir when stat.world_writable? && !stat.sticky? warn "#{name} is world-writable: #{dir}" else - tmp = dir - break + break dir end end raise ArgumentError, "could not find a temporary directory" unless tmp From d55f72bcdbe7f6157c3ad9b1ce32bcc01cf375c4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 25 Oct 2022 15:41:45 +0900 Subject: [PATCH 007/148] [ruby/tmpdir] Update supported and testing ruby versions --- lib/tmpdir.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tmpdir.gemspec b/lib/tmpdir.gemspec index 7b76403002be55..8e471bf1071ca5 100644 --- a/lib/tmpdir.gemspec +++ b/lib/tmpdir.gemspec @@ -8,7 +8,7 @@ Gem::Specification.new do |spec| spec.description = %q{Extends the Dir class to manage the OS temporary file path.} spec.homepage = "https://github.com/ruby/tmpdir" spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") + spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0") spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage From 883d9c305f82b23fbe0ed28e9d74461cc5e13b97 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 25 Oct 2022 16:07:09 +0900 Subject: [PATCH 008/148] [ruby/tmpdir] Found or raise --- lib/tmpdir.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb index 3e76a8f5c31ce7..95d4ca3fce68b5 100644 --- a/lib/tmpdir.rb +++ b/lib/tmpdir.rb @@ -19,7 +19,7 @@ class Dir # Returns the operating system's temporary file path. def self.tmpdir - tmp = ['TMPDIR', 'TMP', 'TEMP', ['system temporary path', @@systmpdir], ['/tmp']*2, ['.']*2].find do |name, dir = ENV[name]| + ['TMPDIR', 'TMP', 'TEMP', ['system temporary path', @@systmpdir], ['/tmp']*2, ['.']*2].find do |name, dir = ENV[name]| next if !dir dir = File.expand_path(dir) stat = File.stat(dir) rescue next @@ -33,9 +33,7 @@ def self.tmpdir else break dir end - end - raise ArgumentError, "could not find a temporary directory" unless tmp - tmp + end or raise ArgumentError, "could not find a temporary directory" end # Dir.mktmpdir creates a temporary directory. From 114e71d06280f9c57b9859ee4405ae89a989ddb6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 25 Oct 2022 16:39:12 +0900 Subject: [PATCH 009/148] [ruby/tmpdir] Ignore empty environment variables Fixes https://github.com/ruby/tmpdir/pull/17 https://github.com/ruby/tmpdir/commit/a79c727a5d --- lib/tmpdir.rb | 6 ++++-- test/test_tmpdir.rb | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb index 95d4ca3fce68b5..55920a4a743ebc 100644 --- a/lib/tmpdir.rb +++ b/lib/tmpdir.rb @@ -19,8 +19,10 @@ class Dir # Returns the operating system's temporary file path. def self.tmpdir - ['TMPDIR', 'TMP', 'TEMP', ['system temporary path', @@systmpdir], ['/tmp']*2, ['.']*2].find do |name, dir = ENV[name]| - next if !dir + ['TMPDIR', 'TMP', 'TEMP', ['system temporary path', @@systmpdir], ['/tmp']*2, ['.']*2].find do |name, dir| + unless dir + next if !(dir = ENV[name]) or dir.empty? + end dir = File.expand_path(dir) stat = File.stat(dir) rescue next case diff --git a/test/test_tmpdir.rb b/test/test_tmpdir.rb index 0be2176bd9576b..1adb6a70b7e2bf 100644 --- a/test/test_tmpdir.rb +++ b/test/test_tmpdir.rb @@ -47,6 +47,18 @@ def test_world_writable end end + def test_tmpdir_not_empty_parent + Dir.mktmpdir do |tmpdir| + envs = %w[TMPDIR TMP TEMP] + oldenv = envs.each_with_object({}) {|v, h| h[v] = ENV.delete(v)} + ENV[envs[0]] = "" + ENV[envs[1]] = tmpdir + assert_equal(tmpdir, Dir.tmpdir) + ensure + ENV.update(oldenv) + end + end + def test_no_homedir bug7547 = '[ruby-core:50793]' home, ENV["HOME"] = ENV["HOME"], nil From 1d2d25dcadda0764f303183ac091d0c87b432566 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Tue, 25 Oct 2022 15:45:40 +0900 Subject: [PATCH 010/148] Prevent potential buffer overrun in onigmo A code pattern `p + enclen(enc, p, pend)` may lead to a buffer overrun if incomplete bytes of a UTF-8 character is placed at the end of a string. Because this pattern is used in several places in onigmo, this change fixes the issue in the side of `enclen`: the function should not return a number that is larger than `pend - p`. Co-Authored-By: Nobuyoshi Nakada --- include/ruby/onigmo.h | 4 ++-- regenc.c | 15 +++++++++++++++ regparse.c | 1 - 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/include/ruby/onigmo.h b/include/ruby/onigmo.h index a7ef59c7c881e6..d71dfb80fb03a0 100644 --- a/include/ruby/onigmo.h +++ b/include/ruby/onigmo.h @@ -356,9 +356,9 @@ int onigenc_ascii_only_case_map(OnigCaseFoldType* flagP, const OnigUChar** pp, c #define ONIGENC_PRECISE_MBC_ENC_LEN(enc,p,e) (enc)->precise_mbc_enc_len(p,e,enc) ONIG_EXTERN -int onigenc_mbclen_approximate(const OnigUChar* p,const OnigUChar* e, const struct OnigEncodingTypeST* enc); +int onigenc_mbclen(const OnigUChar* p,const OnigUChar* e, const struct OnigEncodingTypeST* enc); -#define ONIGENC_MBC_ENC_LEN(enc,p,e) onigenc_mbclen_approximate(p,e,enc) +#define ONIGENC_MBC_ENC_LEN(enc,p,e) onigenc_mbclen(p,e,enc) #define ONIGENC_MBC_MAXLEN(enc) ((enc)->max_enc_len) #define ONIGENC_MBC_MAXLEN_DIST(enc) ONIGENC_MBC_MAXLEN(enc) #define ONIGENC_MBC_MINLEN(enc) ((enc)->min_enc_len) diff --git a/regenc.c b/regenc.c index 16d62fdf4098c4..fc131d2533cb6a 100644 --- a/regenc.c +++ b/regenc.c @@ -51,6 +51,21 @@ onigenc_set_default_encoding(OnigEncoding enc) return 0; } +extern int +onigenc_mbclen(const OnigUChar* p,const OnigUChar* e, OnigEncoding enc) +{ + int ret = ONIGENC_PRECISE_MBC_ENC_LEN(enc, p, e); + if (ONIGENC_MBCLEN_CHARFOUND_P(ret)) { + ret = ONIGENC_MBCLEN_CHARFOUND_LEN(ret); + if (ret > (int)(e - p)) ret = (int)(e - p); // just for case + return ret; + } + else if (ONIGENC_MBCLEN_NEEDMORE_P(ret)) { + return (int)(e - p); + } + return p < e ? 1 : 0; +} + extern int onigenc_mbclen_approximate(const OnigUChar* p,const OnigUChar* e, OnigEncoding enc) { diff --git a/regparse.c b/regparse.c index 513e0a8c7a7a69..4ebd5f1c46053c 100644 --- a/regparse.c +++ b/regparse.c @@ -3799,7 +3799,6 @@ fetch_token(OnigToken* tok, UChar** src, UChar* end, ScanEnv* env) } else { /* string */ p = tok->backp + enclen(enc, tok->backp, end); - if (p > end) return ONIGERR_END_PATTERN_AT_ESCAPE; } } break; From b7644a231100b1e1b70af528f9629d2e39572087 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 25 Oct 2022 09:07:10 -0700 Subject: [PATCH 011/148] YJIT: GC and recompile all code pages (#6406) when it fails to allocate a new page. Co-authored-by: Alan Wu --- cont.c | 2 +- test/ruby/test_yjit.rb | 121 +++++++++++++++++++++- yjit.c | 23 ++++- yjit.rb | 6 +- yjit/bindgen/src/main.rs | 4 + yjit/src/asm/mod.rs | 179 +++++++++++++++++++++++++++++++-- yjit/src/codegen.rs | 29 ++++++ yjit/src/core.rs | 56 ++++++++++- yjit/src/cruby_bindings.inc.rs | 6 ++ yjit/src/options.rs | 2 +- yjit/src/stats.rs | 29 ++++-- yjit/src/virtualmem.rs | 29 ++++++ 12 files changed, 454 insertions(+), 32 deletions(-) diff --git a/cont.c b/cont.c index b3c84d82ac5cac..577a30a57af206 100644 --- a/cont.c +++ b/cont.c @@ -69,7 +69,7 @@ static VALUE rb_cFiberPool; #define FIBER_POOL_ALLOCATION_FREE #endif -#define jit_cont_enabled mjit_enabled // To be used by YJIT later +#define jit_cont_enabled (mjit_enabled || rb_yjit_enabled_p()) enum context_type { CONTINUATION_CONTEXT = 0, diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index 6cafb21698e688..09b5989a066092 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -825,12 +825,126 @@ def foo RUBY end + def test_code_gc + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok) + return :not_paged unless add_pages(100) # prepare freeable pages + code_gc # first code GC + return :not_compiled1 unless compiles { nil } # should be JITable again + + code_gc # second code GC + return :not_compiled2 unless compiles { nil } # should be JITable again + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count != 2 + + :ok + RUBY + end + + def test_on_stack_code_gc_call + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok) + fiber = Fiber.new { + # Loop to call the same basic block again after Fiber.yield + while true + Fiber.yield(nil.to_i) + end + } + + return :not_paged1 unless add_pages(400) # go to a page without initial ocb code + return :broken_resume1 if fiber.resume != 0 # JIT the fiber + code_gc # first code GC, which should not free the fiber page + return :broken_resume2 if fiber.resume != 0 # The code should be still callable + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count != 1 + + :ok + RUBY + end + + def test_on_stack_code_gc_twice + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok) + fiber = Fiber.new { + # Loop to call the same basic block again after Fiber.yield + while Fiber.yield(nil.to_i); end + } + + return :not_paged1 unless add_pages(400) # go to a page without initial ocb code + return :broken_resume1 if fiber.resume(true) != 0 # JIT the fiber + code_gc # first code GC, which should not free the fiber page + + return :not_paged2 unless add_pages(300) # add some stuff to be freed + # Not calling fiber.resume here to test the case that the YJIT payload loses some + # information at the previous code GC. The payload should still be there, and + # thus we could know the fiber ISEQ is still on stack on this second code GC. + code_gc # second code GC, which should still not free the fiber page + + return :not_paged3 unless add_pages(200) # attempt to overwrite the fiber page (it shouldn't) + return :broken_resume2 if fiber.resume(true) != 0 # The fiber code should be still fine + + return :broken_resume3 if fiber.resume(false) != nil # terminate the fiber + code_gc # third code GC, freeing a page that used to be on stack + + return :not_paged4 unless add_pages(100) # check everything still works + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count != 3 + + :ok + RUBY + end + + def test_code_gc_with_many_iseqs + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok, mem_size: 1) + fiber = Fiber.new { + # Loop to call the same basic block again after Fiber.yield + while true + Fiber.yield(nil.to_i) + end + } + + return :not_paged1 unless add_pages(500) # use some pages + return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well + + add_pages(2000) # use a whole lot of pages to run out of 1MiB + return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count == 0 + + :ok + RUBY + end + + private + + def code_gc_helpers + <<~'RUBY' + def compiles(&block) + failures = RubyVM::YJIT.runtime_stats[:compilation_failure] + block.call + failures == RubyVM::YJIT.runtime_stats[:compilation_failure] + end + + def add_pages(num_jits) + pages = RubyVM::YJIT.runtime_stats[:compiled_page_count] + num_jits.times { return false unless eval('compiles { nil.to_i }') } + pages.nil? || pages < RubyVM::YJIT.runtime_stats[:compiled_page_count] + end + + def code_gc + RubyVM::YJIT.simulate_oom! # bump write_pos + eval('proc { nil }.call') # trigger code GC + end + RUBY + end + def assert_no_exits(script) assert_compiles(script) end ANY = Object.new - def assert_compiles(test_script, insns: [], call_threshold: 1, stdout: nil, exits: {}, result: ANY, frozen_string_literal: nil) + def assert_compiles(test_script, insns: [], call_threshold: 1, stdout: nil, exits: {}, result: ANY, frozen_string_literal: nil, mem_size: nil) reset_stats = <<~RUBY RubyVM::YJIT.runtime_stats RubyVM::YJIT.reset_stats! @@ -864,7 +978,7 @@ def collect_insns(iseq) #{write_results} RUBY - status, out, err, stats = eval_with_jit(script, call_threshold: call_threshold) + status, out, err, stats = eval_with_jit(script, call_threshold:, mem_size:) assert status.success?, "exited with status #{status.to_i}, stderr:\n#{err}" @@ -918,12 +1032,13 @@ def script_shell_encode(s) s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join end - def eval_with_jit(script, call_threshold: 1, timeout: 1000) + def eval_with_jit(script, call_threshold: 1, timeout: 1000, mem_size: nil) args = [ "--disable-gems", "--yjit-call-threshold=#{call_threshold}", "--yjit-stats" ] + args << "--yjit-exec-mem-size=#{mem_size}" if mem_size args << "-e" << script_shell_encode(script) stats_r, stats_w = IO.pipe out, err, status = EnvUtil.invoke_ruby(args, diff --git a/yjit.c b/yjit.c index f6e64aad65552f..7e6fc9e3fb5ff9 100644 --- a/yjit.c +++ b/yjit.c @@ -27,6 +27,7 @@ #include "probes_helper.h" #include "iseq.h" #include "ruby/debug.h" +#include "internal/cont.h" // For mmapp(), sysconf() #ifndef _WIN32 @@ -65,10 +66,7 @@ STATIC_ASSERT(pointer_tagging_scheme, USE_FLONUM); bool rb_yjit_mark_writable(void *mem_block, uint32_t mem_size) { - if (mprotect(mem_block, mem_size, PROT_READ | PROT_WRITE)) { - return false; - } - return true; + return mprotect(mem_block, mem_size, PROT_READ | PROT_WRITE) == 0; } void @@ -85,6 +83,20 @@ rb_yjit_mark_executable(void *mem_block, uint32_t mem_size) } } +// Free the specified memory block. +bool +rb_yjit_mark_unused(void *mem_block, uint32_t mem_size) +{ + // On Linux, you need to use madvise MADV_DONTNEED to free memory. + // We might not need to call this on macOS, but it's not really documented. + // We generally prefer to do the same thing on both to ease testing too. + madvise(mem_block, mem_size, MADV_DONTNEED); + + // On macOS, mprotect PROT_NONE seems to reduce RSS. + // We also call this on Linux to avoid executing unused pages. + return mprotect(mem_block, mem_size, PROT_NONE) == 0; +} + // `start` is inclusive and `end` is exclusive. void rb_yjit_icache_invalidate(void *start, void *end) @@ -387,6 +399,9 @@ rb_iseq_reset_jit_func(const rb_iseq_t *iseq) { RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq)); iseq->body->jit_func = NULL; + // Enable re-compiling this ISEQ. Event when it's invalidated for TracePoint, + // we'd like to re-compile ISEQs that haven't been converted to trace_* insns. + iseq->body->total_calls = 0; } // Get the PC for a given index in an iseq diff --git a/yjit.rb b/yjit.rb index b80861dbfbb6fb..2a0b3dc6c63bdf 100644 --- a/yjit.rb +++ b/yjit.rb @@ -212,13 +212,17 @@ def _print_stats $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 - $stderr.puts "compiled_iseq_count: " + ("%10d" % stats[:compiled_iseq_count]) $stderr.puts "compiled_block_count: " + ("%10d" % stats[:compiled_block_count]) + $stderr.puts "compiled_iseq_count: " + ("%10d" % stats[:compiled_iseq_count]) + $stderr.puts "compiled_page_count: " + ("%10d" % stats[:compiled_page_count]) $stderr.puts "freed_iseq_count: " + ("%10d" % stats[:freed_iseq_count]) + $stderr.puts "freed_page_count: " + ("%10d" % stats[:freed_page_count]) $stderr.puts "invalidation_count: " + ("%10d" % stats[:invalidation_count]) $stderr.puts "constant_state_bumps: " + ("%10d" % stats[:constant_state_bumps]) $stderr.puts "inline_code_size: " + ("%10d" % stats[:inline_code_size]) $stderr.puts "outlined_code_size: " + ("%10d" % stats[:outlined_code_size]) + $stderr.puts "freed_code_size: " + ("%10d" % stats[:freed_code_size]) + $stderr.puts "code_gc_count: " + ("%10d" % stats[:code_gc_count]) $stderr.puts "num_gc_obj_refs: " + ("%10d" % stats[:num_gc_obj_refs]) $stderr.puts "total_exit_count: " + ("%10d" % total_exits) diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index f7ebb885777118..60a9d1b87d0dfa 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -263,6 +263,7 @@ fn main() { .allowlist_function("rb_yjit_reserve_addr_space") .allowlist_function("rb_yjit_mark_writable") .allowlist_function("rb_yjit_mark_executable") + .allowlist_function("rb_yjit_mark_unused") .allowlist_function("rb_yjit_get_page_size") .allowlist_function("rb_leaf_invokebuiltin_iseq_p") .allowlist_function("rb_leaf_builtin_function") @@ -297,6 +298,9 @@ fn main() { // From internal/compile.h .allowlist_function("rb_vm_insn_decode") + // from internal/cont.h + .allowlist_function("rb_jit_cont_each_iseq") + // From iseq.h .allowlist_function("rb_vm_insn_addr2opcode") .allowlist_function("rb_iseqw_to_iseq") diff --git a/yjit/src/asm/mod.rs b/yjit/src/asm/mod.rs index ab7c4d6abab13e..b68520a767fa66 100644 --- a/yjit/src/asm/mod.rs +++ b/yjit/src/asm/mod.rs @@ -6,6 +6,9 @@ use std::rc::Rc; use crate::backend::x86_64::JMP_PTR_BYTES; #[cfg(target_arch = "aarch64")] use crate::backend::arm64::JMP_PTR_BYTES; +use crate::core::for_each_on_stack_iseq_payload; +use crate::invariants::rb_yjit_tracing_invalidate_all; +use crate::stats::incr_counter; use crate::virtualmem::WriteError; #[cfg(feature = "disasm")] @@ -115,17 +118,23 @@ impl CodeBlock { pub fn next_page(&mut self, base_ptr: CodePtr, jmp_ptr: F) -> bool { let old_write_ptr = self.get_write_ptr(); self.set_write_ptr(base_ptr); - self.without_page_end_reserve(|cb| assert!(cb.has_capacity(JMP_PTR_BYTES))); + + // Use the freed_pages list if code GC has been used. Otherwise use the next page. + let next_page_idx = if let Some(freed_pages) = CodegenGlobals::get_freed_pages() { + let current_page = self.write_pos / self.page_size; + freed_pages.iter().find(|&&page| current_page < page).map(|&page| page) + } else { + Some(self.write_pos / self.page_size + 1) + }; // Move self to the next page - let next_page_idx = self.write_pos / self.page_size + 1; - if !self.set_page(next_page_idx, &jmp_ptr) { + if next_page_idx.is_none() || !self.set_page(next_page_idx.unwrap(), &jmp_ptr) { self.set_write_ptr(old_write_ptr); // rollback if there are no more pages return false; } // Move the other CodeBlock to the same page if it'S on the furthest page - self.other_cb().unwrap().set_page(next_page_idx, &jmp_ptr); + self.other_cb().unwrap().set_page(next_page_idx.unwrap(), &jmp_ptr); return !self.dropped_bytes; } @@ -151,7 +160,7 @@ impl CodeBlock { // We could remember the last write_pos in page2 and let set_page use that position, // but you need to waste some space for keeping write_pos for every single page. // It doesn't seem necessary for performance either. So we're currently not doing it. - let dst_pos = self.page_size * page_idx + self.page_start(); + let dst_pos = self.get_page_pos(page_idx); if self.page_size * page_idx < self.mem_size && self.write_pos < dst_pos { // Reset dropped_bytes self.dropped_bytes = false; @@ -161,6 +170,7 @@ impl CodeBlock { self.write_pos = dst_pos; let dst_ptr = self.get_write_ptr(); self.write_pos = src_pos; + self.without_page_end_reserve(|cb| assert!(cb.has_capacity(JMP_PTR_BYTES))); // Generate jmp_ptr from src_pos to dst_pos self.without_page_end_reserve(|cb| { @@ -175,6 +185,53 @@ impl CodeBlock { !self.dropped_bytes } + /// Free the memory pages of given code page indexes + fn free_pages(&mut self, page_idxs: &Vec) { + let mut page_idxs = page_idxs.clone(); + page_idxs.reverse(); // to loop with pop() + + // Group adjacent page indexes and free them in batches to reduce the # of syscalls. + while let Some(page_idx) = page_idxs.pop() { + // Group first adjacent page indexes + let mut batch_idxs = vec![page_idx]; + while page_idxs.last() == Some(&(batch_idxs.last().unwrap() + 1)) { + batch_idxs.push(page_idxs.pop().unwrap()); + } + + // Free the grouped pages at once + let start_ptr = self.mem_block.borrow().start_ptr().add_bytes(page_idx * self.page_size); + let batch_size = self.page_size * batch_idxs.len(); + self.mem_block.borrow_mut().free_bytes(start_ptr, batch_size as u32); + } + } + + pub fn page_size(&self) -> usize { + self.page_size + } + + /// Return the number of code pages that have been allocated by the VirtualMemory. + pub fn num_pages(&self) -> usize { + let mapped_region_size = self.mem_block.borrow().mapped_region_size(); + // CodeBlock's page size != VirtualMem's page size on Linux, + // so mapped_region_size % self.page_size may not be 0 + ((mapped_region_size - 1) / self.page_size) + 1 + } + + /// Return the number of code pages that have been freed and not used yet. + pub fn num_freed_pages(&self) -> usize { + (0..self.num_pages()).filter(|&page_idx| self.has_freed_page(page_idx)).count() + } + + pub fn has_freed_page(&self, page_idx: usize) -> bool { + CodegenGlobals::get_freed_pages().as_ref().map_or(false, |pages| pages.contains(&page_idx)) && // code GCed + self.write_pos < page_idx * self.page_size // and not written yet + } + + /// Convert a page index to the write_pos for the page start. + fn get_page_pos(&self, page_idx: usize) -> usize { + self.page_size * page_idx + self.page_start() + } + /// write_pos of the current page start pub fn page_start_pos(&self) -> usize { self.get_write_pos() / self.page_size * self.page_size + self.page_start() @@ -216,21 +273,48 @@ impl CodeBlock { /// Return the address ranges of a given address range that this CodeBlock can write. #[cfg(any(feature = "disasm", target_arch = "aarch64"))] pub fn writable_addrs(&self, start_ptr: CodePtr, end_ptr: CodePtr) -> Vec<(usize, usize)> { - let mut addrs = vec![]; - let mut start = start_ptr.into_usize(); + // CodegenGlobals is not initialized when we write initial ocb code + let freed_pages = if CodegenGlobals::has_instance() { + CodegenGlobals::get_freed_pages().as_ref() + } else { + None + }; + let region_start = self.get_ptr(0).into_usize(); let region_end = self.get_ptr(self.get_mem_size()).into_usize(); + let mut start = start_ptr.into_usize(); let end = std::cmp::min(end_ptr.into_usize(), region_end); + + let mut addrs = vec![]; while start < end { - let current_page = region_start + - (start.saturating_sub(region_start) / self.page_size * self.page_size); + let page_idx = start.saturating_sub(region_start) / self.page_size; + let current_page = region_start + (page_idx * self.page_size); let page_end = std::cmp::min(end, current_page + self.page_end()); - addrs.push((start, page_end)); + // If code GC has been used, skip pages that are used by past on-stack code + if freed_pages.map_or(true, |pages| pages.contains(&page_idx)) { + addrs.push((start, page_end)); + } start = current_page + self.page_size + self.page_start(); } addrs } + /// Return the code size that has been used by this CodeBlock. + pub fn code_size(&self) -> usize { + let mut size = 0; + let current_page_idx = self.write_pos / self.page_size; + for page_idx in 0..self.num_pages() { + if page_idx == current_page_idx { + // Count only actually used bytes for the current page. + size += (self.write_pos % self.page_size).saturating_sub(self.page_start()); + } else if !self.has_freed_page(page_idx) { + // Count an entire range for any non-freed pages that have been used. + size += self.page_end() - self.page_start() + self.page_end_reserve; + } + } + size + } + /// Check if this code block has sufficient remaining capacity pub fn has_capacity(&self, num_bytes: usize) -> bool { let page_offset = self.write_pos % self.page_size; @@ -261,6 +345,11 @@ impl CodeBlock { self.asm_comments.get(&pos) } + pub fn clear_comments(&mut self) { + #[cfg(feature = "disasm")] + self.asm_comments.clear(); + } + pub fn get_mem_size(&self) -> usize { self.mem_size } @@ -293,6 +382,24 @@ impl CodeBlock { self.mem_block.borrow().start_ptr().add_bytes(offset) } + /// Convert an address range to memory page indexes against a num_pages()-sized array. + pub fn addrs_to_pages(&self, start_addr: CodePtr, end_addr: CodePtr) -> Vec { + let mem_start = self.mem_block.borrow().start_ptr().into_usize(); + let mem_end = self.mem_block.borrow().end_ptr().into_usize(); + assert!(mem_start <= start_addr.into_usize()); + assert!(start_addr.into_usize() <= end_addr.into_usize()); + assert!(end_addr.into_usize() <= mem_end); + + // Ignore empty code ranges + if start_addr == end_addr { + return vec![]; + } + + let start_page = (start_addr.into_usize() - mem_start) / self.page_size; + let end_page = (end_addr.into_usize() - mem_start - 1) / self.page_size; + (start_page..=end_page).collect() // TODO: consider returning an iterator + } + /// Get a (possibly dangling) direct pointer to the current write position pub fn get_write_ptr(&self) -> CodePtr { self.get_ptr(self.write_pos) @@ -431,6 +538,58 @@ impl CodeBlock { self.mem_block.borrow_mut().mark_all_executable(); } + /// Code GC. Free code pages that are not on stack and reuse them. + pub fn code_gc(&mut self) { + // The previous code GC failed to free any pages. Give up. + if CodegenGlobals::get_freed_pages() == &Some(vec![]) { + return; + } + + // Check which pages are still in use + let mut pages_in_use = vec![false; self.num_pages()]; + // For each ISEQ, we currently assume that only code pages used by inline code + // are used by outlined code, so we mark only code pages used by inlined code. + for_each_on_stack_iseq_payload(|iseq_payload| { + for page in &iseq_payload.pages { + pages_in_use[*page] = true; + } + }); + // Outlined code generated by CodegenGlobals::init() should also be kept. + for page in CodegenGlobals::get_ocb_pages() { + pages_in_use[*page] = true; + } + + // Let VirtuamMem free the pages + let freed_pages: Vec = pages_in_use.iter().enumerate() + .filter(|&(_, &in_use)| !in_use).map(|(page, _)| page).collect(); + self.free_pages(&freed_pages); + + // Invalidate everything to have more compact code after code GC. + // This currently patches every ISEQ, which works, but in the future, + // we could limit that to patch only on-stack ISEQs for optimizing code GC. + rb_yjit_tracing_invalidate_all(); + // When code GC runs next time, we could have reused pages in between + // invalidated pages. To invalidate them, we skip freezing them here. + // We free or not reuse the bytes frozen by any past invalidation, so this + // can be safely reset to pass the frozen bytes check on invalidation. + CodegenGlobals::set_inline_frozen_bytes(0); + + if let Some(&first_page) = freed_pages.first() { + let mut cb = CodegenGlobals::get_inline_cb(); + cb.write_pos = cb.get_page_pos(first_page); + cb.dropped_bytes = false; + cb.clear_comments(); + + let mut ocb = CodegenGlobals::get_outlined_cb().unwrap(); + ocb.write_pos = ocb.get_page_pos(first_page); + ocb.dropped_bytes = false; + ocb.clear_comments(); + } + + CodegenGlobals::set_freed_pages(freed_pages); + incr_counter!(code_gc_count); + } + pub fn inline(&self) -> bool { !self.outlined } diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index e5e6e4ec845f0b..d6ea8996e1161f 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -643,6 +643,11 @@ pub fn gen_entry_prologue(cb: &mut CodeBlock, iseq: IseqPtr, insn_idx: u32) -> O if cb.has_dropped_bytes() { None } else { + // Mark code pages for code GC + let iseq_payload = get_or_create_iseq_payload(iseq); + for page in cb.addrs_to_pages(code_ptr, cb.get_write_ptr()) { + iseq_payload.pages.insert(page); + } Some(code_ptr) } } @@ -6504,6 +6509,12 @@ pub struct CodegenGlobals { // Methods for generating code for hardcoded (usually C) methods method_codegen_table: HashMap, + + /// Page indexes for outlined code that are not associated to any ISEQ. + ocb_pages: Vec, + + /// Freed page indexes. None if code GC has not been used. + freed_pages: Option>, } /// For implementing global code invalidation. A position in the inline @@ -6570,6 +6581,7 @@ impl CodegenGlobals { #[cfg(test)] let mut ocb = OutlinedCb::wrap(CodeBlock::new_dummy(mem_size / 2)); + let ocb_start_addr = ocb.unwrap().get_write_ptr(); let leave_exit_code = gen_leave_exit(&mut ocb); let stub_exit_code = gen_code_for_exit_from_stub(&mut ocb); @@ -6577,6 +6589,9 @@ impl CodegenGlobals { // Generate full exit code for C func let cfunc_exit_code = gen_full_cfunc_return(&mut ocb); + let ocb_end_addr = ocb.unwrap().get_write_ptr(); + let ocb_pages = ocb.unwrap().addrs_to_pages(ocb_start_addr, ocb_end_addr); + // Mark all code memory as executable cb.mark_all_executable(); ocb.unwrap().mark_all_executable(); @@ -6590,6 +6605,8 @@ impl CodegenGlobals { global_inval_patches: Vec::new(), inline_frozen_bytes: 0, method_codegen_table: HashMap::new(), + ocb_pages, + freed_pages: None, }; // Register the method codegen functions @@ -6725,6 +6742,18 @@ impl CodegenGlobals { Some(&mgf) => Some(mgf), // Deref } } + + pub fn get_ocb_pages() -> &'static Vec { + &CodegenGlobals::get_instance().ocb_pages + } + + pub fn get_freed_pages() -> &'static mut Option> { + &mut CodegenGlobals::get_instance().freed_pages + } + + pub fn set_freed_pages(freed_pages: Vec) { + CodegenGlobals::get_instance().freed_pages = Some(freed_pages) + } } #[cfg(test)] diff --git a/yjit/src/core.rs b/yjit/src/core.rs index 705a0c46ef53a5..19272350ed32a6 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -11,6 +11,7 @@ use crate::utils::*; use crate::disasm::*; use core::ffi::c_void; use std::cell::*; +use std::collections::HashSet; use std::hash::{Hash, Hasher}; use std::mem; use std::rc::{Rc}; @@ -321,7 +322,7 @@ struct Branch { // Positions where the generated code starts and ends start_addr: Option, - end_addr: Option, + end_addr: Option, // exclusive // Context right after the branch instruction #[allow(unused)] // set but not read at the moment @@ -475,7 +476,11 @@ impl Eq for BlockRef {} /// when calling into YJIT #[derive(Default)] pub struct IseqPayload { + // Basic block versions version_map: VersionMap, + + // Indexes of code pages used by this this ISEQ + pub pages: HashSet, } impl IseqPayload { @@ -498,7 +503,7 @@ pub fn get_iseq_payload(iseq: IseqPtr) -> Option<&'static mut IseqPayload> { } /// Get the payload object associated with an iseq. Create one if none exists. -fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload { +pub fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload { type VoidPtr = *mut c_void; let payload_non_null = unsafe { @@ -537,6 +542,21 @@ pub fn for_each_iseq(mut callback: F) { unsafe { rb_yjit_for_each_iseq(Some(callback_wrapper), (&mut data) as *mut _ as *mut c_void) }; } +/// Iterate over all on-stack ISEQ payloads +#[cfg(not(test))] +pub fn for_each_on_stack_iseq_payload(mut callback: F) { + unsafe extern "C" fn callback_wrapper(iseq: IseqPtr, data: *mut c_void) { + let callback: &mut &mut dyn FnMut(&IseqPayload) -> bool = std::mem::transmute(&mut *data); + if let Some(iseq_payload) = get_iseq_payload(iseq) { + callback(iseq_payload); + } + } + let mut data: &mut dyn FnMut(&IseqPayload) = &mut callback; + unsafe { rb_jit_cont_each_iseq(Some(callback_wrapper), (&mut data) as *mut _ as *mut c_void) }; +} +#[cfg(test)] +pub fn for_each_on_stack_iseq_payload(mut _callback: F) {} + /// Free the per-iseq payload #[no_mangle] pub extern "C" fn rb_yjit_iseq_free(payload: *mut c_void) { @@ -854,6 +874,12 @@ fn add_block_version(blockref: &BlockRef, cb: &CodeBlock) { } incr_counter!(compiled_block_count); + + // Mark code pages for code GC + let iseq_payload = get_iseq_payload(block.blockid.iseq).unwrap(); + for page in cb.addrs_to_pages(block.start_addr.unwrap(), block.end_addr.unwrap()) { + iseq_payload.pages.insert(page); + } } /// Remove a block version from the version map of its parent ISEQ @@ -1526,7 +1552,11 @@ pub fn gen_entry_point(iseq: IseqPtr, ec: EcPtr) -> Option { match block { // Compilation failed - None => return None, + None => { + // Trigger code GC. This entry point will be recompiled later. + cb.code_gc(); + return None; + } // If the block contains no Ruby instructions Some(block) => { @@ -1776,6 +1806,18 @@ fn branch_stub_hit_body(branch_ptr: *const c_void, target_idx: u32, ec: EcPtr) - block_rc.borrow().start_addr.unwrap() } None => { + // Code GC needs to borrow blocks for invalidation, so their mutable + // borrows must be dropped first. + drop(block); + drop(branch); + // Trigger code GC. The whole ISEQ will be recompiled later. + // We shouldn't trigger it in the middle of compilation in branch_stub_hit + // because incomplete code could be used when cb.dropped_bytes is flipped + // by code GC. So this place, after all compilation, is the safest place + // to hook code GC on branch_stub_hit. + cb.code_gc(); + branch = branch_rc.borrow_mut(); + // Failed to service the stub by generating a new block so now we // need to exit to the interpreter at the stubbed location. We are // intentionally *not* restoring original_interp_sp. At the time of @@ -1793,7 +1835,8 @@ fn branch_stub_hit_body(branch_ptr: *const c_void, target_idx: u32, ec: EcPtr) - let new_branch_size = branch.code_size(); assert!( new_branch_size <= branch_size_on_entry, - "branch stubs should never enlarge branches" + "branch stubs should never enlarge branches: (old_size: {}, new_size: {})", + branch_size_on_entry, new_branch_size, ); // Return a pointer to the compiled block version @@ -1904,7 +1947,10 @@ pub fn gen_branch( // Get the branch targets or stubs let dst_addr0 = get_branch_target(target0, ctx0, &branchref, 0, ocb); let dst_addr1 = if let Some(ctx) = ctx1 { - get_branch_target(target1.unwrap(), ctx, &branchref, 1, ocb) + match get_branch_target(target1.unwrap(), ctx, &branchref, 1, ocb) { + Some(dst_addr) => Some(dst_addr), + None => return, // avoid unwrap() in gen_fn() + } } else { None }; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 158bdd80dcb7b1..a9242e05ca5e26 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1278,12 +1278,18 @@ extern "C" { lines: *mut ::std::os::raw::c_int, ) -> ::std::os::raw::c_int; } +extern "C" { + pub fn rb_jit_cont_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void); +} extern "C" { pub fn rb_yjit_mark_writable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; } extern "C" { pub fn rb_yjit_mark_executable(mem_block: *mut ::std::os::raw::c_void, mem_size: u32); } +extern "C" { + pub fn rb_yjit_mark_unused(mem_block: *mut ::std::os::raw::c_void, mem_size: u32) -> bool; +} extern "C" { pub fn rb_yjit_icache_invalidate( start: *mut ::std::os::raw::c_void, diff --git a/yjit/src/options.rs b/yjit/src/options.rs index 303ae4980f5fbb..a0cdbbc566ec38 100644 --- a/yjit/src/options.rs +++ b/yjit/src/options.rs @@ -91,7 +91,7 @@ macro_rules! get_option_ref { // Unsafe is ok here because options are initialized // once before any Ruby code executes ($option_name:ident) => { - unsafe { &(OPTIONS.$option_name) } + unsafe { &($crate::options::OPTIONS.$option_name) } }; } pub(crate) use get_option_ref; diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 0ad77fc5df1230..e851d4e4d1ee6a 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -253,6 +253,7 @@ make_counters! { compiled_block_count, compilation_failure, freed_iseq_count, + code_gc_count, exit_from_branch_stub, @@ -351,23 +352,37 @@ fn rb_yjit_gen_stats_dict() -> VALUE { return Qnil; } + macro_rules! hash_aset_usize { + ($hash:ident, $counter_name:expr, $value:expr) => { + let key = rust_str_to_sym($counter_name); + let value = VALUE::fixnum_from_usize($value); + rb_hash_aset($hash, key, value); + } + } + let hash = unsafe { rb_hash_new() }; - // Inline and outlined code size + // CodeBlock stats unsafe { // Get the inline and outlined code blocks let cb = CodegenGlobals::get_inline_cb(); let ocb = CodegenGlobals::get_outlined_cb(); // Inline code size - let key = rust_str_to_sym("inline_code_size"); - let value = VALUE::fixnum_from_usize(cb.get_write_pos()); - rb_hash_aset(hash, key, value); + hash_aset_usize!(hash, "inline_code_size", cb.code_size()); // Outlined code size - let key = rust_str_to_sym("outlined_code_size"); - let value = VALUE::fixnum_from_usize(ocb.unwrap().get_write_pos()); - rb_hash_aset(hash, key, value); + hash_aset_usize!(hash, "outlined_code_size", ocb.unwrap().code_size()); + + // GCed pages + let freed_page_count = cb.num_freed_pages(); + hash_aset_usize!(hash, "freed_page_count", freed_page_count); + + // GCed code size + hash_aset_usize!(hash, "freed_code_size", freed_page_count * cb.page_size()); + + // Compiled pages + hash_aset_usize!(hash, "compiled_page_count", cb.num_pages() - freed_page_count); } // If we're not generating stats, the hash is done diff --git a/yjit/src/virtualmem.rs b/yjit/src/virtualmem.rs index 5234963872f513..1d80983c9e9e44 100644 --- a/yjit/src/virtualmem.rs +++ b/yjit/src/virtualmem.rs @@ -51,6 +51,8 @@ pub trait Allocator { fn mark_writable(&mut self, ptr: *const u8, size: u32) -> bool; fn mark_executable(&mut self, ptr: *const u8, size: u32); + + fn mark_unused(&mut self, ptr: *const u8, size: u32) -> bool; } /// Pointer into a [VirtualMemory]. @@ -91,6 +93,15 @@ impl VirtualMemory { CodePtr(self.region_start) } + pub fn end_ptr(&self) -> CodePtr { + CodePtr(self.region_start.wrapping_add(self.mapped_region_bytes)) + } + + /// Size of the region in bytes that we have allocated physical memory for. + pub fn mapped_region_size(&self) -> usize { + self.mapped_region_bytes + } + /// Size of the region in bytes where writes could be attempted. pub fn virtual_region_size(&self) -> usize { self.region_size_bytes @@ -177,6 +188,12 @@ impl VirtualMemory { // Make mapped region executable self.allocator.mark_executable(region_start, mapped_region_bytes); } + + /// Free a range of bytes. start_ptr must be memory page-aligned. + pub fn free_bytes(&mut self, start_ptr: CodePtr, size: u32) { + assert_eq!(start_ptr.into_usize() % self.page_size_bytes, 0); + self.allocator.mark_unused(start_ptr.0, size); + } } impl CodePtr { @@ -235,6 +252,10 @@ mod sys { fn mark_executable(&mut self, ptr: *const u8, size: u32) { unsafe { rb_yjit_mark_executable(ptr as VoidPtr, size) } } + + fn mark_unused(&mut self, ptr: *const u8, size: u32) -> bool { + unsafe { rb_yjit_mark_unused(ptr as VoidPtr, size) } + } } } @@ -258,6 +279,7 @@ pub mod tests { enum AllocRequest { MarkWritable{ start_idx: usize, length: usize }, MarkExecutable{ start_idx: usize, length: usize }, + MarkUnused{ start_idx: usize, length: usize }, } use AllocRequest::*; @@ -298,6 +320,13 @@ pub mod tests { // We don't try to execute generated code in cfg(test) // so no need to actually request executable memory. } + + fn mark_unused(&mut self, ptr: *const u8, length: u32) -> bool { + let index = self.bounds_check_request(ptr, length); + self.requests.push(MarkUnused { start_idx: index, length: length.as_usize() }); + + true + } } // Fictional architecture where each page is 4 bytes long From 217fdbf9aa6dd850eace9f10c9a74bcec8b510e2 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 25 Oct 2022 18:39:56 +0200 Subject: [PATCH 012/148] [ruby/erb] url_encode: use CGI.escapeURIComponent (https://github.com/ruby/erb/pull/23) Ref: https://github.com/ruby/cgi/pull/26 This native implementation is much faster and available in `cgi 0.3.3`. https://github.com/ruby/erb/commit/2d90e9b010 --- lib/erb.gemspec | 4 +++- lib/erb.rb | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/erb.gemspec b/lib/erb.gemspec index 43ffc89c691a25..e89a358f8a5dd3 100644 --- a/lib/erb.gemspec +++ b/lib/erb.gemspec @@ -27,5 +27,7 @@ Gem::Specification.new do |spec| spec.executables = ['erb'] spec.require_paths = ['lib'] - spec.add_dependency 'cgi' + spec.required_ruby_version = ">= 2.7.0" + + spec.add_dependency 'cgi', '>= 0.3.3' end diff --git a/lib/erb.rb b/lib/erb.rb index 0e42425a6056e6..962eeb6963f081 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -1019,9 +1019,7 @@ def html_escape(s) # Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide # def url_encode(s) - s.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) { |m| - sprintf("%%%02X", m.unpack1("C")) - } + CGI.escapeURIComponent(s.to_s) end alias u url_encode module_function :u From 48339d5c5b6f8b34aad0fe2b44d0e2eb479b069f Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 25 Oct 2022 21:45:16 +0100 Subject: [PATCH 013/148] [ruby/irb] Make sure Encoding's defaults are restored --- test/irb/test_input_method.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index b9832d39ed887f..05580256fae0b0 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -11,19 +11,22 @@ def setup # RelineInputMethod#initialize calls IRB.set_encoding, which mutates standard input/output's encoding # so we need to make sure we set them back - @original_encodings = { + @original_io_encodings = { STDIN => [STDIN.external_encoding, STDIN.internal_encoding], STDOUT => [STDOUT.external_encoding, STDOUT.internal_encoding], STDERR => [STDERR.external_encoding, STDERR.internal_encoding], } + @original_default_encodings = [Encoding.default_external, Encoding.default_internal] end def teardown IRB.conf.replace(@conf_backup) - @original_encodings.each do |io, (external_encoding, internal_encoding)| + @original_io_encodings.each do |io, (external_encoding, internal_encoding)| io.set_encoding(external_encoding, internal_encoding) end + + EnvUtil.suppress_warning { Encoding.default_external, Encoding.default_internal = @original_default_encodings } end def test_initialization From ea989127eb9658ffe4b71a29cd3d6ea66e25b664 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Sun, 23 Oct 2022 08:47:05 +0000 Subject: [PATCH 014/148] Bundler: update docs for gemfile(5) manpage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add mswin/mswin64 to platforms - Use TruffleRuby as example instead of Rubinius Signed-off-by: Takuya Noguchi Co-authored-by: André Arko --- lib/bundler/man/gemfile.5 | 31 +++++++++++++++++++++---------- lib/bundler/man/gemfile.5.ronn | 34 ++++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index 957ba19ff16a61..6fcffb9cc7bdd1 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -73,13 +73,13 @@ Credentials in the source URL will take precedence over credentials set using \f If your application requires a specific Ruby version or engine, specify your requirements using the \fBruby\fR method, with the following arguments\. All parameters are \fBOPTIONAL\fR unless otherwise specified\. . .SS "VERSION (required)" -The version of Ruby that your application requires\. If your application requires an alternate Ruby engine, such as JRuby, Rubinius or TruffleRuby, this should be the Ruby version that the engine is compatible with\. +The version of Ruby that your application requires\. If your application requires an alternate Ruby engine, such as JRuby, TruffleRuby, etc\., this should be the Ruby version that the engine is compatible with\. . .IP "" 4 . .nf -ruby "1\.9\.3" +ruby "3\.1\.2" . .fi . @@ -95,7 +95,7 @@ What exactly is an Engine? \- A Ruby engine is an implementation of the Ruby lan For background: the reference or original implementation of the Ruby programming language is called Matz\'s Ruby Interpreter \fIhttps://en\.wikipedia\.org/wiki/Ruby_MRI\fR, or MRI for short\. This is named after Ruby creator Yukihiro Matsumoto, also known as Matz\. MRI is also known as CRuby, because it is written in C\. MRI is the most widely used Ruby engine\. . .IP "\(bu" 4 -Other implementations \fIhttps://www\.ruby\-lang\.org/en/about/\fR of Ruby exist\. Some of the more well\-known implementations include Rubinius \fIhttps://rubinius\.com/\fR, and JRuby \fIhttp://jruby\.org/\fR\. Rubinius is an alternative implementation of Ruby written in Ruby\. JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine\. +Other implementations \fIhttps://www\.ruby\-lang\.org/en/about/\fR of Ruby exist\. Some of the more well\-known implementations include JRuby \fIhttp://jruby\.org/\fR and TruffleRuby \fIhttps://www\.graalvm\.org/ruby/\fR\. Rubinius is an alternative implementation of Ruby written in Ruby\. JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine\. TruffleRuby is a Ruby implementation on the GraalVM, a language toolkit built on the JVM\. . .IP "" 0 . @@ -106,20 +106,23 @@ Each application \fImay\fR specify a Ruby engine version\. If an engine version . .nf -ruby "1\.8\.7", engine: "jruby", engine_version: "1\.6\.7" +ruby "2\.6\.8", engine: "jruby", engine_version: "9\.3\.8\.0" . .fi . .IP "" 0 . .SS "PATCHLEVEL" -Each application \fImay\fR specify a Ruby patchlevel\. +Each application \fImay\fR specify a Ruby patchlevel\. Specifying the patchlevel has been meaningless since Ruby 2\.1\.0 was released as the patchlevel is now uniquely determined by a combination of major, minor, and teeny version numbers\. +. +.P +This option was implemented in Bundler 1\.4\.0 for Ruby 2\.0 or earlier\. . .IP "" 4 . .nf -ruby "2\.0\.0", patchlevel: "247" +ruby "3\.1\.2", patchlevel: "20" . .fi . @@ -265,6 +268,14 @@ C Ruby (MRI) only, but not Windows Windows C Ruby (MRI), including RubyInstaller 32\-bit and 64\-bit versions . .TP +\fBmswin\fR +Windows C Ruby (MRI), including RubyInstaller 32\-bit versions +. +.TP +\fBmswin64\fR +Windows C Ruby (MRI), including RubyInstaller 64\-bit versions +. +.TP \fBrbx\fR Rubinius . @@ -277,13 +288,13 @@ JRuby TruffleRuby . .P -On platforms \fBruby\fR, \fBmri\fR, and \fBwindows\fR, you may additionally specify a version by appending the major and minor version numbers without a delimiter\. For example, to specify that a gem should only be used on platform \fBruby\fR version 2\.3, use: +On platforms \fBruby\fR, \fBmri\fR, \fBmswin\fR, \fBmswin64\fR, and \fBwindows\fR, you may additionally specify a version by appending the major and minor version numbers without a delimiter\. For example, to specify that a gem should only be used on platform \fBruby\fR version 3\.1, use: . .IP "" 4 . .nf -ruby_23 +ruby_31 . .fi . @@ -297,8 +308,8 @@ As with groups (above), you may specify one or more platforms: .nf gem "weakling", platforms: :jruby -gem "ruby\-debug", platforms: :mri_18 -gem "nokogiri", platforms: [:windows_26, :jruby] +gem "ruby\-debug", platforms: :mri_31 +gem "nokogiri", platforms: [:windows_31, :jruby] . .fi . diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn index d27849ae6aa34f..a3affc30ccfcfc 100644 --- a/lib/bundler/man/gemfile.5.ronn +++ b/lib/bundler/man/gemfile.5.ronn @@ -64,10 +64,10 @@ All parameters are `OPTIONAL` unless otherwise specified. ### VERSION (required) The version of Ruby that your application requires. If your application -requires an alternate Ruby engine, such as JRuby, Rubinius or TruffleRuby, this +requires an alternate Ruby engine, such as JRuby, TruffleRuby, etc., this should be the Ruby version that the engine is compatible with. - ruby "1.9.3" + ruby "3.1.2" ### ENGINE @@ -86,9 +86,10 @@ What exactly is an Engine? - [Other implementations](https://www.ruby-lang.org/en/about/) of Ruby exist. Some of the more well-known implementations include - [Rubinius](https://rubinius.com/), and [JRuby](http://jruby.org/). + [JRuby](http://jruby.org/) and [TruffleRuby](https://www.graalvm.org/ruby/). Rubinius is an alternative implementation of Ruby written in Ruby. JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine. + TruffleRuby is a Ruby implementation on the GraalVM, a language toolkit built on the JVM. ### ENGINE VERSION @@ -96,13 +97,17 @@ Each application _may_ specify a Ruby engine version. If an engine version is specified, an engine _must_ also be specified. If the engine is "ruby" the engine version specified _must_ match the Ruby version. - ruby "1.8.7", engine: "jruby", engine_version: "1.6.7" + ruby "2.6.8", engine: "jruby", engine_version: "9.3.8.0" ### PATCHLEVEL -Each application _may_ specify a Ruby patchlevel. +Each application _may_ specify a Ruby patchlevel. Specifying the patchlevel has +been meaningless since Ruby 2.1.0 was released as the patchlevel is now +uniquely determined by a combination of major, minor, and teeny version numbers. - ruby "2.0.0", patchlevel: "247" +This option was implemented in Bundler 1.4.0 for Ruby 2.0 or earlier. + + ruby "3.1.2", patchlevel: "20" ## GEMS @@ -195,6 +200,10 @@ There are a number of `Gemfile` platforms: C Ruby (MRI) only, but not Windows * `windows`: Windows C Ruby (MRI), including RubyInstaller 32-bit and 64-bit versions + * `mswin`: + Windows C Ruby (MRI), including RubyInstaller 32-bit versions + * `mswin64`: + Windows C Ruby (MRI), including RubyInstaller 64-bit versions * `rbx`: Rubinius * `jruby`: @@ -202,17 +211,18 @@ There are a number of `Gemfile` platforms: * `truffleruby`: TruffleRuby -On platforms `ruby`, `mri`, and `windows`, you may additionally specify a version -by appending the major and minor version numbers without a delimiter. For example, -to specify that a gem should only be used on platform `ruby` version 2.3, use: +On platforms `ruby`, `mri`, `mswin`, `mswin64`, and `windows`, you may +additionally specify a version by appending the major and minor version numbers +without a delimiter. For example, to specify that a gem should only be used on +platform `ruby` version 3.1, use: - ruby_23 + ruby_31 As with groups (above), you may specify one or more platforms: gem "weakling", platforms: :jruby - gem "ruby-debug", platforms: :mri_18 - gem "nokogiri", platforms: [:windows_26, :jruby] + gem "ruby-debug", platforms: :mri_31 + gem "nokogiri", platforms: [:windows_31, :jruby] All operations involving groups ([`bundle install`](bundle-install.1.html), `Bundler.setup`, `Bundler.require`) behave exactly the same as if any groups not From d6d9b5130e5545e978fc9763e9419bdddc5ec4bd Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 25 Oct 2022 16:11:42 -0700 Subject: [PATCH 015/148] [ruby/erb] Version 3.0.0 --- lib/erb/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erb/version.rb b/lib/erb/version.rb index 0aaa38258fcec0..572b29258320df 100644 --- a/lib/erb/version.rb +++ b/lib/erb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class ERB - VERSION = '2.2.3' + VERSION = '3.0.0' private_constant :VERSION end From a733633c5765beead215002563922aa03847c9d0 Mon Sep 17 00:00:00 2001 From: git Date: Tue, 25 Oct 2022 23:14:50 +0000 Subject: [PATCH 016/148] Update default gems list at d6d9b5130e5545e978fc9763e9419b [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index dd40d55e21ee13..a4cbae71038bdc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -231,6 +231,7 @@ Note: We're only listing outstanding class updates. * bundler 2.4.0.dev * cgi 0.3.3 * date 3.2.3 + * erb 3.0.0 * error_highlight 0.4.0 * etc 1.4.0 * fiddle 1.1.1 From 1dd9511b6723b2781e65e2b4093a714576802302 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 25 Oct 2022 17:53:08 -0700 Subject: [PATCH 017/148] Use ruby/action-slack@v3.0.0 and ref_name (#6633) It's moved from k0kubun to ruby org. Also, we don't need JavaScript eval to generate branch if we use github.ref_name, so v3.0.0 is a version that doesn't use eval. Co-Authored-By: Nobuyoshi Nakada Co-authored-by: Nobuyoshi Nakada --- .github/workflows/baseruby.yml | 4 ++-- .github/workflows/check_dependencies.yml | 4 ++-- .github/workflows/check_misc.yml | 4 ++-- .github/workflows/compilers.yml | 4 ++-- .github/workflows/macos.yml | 4 ++-- .github/workflows/mingw.yml | 4 ++-- .github/workflows/mjit-bindgen.yml | 4 ++-- .github/workflows/mjit.yml | 4 ++-- .github/workflows/spec_guards.yml | 4 ++-- .github/workflows/ubuntu.yml | 4 ++-- .github/workflows/windows.yml | 4 ++-- .github/workflows/yjit-ubuntu.yml | 4 ++-- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 65d2813ac23a5c..8152cad7685082 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -57,7 +57,7 @@ jobs: - run: make incs - run: make all - run: make test - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@v3.0.0 with: payload: | { @@ -65,7 +65,7 @@ jobs: "env": "${{ github.workflow }} / BASERUBY @ ${{ matrix.ruby }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 1753f4657409cb..16e2829f734353 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -56,7 +56,7 @@ jobs: - run: make all golf - run: ruby tool/update-deps --fix - run: git diff --no-ext-diff --ignore-submodules --exit-code - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@v3.0.0 with: payload: | { @@ -64,7 +64,7 @@ jobs: "env": "${{ matrix.os }} / Dependencies need to update", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 71ac9566955300..e897015d564c72 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -98,7 +98,7 @@ jobs: GIT_COMMITTER_NAME: git if: ${{ github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull') && steps.diff.outcome == 'failure' }} - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@v3.0.0 with: payload: | { @@ -106,7 +106,7 @@ jobs: "env": "${{ github.workflow }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 8080963fa6303a..60fcc94c5ec3fa 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -268,7 +268,7 @@ jobs: - run: make test-annocheck if: ${{ matrix.entry.check && endsWith(matrix.entry.name, 'annocheck') }} - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@v3.0.0 with: payload: | { @@ -276,7 +276,7 @@ jobs: "env": "${{ github.workflow }} / ${{ matrix.entry.name }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 04446d3f053bcd..e5f055f8c43242 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -85,7 +85,7 @@ jobs: PRECHECK_BUNDLED_GEMS: "no" if: ${{ matrix.test_task == 'check' && matrix.skipped_tests != '' }} continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@v3.0.0 with: payload: | { @@ -93,7 +93,7 @@ jobs: "env": "${{ matrix.os }} / ${{ matrix.test_task }}${{ matrix.configure }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 5c8189428ca177..001fab9cf10ae3 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -151,7 +151,7 @@ jobs: make ${{ StartsWith(matrix.test_task, 'spec/') && matrix.test_task || 'test-spec' }} if: ${{matrix.test_task == 'check' || matrix.test_task == 'test-spec' || StartsWith(matrix.test_task, 'spec/')}} - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@v3.0.0 with: payload: | { @@ -159,7 +159,7 @@ jobs: "env": "${{ github.workflow }} ${{ matrix.msystem }} / ${{ matrix.test_task }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/mjit-bindgen.yml b/.github/workflows/mjit-bindgen.yml index e1d22d91f2d92f..1252d3b50987c6 100644 --- a/.github/workflows/mjit-bindgen.yml +++ b/.github/workflows/mjit-bindgen.yml @@ -83,7 +83,7 @@ jobs: - run: $SETARCH make ${{ matrix.task }} - run: git diff --exit-code working-directory: src - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@v3.0.0 with: payload: | { @@ -91,7 +91,7 @@ jobs: "env": "${{ matrix.os }} / ${{ matrix.test_task }}${{ matrix.configure }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/mjit.yml b/.github/workflows/mjit.yml index 89bc0a226becff..c468e0181173fe 100644 --- a/.github/workflows/mjit.yml +++ b/.github/workflows/mjit.yml @@ -84,7 +84,7 @@ jobs: ulimit -c unlimited make -s test-spec RUN_OPTS="$RUN_OPTS" timeout-minutes: 60 - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@v3.0.0 with: payload: | { @@ -92,7 +92,7 @@ jobs: "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.jit_opts }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 6d51320c2f969e..e3201eb6dbc57e 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -46,7 +46,7 @@ jobs: working-directory: spec/ruby env: CHECK_LEAKS: true - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@v3.0.0 with: payload: | { @@ -54,7 +54,7 @@ jobs: "env": "${{ github.workflow }} / rubyspec @ ${{ matrix.ruby }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 1a5b6661fe1f8a..508d2c7733a575 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -120,7 +120,7 @@ jobs: TESTS: ${{ matrix.skipped_tests }} if: ${{ matrix.test_task == 'check' && matrix.skipped_tests != '' }} continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@v3.0.0 with: payload: | { @@ -128,7 +128,7 @@ jobs: "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.configure }}${{ matrix.arch }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 6f5106e815639c..7f96d3464bbd7f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -138,7 +138,7 @@ jobs: env: RUBY_TESTOPTS: -j${{env.TEST_JOBS}} --job-status=normal timeout-minutes: 60 - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@v3.0.0 with: payload: | { @@ -146,7 +146,7 @@ jobs: "env": "VS${{ matrix.vs }} / ${{ matrix.test_task || 'check' }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 176181f11c5b3e..62356e8b45d6a5 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -135,7 +135,7 @@ jobs: BASE_REPO: ${{ github.event.pull_request.base.repo.full_name }} BASE_SHA: ${{ github.event.pull_request.base.sha }} if: ${{ matrix.test_task == 'yjit-bench' && startsWith(github.event_name, 'pull') }} - - uses: k0kubun/action-slack@v2.0.0 + - uses: ruby/action-slack@v3.0.0 with: payload: | { @@ -143,7 +143,7 @@ jobs: "env": "${{ github.workflow }} / ${{ matrix.test_task }} ${{ matrix.configure }}", "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}", "commit": "${{ github.sha }}", - "branch": "${{ github.ref }}".split('/').reverse()[0] + "branch": "${{ github.ref_name }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot From 1670e96c0ddf9e17e94e25517fb424f3a8e20e33 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 25 Oct 2022 16:22:57 -0700 Subject: [PATCH 018/148] [win32] Only include base windows types esent.h is the header for MS essential storage engine (JET) which is not needed in ruby. basetsd.h has existed since _MSC_VER >= 1200 (VS 6.0) and is the preferred header to use for WCHAR. --- win32/dir.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win32/dir.h b/win32/dir.h index de12f79158d2b0..0292c27c9c6c2b 100644 --- a/win32/dir.h +++ b/win32/dir.h @@ -1,7 +1,7 @@ #ifndef RUBY_WIN32_DIR_H #define RUBY_WIN32_DIR_H #include /* for uint8_t */ -#include /* for WCHAR */ +#include /* for WCHAR */ #include "ruby/encoding.h" /* for rb_encoding */ #define DT_UNKNOWN 0 From 923aed26ff217f0b5619d5c53c24be7e0823b951 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 25 Oct 2022 21:49:35 -0700 Subject: [PATCH 019/148] Start notifying Cirrus CI failures It looks like Cirrus doesn't natively support notifications and they recomment to use GitHub Actions for it. https://cirrus-ci.org/guide/notifications/ Because I don't know what the payload looks like, I just added a basic payload and dumped GitHub context so that we could improve it later. --- .github/workflows/cirrus-notify.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/cirrus-notify.yml diff --git a/.github/workflows/cirrus-notify.yml b/.github/workflows/cirrus-notify.yml new file mode 100644 index 00000000000000..156fd529f85a58 --- /dev/null +++ b/.github/workflows/cirrus-notify.yml @@ -0,0 +1,29 @@ +on: + check_suite: + type: ['completed'] +name: Cirrus CI failure notification +jobs: + cirrus-notify: + name: After Cirrus CI Failure + if: >- + github.event.check_suite.app.name == 'Cirrus CI' + && github.event.check_suite.conclusion != 'success' + && github.event.check_suite.conclusion != 'cancelled' + runs-on: ubuntu-latest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - uses: ruby/action-slack@v3.0.0 + with: + payload: | + { + "ci": "Cirrus CI", + "env": "Cirrus CI", + "url": "https://cirrus-ci.com/github/ruby/ruby", + "commit": "${{ github.sha }}", + "branch": "${{ github.ref_name }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot From 1161454806f50a4be56347e5da897dda6b841d27 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 25 Oct 2022 22:26:20 -0700 Subject: [PATCH 020/148] Add NEWS entries about MJIT [ci skip] --- NEWS.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS.md b/NEWS.md index a4cbae71038bdc..3a866040bdf41e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -327,6 +327,12 @@ The following deprecated APIs are removed. ### MJIT +* The MJIT compiler is re-implemented in Ruby as a standard library `mjit`. +* MJIT compiler is executed under a forked Ruby process instead of + doing it in a native thread called MJIT worker. [[Feature #18968]] + * As a result, Microsoft Visual Studio (MSWIN) is no longer supported. +* MinGW is no longer supported. [[Feature #18824]] + ## Static analysis ### RBS @@ -381,3 +387,5 @@ The following deprecated APIs are removed. [Feature #18630]: https://bugs.ruby-lang.org/issues/18630 [Feature #18589]: https://bugs.ruby-lang.org/issues/18589 [Feature #19060]: https://bugs.ruby-lang.org/issues/19060 +[Feature #18824]: https://bugs.ruby-lang.org/issues/18824 +[Feature #18968]: https://bugs.ruby-lang.org/issues/18968 From df4361102158658a9419cd43a1ceb5a2e0ef29b9 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 25 Oct 2022 22:49:31 -0700 Subject: [PATCH 021/148] CI skip should not be notified [ci skip] --- .github/workflows/cirrus-notify.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cirrus-notify.yml b/.github/workflows/cirrus-notify.yml index 156fd529f85a58..3fd693c2a368a6 100644 --- a/.github/workflows/cirrus-notify.yml +++ b/.github/workflows/cirrus-notify.yml @@ -9,6 +9,7 @@ jobs: github.event.check_suite.app.name == 'Cirrus CI' && github.event.check_suite.conclusion != 'success' && github.event.check_suite.conclusion != 'cancelled' + && github.event.check_suite.conclusion != 'skipped' runs-on: ubuntu-latest steps: - name: Dump GitHub context From 94f3aa2126231fb04f4c3125162e84f6acc3df09 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 25 Oct 2022 23:02:04 -0700 Subject: [PATCH 022/148] Improve Cirrus notification metadata [ci skip] --- .github/workflows/cirrus-notify.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cirrus-notify.yml b/.github/workflows/cirrus-notify.yml index 3fd693c2a368a6..bbc84bf93bb2e2 100644 --- a/.github/workflows/cirrus-notify.yml +++ b/.github/workflows/cirrus-notify.yml @@ -22,9 +22,11 @@ jobs: { "ci": "Cirrus CI", "env": "Cirrus CI", - "url": "https://cirrus-ci.com/github/ruby/ruby", - "commit": "${{ github.sha }}", - "branch": "${{ github.ref_name }}" + "url": "https://github.com/ruby/ruby/actions/runs/${{ github.run_id }}", + "commit": "${{ github.event.check_suite.head_commit.id }}", + "branch": "${{ github.event.check_suite.head_branch }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot + # head_branch can be null. checking it here to see GITHUB_CONTEXT on master. + if: ${{ github.event.check_suite.head_branch == 'master' }} From 287eac5e8ecb774edcc3e4000f3b4848700d23e4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Oct 2022 10:43:41 +0900 Subject: [PATCH 023/148] Fix format specifiers for `size_t` --- io_buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_buffer.c b/io_buffer.c index e252af35130371..a0d988e25903fc 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -1462,7 +1462,7 @@ static void io_buffer_validate_type(size_t size, size_t offset) { if (offset > size) { - rb_raise(rb_eArgError, "Type extends beyond end of buffer! (offset=%ld > size=%ld)", offset, size); + rb_raise(rb_eArgError, "Type extends beyond end of buffer! (offset=%"PRIdSIZE" > size=%"PRIdSIZE")", offset, size); } } From a14611cd54d2ac02daad5ccfe99fc3ec9b0818bd Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Oct 2022 10:53:10 +0900 Subject: [PATCH 024/148] Fix -Wundef warnings --- ccan/check_type/check_type.h | 2 +- ccan/container_of/container_of.h | 4 ++-- ccan/list/list.h | 2 +- include/ruby/internal/intern/gc.h | 2 +- include/ruby/memory_view.h | 2 +- include/ruby/util.h | 2 +- vm.c | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ccan/check_type/check_type.h b/ccan/check_type/check_type.h index e795ad71d0e80e..659e1a5a830e9a 100644 --- a/ccan/check_type/check_type.h +++ b/ccan/check_type/check_type.h @@ -44,7 +44,7 @@ * ((encl_type *) \ * ((char *)(mbr_ptr) - offsetof(enclosing_type, mbr)))) */ -#if HAVE_TYPEOF +#if defined(HAVE_TYPEOF) && HAVE_TYPEOF #define ccan_check_type(expr, type) \ ((typeof(expr) *)0 != (type *)0) diff --git a/ccan/container_of/container_of.h b/ccan/container_of/container_of.h index b30c347d57fbda..872bb6ea6ec5b0 100644 --- a/ccan/container_of/container_of.h +++ b/ccan/container_of/container_of.h @@ -112,7 +112,7 @@ static inline char *container_of_or_null_(void *member_ptr, size_t offset) * return i; * } */ -#if HAVE_TYPEOF +#if defined(HAVE_TYPEOF) && HAVE_TYPEOF #define ccan_container_of_var(member_ptr, container_var, member) \ ccan_container_of(member_ptr, typeof(*container_var), member) #else @@ -131,7 +131,7 @@ static inline char *container_of_or_null_(void *member_ptr, size_t offset) * structure memory layout. * */ -#if HAVE_TYPEOF +#if defined(HAVE_TYPEOF) && HAVE_TYPEOF #define ccan_container_off_var(var, member) \ ccan_container_off(typeof(*var), member) #else diff --git a/ccan/list/list.h b/ccan/list/list.h index 91787bfdb32769..30b2af04e954ad 100644 --- a/ccan/list/list.h +++ b/ccan/list/list.h @@ -770,7 +770,7 @@ static inline struct ccan_list_node *ccan_list_node_from_off_(void *ptr, size_t (ccan_container_off_var(var, member) + \ ccan_check_type(var->member, struct ccan_list_node)) -#if HAVE_TYPEOF +#if defined(HAVE_TYPEOF) && HAVE_TYPEOF #define ccan_list_typeof(var) typeof(var) #else #define ccan_list_typeof(var) void * diff --git a/include/ruby/internal/intern/gc.h b/include/ruby/internal/intern/gc.h index e7b8008729b058..ae79498cf372ed 100644 --- a/include/ruby/internal/intern/gc.h +++ b/include/ruby/internal/intern/gc.h @@ -26,7 +26,7 @@ # include /* size_t */ #endif -#if HAVE_SYS_TYPES_H +#ifdef HAVE_SYS_TYPES_H # include /* ssize_t */ #endif diff --git a/include/ruby/memory_view.h b/include/ruby/memory_view.h index 83931038a05605..1ddca2d46f0663 100644 --- a/include/ruby/memory_view.h +++ b/include/ruby/memory_view.h @@ -16,7 +16,7 @@ # include /* size_t */ #endif -#if HAVE_SYS_TYPES_H +#ifdef HAVE_SYS_TYPES_H # include /* ssize_t */ #endif diff --git a/include/ruby/util.h b/include/ruby/util.h index f0ea874322c338..e8727a32005648 100644 --- a/include/ruby/util.h +++ b/include/ruby/util.h @@ -19,7 +19,7 @@ # include /* size_t */ #endif -#if HAVE_SYS_TYPES_H +#ifdef HAVE_SYS_TYPES_H # include /* ssize_t */ #endif diff --git a/vm.c b/vm.c index 7f7ae3a6d6ae4a..0077522317fd39 100644 --- a/vm.c +++ b/vm.c @@ -4031,7 +4031,7 @@ Init_vm_objects(void) vm->loading_table = st_init_strtable(); vm->frozen_strings = st_init_table_with_size(&rb_fstring_hash_type, 10000); -#if HAVE_MMAP +#ifdef HAVE_MMAP vm->shape_list = (rb_shape_t *)mmap(NULL, rb_size_mul_or_raise(SHAPE_BITMAP_SIZE * 32, sizeof(rb_shape_t), rb_eRuntimeError), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (vm->shape_list == MAP_FAILED) { From 131c31a9209c61f84d318aa18b61f468f48b8219 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Oct 2022 19:43:14 +0900 Subject: [PATCH 025/148] [Bug #19081] Show the caller location in warning for Ractor The internal location in ractor.rb is not usefull at all. ``` $ ruby -e 'Ractor.new {}' :267: warning: Ractor is experimental, ... ``` --- bootstraptest/test_ractor.rb | 6 ++++++ ractor.c | 6 ------ ractor.rb | 4 ++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 6373349e427d61..0fad04ebbc05a6 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1618,4 +1618,10 @@ def initialize "ok" } +assert_match /\Atest_ractor\.rb:1:\s+warning:\s+Ractor is experimental/, %q{ + Warning[:experimental] = $VERBOSE = true + STDERR.reopen(STDOUT) + eval("Ractor.new{}.take", nil, "test_ractor.rb", 1) +} + end # if !ENV['GITHUB_WORKFLOW'] diff --git a/ractor.c b/ractor.c index 7fea312ab0e31b..3569202e58ee27 100644 --- a/ractor.c +++ b/ractor.c @@ -1438,12 +1438,6 @@ cancel_single_ractor_mode(void) } ruby_single_main_ractor = NULL; - - if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL)) { - rb_category_warn(RB_WARN_CATEGORY_EXPERIMENTAL, - "Ractor is experimental, and the behavior may change in future versions of Ruby! " - "Also there are many implementation issues."); - } } static void diff --git a/ractor.rb b/ractor.rb index 8e229d47005521..1031fe499b9704 100644 --- a/ractor.rb +++ b/ractor.rb @@ -262,6 +262,10 @@ class Ractor def self.new(*args, name: nil, &block) b = block # TODO: builtin bug raise ArgumentError, "must be called with a block" unless block + if __builtin_cexpr!("RBOOL(ruby_single_main_ractor)") + warn("Ractor is experimental, and the behavior may change in future versions of Ruby! " \ + "Also there are many implementation issues.", uplevel: 0, category: :experimental) + end loc = caller_locations(1, 1).first loc = "#{loc.path}:#{loc.lineno}" __builtin_ractor_create(loc, name, args, b) From b795e66a2d085d0bb54eaf51cbc0df3d377d3618 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 26 Oct 2022 07:40:49 -0700 Subject: [PATCH 026/148] Add NEWS entries about YJIT [ci skip] (#6636) --- NEWS.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS.md b/NEWS.md index 3a866040bdf41e..93c0c0713e7c5b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -324,6 +324,12 @@ The following deprecated APIs are removed. * Support arm64 / aarch64 on UNIX platforms. * Building YJIT requires Rust 1.58.1+. [[Feature #18481]] +* Physical memory for JIT code is lazily allocated. Unlike Ruby 3.1, + the RSS of a Ruby process is minimized because virtual memory pages + allocated by `--yjit-exec-mem-size` will not be mapped to physical + memory pages until actually utilized by JIT code. +* Introduce Code GC that frees all code pages when the memory consumption + by JIT code reaches `--yjit-exec-mem-size`. ### MJIT From 40f8a781a39042cdccd641fef6e10cb2c8d98c41 Mon Sep 17 00:00:00 2001 From: st0012 Date: Mon, 24 Oct 2022 23:08:32 +0100 Subject: [PATCH 027/148] [ruby/irb] Extract without_rdoc helper --- test/irb/test_input_method.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index 05580256fae0b0..0e1af7bdc7dfff 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -86,19 +86,6 @@ def test_initialization_with_use_autocomplete_but_without_rdoc ensure Reline.add_dialog_proc(:show_doc, original_show_doc_proc, Reline::DEFAULT_DIALOG_CONTEXT) end - - def without_rdoc(&block) - ::Kernel.send(:alias_method, :old_require, :require) - - ::Kernel.define_method(:require) do |name| - raise LoadError, "cannot load such file -- rdoc (test)" if name == "rdoc" - original_require(name) - end - - yield - ensure - ::Kernel.send(:alias_method, :require, :old_require) - end end end From d889e810f3d9061fdab5a21470bf32d078d2b06a Mon Sep 17 00:00:00 2001 From: st0012 Date: Mon, 24 Oct 2022 22:52:52 +0100 Subject: [PATCH 028/148] [ruby/irb] Add test for the help command --- test/irb/test_cmd.rb | 51 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 7b7ab55a64cd7c..e5e3a3fabf139c 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -406,6 +406,57 @@ def test_irb_source ], out) end + def test_help + IRB.init_config(nil) + input = TestInputMethod.new([ + "help 'String#gsub'\n", + "\n", + ]) + IRB.conf[:VERBOSE] = false + IRB.conf[:PROMPT_MODE] = :SIMPLE + irb = IRB::Irb.new(IRB::WorkSpace.new(self), input) + IRB.conf[:MAIN_CONTEXT] = irb.context + out, err = capture_output do + irb.eval_input + end + + # the help command lazily loads rdoc by redefining the execute method + assert_match(/discarding old execute/, err) unless RUBY_ENGINE == 'truffleruby' + + # the former is what we'd get without document content installed, like on CI + # the latter is what we may get locally + possible_rdoc_output = [/Nothing known about String#gsub/, /Returns a copy of self with all occurrences of the given pattern/] + assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the help command to match one of the possible outputs") + ensure + # this is the only way to reset the redefined method without coupling the test with its implementation + load "irb/cmd/help.rb" + end + + def test_help_without_rdoc + IRB.init_config(nil) + input = TestInputMethod.new([ + "help 'String#gsub'\n", + "\n", + ]) + IRB.conf[:VERBOSE] = false + IRB.conf[:PROMPT_MODE] = :SIMPLE + irb = IRB::Irb.new(IRB::WorkSpace.new(self), input) + IRB.conf[:MAIN_CONTEXT] = irb.context + out, err = capture_output do + without_rdoc do + irb.eval_input + end + end + + # since LoadError will be raised, the execute won't be redefined + assert_no_match(/discarding old execute/, err) + # if it fails to require rdoc, it only returns the command object + assert_match(/=> IRB::ExtendCommand::Help\n/, out) + ensure + # this is the only way to reset the redefined method without coupling the test with its implementation + load "irb/cmd/help.rb" + end + def test_irb_load IRB.init_config(nil) File.write("#{@tmpdir}/a.rb", "a = 'hi'\n") From 2022470a954ad673b0c08aecaa9a7881188cd105 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 25 Oct 2022 20:25:49 +0100 Subject: [PATCH 029/148] [ruby/irb] Suppress warnings Co-authored-by: Peter Zhu --- test/irb/test_cmd.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index e5e3a3fabf139c..6c03a9de079d9d 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -429,7 +429,7 @@ def test_help assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the help command to match one of the possible outputs") ensure # this is the only way to reset the redefined method without coupling the test with its implementation - load "irb/cmd/help.rb" + EnvUtil.suppress_warning { load "irb/cmd/help.rb" } end def test_help_without_rdoc @@ -454,7 +454,7 @@ def test_help_without_rdoc assert_match(/=> IRB::ExtendCommand::Help\n/, out) ensure # this is the only way to reset the redefined method without coupling the test with its implementation - load "irb/cmd/help.rb" + EnvUtil.suppress_warning { load "irb/cmd/help.rb" } end def test_irb_load From cb95d834cc19a45a84d8d2c6d0f25d0c44e0d813 Mon Sep 17 00:00:00 2001 From: st0012 Date: Wed, 26 Oct 2022 12:40:48 +0100 Subject: [PATCH 030/148] [ruby/irb] Don't insert new methods to Test::Unit::TestCase Ruby CI runs irb and other Ruby core/stdlib tests in the same process. So adding irb-specific helper to Test::Unit::TestCase could potentially pollute other components' tests and should be avoided. --- test/irb/test_cmd.rb | 4 +++- test/irb/test_helper.rb | 16 ++++++++++++++++ test/irb/test_input_method.rb | 4 +++- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 test/irb/test_helper.rb diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 6c03a9de079d9d..6ff2afc3e3cbbd 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -3,6 +3,8 @@ require "irb" require "irb/extend-command" +require_relative "test_helper" + module TestIRB class ExtendCommand < Test::Unit::TestCase class TestInputMethod < ::IRB::InputMethod @@ -443,7 +445,7 @@ def test_help_without_rdoc irb = IRB::Irb.new(IRB::WorkSpace.new(self), input) IRB.conf[:MAIN_CONTEXT] = irb.context out, err = capture_output do - without_rdoc do + IRB::TestHelper.without_rdoc do irb.eval_input end end diff --git a/test/irb/test_helper.rb b/test/irb/test_helper.rb new file mode 100644 index 00000000000000..19c39a4a59483f --- /dev/null +++ b/test/irb/test_helper.rb @@ -0,0 +1,16 @@ +module IRB + module TestHelper + def self.without_rdoc(&block) + ::Kernel.send(:alias_method, :old_require, :require) + + ::Kernel.define_method(:require) do |name| + raise LoadError, "cannot load such file -- rdoc (test)" if name.match?("rdoc") || name.match?(/^rdoc\/.*/) + ::Kernel.send(:old_require, name) + end + + yield + ensure + EnvUtil.suppress_warning { ::Kernel.send(:alias_method, :require, :old_require) } + end + end +end diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index 0e1af7bdc7dfff..3618fa46f1f4f2 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -3,6 +3,8 @@ require "test/unit" require "irb" +require_relative "test_helper" + module TestIRB class TestRelineInputMethod < Test::Unit::TestCase def setup @@ -76,7 +78,7 @@ def test_initialization_with_use_autocomplete_but_without_rdoc IRB.conf[:USE_AUTOCOMPLETE] = true - without_rdoc do + IRB::TestHelper.without_rdoc do IRB::RelineInputMethod.new end From 73a0223f952ef40d4986dde65eadb8f0b59521f2 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 26 Oct 2022 11:12:32 -0400 Subject: [PATCH 031/148] [ruby/irb] Rename test_helper.rb to helper.rb The name test_helper.rb conflicts with the test_helper.rb in JSON, causing build failures. This commit renames test_helper.rb to helper.rb. https://github.com/ruby/irb/commit/b6a92bf6b3 Co-Authored-By: Stan Lo --- test/irb/{test_helper.rb => helper.rb} | 0 test/irb/test_cmd.rb | 2 +- test/irb/test_input_method.rb | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename test/irb/{test_helper.rb => helper.rb} (100%) diff --git a/test/irb/test_helper.rb b/test/irb/helper.rb similarity index 100% rename from test/irb/test_helper.rb rename to test/irb/helper.rb diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 6ff2afc3e3cbbd..69830c061b635f 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -3,7 +3,7 @@ require "irb" require "irb/extend-command" -require_relative "test_helper" +require_relative "helper" module TestIRB class ExtendCommand < Test::Unit::TestCase diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index 3618fa46f1f4f2..90d6bf536400e1 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -3,7 +3,7 @@ require "test/unit" require "irb" -require_relative "test_helper" +require_relative "helper" module TestIRB class TestRelineInputMethod < Test::Unit::TestCase From 0dc2e1a764ba2a18d3646f11f272b05395b01201 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 26 Oct 2022 15:44:01 +0100 Subject: [PATCH 032/148] [ruby/irb] Remove unnecessary warning check on help command It's not an intentional behavior of the command and it behaves differently in different environments. So checking it actually brings more problem than benefit. https://github.com/ruby/irb/commit/b3203bc784 --- test/irb/test_cmd.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 69830c061b635f..2fe60a7ca8ec42 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -422,9 +422,6 @@ def test_help irb.eval_input end - # the help command lazily loads rdoc by redefining the execute method - assert_match(/discarding old execute/, err) unless RUBY_ENGINE == 'truffleruby' - # the former is what we'd get without document content installed, like on CI # the latter is what we may get locally possible_rdoc_output = [/Nothing known about String#gsub/, /Returns a copy of self with all occurrences of the given pattern/] @@ -450,8 +447,6 @@ def test_help_without_rdoc end end - # since LoadError will be raised, the execute won't be redefined - assert_no_match(/discarding old execute/, err) # if it fails to require rdoc, it only returns the command object assert_match(/=> IRB::ExtendCommand::Help\n/, out) ensure From fa0adbad92fc1216ba0d1757fe40f0453e3a6574 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 26 Oct 2022 08:29:12 -0700 Subject: [PATCH 033/148] YJIT: Invalidate i-cache for the other cb on next_page (#6631) * YJIT: Invalidate i-cache for the other cb on next_page * YJIT: Invalidate only what's written by jmp_ptr * YJIT: Move the code to the arm64 backend --- yjit/src/backend/arm64/mod.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/yjit/src/backend/arm64/mod.rs b/yjit/src/backend/arm64/mod.rs index 7aeb1435d2518e..0c784c0beadbe2 100644 --- a/yjit/src/backend/arm64/mod.rs +++ b/yjit/src/backend/arm64/mod.rs @@ -708,6 +708,23 @@ impl Assembler } } + /// Call emit_jmp_ptr and immediately invalidate the written range. + /// This is needed when next_page also moves other_cb that is not invalidated + /// by compile_with_regs. Doing it here allows you to avoid invalidating a lot + /// more than necessary when other_cb jumps from a position early in the page. + /// This invalidates a small range of cb twice, but we accept the small cost. + fn emit_jmp_ptr_with_invalidation(cb: &mut CodeBlock, dst_ptr: CodePtr) { + #[cfg(not(test))] + let start = cb.get_write_ptr(); + emit_jmp_ptr(cb, dst_ptr); + #[cfg(not(test))] + { + let end = cb.get_write_ptr(); + use crate::cruby::rb_yjit_icache_invalidate; + unsafe { rb_yjit_icache_invalidate(start.raw_ptr() as _, end.raw_ptr() as _) }; + } + } + // dbg!(&self.insns); // List of GC offsets @@ -1018,7 +1035,7 @@ impl Assembler }; // On failure, jump to the next page and retry the current insn - if !had_dropped_bytes && cb.has_dropped_bytes() && cb.next_page(src_ptr, emit_jmp_ptr) { + if !had_dropped_bytes && cb.has_dropped_bytes() && cb.next_page(src_ptr, emit_jmp_ptr_with_invalidation) { // Reset cb states before retrying the current Insn cb.set_label_state(old_label_state); } else { From c746f380f278683e98262883ed69319bd9fa680e Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Thu, 27 Oct 2022 05:57:59 +1030 Subject: [PATCH 034/148] YJIT: Support nil and blockparamproxy as blockarg in send (#6492) Co-authored-by: John Hawthorn Co-authored-by: John Hawthorn --- bootstraptest/test_yjit.rb | 43 ++++++++ test/ruby/test_yjit.rb | 8 +- yjit.c | 6 + yjit/bindgen/src/main.rs | 1 + yjit/src/codegen.rs | 195 +++++++++++++++++++++++++-------- yjit/src/core.rs | 12 +- yjit/src/cruby_bindings.inc.rs | 3 + 7 files changed, 221 insertions(+), 47 deletions(-) diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 2dc460c628fb40..7361d2a7257b1d 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -3337,3 +3337,46 @@ def respond_to_missing?(*) = true 5.times.map { opt_and_kwargs(1, c: 2) }.uniq } + +# bmethod with forwarded block +assert_equal '2', %q{ + define_method(:foo) do |&block| + block.call + end + + def bar(&block) + foo(&block) + end + + bar { 1 } + bar { 2 } +} + +# bmethod with forwarded block and arguments +assert_equal '5', %q{ + define_method(:foo) do |n, &block| + n + block.call + end + + def bar(n, &block) + foo(n, &block) + end + + bar(0) { 1 } + bar(3) { 2 } +} + +# bmethod with forwarded unwanted block +assert_equal '1', %q{ + one = 1 + define_method(:foo) do + one + end + + def bar(&block) + foo(&block) + end + + bar { } + bar { } +} diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index 09b5989a066092..d740870736c6bc 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -513,9 +513,8 @@ def foo &blk RUBY end - def test_getblockparamproxy_with_no_block - # Currently side exits on the send - assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: { send: 2 }) + def test_send_blockarg + assert_compiles(<<~'RUBY', insns: [:getblockparamproxy, :send], exits: {}) def bar end @@ -526,6 +525,9 @@ def foo &blk foo foo + + foo { } + foo { } RUBY end diff --git a/yjit.c b/yjit.c index 7e6fc9e3fb5ff9..c53444d5a3824c 100644 --- a/yjit.c +++ b/yjit.c @@ -592,6 +592,12 @@ rb_get_iseq_body_local_iseq(const rb_iseq_t *iseq) return iseq->body->local_iseq; } +const rb_iseq_t * +rb_get_iseq_body_parent_iseq(const rb_iseq_t *iseq) +{ + return iseq->body->parent_iseq; +} + unsigned int rb_get_iseq_body_local_table_size(const rb_iseq_t *iseq) { diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 60a9d1b87d0dfa..21aaec84cbf95b 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -350,6 +350,7 @@ fn main() { .allowlist_function("rb_get_def_bmethod_proc") .allowlist_function("rb_iseq_encoded_size") .allowlist_function("rb_get_iseq_body_local_iseq") + .allowlist_function("rb_get_iseq_body_parent_iseq") .allowlist_function("rb_get_iseq_body_iseq_encoded") .allowlist_function("rb_get_iseq_body_stack_max") .allowlist_function("rb_get_iseq_flags_has_opt") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index d6ea8996e1161f..51645f0744e742 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -1542,6 +1542,22 @@ fn gen_get_ep(asm: &mut Assembler, level: u32) -> Opnd { ep_opnd } +// Gets the EP of the ISeq of the containing method, or "local level". +// Equivalent of GET_LEP() macro. +fn gen_get_lep(jit: &mut JITState, asm: &mut Assembler) -> Opnd { + // Equivalent of get_lvar_level() in compile.c + fn get_lvar_level(iseq: IseqPtr) -> u32 { + if iseq == unsafe { rb_get_iseq_body_local_iseq(iseq) } { + 0 + } else { + 1 + get_lvar_level(unsafe { rb_get_iseq_body_parent_iseq(iseq) }) + } + } + + let level = get_lvar_level(jit.get_iseq()); + gen_get_ep(asm, level) +} + fn gen_getlocal_generic( ctx: &mut Context, asm: &mut Assembler, @@ -3995,9 +4011,14 @@ unsafe extern "C" fn build_kwhash(ci: *const rb_callinfo, sp: *const VALUE) -> V hash } -enum BlockHandler { +// SpecVal is a single value in an iseq invocation's environment on the stack, +// at sp[-2]. Depending on the frame type, it can serve different purposes, +// which are covered here by enum variants. +enum SpecVal { None, - CurrentFrame, + BlockISeq(IseqPtr), + BlockParamProxy, + PrevEP(*const VALUE), } struct ControlFrame { @@ -4006,8 +4027,7 @@ struct ControlFrame { iseq: Option, pc: Option, frame_type: u32, - block_handler: BlockHandler, - prev_ep: Option<*const VALUE>, + specval: SpecVal, cme: *const rb_callable_method_entry_t, local_size: i32 } @@ -4028,7 +4048,7 @@ struct ControlFrame { // * Stack overflow is not checked (should be done by the caller) // * Interrupts are not checked (should be done by the caller) fn gen_push_frame( - _jit: &mut JITState, + jit: &mut JITState, _ctx: &mut Context, asm: &mut Assembler, set_sp_cfp: bool, // if true CFP and SP will be switched to the callee @@ -4060,19 +4080,33 @@ fn gen_push_frame( // Write special value at sp[-2]. It's either a block handler or a pointer to // the outer environment depending on the frame type. // sp[-2] = specval; - let specval: Opnd = match (frame.prev_ep, frame.block_handler) { - (None, BlockHandler::None) => { + let specval: Opnd = match frame.specval { + SpecVal::None => { VM_BLOCK_HANDLER_NONE.into() } - (None, BlockHandler::CurrentFrame) => { + SpecVal::BlockISeq(block_iseq) => { + // Change cfp->block_code in the current frame. See vm_caller_setup_arg_block(). + // VM_CFP_TO_CAPTURED_BLOCK does &cfp->self, rb_captured_block->code.iseq aliases + // with cfp->block_code. + asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_BLOCK_CODE), VALUE::from(block_iseq).into()); + let cfp_self = asm.lea(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF)); asm.or(cfp_self, Opnd::Imm(1)) } - (Some(prev_ep), BlockHandler::None) => { + SpecVal::BlockParamProxy => { + let ep_opnd = gen_get_lep(jit, asm); + let block_handler = asm.load( + Opnd::mem(64, ep_opnd, (SIZEOF_VALUE as i32) * (VM_ENV_DATA_INDEX_SPECVAL as i32)) + ); + + asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_BLOCK_CODE), block_handler); + + block_handler + } + SpecVal::PrevEP(prev_ep) => { let tagged_prev_ep = (prev_ep as usize) | 1; VALUE(tagged_prev_ep).into() } - (_, _) => panic!("specval can only be one of prev_ep or block_handler") }; asm.store(Opnd::mem(64, sp, SIZEOF_VALUE_I32 * -2), specval); @@ -4218,6 +4252,43 @@ fn gen_send_cfunc( return CantCompile; } + let block_arg = flags & VM_CALL_ARGS_BLOCKARG != 0; + let block_arg_type = if block_arg { + Some(ctx.get_opnd_type(StackOpnd(0))) + } else { + None + }; + + match block_arg_type { + Some(Type::Nil | Type::BlockParamProxy) => { + // We'll handle this later + } + None => { + // Nothing to do + } + _ => { + gen_counter_incr!(asm, send_block_arg); + return CantCompile; + } + } + + match block_arg_type { + Some(Type::Nil) => { + // We have a nil block arg, so let's pop it off the args + ctx.stack_pop(1); + } + Some(Type::BlockParamProxy) => { + // We don't need the actual stack value + ctx.stack_pop(1); + } + None => { + // Nothing to do + } + _ => { + assert!(false); + } + } + // This is a .send call and we need to adjust the stack if flags & VM_CALL_OPT_SEND != 0 { handle_opt_send_shift_stack(asm, argc as i32, ctx); @@ -4229,21 +4300,16 @@ fn gen_send_cfunc( // Store incremented PC into current control frame in case callee raises. jit_save_pc(jit, asm); - if let Some(block_iseq) = block { - // Change cfp->block_code in the current frame. See vm_caller_setup_arg_block(). - // VM_CFP_TO_CAPTURED_BLOCK does &cfp->self, rb_captured_block->code.iseq aliases - // with cfp->block_code. - asm.mov(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_BLOCK_CODE), VALUE::from(block_iseq).into()); - } - // Increment the stack pointer by 3 (in the callee) // sp += 3 let sp = asm.lea(ctx.sp_opnd((SIZEOF_VALUE as isize) * 3)); - let frame_block_handler = if let Some(_block_iseq) = block { - BlockHandler::CurrentFrame + let specval = if block_arg_type == Some(Type::BlockParamProxy) { + SpecVal::BlockParamProxy + } else if let Some(block_iseq) = block { + SpecVal::BlockISeq(block_iseq) } else { - BlockHandler::None + SpecVal::None }; let mut frame_type = VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL; @@ -4253,11 +4319,10 @@ fn gen_send_cfunc( gen_push_frame(jit, ctx, asm, false, ControlFrame { frame_type, - block_handler: frame_block_handler, + specval, cme, recv, sp, - prev_ep: None, pc: Some(0), iseq: None, local_size: 0, @@ -4606,6 +4671,26 @@ fn gen_send_iseq( return CantCompile; } + let block_arg = flags & VM_CALL_ARGS_BLOCKARG != 0; + let block_arg_type = if block_arg { + Some(ctx.get_opnd_type(StackOpnd(0))) + } else { + None + }; + + match block_arg_type { + Some(Type::Nil | Type::BlockParamProxy) => { + // We'll handle this later + } + None => { + // Nothing to do + } + _ => { + gen_counter_incr!(asm, send_block_arg); + return CantCompile; + } + } + // If we have unfilled optional arguments and keyword arguments then we // would need to move adjust the arguments location to account for that. // For now we aren't handling this case. @@ -4700,6 +4785,23 @@ fn gen_send_iseq( // Check for interrupts gen_check_ints(asm, side_exit); + match block_arg_type { + Some(Type::Nil) => { + // We have a nil block arg, so let's pop it off the args + ctx.stack_pop(1); + } + Some(Type::BlockParamProxy) => { + // We don't need the actual stack value + ctx.stack_pop(1); + } + None => { + // Nothing to do + } + _ => { + assert!(false); + } + } + let leaf_builtin_raw = unsafe { rb_leaf_builtin_function(iseq) }; let leaf_builtin: Option<*const rb_builtin_function> = if leaf_builtin_raw.is_null() { None @@ -4914,32 +5016,30 @@ fn gen_send_iseq( // Store the next PC in the current frame jit_save_pc(jit, asm); - if let Some(block_val) = block { - // Change cfp->block_code in the current frame. See vm_caller_setup_arg_block(). - // VM_CFP_TO_CAPTURED_BLCOK does &cfp->self, rb_captured_block->code.iseq aliases - // with cfp->block_code. - asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_BLOCK_CODE), VALUE(block_val as usize).into()); - } - // Adjust the callee's stack pointer let offs = (SIZEOF_VALUE as isize) * (3 + (num_locals as isize) + if doing_kw_call { 1 } else { 0 }); let callee_sp = asm.lea(ctx.sp_opnd(offs)); - let frame_block_handler = if let Some(_) = block { - BlockHandler::CurrentFrame + let specval = if let Some(prev_ep) = prev_ep { + // We've already side-exited if the callee expects a block, so we + // ignore any supplied block here + SpecVal::PrevEP(prev_ep) + } else if block_arg_type == Some(Type::BlockParamProxy) { + SpecVal::BlockParamProxy + } else if let Some(block_val) = block { + SpecVal::BlockISeq(block_val) } else { - BlockHandler::None + SpecVal::None }; // Setup the new frame gen_push_frame(jit, ctx, asm, true, ControlFrame { frame_type, - block_handler: frame_block_handler, + specval, cme, recv, sp: callee_sp, - prev_ep, iseq: Some(iseq), pc: None, // We are calling into jitted code, which will set the PC as necessary local_size: num_locals @@ -5146,26 +5246,23 @@ fn gen_send_general( return CantCompile; } - if flags & VM_CALL_ARGS_BLOCKARG != 0 { - gen_counter_incr!(asm, send_block_arg); - return CantCompile; - } - // Defer compilation so we can specialize on class of receiver if !jit_at_current_insn(jit) { defer_compilation(jit, ctx, asm, ocb); return EndBlock; } - let comptime_recv = jit_peek_at_stack(jit, ctx, argc as isize); + let recv_idx = argc + if flags & VM_CALL_ARGS_BLOCKARG != 0 { 1 } else { 0 }; + + let comptime_recv = jit_peek_at_stack(jit, ctx, recv_idx as isize); let comptime_recv_klass = comptime_recv.class_of(); // Guard that the receiver has the same class as the one from compile time let side_exit = get_side_exit(jit, ocb, ctx); // Points to the receiver operand on the stack - let recv = ctx.stack_opnd(argc); - let recv_opnd = StackOpnd(argc.try_into().unwrap()); + let recv = ctx.stack_opnd(recv_idx); + let recv_opnd = StackOpnd(recv_idx.try_into().unwrap()); jit_guard_known_klass( jit, ctx, @@ -5273,6 +5370,11 @@ fn gen_send_general( let ivar_name = unsafe { get_cme_def_body_attr_id(cme) }; + if flags & VM_CALL_ARGS_BLOCKARG != 0 { + gen_counter_incr!(asm, send_block_arg); + return CantCompile; + } + return gen_get_ivar( jit, ctx, @@ -5298,6 +5400,9 @@ fn gen_send_general( // See :attr-tracing: gen_counter_incr!(asm, send_cfunc_tracing); return CantCompile; + } else if flags & VM_CALL_ARGS_BLOCKARG != 0 { + gen_counter_incr!(asm, send_block_arg); + return CantCompile; } else { let ivar_name = unsafe { get_cme_def_body_attr_id(cme) }; return gen_set_ivar(jit, ctx, asm, comptime_recv, ivar_name, flags, argc); @@ -5326,6 +5431,10 @@ fn gen_send_general( } // Send family of methods, e.g. call/apply VM_METHOD_TYPE_OPTIMIZED => { + if flags & VM_CALL_ARGS_BLOCKARG != 0 { + gen_counter_incr!(asm, send_block_arg); + return CantCompile; + } let opt_type = unsafe { get_cme_def_body_optimized_type(cme) }; match opt_type { @@ -6191,7 +6300,7 @@ fn gen_getblockparamproxy( // Push rb_block_param_proxy. It's a root, so no need to use jit_mov_gc_ptr. assert!(!unsafe { rb_block_param_proxy }.special_const_p()); - let top = ctx.stack_push(Type::Unknown); + let top = ctx.stack_push(Type::BlockParamProxy); asm.mov(top, Opnd::const_ptr(unsafe { rb_block_param_proxy }.as_ptr())); } diff --git a/yjit/src/core.rs b/yjit/src/core.rs index 19272350ed32a6..9288a0188b83ad 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -44,6 +44,9 @@ pub enum Type { TString, // An object with the T_STRING flag set, possibly an rb_cString CString, // An un-subclassed string of type rb_cString (can have instance vars in some cases) + + BlockParamProxy, // A special sentinel value indicating the block parameter should be read from + // the current surrounding cfp } // Default initialization @@ -79,6 +82,12 @@ impl Type { if val.class_of() == unsafe { rb_cString } { return Type::CString; } + // We likewise can't reference rb_block_param_proxy, but it's again an optimisation; + // we can just treat it as a normal Object. + #[cfg(not(test))] + if val == unsafe { rb_block_param_proxy } { + return Type::BlockParamProxy; + } match val.builtin_type() { RUBY_T_ARRAY => Type::Array, RUBY_T_HASH => Type::Hash, @@ -142,7 +151,8 @@ impl Type { Type::Hash => Some(RUBY_T_HASH), Type::ImmSymbol | Type::HeapSymbol => Some(RUBY_T_SYMBOL), Type::TString | Type::CString => Some(RUBY_T_STRING), - Type::Unknown | Type::UnknownImm | Type::UnknownHeap => None + Type::Unknown | Type::UnknownImm | Type::UnknownHeap => None, + Type::BlockParamProxy => None, } } diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index a9242e05ca5e26..da7b332e07f841 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1418,6 +1418,9 @@ extern "C" { extern "C" { pub fn rb_get_iseq_body_local_iseq(iseq: *const rb_iseq_t) -> *const rb_iseq_t; } +extern "C" { + pub fn rb_get_iseq_body_parent_iseq(iseq: *const rb_iseq_t) -> *const rb_iseq_t; +} extern "C" { pub fn rb_get_iseq_body_local_table_size(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; } From a8e2d73628fd859dd6d33d989e4a0ee22a0c74df Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 26 Oct 2022 13:52:07 -0700 Subject: [PATCH 035/148] Fix the url of Cirrus failure notifications [ci skip] The previous one was the url of this notification action, but we want to see a link to Cirrus CI. This code follows https://cirrus-ci.org/guide/notifications/. Also, head_branch was null only for pull requests from a fork, so we can use it for branch-based filtering, which is good. --- .github/workflows/cirrus-notify.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cirrus-notify.yml b/.github/workflows/cirrus-notify.yml index bbc84bf93bb2e2..fff717f1d6a386 100644 --- a/.github/workflows/cirrus-notify.yml +++ b/.github/workflows/cirrus-notify.yml @@ -10,23 +10,33 @@ jobs: && github.event.check_suite.conclusion != 'success' && github.event.check_suite.conclusion != 'cancelled' && github.event.check_suite.conclusion != 'skipped' + && github.event.check_suite.head_branch == 'master' runs-on: ubuntu-latest steps: + - uses: octokit/request-action@v2.x + id: get_failed_check_run + with: + route: GET /repos/${{ github.repository }}/check-suites/${{ github.event.check_suite.id }}/check-runs?status=completed + mediaType: '{"previews": ["antiope"]}' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" + - name: Dump check_runs + env: + CHECK_RUNS: ${{ steps.get_failed_check_run.outputs.data }} + run: echo "$CHECK_RUNS" - uses: ruby/action-slack@v3.0.0 with: payload: | { "ci": "Cirrus CI", "env": "Cirrus CI", - "url": "https://github.com/ruby/ruby/actions/runs/${{ github.run_id }}", + "url": "${{ fromJson(steps.get_failed_check_run.outputs.data).check_runs[0].html_url }}", "commit": "${{ github.event.check_suite.head_commit.id }}", "branch": "${{ github.event.check_suite.head_branch }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot - # head_branch can be null. checking it here to see GITHUB_CONTEXT on master. - if: ${{ github.event.check_suite.head_branch == 'master' }} From 792dc553f19f99113284b72f59acfd7a7614dc4f Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 26 Oct 2022 15:20:33 -0700 Subject: [PATCH 036/148] YJIT: Test --yjit-verify-ctx on GitHub Actions as well (#6639) --- .cirrus.yml | 4 ++-- .github/workflows/yjit-ubuntu.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 800abb96388aa8..c496d5941bcede 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -129,5 +129,5 @@ yjit_task: full_build_script: source $HOME/.cargo/env && make cargo_test_script: source $HOME/.cargo/env && cd yjit && cargo test make_test_script: source $HOME/.cargo/env && make test RUN_OPTS="--yjit-call-threshold=1 --yjit-verify-ctx" - make_test_all_script: source $HOME/.cargo/env && make test-all RUN_OPTS="--yjit-call-threshold=1" TESTOPTS="$RUBY_TESTOPTS" - make_test_spec_script: source $HOME/.cargo/env && make test-spec RUN_OPTS="--yjit-call-threshold=1" + make_test_all_script: source $HOME/.cargo/env && make test-all RUN_OPTS="--yjit-call-threshold=1 --yjit-verify-ctx" TESTOPTS="$RUBY_TESTOPTS" + make_test_spec_script: source $HOME/.cargo/env && make test-spec RUN_OPTS="--yjit-call-threshold=1 --yjit-verify-ctx" diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 62356e8b45d6a5..d0e50154d2ca05 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -56,7 +56,7 @@ jobs: - test_task: "check" configure: "--enable-yjit=dev" - yjit_opts: "--yjit-call-threshold=1" + yjit_opts: "--yjit-call-threshold=1 --yjit-verify-ctx" - test_task: "test-all TESTS=--repeat-count=2" configure: "--enable-yjit=dev" From cb80ee7a4a3967be3a0ce687ec76522f1680600b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Oct 2022 21:20:17 +0900 Subject: [PATCH 037/148] [ruby/tmpdir] Warnings should contain the environment variable name --- test/test_tmpdir.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_tmpdir.rb b/test/test_tmpdir.rb index 1adb6a70b7e2bf..eada416f6fceb5 100644 --- a/test/test_tmpdir.rb +++ b/test/test_tmpdir.rb @@ -23,14 +23,14 @@ def test_world_writable ENV[e] = tmpdirx assert_not_equal(tmpdirx, assert_warn('') {Dir.tmpdir}) File.write(tmpdirx, "") - assert_not_equal(tmpdirx, assert_warn(/not a directory/) {Dir.tmpdir}) + assert_not_equal(tmpdirx, assert_warn(/\A#{e} is not a directory/) {Dir.tmpdir}) File.unlink(tmpdirx) ENV[e] = tmpdir assert_equal(tmpdir, Dir.tmpdir) File.chmod(0555, tmpdir) - assert_not_equal(tmpdir, assert_warn(/not writable/) {Dir.tmpdir}) + assert_not_equal(tmpdir, assert_warn(/\A#{e} is not writable/) {Dir.tmpdir}) File.chmod(0777, tmpdir) - assert_not_equal(tmpdir, assert_warn(/world-writable/) {Dir.tmpdir}) + assert_not_equal(tmpdir, assert_warn(/\A#{e} is world-writable/) {Dir.tmpdir}) newdir = Dir.mktmpdir("d", tmpdir) do |dir| assert_file.directory? dir assert_equal(tmpdir, File.dirname(dir)) From 739ad81ff1ba28608fbc492cc0b09f96c0bd463a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 27 Oct 2022 14:12:27 +0900 Subject: [PATCH 038/148] [ruby/date] Check month range as civil --- ext/date/date_core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 96653e0a7879e2..e58da719e0c23a 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -761,6 +761,8 @@ c_valid_civil_p(int y, int m, int d, double sg, if (m < 0) m += 13; + if (m < 1 || m > 12) + return 0; if (d < 0) { if (!c_find_ldom(y, m, sg, rjd, ns)) return 0; From 5129ca3e056e1ce3189ba39fa311d4d687b97b45 Mon Sep 17 00:00:00 2001 From: Shugo Maeda Date: Thu, 27 Oct 2022 10:29:02 +0900 Subject: [PATCH 039/148] [ruby/rdoc] Delay `require "readline"` in case the terminal is in raw mode --- lib/rdoc/ri/driver.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb index d24f8d5eff7ead..819cff8aa3dd04 100644 --- a/lib/rdoc/ri/driver.rb +++ b/lib/rdoc/ri/driver.rb @@ -1,11 +1,6 @@ # frozen_string_literal: true require 'optparse' -begin - require 'readline' -rescue LoadError -end - require_relative '../../rdoc' require_relative 'formatter' # For RubyGems backwards compatibility @@ -1079,6 +1074,10 @@ def formatter(io) def interactive puts "\nEnter the method name you want to look up." + begin + require 'readline' + rescue LoadError + end if defined? Readline then Readline.completion_proc = method :complete puts "You can use tab to autocomplete." From 26b913c88b087a93f5a7a5f8282d1934b6c5073e Mon Sep 17 00:00:00 2001 From: st0012 Date: Thu, 27 Oct 2022 11:55:40 +0100 Subject: [PATCH 040/148] [ruby/irb] Add test for IRB::InputCompletor::PerfectMatchedProc This proc displays rdoc document when the input matches certain symbols perfectly, like "String". It's commonly triggered with autocompletion but only has 1 test case. So this commit increases its test coverage. https://github.com/ruby/irb/commit/d85d719313 --- test/irb/test_completion.rb | 94 +++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index df5a78c69dcd44..e2af6029bc01a5 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -211,6 +211,96 @@ def test_complete_constants end end + class TestPerfectMatching < TestCompletion + def setup + # trigger PerfectMatchedProc to set up RDocRIDriver constant + IRB::InputCompletor::PerfectMatchedProc.("foo", bind: binding) + + @original_use_stdout = IRB::InputCompletor::RDocRIDriver.use_stdout + # force the driver to use stdout so it doesn't start a pager and interrupt tests + IRB::InputCompletor::RDocRIDriver.use_stdout = true + end + + def teardown + IRB::InputCompletor::RDocRIDriver.use_stdout = @original_use_stdout + end + + def test_perfectly_matched_namespace_triggers_document_display + omit unless has_rdoc_content? + + out, err = capture_output do + IRB::InputCompletor::PerfectMatchedProc.("String", bind: binding) + end + + assert_empty(err) + + assert_include(out, " S\bSt\btr\bri\bin\bng\bg") + end + + def test_perfectly_matched_multiple_namespaces_triggers_document_display + result = nil + out, err = capture_output do + result = IRB::InputCompletor::PerfectMatchedProc.("{}.nil?", bind: binding) + end + + assert_empty(err) + + # check if there're rdoc contents (e.g. CI doesn't generate them) + if has_rdoc_content? + # if there's rdoc content, we can verify by checking stdout + # rdoc generates control characters for formatting method names + assert_include(out, "P\bPr\bro\boc\bc.\b.n\bni\bil\bl?\b?") # Proc.nil? + assert_include(out, "H\bHa\bas\bsh\bh.\b.n\bni\bil\bl?\b?") # Hash.nil? + else + # this is a hacky way to verify the rdoc rendering code path because CI doesn't have rdoc content + # if there are multiple namespaces to be rendered, PerfectMatchedProc renders the result with a document + # which always returns the bytes rendered, even if it's 0 + assert_equal(0, result) + end + end + + def test_not_matched_namespace_triggers_nothing + result = nil + out, err = capture_output do + result = IRB::InputCompletor::PerfectMatchedProc.("Stri", bind: binding) + end + + assert_empty(err) + assert_empty(out) + assert_nil(result) + end + + def test_perfect_matching_stops_without_rdoc + result = nil + + out, err = capture_output do + IRB::TestHelper.without_rdoc do + result = IRB::InputCompletor::PerfectMatchedProc.("String", bind: binding) + end + end + + assert_empty(err) + assert_not_match(/from ruby core/, out) + assert_nil(result) + end + + def test_perfect_matching_handles_nil_namespace + out, err = capture_output do + # symbol literal has `nil` doc namespace so it's a good test subject + assert_nil(IRB::InputCompletor::PerfectMatchedProc.(":aiueo", bind: binding)) + end + + assert_empty(err) + assert_empty(out) + end + + private + + def has_rdoc_content? + File.exist?(RDoc::RI::Paths::BASE) + end + end + def test_complete_symbol %w"UTF-16LE UTF-7".each do |enc| "K".force_encoding(enc).to_sym @@ -233,10 +323,6 @@ def test_complete_absolute_constants_with_special_characters assert_empty(IRB::InputCompletor.retrieve_completion_data("::A)", bind: binding)) end - def test_complete_symbol_failure - assert_nil(IRB::InputCompletor::PerfectMatchedProc.(":aiueo", bind: binding)) - end - def test_complete_reserved_words candidates = IRB::InputCompletor.retrieve_completion_data("de", bind: binding) %w[def defined?].each do |word| From 7cf7e6c33226093c9c4e3078f7ced3b9b9c99b05 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 27 Oct 2022 16:30:42 +0100 Subject: [PATCH 041/148] [ruby/irb] Add missing require --- test/irb/test_completion.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index e2af6029bc01a5..e9b422fe7aa405 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -3,6 +3,8 @@ require "pathname" require "irb" +require_relative "helper" + module TestIRB class TestCompletion < Test::Unit::TestCase def setup From c6f439a6a8df582416e756d7511aa4d9c72071a9 Mon Sep 17 00:00:00 2001 From: "S.H" Date: Fri, 28 Oct 2022 01:13:16 +0900 Subject: [PATCH 042/148] Improve performance some `Integer` and `Float` methods [Feature #19085] (#6638) * Improve some Integer and Float methods * Using alias and Remove unnecessary code * Remove commentout code --- benchmark/numeric_methods.yml | 16 ++++++++ complex.c | 43 -------------------- numeric.rb | 74 ++++++++++++++++++++++++++++++----- rational.c | 27 ------------- 4 files changed, 81 insertions(+), 79 deletions(-) diff --git a/benchmark/numeric_methods.yml b/benchmark/numeric_methods.yml index 433c2268a37027..138490293538b4 100644 --- a/benchmark/numeric_methods.yml +++ b/benchmark/numeric_methods.yml @@ -10,4 +10,20 @@ benchmark: int.finite? infinite?: | int.infinite? + integer_real: | + int.real + float_real: | + flo.real + integr_imag: | + int.imag + float_imag: | + flo.imag + integer_conj: | + int.conj + float_conj: | + flo.conj + integer_numerator: | + int.numerator + integer_denominator: | + int.denominator loop_count: 20000000 diff --git a/complex.c b/complex.c index db114cd9142262..d927f62d5578ec 100644 --- a/complex.c +++ b/complex.c @@ -2159,31 +2159,6 @@ nucomp_s_convert(int argc, VALUE *argv, VALUE klass) return nucomp_convert(klass, a1, a2, TRUE); } -/* - * call-seq: - * num.real -> self - * - * Returns self. - */ -static VALUE -numeric_real(VALUE self) -{ - return self; -} - -/* - * call-seq: - * num.imag -> 0 - * num.imaginary -> 0 - * - * Returns zero. - */ -static VALUE -numeric_imag(VALUE self) -{ - return INT2FIX(0); -} - /* * call-seq: * num.abs2 -> real @@ -2255,19 +2230,6 @@ numeric_polar(VALUE self) return rb_assoc_new(abs, arg); } -/* - * call-seq: - * num.conj -> self - * num.conjugate -> self - * - * Returns self. - */ -static VALUE -numeric_conj(VALUE self) -{ - return self; -} - /* * call-seq: * flo.arg -> 0 or float @@ -2433,9 +2395,6 @@ Init_Complex(void) rb_define_private_method(CLASS_OF(rb_cComplex), "convert", nucomp_s_convert, -1); - rb_define_method(rb_cNumeric, "real", numeric_real, 0); - rb_define_method(rb_cNumeric, "imaginary", numeric_imag, 0); - rb_define_method(rb_cNumeric, "imag", numeric_imag, 0); rb_define_method(rb_cNumeric, "abs2", numeric_abs2, 0); rb_define_method(rb_cNumeric, "arg", numeric_arg, 0); rb_define_method(rb_cNumeric, "angle", numeric_arg, 0); @@ -2443,8 +2402,6 @@ Init_Complex(void) rb_define_method(rb_cNumeric, "rectangular", numeric_rect, 0); rb_define_method(rb_cNumeric, "rect", numeric_rect, 0); rb_define_method(rb_cNumeric, "polar", numeric_polar, 0); - rb_define_method(rb_cNumeric, "conjugate", numeric_conj, 0); - rb_define_method(rb_cNumeric, "conj", numeric_conj, 0); rb_define_method(rb_cFloat, "arg", float_arg, 0); rb_define_method(rb_cFloat, "angle", float_arg, 0); diff --git a/numeric.rb b/numeric.rb index c2091465f88fd5..f02667921063c6 100644 --- a/numeric.rb +++ b/numeric.rb @@ -6,7 +6,17 @@ class Numeric # Returns +true+ if +num+ is a real number (i.e. not Complex). # def real? - return true + true + end + + # + # call-seq: + # num.real -> self + # + # Returns self. + # + def real + self end # @@ -19,7 +29,7 @@ def real? # 1.integer? #=> true # def integer? - return false + false end # @@ -29,7 +39,7 @@ def integer? # Returns +true+ if +num+ is a finite number, otherwise returns +false+. # def finite? - return true + true end # @@ -40,8 +50,34 @@ def finite? # finite, -Infinity, or +Infinity. # def infinite? - return nil + nil + end + + # + # call-seq: + # num.imag -> 0 + # num.imaginary -> 0 + # + # Returns zero. + # + def imaginary + 0 + end + + alias imag imaginary + + # + # call-seq: + # num.conj -> self + # num.conjugate -> self + # + # Returns self. + # + def conjugate + self end + + alias conj conjugate end class Integer @@ -146,7 +182,7 @@ def even? # # Since +int+ is already an Integer, this always returns +true+. def integer? - return true + true end alias magnitude abs @@ -178,7 +214,7 @@ def odd? # # For example, ?a.ord returns 97 both in 1.8 and 1.9. def ord - return self + self end # @@ -208,7 +244,7 @@ def size # # #to_int is an alias for #to_i. def to_i - return self + self end # call-seq: @@ -216,7 +252,7 @@ def to_i # # Since +int+ is already an Integer, returns +self+. def to_int - return self + self end # call-seq: @@ -244,6 +280,26 @@ def zero? def ceildiv(other) -div(-other) end + + # + # call-seq: + # int.numerator -> self + # + # Returns self. + # + def numerator + self + end + + # + # call-seq: + # int.denominator -> 1 + # + # Returns 1. + # + def denominator + 1 + end end # call-seq: @@ -276,7 +332,7 @@ class Float # Since +float+ is already a Float, returns +self+. # def to_f - return self + self end # diff --git a/rational.c b/rational.c index e537bd498b072e..48a9ab2ed28932 100644 --- a/rational.c +++ b/rational.c @@ -2059,30 +2059,6 @@ rb_rational_canonicalize(VALUE x) return x; } -/* - * call-seq: - * int.numerator -> self - * - * Returns self. - */ -static VALUE -integer_numerator(VALUE self) -{ - return self; -} - -/* - * call-seq: - * int.denominator -> 1 - * - * Returns 1. - */ -static VALUE -integer_denominator(VALUE self) -{ - return INT2FIX(1); -} - /* * call-seq: * flo.numerator -> integer @@ -2832,9 +2808,6 @@ Init_Rational(void) rb_define_method(rb_cNumeric, "denominator", numeric_denominator, 0); rb_define_method(rb_cNumeric, "quo", rb_numeric_quo, 1); - rb_define_method(rb_cInteger, "numerator", integer_numerator, 0); - rb_define_method(rb_cInteger, "denominator", integer_denominator, 0); - rb_define_method(rb_cFloat, "numerator", rb_float_numerator, 0); rb_define_method(rb_cFloat, "denominator", rb_float_denominator, 0); From 8d7844235cd9ba9836e9eacb74c75d31aaa567b3 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 27 Oct 2022 20:54:12 +0200 Subject: [PATCH 043/148] [ruby/irb] Remove unecesary and harmful pend for TruffleRuby in TestRaiseNoBacktraceException * Specifically the second one causes `$HOME` to be unset, which breaks `File.expand_path('~')`. https://github.com/ruby/irb/commit/61963305f5 --- test/irb/test_raise_no_backtrace_exception.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/irb/test_raise_no_backtrace_exception.rb b/test/irb/test_raise_no_backtrace_exception.rb index 530adc0cbf1cc3..5f11c9f3dc6b31 100644 --- a/test/irb/test_raise_no_backtrace_exception.rb +++ b/test/irb/test_raise_no_backtrace_exception.rb @@ -4,7 +4,6 @@ module TestIRB class TestRaiseNoBacktraceException < Test::Unit::TestCase def test_raise_exception - pend if RUBY_ENGINE == 'truffleruby' bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /Exception: foo/, []) e = Exception.new("foo") @@ -23,7 +22,6 @@ def test_raise_exception_with_invalid_byte_sequence end def test_raise_exception_with_different_encoding_containing_invalid_byte_sequence - pend if RUBY_ENGINE == 'truffleruby' backup_home = ENV["HOME"] Dir.mktmpdir("test_irb_raise_no_backtrace_exception_#{$$}") do |tmpdir| ENV["HOME"] = tmpdir From bb7067cbdfe1cf1b8bbd742e87f41c9707ddb2d2 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 27 Oct 2022 21:10:39 +0200 Subject: [PATCH 044/148] [ruby/irb] Suppress warning for test which uses a locale non-existing on GitHub Actions --- test/irb/test_raise_no_backtrace_exception.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/irb/test_raise_no_backtrace_exception.rb b/test/irb/test_raise_no_backtrace_exception.rb index 5f11c9f3dc6b31..a532d8b3ec1066 100644 --- a/test/irb/test_raise_no_backtrace_exception.rb +++ b/test/irb/test_raise_no_backtrace_exception.rb @@ -38,6 +38,8 @@ def raise_euc_with_invalid_byte_sequence end env = {} %w(LC_MESSAGES LC_ALL LC_CTYPE LANG).each {|n| env[n] = "ja_JP.UTF-8" } + # TruffleRuby warns when the locale does not exist + env['TRUFFLERUBYOPT'] = "#{ENV['TRUFFLERUBYOPT']} --log.level=SEVERE" if RUBY_ENGINE == 'truffleruby' args = [env] + bundle_exec + %W[-rirb -C #{tmpdir} -W0 -e IRB.start(__FILE__) -- -f --] error = /`raise_euc_with_invalid_byte_sequence': あ\\xFF \(RuntimeError\)/ assert_in_out_err(args, <<~IRB, error, [], encoding: "UTF-8") From b260c1e8c8a2b6c4cef944e3247ca743079478df Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Tue, 25 Oct 2022 20:43:45 +0100 Subject: [PATCH 045/148] [ruby/irb] Remove unnecessary test setup --- test/irb/test_cmd.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 2fe60a7ca8ec42..ca081b65d558bb 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -414,10 +414,8 @@ def test_help "help 'String#gsub'\n", "\n", ]) - IRB.conf[:VERBOSE] = false IRB.conf[:PROMPT_MODE] = :SIMPLE irb = IRB::Irb.new(IRB::WorkSpace.new(self), input) - IRB.conf[:MAIN_CONTEXT] = irb.context out, err = capture_output do irb.eval_input end @@ -437,10 +435,8 @@ def test_help_without_rdoc "help 'String#gsub'\n", "\n", ]) - IRB.conf[:VERBOSE] = false IRB.conf[:PROMPT_MODE] = :SIMPLE irb = IRB::Irb.new(IRB::WorkSpace.new(self), input) - IRB.conf[:MAIN_CONTEXT] = irb.context out, err = capture_output do IRB::TestHelper.without_rdoc do irb.eval_input From ea5972572b116071a5b62d001c7d39e7a080779c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 27 Oct 2022 15:24:05 -0400 Subject: [PATCH 046/148] [ruby/irb] Fix warnings in test_cmd.rb Fixes this warning: warning: assigned but unused variable - err https://github.com/ruby/irb/commit/298fcb57a3 --- test/irb/test_cmd.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index ca081b65d558bb..1739bc293da73f 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -416,7 +416,7 @@ def test_help ]) IRB.conf[:PROMPT_MODE] = :SIMPLE irb = IRB::Irb.new(IRB::WorkSpace.new(self), input) - out, err = capture_output do + out, _ = capture_output do irb.eval_input end @@ -437,7 +437,7 @@ def test_help_without_rdoc ]) IRB.conf[:PROMPT_MODE] = :SIMPLE irb = IRB::Irb.new(IRB::WorkSpace.new(self), input) - out, err = capture_output do + out, _ = capture_output do IRB::TestHelper.without_rdoc do irb.eval_input end From 2812a57b14de8bfab7feccb7924039edc2611aad Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Thu, 27 Oct 2022 16:50:00 -0400 Subject: [PATCH 047/148] Update docs wrt YJIT limitations and building YJIT (#6641) * Update docs wrt YJIT limitations and building YJIT * Update building_ruby.md Fix relative link --- doc/contributing/building_ruby.md | 1 + doc/yjit/yjit.md | 10 +++------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index 52d6042ec3a41d..d2f175f4a9f9c9 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -18,6 +18,7 @@ * libffi * libyaml * libexecinfo (FreeBSD) + * rustc - 1.58.1 or later (if you wish to build [YJIT](/doc/yjit/yjit.md)) 3. Checkout the CRuby source code: diff --git a/doc/yjit/yjit.md b/doc/yjit/yjit.md index d4a15d0c7795e7..bf11eb523d5b79 100644 --- a/doc/yjit/yjit.md +++ b/doc/yjit/yjit.md @@ -41,13 +41,9 @@ To cite this repository in your publications, please use this bibtex snippet: ## Current Limitations -YJIT is a work in progress and as such may not yet be mature enough for mission-critical software. Below is a list of known limitations, all of which we plan to eventually address: - -- No garbage collection for generated code. -- Currently supports only macOS and Linux. -- Supports x86-64 and arm64/aarch64 CPUs only. - -Because there is no GC for generated code yet, your software could run out of executable memory if it is large enough. You can change how much executable memory is allocated using [YJIT's command-line options](#command-line-options). +YJIT may not be suitable for certain applications. It currently only supports macOS and Linux on x86-64 and arm64/aarch64 CPUs. YJIT will use more memory than the Ruby interpreter because the JIT compiler needs to generate machine code in memory and maintain additional state +information. +You can change how much executable memory is allocated using [YJIT's command-line options](#command-line-options). There is a slight performance tradeoff because allocating less executable memory could result in the generated machine code being collected more often. ## Installation From 9cf027f83afba538bea050ad19f4f597c50dcfc1 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 27 Oct 2022 18:52:58 -0400 Subject: [PATCH 048/148] YJIT: Use guard_known_class() for opt_aref on Arrays (#6643) This code used to roll its own heap object check before we made a better version in guard_known_class(). The improved version uses one fewer comparison, so let's use that. --- yjit/src/codegen.rs | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 51645f0744e742..bd40af9f797312 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -2626,9 +2626,6 @@ fn gen_opt_aref( return EndBlock; } - // Remember the context on entry for adding guard chains - let starting_context = *ctx; - // Specialize base on compile time values let comptime_idx = jit_peek_at_stack(jit, ctx, 0); let comptime_recv = jit_peek_at_stack(jit, ctx, 1); @@ -2641,29 +2638,21 @@ fn gen_opt_aref( return CantCompile; } - // Pop the stack operands - let idx_opnd = ctx.stack_pop(1); - let recv_opnd = ctx.stack_pop(1); - let recv_reg = asm.load(recv_opnd); - - // if (SPECIAL_CONST_P(recv)) { - // Bail if receiver is not a heap object - asm.test(recv_reg, (RUBY_IMMEDIATE_MASK as u64).into()); - asm.jnz(side_exit.into()); - asm.cmp(recv_reg, Qfalse.into()); - asm.je(side_exit.into()); - asm.cmp(recv_reg, Qnil.into()); - asm.je(side_exit.into()); + // Get the stack operands + let idx_opnd = ctx.stack_opnd(0); + let recv_opnd = ctx.stack_opnd(1); - // Bail if recv has a class other than ::Array. + // Guard that the receiver is an ::Array // BOP_AREF check above is only good for ::Array. - asm.cmp(unsafe { rb_cArray }.into(), Opnd::mem(64, recv_reg, RUBY_OFFSET_RBASIC_KLASS)); - jit_chain_guard( - JCC_JNE, + jit_guard_known_klass( jit, - &starting_context, + ctx, asm, ocb, + unsafe { rb_cArray }, + recv_opnd, + StackOpnd(1), + comptime_recv, OPT_AREF_MAX_CHAIN_DEPTH, side_exit, ); @@ -2679,6 +2668,9 @@ fn gen_opt_aref( let idx_reg = asm.rshift(idx_reg, Opnd::UImm(1)); // Convert fixnum to int let val = asm.ccall(rb_ary_entry_internal as *const u8, vec![recv_opnd, idx_reg]); + // Pop the argument and the receiver + ctx.stack_pop(2); + // Push the return value onto the stack let stack_ret = ctx.stack_push(Type::Unknown); asm.mov(stack_ret, val); From e6e202234c0f5d9755cf2aa88b178371fd8b38e4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 28 Oct 2022 13:15:14 +0900 Subject: [PATCH 049/148] sync_default_gems.rb: append orignal commit URLs to subject only log --- tool/sync_default_gems.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 8d33004ffb00a3..b669be7f555945 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -439,11 +439,14 @@ def message_filter(repo, sha) subject.gsub!(/\G.{,67}[^\s.,][.,]*\K\s+/, "\n") end end + url = "#{url}/commit/#{sha[0,10]}\n" if log conv[log] log.sub!(/\s*(?=(?i:\nCo-authored-by:.*)*\Z)/) { - "\n\n" "#{url}/commit/#{sha[0,10]}\n" + "\n\n#{url}" } + else + log = url end print subject, "\n\n", log end From 7440fc3eb4ee6409f2204523c8c9875d8e80a208 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 28 Oct 2022 16:32:36 +0900 Subject: [PATCH 050/148] [ruby/irb] Suppress "switching inspect mode" messages https://github.com/ruby/irb/commit/565eeb3c19 --- test/irb/test_cmd.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 1739bc293da73f..e330aa3a4441b4 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -415,6 +415,7 @@ def test_help "\n", ]) IRB.conf[:PROMPT_MODE] = :SIMPLE + IRB.conf[:VERBOSE] = false irb = IRB::Irb.new(IRB::WorkSpace.new(self), input) out, _ = capture_output do irb.eval_input @@ -436,6 +437,7 @@ def test_help_without_rdoc "\n", ]) IRB.conf[:PROMPT_MODE] = :SIMPLE + IRB.conf[:VERBOSE] = false irb = IRB::Irb.new(IRB::WorkSpace.new(self), input) out, _ = capture_output do IRB::TestHelper.without_rdoc do From 13e968c1cdd2da470173e2cc15b44ebb936be534 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 28 Oct 2022 16:35:00 +0900 Subject: [PATCH 051/148] [ruby/irb] Suppress sequence to inspect asian ambiguous width https://github.com/ruby/irb/commit/a7097c5b80 --- test/irb/test_cmd.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index e330aa3a4441b4..060f70c9cc3aa2 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -113,7 +113,17 @@ def test_irb_info_singleline East\sAsian\sAmbiguous\sWidth:\s\d\n #{@is_win ? 'Code\spage:\s\d+\n' : ''} }x - assert_match expected, irb.context.main.irb_info.to_s + info = irb.context.main.irb_info + capture_output do + # Reline::Core#ambiguous_width may access STDOUT, not $stdout + stdout = STDOUT.dup + STDOUT.reopen(IO::NULL, "w") + info = info.to_s + ensure + STDOUT.reopen(stdout) + stdout.close + end + assert_match expected, info ensure ENV["LANG"] = lang_backup ENV["LC_ALL"] = lc_all_backup From 56c97a6621241db99f7c96740164bdd8f898d881 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 28 Oct 2022 18:30:20 +0900 Subject: [PATCH 052/148] [ruby/irb] Update regarding NO_COLOR value https://no-color.org has been updated (jcs/no_color#83): > Command-line software which adds ANSI color to its output by default should check for a `NO_COLOR` environment variable that, when present and **not an empty string** (regardless of its value), prevents the addition of ANSI color. https://github.com/ruby/irb/commit/46e0f7e370 Co-authored-by: Stan Lo --- lib/irb/init.rb | 2 +- test/irb/test_init.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/irb/init.rb b/lib/irb/init.rb index d9c4353f39ced4..11d10c5bab43f1 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -44,7 +44,7 @@ def IRB.init_config(ap_path) @CONF[:IRB_RC] = nil @CONF[:USE_SINGLELINE] = false unless defined?(ReadlineInputMethod) - @CONF[:USE_COLORIZE] = !ENV['NO_COLOR'] + @CONF[:USE_COLORIZE] = (nc = ENV['NO_COLOR']).nil? || nc.empty? @CONF[:USE_AUTOCOMPLETE] = true @CONF[:INSPECT_MODE] = true @CONF[:USE_TRACER] = false diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 3293b98d345264..074aae40709836 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -80,6 +80,10 @@ def test_no_color_environment_variable IRB.setup(__FILE__) refute IRB.conf[:USE_COLORIZE] + ENV['NO_COLOR'] = '' + IRB.setup(__FILE__) + assert IRB.conf[:USE_COLORIZE] + ENV['NO_COLOR'] = nil IRB.setup(__FILE__) assert IRB.conf[:USE_COLORIZE] From 4021c6565f9655bfd667152f76f22f7cc81c7e17 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 31 Mar 2022 12:51:04 +0900 Subject: [PATCH 053/148] [ruby/irb] Do not make non-existent XDG directory on start (https://github.com/ruby/irb/pull/357) https://github.com/ruby/irb/commit/298b134792 --- lib/irb/init.rb | 6 ++---- test/irb/test_init.rb | 3 +++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 11d10c5bab43f1..5409528faeb7e7 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -379,11 +379,9 @@ def IRB.rc_file_generators end if xdg_config_home = ENV["XDG_CONFIG_HOME"] irb_home = File.join(xdg_config_home, "irb") - unless File.exist? irb_home - require 'fileutils' - FileUtils.mkdir_p irb_home + if File.directory?(irb_home) + yield proc{|rc| irb_home + "/irb#{rc}"} end - yield proc{|rc| irb_home + "/irb#{rc}"} end if home = ENV["HOME"] yield proc{|rc| home+"/.irb#{rc}"} diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 074aae40709836..39c5cfa8a0acee 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -36,13 +36,16 @@ def test_setup_with_minimum_argv_does_not_change_dollar0 def test_rc_file tmpdir = @tmpdir Dir.chdir(tmpdir) do + ENV["XDG_CONFIG_HOME"] = "#{tmpdir}/xdg" IRB.conf[:RC_NAME_GENERATOR] = nil assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file) assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) + assert_file.not_exist?(tmpdir+"/xdg") IRB.conf[:RC_NAME_GENERATOR] = nil FileUtils.touch(tmpdir+"/.irb#{IRB::IRBRC_EXT}") assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file) assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) + assert_file.not_exist?(tmpdir+"/xdg") end end From 1de8a4286950b554093e4fea4ace85a312a7fce8 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 28 Oct 2022 19:04:10 +0900 Subject: [PATCH 054/148] sync_default_gems.rb: do not add extra empty lines [ci skip] --- tool/sync_default_gems.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index b669be7f555945..7543b4f73ccc7c 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -422,7 +422,7 @@ def message_filter(repo, sha) log = STDIN.read log.delete!("\r") url = "https://github.com/#{repo}" - subject, log = log.split("\n\n", 2) + subject, log = log.split(/\n(?:[\s\t]*(?:\n|\z))/, 2) conv = proc do |s| mod = true if s.gsub!(/\b(?:(?i:fix(?:e[sd])?) +)\K#(?=\d+\b)|\bGH-#?(?=\d+\b)|\(\K#(?=\d+\))/) { "#{url}/pull/" @@ -440,7 +440,7 @@ def message_filter(repo, sha) end end url = "#{url}/commit/#{sha[0,10]}\n" - if log + if log and !log.empty? conv[log] log.sub!(/\s*(?=(?i:\nCo-authored-by:.*)*\Z)/) { "\n\n#{url}" From 9b462aec4af0fbf9d82cbd3efe1d2cfe15f5e539 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 28 Oct 2022 19:33:19 +0900 Subject: [PATCH 055/148] Follow up "Rework `first_lineno` to be `int`." --- template/prelude.c.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/prelude.c.tmpl b/template/prelude.c.tmpl index 58453636bf15ce..428c9f4d014ebb 100644 --- a/template/prelude.c.tmpl +++ b/template/prelude.c.tmpl @@ -193,7 +193,7 @@ prelude_eval(VALUE code, VALUE name, int line) }; rb_ast_t *ast = prelude_ast(name, code, line); - rb_iseq_eval(rb_iseq_new_with_opt(&ast->body, name, name, Qnil, INT2FIX(line), + rb_iseq_eval(rb_iseq_new_with_opt(&ast->body, name, name, Qnil, line, NULL, 0, ISEQ_TYPE_TOP, &optimization)); rb_ast_dispose(ast); } From 5e0432f59bb85dd3d98be3c1043a1f9c5b41d86b Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 28 Oct 2022 16:10:45 -0700 Subject: [PATCH 056/148] fix ASAN error in GC --- gc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gc.c b/gc.c index b8c4bfb009b792..76a1f0f2de66fd 100644 --- a/gc.c +++ b/gc.c @@ -5217,7 +5217,9 @@ try_move(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *free_page, if (gc_is_moveable_obj(objspace, src)) { GC_ASSERT(MARKED_IN_BITMAP(GET_HEAP_MARK_BITS(src), src)); + asan_unlock_freelist(free_page); VALUE dest = (VALUE)free_page->freelist; + asan_lock_freelist(free_page); asan_unpoison_object(dest, false); if (!dest) { /* if we can't get something from the freelist then the page must be From c5ca250eb5a0e55391607cc20fa382cd64e49e5e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 29 Oct 2022 16:14:19 +0900 Subject: [PATCH 057/148] Clear `_FORTIFY_SOURCE` before definition As clang on macOS defines this macro as 0 internally when a sanitizer option is given, clear it before definition to suppress redefinition warnings. --- configure.ac | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 0e22aabbe55352..711ff997e73229 100644 --- a/configure.ac +++ b/configure.ac @@ -785,7 +785,8 @@ AS_IF([test "$GCC" = yes], [ [disable -D_FORTIFY_SOURCE=2 option, which causes link error on mingw]), [fortify_source=$enableval]) AS_IF([test "x$fortify_source" != xno], [ - RUBY_TRY_CFLAGS([$optflags -D_FORTIFY_SOURCE=2], [RUBY_APPEND_OPTION(XCFLAGS, -D_FORTIFY_SOURCE=2)], [], + RUBY_TRY_CFLAGS([$optflags -D_FORTIFY_SOURCE=2], + [RUBY_APPEND_OPTION(XCFLAGS, -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2)], [], [@%:@include ]) ]) From bc28acc347eace4d02bbb4b672655216f7dd3a81 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 29 Oct 2022 16:31:54 +0900 Subject: [PATCH 058/148] [ruby/digest] Use CommonDigest by default if available https://github.com/ruby/digest/commit/cce9ada85e --- ext/digest/digest_conf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/digest/digest_conf.rb b/ext/digest/digest_conf.rb index 1b929d87322577..36a7d75289d081 100644 --- a/ext/digest/digest_conf.rb +++ b/ext/digest/digest_conf.rb @@ -3,7 +3,7 @@ def digest_conf(name) unless with_config("bundled-#{name}") cc = with_config("common-digest") - if cc == true or /\b#{name}\b/ =~ cc + if cc != false or /\b#{name}\b/ =~ cc if File.exist?("#$srcdir/#{name}cc.h") and have_header("CommonCrypto/CommonDigest.h") $defs << "-D#{name.upcase}_USE_COMMONDIGEST" From 4dac53f0a48920f80ccaa4de151631659ab1d2ba Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 29 Oct 2022 22:23:14 +0900 Subject: [PATCH 059/148] [ruby/optparse] Add tests for `OptionParser#load` https://github.com/ruby/optparse/commit/fb34a1d3a3 --- test/optparse/test_load.rb | 139 +++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 test/optparse/test_load.rb diff --git a/test/optparse/test_load.rb b/test/optparse/test_load.rb new file mode 100644 index 00000000000000..de24f1c3809666 --- /dev/null +++ b/test/optparse/test_load.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: false +require 'test/unit' +require 'optparse' +require 'tmpdir' + +class TestOptionParserLoad < Test::Unit::TestCase + def setup + @tmpdir = Dir.mktmpdir("optparse_test-") + @basename = File.basename($0, '.*') + @envs = %w[HOME XDG_CONFIG_HOME XDG_CONFIG_DIRS].each_with_object({}) do |v, h| + h[v] = ENV.delete(v) + end + end + + def teardown + ENV.update(@envs) + FileUtils.rm_rf(@tmpdir) + end + + def new_parser + @result = nil + OptionParser.new do |opt| + opt.on("--test=arg") {|v| @result = v} + end + end + + def assert_load(result) + assert new_parser.load + assert_equal(result, @result) + end + + def setup_options(env, dir, suffix = nil) + optdir = File.join(@tmpdir, dir) + FileUtils.mkdir_p(optdir) + file = File.join(optdir, [@basename, suffix].join("")) + File.write(file, "--test=#{dir}") + ENV.update(env) + if block_given? + begin + yield dir, optdir + ensure + File.unlink(file) + Dir.rmdir(optdir) rescue nil + end + else + return dir, optdir + end + end + + def setup_options_home(&block) + setup_options({'HOME'=>@tmpdir}, ".options", &block) + end + + def setup_options_xdg_config_home(&block) + setup_options({'XDG_CONFIG_HOME'=>@tmpdir+"/xdg"}, "xdg", ".options", &block) + end + + def setup_options_home_config(&block) + setup_options({'HOME'=>@tmpdir}, ".config", ".options", &block) + end + + def setup_options_xdg_config_dirs(&block) + setup_options({'XDG_CONFIG_DIRS'=>@tmpdir+"/xdgconf"}, "xdgconf", ".options", &block) + end + + def setup_options_home_config_settings(&block) + setup_options({'HOME'=>@tmpdir}, "config/settings", ".options", &block) + end + + def test_load_home_options + result, = setup_options_home + assert_load(result) + + setup_options_xdg_config_home do + assert_load(result) + end + + setup_options_home_config do + assert_load(result) + end + + setup_options_xdg_config_dirs do + assert_load(result) + end + + setup_options_home_config_settings do + assert_load(result) + end + end + + def test_load_xdg_config_home + result, = setup_options_xdg_config_home + assert_load(result) + + setup_options_home_config do + assert_load(result) + end + + setup_options_xdg_config_dirs do + assert_load(result) + end + + setup_options_home_config_settings do + assert_load(result) + end + end + + def test_load_home_config + result, = setup_options_home_config + assert_load(result) + + setup_options_xdg_config_dirs do + assert_load(result) + end + + setup_options_home_config_settings do + assert_load(result) + end + end + + def test_load_xdg_config_dirs + result, = setup_options_xdg_config_dirs + assert_load(result) + + setup_options_home_config_settings do + assert_load(result) + end + end + + def test_load_home_config_settings + result, = setup_options_home_config_settings + assert_load(result) + end + + def test_load_nothing + assert !new_parser.load + assert_nil @result + end +end From 37291df91dea2616559e64751ae6f8ed799c4310 Mon Sep 17 00:00:00 2001 From: Whyme Lyu <5longluna@gmail.com> Date: Sat, 29 Oct 2022 21:46:23 +0800 Subject: [PATCH 060/148] [ruby/optparse] #load() into hash (https://github.com/ruby/optparse/pull/42) OptionParser#load learns .load(into: Hash) https://github.com/ruby/optparse/commit/2ea626fcff Co-authored-by: Nobuyoshi Nakada --- lib/optparse.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/optparse.rb b/lib/optparse.rb index 9dad2e4edaaf8c..238fa1e2195256 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -1903,10 +1903,13 @@ def candidate(word) # directory ~/.options, then the basename with '.options' suffix # under XDG and Haiku standard places. # - def load(filename = nil) + # The optional +into+ keyword argument works exactly like that accepted in + # method #parse. + # + def load(filename = nil, into: nil) unless filename basename = File.basename($0, '.*') - return true if load(File.expand_path(basename, '~/.options')) rescue nil + return true if load(File.expand_path(basename, '~/.options'), into: into) rescue nil basename << ".options" return [ # XDG @@ -1918,11 +1921,11 @@ def load(filename = nil) '~/config/settings', ].any? {|dir| next if !dir or dir.empty? - load(File.expand_path(basename, dir)) rescue nil + load(File.expand_path(basename, dir), into: into) rescue nil } end begin - parse(*IO.readlines(filename).each {|s| s.chomp!}) + parse(*IO.readlines(filename).each {|s| s.chomp!}, into: into) true rescue Errno::ENOENT, Errno::ENOTDIR false From d5fb76a6c8d86253110d265070e775a1bf9f9730 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 29 Oct 2022 22:25:42 +0900 Subject: [PATCH 061/148] [ruby/optparse] Add tests for `load(into:)` https://github.com/ruby/optparse/commit/51f7e060ee --- test/optparse/test_load.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/optparse/test_load.rb b/test/optparse/test_load.rb index de24f1c3809666..0ebe855682a6c4 100644 --- a/test/optparse/test_load.rb +++ b/test/optparse/test_load.rb @@ -27,6 +27,8 @@ def new_parser def assert_load(result) assert new_parser.load assert_equal(result, @result) + assert new_parser.load(into: into = {}) + assert_equal({test: result}, into) end def setup_options(env, dir, suffix = nil) From 572cd10a868b726f8a71cf7ffe2b616e22c273ad Mon Sep 17 00:00:00 2001 From: Kazuhiro NISHIYAMA Date: Sun, 30 Oct 2022 00:53:10 +0900 Subject: [PATCH 062/148] Fix links and sort [ci skip] --- NEWS.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 93c0c0713e7c5b..8b6d5ef4e68fa2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -362,6 +362,7 @@ The following deprecated APIs are removed. [Feature #15231]: https://bugs.ruby-lang.org/issues/15231 [Feature #15357]: https://bugs.ruby-lang.org/issues/15357 [Bug #15928]: https://bugs.ruby-lang.org/issues/15928 +[Feature #16122]: https://bugs.ruby-lang.org/issues/16122 [Feature #16131]: https://bugs.ruby-lang.org/issues/16131 [Bug #16466]: https://bugs.ruby-lang.org/issues/16466 [Feature #16806]: https://bugs.ruby-lang.org/issues/16806 @@ -375,23 +376,24 @@ The following deprecated APIs are removed. [Feature #18037]: https://bugs.ruby-lang.org/issues/18037 [Feature #18159]: https://bugs.ruby-lang.org/issues/18159 [Feature #18351]: https://bugs.ruby-lang.org/issues/18351 +[Feature #18481]: https://bugs.ruby-lang.org/issues/18481 [Bug #18487]: https://bugs.ruby-lang.org/issues/18487 [Feature #18571]: https://bugs.ruby-lang.org/issues/18571 [Feature #18585]: https://bugs.ruby-lang.org/issues/18585 +[Feature #18589]: https://bugs.ruby-lang.org/issues/18589 [Feature #18598]: https://bugs.ruby-lang.org/issues/18598 [Bug #18625]: https://bugs.ruby-lang.org/issues/18625 +[Feature #18630]: https://bugs.ruby-lang.org/issues/18630 [Bug #18633]: https://bugs.ruby-lang.org/issues/18633 [Feature #18685]: https://bugs.ruby-lang.org/issues/18685 [Bug #18782]: https://bugs.ruby-lang.org/issues/18782 [Feature #18788]: https://bugs.ruby-lang.org/issues/18788 [Feature #18809]: https://bugs.ruby-lang.org/issues/18809 -[Feature #18481]: https://bugs.ruby-lang.org/issues/18481 +[Feature #18821]: https://bugs.ruby-lang.org/issues/18821 +[Feature #18824]: https://bugs.ruby-lang.org/issues/18824 [Feature #18949]: https://bugs.ruby-lang.org/issues/18949 +[Feature #18968]: https://bugs.ruby-lang.org/issues/18968 [Feature #19008]: https://bugs.ruby-lang.org/issues/19008 +[Feature #19013]: https://bugs.ruby-lang.org/issues/19013 [Feature #19026]: https://bugs.ruby-lang.org/issues/19026 -[Feature #16122]: https://bugs.ruby-lang.org/issues/16122 -[Feature #18630]: https://bugs.ruby-lang.org/issues/18630 -[Feature #18589]: https://bugs.ruby-lang.org/issues/18589 [Feature #19060]: https://bugs.ruby-lang.org/issues/19060 -[Feature #18824]: https://bugs.ruby-lang.org/issues/18824 -[Feature #18968]: https://bugs.ruby-lang.org/issues/18968 From 91c28ab2ee58f9b5da33dc566a4c263449b8520f Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 29 Oct 2022 14:47:16 -0500 Subject: [PATCH 063/148] [DOC] Enhanced RDOc for IO (#6642) In io.c treats: #close #close_read #close_write #closed --- doc/io_streams.rdoc | 11 ++--- io.c | 109 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 89 insertions(+), 31 deletions(-) diff --git a/doc/io_streams.rdoc b/doc/io_streams.rdoc index 3ee85926871ce5..c8ce9991cfcce9 100644 --- a/doc/io_streams.rdoc +++ b/doc/io_streams.rdoc @@ -189,14 +189,13 @@ The relevant methods: A new \IO stream may be open for reading, open for writing, or both. -You can close a stream using these methods: +A stream is automatically closed when claimed by the garbage collector. -- IO#close: Closes the stream for both reading and writing. -- IO#close_read (not in \ARGF): Closes the stream for reading. -- IO#close_write (not in \ARGF): Closes the stream for writing. - -You can query whether a stream is closed using this method: +Attempted reading or writing on a closed stream raises an exception. +- IO#close: Closes the stream for both reading and writing. +- IO#close_read: Closes the stream for reading; not in ARGF. +- IO#close_write: Closes the stream for writing; not in ARGF. - IO#closed?: Returns whether the stream is closed. ==== End-of-Stream diff --git a/io.c b/io.c index a7da551a6a0a98..62b25c2ebbcaa6 100644 --- a/io.c +++ b/io.c @@ -5637,13 +5637,31 @@ rb_io_close(VALUE io) * call-seq: * close -> nil * - * Closes the stream, if it is open, after flushing any buffered writes - * to the operating system; does nothing if the stream is already closed. - * A stream is automatically closed when claimed by the garbage collector. + * Closes the stream for both reading and writing + * if open for either or both; returns +nil+. * - * If the stream was opened by IO.popen, #close sets global variable $?. + * If the stream is open for writing, flushes any buffered writes + * to the operating system before closing. * - * See also {Open and Closed Streams}[rdoc-ref:io_streams.rdoc@Open+and+Closed+Streams]. + * If the stream was opened by IO.popen, sets global variable $? + * (child exit status). + * + * Example: + * + * IO.popen('ruby', 'r+') do |pipe| + * puts pipe.closed? + * pipe.close + * puts $? + * puts pipe.closed? + * end + * + * Output: + * + * false + * pid 13760 exit 0 + * true + * + * Related: IO#close_read, IO#close_write, IO#closed?. */ static VALUE @@ -5694,17 +5712,23 @@ io_close(VALUE io) * Returns +true+ if the stream is closed for both reading and writing, * +false+ otherwise: * - * f = File.new('t.txt') - * f.close # => nil - * f.closed? # => true - * f = IO.popen('/bin/sh','r+') - * f.close_write # => nil - * f.closed? # => false - * f.close_read # => nil - * f.closed? # => true + * IO.popen('ruby', 'r+') do |pipe| + * puts pipe.closed? + * pipe.close_read + * puts pipe.closed? + * pipe.close_write + * puts pipe.closed? + * end * + * Output: + * + * false + * false + * true * * See also {Open and Closed Streams}[rdoc-ref:io_streams.rdoc@Open+and+Closed+Streams]. + * + * Related: IO#close_read, IO#close_write, IO#close. */ @@ -5731,17 +5755,33 @@ rb_io_closed(VALUE io) * call-seq: * close_read -> nil * - * Closes the read end of a duplexed stream (i.e., one that is both readable - * and writable, such as a pipe); does nothing if already closed: + * Closes the stream for reading if open for reading; + * returns +nil+. * - * f = IO.popen('/bin/sh','r+') - * f.close_read - * f.readlines # Raises IOError + * If the stream was opened by IO.popen and is also closed for writing, + * sets global variable $? (child exit status). * - * See also {Open and Closed Streams}[rdoc-ref:io_streams.rdoc@Open+and+Closed+Streams]. + * Example: + * + * IO.popen('ruby', 'r+') do |pipe| + * puts pipe.closed? + * pipe.close_write + * puts pipe.closed? + * pipe.close_read + * puts $? + * puts pipe.closed? + * end * - * Raises an exception if the stream is not duplexed. + * Output: + * + * false + * false + * pid 14748 exit 0 + * true + * + * See also {Open and Closed Streams}[rdoc-ref:io_streams.rdoc@Open+and+Closed+Streams]. * + * Related: IO#close, IO#close_write, IO#closed?. */ static VALUE @@ -5789,14 +5829,33 @@ rb_io_close_read(VALUE io) * call-seq: * close_write -> nil * - * Closes the write end of a duplexed stream (i.e., one that is both readable - * and writable, such as a pipe); does nothing if already closed: + * Closes the stream for writing if open for writing; + * returns +nil+: * - * f = IO.popen('/bin/sh', 'r+') - * f.close_write - * f.print 'nowhere' # Raises IOError. + * Flushes any buffered writes to the operating system before closing. + * + * If the stream was opened by IO.popen and is also closed for reading, + * sets global variable $? (child exit status). + * + * IO.popen('ruby', 'r+') do |pipe| + * puts pipe.closed? + * pipe.close_read + * puts pipe.closed? + * pipe.close_write + * puts $? + * puts pipe.closed? + * end + * + * Output: + * + * false + * false + * pid 15044 exit 0 + * true * * See also {Open and Closed Streams}[rdoc-ref:io_streams.rdoc@Open+and+Closed+Streams]. + * + * Related: IO#close, IO#close_read, IO#closed?. */ static VALUE From 1acbcf0e58f60addf5aa7bb5e0ff7f6907a10fbc Mon Sep 17 00:00:00 2001 From: git Date: Sun, 30 Oct 2022 07:04:08 +0000 Subject: [PATCH 064/148] Update bundled gems list at 2022-10-30 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 8b6d5ef4e68fa2..9156ef7a68b6ec 100644 --- a/NEWS.md +++ b/NEWS.md @@ -260,7 +260,7 @@ Note: We're only listing outstanding class updates. * net-ftp 0.2.0 * net-imap 0.3.1 * net-pop 0.1.2 - * net-smtp 0.3.2 + * net-smtp 0.3.3 * rbs 2.7.0 * typeprof 0.21.3 * debug 1.6.3 diff --git a/gems/bundled_gems b/gems/bundled_gems index e01e71225034d3..b97fa0343320a6 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -8,7 +8,7 @@ rss 0.2.9 https://github.com/ruby/rss net-ftp 0.2.0 https://github.com/ruby/net-ftp net-imap 0.3.1 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop -net-smtp 0.3.2 https://github.com/ruby/net-smtp +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.7.0 https://github.com/ruby/rbs From b64514f132615c00982d008bb2d7d2d1230d4f1c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 30 Oct 2022 12:39:43 +0900 Subject: [PATCH 065/148] vcs.rb: prettify debug print --- tool/lib/vcs.rb | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index 3b60385e59eb04..0014b67c285cb0 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -1,6 +1,7 @@ # vcs require 'fileutils' require 'optparse' +require 'pp' # This library is used by several other tools/ scripts to detect the current # VCS in use (e.g. SVN, Git) or to interact with that VCS. @@ -9,6 +10,22 @@ class VCS DEBUG_OUT = STDERR.dup + + def self.dump(obj, pre = nil) + out = DEBUG_OUT + @pp ||= PP.new(out) + @pp.guard_inspect_key do + if pre + @pp.group(pre.size, pre) { + obj.pretty_print(@pp) + } + else + obj.pretty_print(@pp) + end + @pp.flush + out << "\n" + end + end end unless File.respond_to? :realpath @@ -19,14 +36,14 @@ def File.realpath(arg) end def IO.pread(*args) - VCS::DEBUG_OUT.puts(args.inspect) if $DEBUG + VCS.dump(args, "args: ") if $DEBUG popen(*args) {|f|f.read} end module DebugPOpen refine IO.singleton_class do def popen(*args) - VCS::DEBUG_OUT.puts args.inspect if $DEBUG + VCS.dump(args, "args: ") if $DEBUG super end end @@ -34,7 +51,7 @@ def popen(*args) using DebugPOpen module DebugSystem def system(*args) - VCS::DEBUG_OUT.puts args.inspect if $DEBUG + VCS.dump(args, "args: ") if $DEBUG exception = false opts = Hash.try_convert(args[-1]) if RUBY_VERSION >= "2.6" @@ -394,7 +411,7 @@ def export_changelog(url = '.', from = nil, to = nil, _path = nil, path: _path) def commit args = %W"#{COMMAND} commit" if dryrun? - VCS::DEBUG_OUT.puts(args.inspect) + VCS.dump(args, "commit: ") return true end system(*args) @@ -411,7 +428,7 @@ def cmd_args(cmds, srcdir = nil) if srcdir opts[:chdir] ||= srcdir end - VCS::DEBUG_OUT.puts cmds.inspect if debug? + VCS.dump(cmds, "cmds: ") if debug? and !$DEBUG cmds end @@ -421,7 +438,7 @@ def cmd_pipe_at(srcdir, cmds, &block) def cmd_read_at(srcdir, cmds) result = without_gitconfig { IO.pread(*cmd_args(cmds, srcdir)) } - VCS::DEBUG_OUT.puts result.inspect if debug? + VCS.dump(result, "result: ") if debug? result end @@ -516,7 +533,7 @@ def without_gitconfig def initialize(*) super @srcdir = File.realpath(@srcdir) - VCS::DEBUG_OUT.puts @srcdir.inspect if debug? + VCS.dump(@srcdir, "srcdir: ") if debug? self end @@ -721,13 +738,13 @@ def upstream def commit(opts = {}) args = [COMMAND, "push"] - args << "-n" if dryrun + args << "-n" if dryrun? remote, branch = upstream args << remote branches = %W[refs/notes/commits:refs/notes/commits HEAD:#{branch}] if dryrun? branches.each do |b| - VCS::DEBUG_OUT.puts((args + [b]).inspect) + VCS.dump(args + [b], "commit: ") end return true end @@ -757,7 +774,7 @@ def commit(opts = {}) commits.each_with_index do |l, i| r, a, c = l.split(' ') dcommit = [COMMAND, "svn", "dcommit"] - dcommit.insert(-2, "-n") if dryrun + dcommit.insert(-2, "-n") if dryrun? dcommit << "--add-author-from" unless a == c dcommit << r system(*dcommit) or return false From 00d5b7ce7c2e72170b7f563b9de0e7ac4bc8f772 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 30 Oct 2022 15:05:34 +0900 Subject: [PATCH 066/148] vcs.rb: copy safe directory configuration Now revision.tmp will be regenerated always and every times, even if the recent file exists in the source directory, as far as using git. On the other hand, VirtualBox mounts shared folders as root, and git rejects the repository there as dubious ownership. --- tool/lib/vcs.rb | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index 0014b67c285cb0..d96caddbf2290c 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -2,6 +2,7 @@ require 'fileutils' require 'optparse' require 'pp' +require 'tempfile' # This library is used by several other tools/ scripts to detect the current # VCS in use (e.g. SVN, Git) or to interact with that VCS. @@ -419,8 +420,21 @@ def commit end class GIT < self - register(".git") { |path, dir| File.exist?(File.join(path, dir)) } - COMMAND = ENV["GIT"] || 'git' + register(".git") do |path, dir| + SAFE_DIRECTORIES ||= + begin + command = ENV["GIT"] || 'git' + IO.popen(%W"#{command} config --global --get-all safe.directory", &:read).split("\n") + rescue + command = nil + [] + ensure + VCS.dump(SAFE_DIRECTORIES, "safe.directory: ") if $DEBUG + COMMAND = command + end + + COMMAND and File.exist?(File.join(path, dir)) + end def cmd_args(cmds, srcdir = nil) (opts = cmds.last).kind_of?(Hash) or cmds << (opts = {}) @@ -459,7 +473,14 @@ def svn_revision(log) def _get_revisions(path, srcdir = nil) ref = Branch === path ? path.to_str : 'HEAD' gitcmd = [COMMAND] - last = cmd_read_at(srcdir, [[*gitcmd, 'rev-parse', ref]]).rstrip + last = nil + IO.pipe do |r, w| + last = cmd_read_at(srcdir, [[*gitcmd, 'rev-parse', ref, err: w]]).rstrip + w.close + unless r.eof? + raise "#{COMMAND} rev-parse failed\n#{r.read.gsub(/^(?=\s*\S)/, ' ')}" + end + end log = cmd_read_at(srcdir, [[*gitcmd, 'log', '-n1', '--date=iso', '--pretty=fuller', *path]]) changed = log[/\Acommit (\h+)/, 1] modified = log[/^CommitDate:\s+(.*)/, 1] @@ -521,18 +542,34 @@ def revision_handler(rev) end def without_gitconfig - envs = %w'HOME XDG_CONFIG_HOME GIT_SYSTEM_CONFIG GIT_CONFIG_SYSTEM'.each_with_object({}) do |v, h| + envs = (%w'HOME XDG_CONFIG_HOME' + ENV.keys.grep(/\AGIT_/)).each_with_object({}) do |v, h| h[v] = ENV.delete(v) - ENV[v] = NullDevice if v.start_with?('GIT_') end + ENV['GIT_CONFIG_SYSTEM'] = NullDevice + ENV['GIT_CONFIG_GLOBAL'] = global_config yield ensure ENV.update(envs) end + def global_config + return NullDevice if SAFE_DIRECTORIES.empty? + unless @gitconfig + @gitconfig = Tempfile.new(%w"vcs_ .gitconfig") + @gitconfig.close + ENV['GIT_CONFIG_GLOBAL'] = @gitconfig.path + SAFE_DIRECTORIES.each do |dir| + system(*%W[#{COMMAND} config --global --add safe.directory #{dir}]) + end + VCS.dump(`#{COMMAND} config --global --get-all safe.directory`, "safe.directory: ") if debug? + end + @gitconfig.path + end + def initialize(*) super @srcdir = File.realpath(@srcdir) + @gitconfig = nil VCS.dump(@srcdir, "srcdir: ") if debug? self end From 28214231055d3baf3ee4191520736691146e29c1 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 30 Oct 2022 17:10:00 +0900 Subject: [PATCH 067/148] Run spec_guards only when spec files changed [ci skip] --- .github/workflows/spec_guards.yml | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index e3201eb6dbc57e..1991e016a3d596 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -2,21 +2,13 @@ name: Rubyspec Version Guards Check on: push: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - - '**/.document' - - '**.[1-8]' - - '**.ronn' + paths: + - 'spec/**' + - '!spec/*.md' pull_request: paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - - '**/.document' - - '**.[1-8]' - - '**.ronn' + - 'spec/**' + - '!spec/*.md' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} From 0717cb84193083cbfb7daee115a82418fd9c1474 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 30 Oct 2022 18:22:50 +0900 Subject: [PATCH 068/148] Try -fstack-protector-strong on MinGW The CI for MinGW has used it. --- .github/workflows/mingw.yml | 4 ++-- configure.ac | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 001fab9cf10ae3..6f7af8ccf829d5 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -32,10 +32,10 @@ jobs: MSYSTEM: ${{ matrix.msystem }} MSYS2_ARCH: x86_64 CHOST: "x86_64-w64-mingw32" - CFLAGS: "-march=x86-64 -mtune=generic -O3 -pipe -fstack-protector-strong" + CFLAGS: "-march=x86-64 -mtune=generic -O3 -pipe" CXXFLAGS: "-march=x86-64 -mtune=generic -O3 -pipe" CPPFLAGS: "-D_FORTIFY_SOURCE=2 -D__USE_MINGW_ANSI_STDIO=1 -DFD_SETSIZE=2048" - LDFLAGS: "-pipe -fstack-protector-strong" + LDFLAGS: "-pipe" UPDATE_UNICODE: "UNICODE_FILES=. UNICODE_PROPERTY_FILES=. UNICODE_AUXILIARY_FILES=. UNICODE_EMOJI_FILES=." GITPULLOPTIONS: --no-tags origin ${{github.ref}} strategy: diff --git a/configure.ac b/configure.ac index 711ff997e73229..87966fdfcfbfe5 100644 --- a/configure.ac +++ b/configure.ac @@ -794,7 +794,7 @@ AS_IF([test "$GCC" = yes], [ # -fstack-protector AS_CASE(["$target_os"], - [mingw*|emscripten*|wasi*], [ + [emscripten*|wasi*], [ stack_protector=no ]) AS_IF([test -z "${stack_protector+set}"], [ @@ -806,6 +806,8 @@ AS_IF([test "$GCC" = yes], [ AS_IF([test "x$stack_protector" = xyes], [stack_protector=option; break]) ]) ]) + AC_MSG_CHECKING([for -fstack-protector]) + AC_MSG_RESULT(["$stack_protector"]) AS_CASE(["$stack_protector"], [-*], [ RUBY_APPEND_OPTION(XCFLAGS, $stack_protector) RUBY_APPEND_OPTION(XLDFLAGS, $stack_protector) From 37593c795040ec5266bf86f9ae1c1e052af5dc9f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 30 Oct 2022 20:35:41 +0900 Subject: [PATCH 069/148] Ignore failure at moving revision.h [ci skip] The source directory may be read-only. --- common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.mk b/common.mk index d6cbffe6d60339..2bf44b530f336f 100644 --- a/common.mk +++ b/common.mk @@ -1234,7 +1234,7 @@ revision.$(HAVE_BASERUBY:yes=tmp):: $(srcdir)/version.h $(tooldir)/file2lastrev. $(Q) $(BASERUBY) $(tooldir)/file2lastrev.rb -q --revision.h --srcdir="$(srcdir)" > $@ $(REVISION_H): revision.tmp - $(Q)$(IFCHANGE) "--timestamp=$@" "$(srcdir)/revision.h" revision.tmp + -$(Q)$(IFCHANGE) "--timestamp=$@" "$(srcdir)/revision.h" revision.tmp $(srcdir)/ext/ripper/ripper.c: $(srcdir)/ext/ripper/tools/preproc.rb $(srcdir)/parse.y $(srcdir)/defs/id.def $(srcdir)/ext/ripper/depend $(ECHO) generating $@ From 7ed10abdd97a324583c9b9794088a41a550abcd0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 30 Oct 2022 22:21:18 +0900 Subject: [PATCH 070/148] [ruby/bigdecimal] Suppress macro redefinition warnings `HAVE_` macros by autoconf are defined as 1. https://github.com/ruby/bigdecimal/commit/cd35868aa6 --- ext/bigdecimal/missing.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h index 307147c0fdc6bf..325554b5f536d3 100644 --- a/ext/bigdecimal/missing.h +++ b/ext/bigdecimal/missing.h @@ -35,10 +35,10 @@ extern "C" { #endif /* RB_UNUSED_VAR */ #if defined(_MSC_VER) && _MSC_VER >= 1310 -# define HAVE___ASSUME +# define HAVE___ASSUME 1 #elif defined(__INTEL_COMPILER) && __INTEL_COMPILER >= 1300 -# define HAVE___ASSUME +# define HAVE___ASSUME 1 #endif #ifndef UNREACHABLE From 3391c51effcd61f9a718adf59740661d99f57b5b Mon Sep 17 00:00:00 2001 From: eileencodes Date: Wed, 19 Oct 2022 13:23:53 -0400 Subject: [PATCH 071/148] Add `node_id_for_backtrace_location` function We want to use error highlight with eval'd code, specifically ERB templates. We're able to recover the generated code for eval'd templates and can get a parse tree for the ERB generated code, but we don't have a way to get the node id from the backtrace location. So we can't pass the right node into error highlight. This patch gives us an API to get the node id from the backtrace location so we can find the node in the AST. Error Highlight PR: https://github.com/ruby/error_highlight/pull/26 Co-authored-by: Aaron Patterson --- ast.c | 12 ++++++++++++ ast.rb | 15 +++++++++++++++ test/ruby/test_ast.rb | 13 +++++++++++++ 3 files changed, 40 insertions(+) diff --git a/ast.c b/ast.c index c9d0b41facdc99..93c33a1f8dd89e 100644 --- a/ast.c +++ b/ast.c @@ -195,6 +195,18 @@ script_lines(VALUE path) return lines; } +static VALUE +node_id_for_backtrace_location(rb_execution_context_t *ec, VALUE module, VALUE location) +{ + int node_id; + node_id = rb_get_node_id_from_frame_info(location); + if (node_id == -1) { + return Qnil; + } + + return INT2NUM(node_id); +} + static VALUE ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script_lines, VALUE error_tolerant) { diff --git a/ast.rb b/ast.rb index 24fd8e55269ada..1bc1168b806172 100644 --- a/ast.rb +++ b/ast.rb @@ -67,6 +67,21 @@ def self.of body, keep_script_lines: false, error_tolerant: false Primitive.ast_s_of body, keep_script_lines, error_tolerant end + # call-seq: + # RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(backtrace_location) -> integer + # + # Returns the node id for the given backtrace location. + # + # begin + # raise + # rescue => e + # loc = e.backtrace_locations.first + # RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc) + # end # => 0 + def self.node_id_for_backtrace_location backtrace_location + Primitive.node_id_for_backtrace_location backtrace_location + end + # RubyVM::AbstractSyntaxTree::Node instances are created by parse methods in # RubyVM::AbstractSyntaxTree. # diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index aaf626e801f6b4..0932e93d5a909e 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -186,6 +186,19 @@ def test_parse_file_raises_syntax_error end end + def test_node_id_for_location + exception = begin + raise + rescue => e + e + end + loc = exception.backtrace_locations.first + node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc) + node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true) + + assert_equal node.node_id, node_id + end + def test_of_proc_and_method proc = Proc.new { 1 + 2 } method = self.method(__method__) From 350d0aa023223f560d812ef61396a2c5359f09a6 Mon Sep 17 00:00:00 2001 From: eileencodes Date: Wed, 19 Oct 2022 13:50:30 -0400 Subject: [PATCH 072/148] [ruby/error_highlight] Support nodes in `spot` Fixes a bug where `spot` was using the wrong local variable. We want to use error highlight with code that has been eval'd, specifically ERB templates. We can recover the compiled source code of the ERB template but we need an API to pass the node into error highlight's `spot`. Required Ruby PR: https://github.com/ruby/ruby/pull/6593 https://github.com/ruby/error_highlight/commit/0b1b650a59 Co-authored-by: Aaron Patterson --- lib/error_highlight/base.rb | 3 +- test/error_highlight/test_error_highlight.rb | 32 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/error_highlight/base.rb b/lib/error_highlight/base.rb index 4c115cc8285c6b..062871ee165a8a 100644 --- a/lib/error_highlight/base.rb +++ b/lib/error_highlight/base.rb @@ -59,8 +59,7 @@ def self.spot(obj, **opts) Spotter.new(node, **opts).spot when RubyVM::AbstractSyntaxTree::Node - # Just for compatibility - Spotter.new(node, **opts).spot + Spotter.new(obj, **opts).spot else raise TypeError, "Exception is expected" diff --git a/test/error_highlight/test_error_highlight.rb b/test/error_highlight/test_error_highlight.rb index c4a998092b5686..d5f57a88bc8805 100644 --- a/test/error_highlight/test_error_highlight.rb +++ b/test/error_highlight/test_error_highlight.rb @@ -1257,4 +1257,36 @@ def test_spot_with_backtrace_location assert_equal(22, spot[:last_column]) assert_equal(" raise_name_error\n", spot[:snippet]) end + + def test_spot_with_node + omit unless RubyVM::AbstractSyntaxTree.respond_to?(:node_id_for_backtrace_location) + + begin + raise_name_error + rescue NameError => exc + end + + bl = exc.backtrace_locations.first + expected_spot = ErrorHighlight.spot(exc, backtrace_location: bl) + ast = RubyVM::AbstractSyntaxTree.parse_file(__FILE__, keep_script_lines: true) + node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(bl) + node = find_node_by_id(ast, node_id) + actual_spot = ErrorHighlight.spot(node) + + assert_equal expected_spot, actual_spot + end + + private + + def find_node_by_id(node, node_id) + return node if node.node_id == node_id + + node.children.each do |child| + next unless child.is_a?(RubyVM::AbstractSyntaxTree::Node) + found = find_node_by_id(child, node_id) + return found if found + end + + return false + end end From c3de08cb245467efb8fe3a661e9ec4a52514faf1 Mon Sep 17 00:00:00 2001 From: S-H-GAMELINKS Date: Wed, 26 Oct 2022 13:55:18 +0900 Subject: [PATCH 073/148] Reuse FIBER_RESUMED_P macro --- cont.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cont.c b/cont.c index 577a30a57af206..635faf88092ba4 100644 --- a/cont.c +++ b/cont.c @@ -2269,7 +2269,7 @@ root_fiber_alloc(rb_thread_t *th) VM_ASSERT(DATA_PTR(fiber_value) == NULL); VM_ASSERT(fiber->cont.type == FIBER_CONTEXT); - VM_ASSERT(fiber->status == FIBER_RESUMED); + VM_ASSERT(FIBER_RESUMED_P(fiber)); th->root_fiber = fiber; DATA_PTR(fiber_value) = fiber; From 5e6633fcf988e5874d0a9929bdf2cd496289e75d Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 31 Oct 2022 14:29:11 -0400 Subject: [PATCH 074/148] YJIT: reduce default `--yjit-exec-mem-size` to 128MiB instead of 256 (#6649) Reduce default --yjit-exec-mem-size to 128MiB instead of 256 --- yjit/src/options.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yjit/src/options.rs b/yjit/src/options.rs index a0cdbbc566ec38..645af0faacc8b0 100644 --- a/yjit/src/options.rs +++ b/yjit/src/options.rs @@ -53,7 +53,7 @@ pub struct Options { // Initialize the options to default values pub static mut OPTIONS: Options = Options { - exec_mem_size: 256 * 1024 * 1024, + exec_mem_size: 128 * 1024 * 1024, code_page_size: 16 * 1024, call_threshold: 10, greedy_versioning: false, From 2b39640b0bbf7459b305d8a98bb01f197975b8d9 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 31 Oct 2022 11:29:45 -0700 Subject: [PATCH 075/148] YJIT: Add RubyVM::YJIT.code_gc (#6644) * YJIT: Add RubyVM::YJIT.code_gc * Rename compiled_page_count to live_page_count --- test/ruby/test_yjit.rb | 17 ++++++----------- yjit.c | 1 + yjit.rb | 9 +++++++-- yjit/src/asm/mod.rs | 24 ++++++++++++++++++------ yjit/src/stats.rs | 4 ++-- yjit/src/yjit.rs | 12 ++++++++++++ 6 files changed, 46 insertions(+), 21 deletions(-) diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index d740870736c6bc..57a60db89b5b7f 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -830,10 +830,10 @@ def foo def test_code_gc assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok) return :not_paged unless add_pages(100) # prepare freeable pages - code_gc # first code GC + RubyVM::YJIT.code_gc # first code GC return :not_compiled1 unless compiles { nil } # should be JITable again - code_gc # second code GC + RubyVM::YJIT.code_gc # second code GC return :not_compiled2 unless compiles { nil } # should be JITable again code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] @@ -854,7 +854,7 @@ def test_on_stack_code_gc_call return :not_paged1 unless add_pages(400) # go to a page without initial ocb code return :broken_resume1 if fiber.resume != 0 # JIT the fiber - code_gc # first code GC, which should not free the fiber page + RubyVM::YJIT.code_gc # first code GC, which should not free the fiber page return :broken_resume2 if fiber.resume != 0 # The code should be still callable code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] @@ -873,19 +873,19 @@ def test_on_stack_code_gc_twice return :not_paged1 unless add_pages(400) # go to a page without initial ocb code return :broken_resume1 if fiber.resume(true) != 0 # JIT the fiber - code_gc # first code GC, which should not free the fiber page + RubyVM::YJIT.code_gc # first code GC, which should not free the fiber page return :not_paged2 unless add_pages(300) # add some stuff to be freed # Not calling fiber.resume here to test the case that the YJIT payload loses some # information at the previous code GC. The payload should still be there, and # thus we could know the fiber ISEQ is still on stack on this second code GC. - code_gc # second code GC, which should still not free the fiber page + RubyVM::YJIT.code_gc # second code GC, which should still not free the fiber page return :not_paged3 unless add_pages(200) # attempt to overwrite the fiber page (it shouldn't) return :broken_resume2 if fiber.resume(true) != 0 # The fiber code should be still fine return :broken_resume3 if fiber.resume(false) != nil # terminate the fiber - code_gc # third code GC, freeing a page that used to be on stack + RubyVM::YJIT.code_gc # third code GC, freeing a page that used to be on stack return :not_paged4 unless add_pages(100) # check everything still works @@ -933,11 +933,6 @@ def add_pages(num_jits) num_jits.times { return false unless eval('compiles { nil.to_i }') } pages.nil? || pages < RubyVM::YJIT.runtime_stats[:compiled_page_count] end - - def code_gc - RubyVM::YJIT.simulate_oom! # bump write_pos - eval('proc { nil }.call') # trigger code GC - end RUBY end diff --git a/yjit.c b/yjit.c index c53444d5a3824c..d7f369ca2ec568 100644 --- a/yjit.c +++ b/yjit.c @@ -1053,6 +1053,7 @@ VALUE rb_yjit_get_stats(rb_execution_context_t *ec, VALUE self); VALUE rb_yjit_reset_stats_bang(rb_execution_context_t *ec, VALUE self); VALUE rb_yjit_disasm_iseq(rb_execution_context_t *ec, VALUE self, VALUE iseq); VALUE rb_yjit_insns_compiled(rb_execution_context_t *ec, VALUE self, VALUE iseq); +VALUE rb_yjit_code_gc(rb_execution_context_t *ec, VALUE self); VALUE rb_yjit_simulate_oom_bang(rb_execution_context_t *ec, VALUE self); VALUE rb_yjit_get_exit_locations(rb_execution_context_t *ec, VALUE self); diff --git a/yjit.rb b/yjit.rb index 2a0b3dc6c63bdf..ac49a30e90a728 100644 --- a/yjit.rb +++ b/yjit.rb @@ -162,6 +162,11 @@ def self.insns_compiled(iseq) end end + # Free and recompile all existing JIT code + def self.code_gc + Primitive.rb_yjit_code_gc + end + def self.simulate_oom! Primitive.rb_yjit_simulate_oom_bang end @@ -214,14 +219,14 @@ def _print_stats $stderr.puts "compilation_failure: " + ("%10d" % compilation_failure) if compilation_failure != 0 $stderr.puts "compiled_block_count: " + ("%10d" % stats[:compiled_block_count]) $stderr.puts "compiled_iseq_count: " + ("%10d" % stats[:compiled_iseq_count]) - $stderr.puts "compiled_page_count: " + ("%10d" % stats[:compiled_page_count]) $stderr.puts "freed_iseq_count: " + ("%10d" % stats[:freed_iseq_count]) - $stderr.puts "freed_page_count: " + ("%10d" % stats[:freed_page_count]) $stderr.puts "invalidation_count: " + ("%10d" % stats[:invalidation_count]) $stderr.puts "constant_state_bumps: " + ("%10d" % stats[:constant_state_bumps]) $stderr.puts "inline_code_size: " + ("%10d" % stats[:inline_code_size]) $stderr.puts "outlined_code_size: " + ("%10d" % stats[:outlined_code_size]) $stderr.puts "freed_code_size: " + ("%10d" % stats[:freed_code_size]) + $stderr.puts "live_page_count: " + ("%10d" % stats[:live_page_count]) + $stderr.puts "freed_page_count: " + ("%10d" % stats[:freed_page_count]) $stderr.puts "code_gc_count: " + ("%10d" % stats[:code_gc_count]) $stderr.puts "num_gc_obj_refs: " + ("%10d" % stats[:num_gc_obj_refs]) diff --git a/yjit/src/asm/mod.rs b/yjit/src/asm/mod.rs index b68520a767fa66..7ac3625fbd25f7 100644 --- a/yjit/src/asm/mod.rs +++ b/yjit/src/asm/mod.rs @@ -209,17 +209,25 @@ impl CodeBlock { self.page_size } - /// Return the number of code pages that have been allocated by the VirtualMemory. - pub fn num_pages(&self) -> usize { + /// Return the number of code pages that have been mapped by the VirtualMemory. + pub fn num_mapped_pages(&self) -> usize { let mapped_region_size = self.mem_block.borrow().mapped_region_size(); // CodeBlock's page size != VirtualMem's page size on Linux, // so mapped_region_size % self.page_size may not be 0 ((mapped_region_size - 1) / self.page_size) + 1 } + /// Return the number of code pages that have been reserved by the VirtualMemory. + pub fn num_virtual_pages(&self) -> usize { + let virtual_region_size = self.mem_block.borrow().virtual_region_size(); + // CodeBlock's page size != VirtualMem's page size on Linux, + // so mapped_region_size % self.page_size may not be 0 + ((virtual_region_size - 1) / self.page_size) + 1 + } + /// Return the number of code pages that have been freed and not used yet. pub fn num_freed_pages(&self) -> usize { - (0..self.num_pages()).filter(|&page_idx| self.has_freed_page(page_idx)).count() + (0..self.num_mapped_pages()).filter(|&page_idx| self.has_freed_page(page_idx)).count() } pub fn has_freed_page(&self, page_idx: usize) -> bool { @@ -303,7 +311,7 @@ impl CodeBlock { pub fn code_size(&self) -> usize { let mut size = 0; let current_page_idx = self.write_pos / self.page_size; - for page_idx in 0..self.num_pages() { + for page_idx in 0..self.num_mapped_pages() { if page_idx == current_page_idx { // Count only actually used bytes for the current page. size += (self.write_pos % self.page_size).saturating_sub(self.page_start()); @@ -546,7 +554,7 @@ impl CodeBlock { } // Check which pages are still in use - let mut pages_in_use = vec![false; self.num_pages()]; + let mut pages_in_use = vec![false; self.num_mapped_pages()]; // For each ISEQ, we currently assume that only code pages used by inline code // are used by outlined code, so we mark only code pages used by inlined code. for_each_on_stack_iseq_payload(|iseq_payload| { @@ -560,10 +568,14 @@ impl CodeBlock { } // Let VirtuamMem free the pages - let freed_pages: Vec = pages_in_use.iter().enumerate() + let mut freed_pages: Vec = pages_in_use.iter().enumerate() .filter(|&(_, &in_use)| !in_use).map(|(page, _)| page).collect(); self.free_pages(&freed_pages); + // Append virtual pages in case RubyVM::YJIT.code_gc is manually triggered. + let mut virtual_pages: Vec = (self.num_mapped_pages()..self.num_virtual_pages()).collect(); + freed_pages.append(&mut virtual_pages); + // Invalidate everything to have more compact code after code GC. // This currently patches every ISEQ, which works, but in the future, // we could limit that to patch only on-stack ISEQs for optimizing code GC. diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index e851d4e4d1ee6a..e07b475a9f1d6e 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -381,8 +381,8 @@ fn rb_yjit_gen_stats_dict() -> VALUE { // GCed code size hash_aset_usize!(hash, "freed_code_size", freed_page_count * cb.page_size()); - // Compiled pages - hash_aset_usize!(hash, "compiled_page_count", cb.num_pages() - freed_page_count); + // Live pages + hash_aset_usize!(hash, "live_page_count", cb.num_mapped_pages() - freed_page_count); } // If we're not generating stats, the hash is done diff --git a/yjit/src/yjit.rs b/yjit/src/yjit.rs index 5cd23f066f52d8..4850dca7a84e1a 100644 --- a/yjit/src/yjit.rs +++ b/yjit/src/yjit.rs @@ -79,6 +79,18 @@ pub extern "C" fn rb_yjit_iseq_gen_entry_point(iseq: IseqPtr, ec: EcPtr) -> *con } } +/// Free and recompile all existing JIT code +#[no_mangle] +pub extern "C" fn rb_yjit_code_gc(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { + if !yjit_enabled_p() { + return Qnil; + } + + let cb = CodegenGlobals::get_inline_cb(); + cb.code_gc(); + Qnil +} + /// Simulate a situation where we are out of executable memory #[no_mangle] pub extern "C" fn rb_yjit_simulate_oom_bang(_ec: EcPtr, _ruby_self: VALUE) -> VALUE { From 02f15542245222ee392e68fb244b3b8c4a12ad82 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 31 Oct 2022 14:05:37 -0700 Subject: [PATCH 076/148] Implement object shapes for T_CLASS and T_MODULE (#6637) * Avoid RCLASS_IV_TBL in marshal.c * Avoid RCLASS_IV_TBL for class names * Avoid RCLASS_IV_TBL for autoload * Avoid RCLASS_IV_TBL for class variables * Avoid copying RCLASS_IV_TBL onto ICLASSes * Use object shapes for Class and Module IVs --- class.c | 21 +-- gc.c | 23 ++- internal/class.h | 4 +- internal/variable.h | 2 +- marshal.c | 2 +- object.c | 18 +-- shape.c | 9 +- shape.h | 24 ++++ variable.c | 336 ++++++++++++++++++++++++-------------------- vm_insnhelper.c | 28 +++- 10 files changed, 265 insertions(+), 202 deletions(-) diff --git a/class.c b/class.c index 3d8e205007d6fc..d181fb0b2ee635 100644 --- a/class.c +++ b/class.c @@ -213,7 +213,6 @@ class_alloc(VALUE flags, VALUE klass) #endif /* ZALLOC - RCLASS_IV_TBL(obj) = 0; RCLASS_CONST_TBL(obj) = 0; RCLASS_M_TBL(obj) = 0; RCLASS_IV_INDEX_TBL(obj) = 0; @@ -402,23 +401,19 @@ class_init_copy_check(VALUE clone, VALUE orig) static void copy_tables(VALUE clone, VALUE orig) { - if (RCLASS_IV_TBL(clone)) { - st_free_table(RCLASS_IV_TBL(clone)); - RCLASS_IV_TBL(clone) = 0; - } if (RCLASS_CONST_TBL(clone)) { rb_free_const_table(RCLASS_CONST_TBL(clone)); RCLASS_CONST_TBL(clone) = 0; } RCLASS_M_TBL(clone) = 0; - if (RCLASS_IV_TBL(orig)) { + if (!RB_TYPE_P(clone, T_ICLASS)) { st_data_t id; rb_iv_tbl_copy(clone, orig); CONST_ID(id, "__tmp_classpath__"); - st_delete(RCLASS_IV_TBL(clone), &id, 0); + rb_attr_delete(clone, id); CONST_ID(id, "__classpath__"); - st_delete(RCLASS_IV_TBL(clone), &id, 0); + rb_attr_delete(clone, id); } if (RCLASS_CONST_TBL(orig)) { struct clone_const_arg arg; @@ -520,7 +515,6 @@ rb_mod_init_copy(VALUE clone, VALUE orig) prev_clone_p = clone_p; RCLASS_M_TBL(clone_p) = RCLASS_M_TBL(p); RCLASS_CONST_TBL(clone_p) = RCLASS_CONST_TBL(p); - RCLASS_IV_TBL(clone_p) = RCLASS_IV_TBL(p); RCLASS_ALLOCATOR(clone_p) = RCLASS_ALLOCATOR(p); if (RB_TYPE_P(clone, T_CLASS)) { RCLASS_SET_INCLUDER(clone_p, clone); @@ -607,9 +601,7 @@ rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach) RCLASS_SET_SUPER(clone, RCLASS_SUPER(klass)); RCLASS_ALLOCATOR(clone) = RCLASS_ALLOCATOR(klass); - if (RCLASS_IV_TBL(klass)) { - rb_iv_tbl_copy(clone, klass); - } + rb_iv_tbl_copy(clone, klass); if (RCLASS_CONST_TBL(klass)) { struct clone_const_arg arg; arg.tbl = RCLASS_CONST_TBL(clone) = rb_id_table_create(0); @@ -1062,13 +1054,10 @@ rb_include_class_new(VALUE module, VALUE super) module = METACLASS_OF(module); } RUBY_ASSERT(!RB_TYPE_P(module, T_ICLASS)); - if (!RCLASS_IV_TBL(module)) { - RCLASS_IV_TBL(module) = st_init_numtable(); - } if (!RCLASS_CONST_TBL(module)) { RCLASS_CONST_TBL(module) = rb_id_table_create(0); } - RCLASS_IV_TBL(klass) = RCLASS_IV_TBL(module); + RCLASS_CVC_TBL(klass) = RCLASS_CVC_TBL(module); RCLASS_CONST_TBL(klass) = RCLASS_CONST_TBL(module); diff --git a/gc.c b/gc.c index 76a1f0f2de66fd..75b330ede502d3 100644 --- a/gc.c +++ b/gc.c @@ -3447,8 +3447,8 @@ obj_free(rb_objspace_t *objspace, VALUE obj) case T_CLASS: rb_id_table_free(RCLASS_M_TBL(obj)); cc_table_free(objspace, obj, FALSE); - if (RCLASS_IV_TBL(obj)) { - st_free_table(RCLASS_IV_TBL(obj)); + if (RCLASS_IVPTR(obj)) { + xfree(RCLASS_IVPTR(obj)); } if (RCLASS_CONST_TBL(obj)) { rb_free_const_table(RCLASS_CONST_TBL(obj)); @@ -4865,15 +4865,11 @@ obj_memsize_of(VALUE obj, int use_all_types) if (RCLASS_M_TBL(obj)) { size += rb_id_table_memsize(RCLASS_M_TBL(obj)); } - if (RCLASS_IV_TBL(obj)) { - size += st_memsize(RCLASS_IV_TBL(obj)); - } + // class IV sizes are allocated as powers of two + size += SIZEOF_VALUE << bit_length(RCLASS_IV_COUNT(obj)); if (RCLASS_CVC_TBL(obj)) { size += rb_id_table_memsize(RCLASS_CVC_TBL(obj)); } - if (RCLASS_EXT(obj)->iv_tbl) { - size += st_memsize(RCLASS_EXT(obj)->iv_tbl); - } if (RCLASS_EXT(obj)->const_tbl) { size += rb_id_table_memsize(RCLASS_EXT(obj)->const_tbl); } @@ -7212,7 +7208,9 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj) mark_m_tbl(objspace, RCLASS_M_TBL(obj)); cc_table_mark(objspace, obj); - mark_tbl_no_pin(objspace, RCLASS_IV_TBL(obj)); + for (attr_index_t i = 0; i < RCLASS_IV_COUNT(obj); i++) { + gc_mark(objspace, RCLASS_IVPTR(obj)[i]); + } mark_const_tbl(objspace, RCLASS_CONST_TBL(obj)); break; @@ -10439,7 +10437,9 @@ gc_update_object_references(rb_objspace_t *objspace, VALUE obj) update_cvc_tbl(objspace, obj); update_superclasses(objspace, obj); - gc_update_tbl_refs(objspace, RCLASS_IV_TBL(obj)); + for (attr_index_t i = 0; i < RCLASS_IV_COUNT(obj); i++) { + UPDATE_IF_MOVED(objspace, RCLASS_IVPTR(obj)[i]); + } update_class_ext(objspace, RCLASS_EXT(obj)); update_const_tbl(objspace, RCLASS_CONST_TBL(obj)); @@ -10454,9 +10454,6 @@ gc_update_object_references(rb_objspace_t *objspace, VALUE obj) UPDATE_IF_MOVED(objspace, RCLASS(obj)->super); } if (!RCLASS_EXT(obj)) break; - if (RCLASS_IV_TBL(obj)) { - gc_update_tbl_refs(objspace, RCLASS_IV_TBL(obj)); - } update_class_ext(objspace, RCLASS_EXT(obj)); update_m_tbl(objspace, RCLASS_CALLABLE_M_TBL(obj)); update_cc_tbl(objspace, obj); diff --git a/internal/class.h b/internal/class.h index 63fe84c6ab0013..784d508e20d194 100644 --- a/internal/class.h +++ b/internal/class.h @@ -33,7 +33,7 @@ struct rb_cvar_class_tbl_entry { }; struct rb_classext_struct { - struct st_table *iv_tbl; + VALUE *iv_ptr; struct rb_id_table *const_tbl; struct rb_id_table *callable_m_tbl; struct rb_id_table *cc_tbl; /* ID -> [[ci, cc1], cc2, ...] */ @@ -75,9 +75,9 @@ typedef struct rb_classext_struct rb_classext_t; #else # define RCLASS_EXT(c) (RCLASS(c)->ptr) #endif -#define RCLASS_IV_TBL(c) (RCLASS_EXT(c)->iv_tbl) #define RCLASS_CONST_TBL(c) (RCLASS_EXT(c)->const_tbl) #define RCLASS_M_TBL(c) (RCLASS(c)->m_tbl) +#define RCLASS_IVPTR(c) (RCLASS_EXT(c)->iv_ptr) #define RCLASS_CALLABLE_M_TBL(c) (RCLASS_EXT(c)->callable_m_tbl) #define RCLASS_CC_TBL(c) (RCLASS_EXT(c)->cc_tbl) #define RCLASS_CVC_TBL(c) (RCLASS_EXT(c)->cvc_tbl) diff --git a/internal/variable.h b/internal/variable.h index bb24f5129ff6a3..734884a5f6baa1 100644 --- a/internal/variable.h +++ b/internal/variable.h @@ -24,7 +24,6 @@ void rb_gc_update_global_tbl(void); size_t rb_generic_ivar_memsize(VALUE); VALUE rb_search_class_path(VALUE); VALUE rb_attr_delete(VALUE, ID); -VALUE rb_ivar_lookup(VALUE obj, ID id, VALUE undef); void rb_autoload_str(VALUE mod, ID id, VALUE file); VALUE rb_autoload_at_p(VALUE, ID, int); NORETURN(VALUE rb_mod_const_missing(VALUE,VALUE)); @@ -49,6 +48,7 @@ void rb_iv_tbl_copy(VALUE dst, VALUE src); RUBY_SYMBOL_EXPORT_END MJIT_SYMBOL_EXPORT_BEGIN +VALUE rb_ivar_lookup(VALUE obj, ID id, VALUE undef); VALUE rb_gvar_get(ID); VALUE rb_gvar_set(ID, VALUE); VALUE rb_gvar_defined(ID); diff --git a/marshal.c b/marshal.c index 59edbfe53a2a88..d956260d96f0a3 100644 --- a/marshal.c +++ b/marshal.c @@ -523,7 +523,7 @@ hash_each(VALUE key, VALUE value, VALUE v) #define SINGLETON_DUMP_UNABLE_P(klass) \ (rb_id_table_size(RCLASS_M_TBL(klass)) > 0 || \ - (RCLASS_IV_TBL(klass) && RCLASS_IV_TBL(klass)->num_entries > 1)) + rb_ivar_count(klass) > 1) static void w_extended(VALUE klass, struct dump_arg *arg, int check) diff --git a/object.c b/object.c index aae6cc45a4996a..5f8568e3b4f747 100644 --- a/object.c +++ b/object.c @@ -298,20 +298,22 @@ init_copy(VALUE dest, VALUE obj) rb_copy_generic_ivar(dest, obj); rb_gc_copy_finalizer(dest, obj); - rb_shape_t *shape_to_set = rb_shape_get_shape(obj); + if (!RB_TYPE_P(obj, T_CLASS) && !RB_TYPE_P(obj, T_MODULE)) { + rb_shape_t *shape_to_set = rb_shape_get_shape(obj); - // If the object is frozen, the "dup"'d object will *not* be frozen, - // so we need to copy the frozen shape's parent to the new object. - if (rb_shape_frozen_shape_p(shape_to_set)) { - shape_to_set = rb_shape_get_shape_by_id(shape_to_set->parent_id); + // If the object is frozen, the "dup"'d object will *not* be frozen, + // so we need to copy the frozen shape's parent to the new object. + if (rb_shape_frozen_shape_p(shape_to_set)) { + shape_to_set = rb_shape_get_shape_by_id(shape_to_set->parent_id); + } + + // shape ids are different + rb_shape_set_shape(dest, shape_to_set); } if (RB_TYPE_P(obj, T_OBJECT)) { rb_obj_copy_ivar(dest, obj); } - - // shape ids are different - rb_shape_set_shape(dest, shape_to_set); } static VALUE immutable_obj_clone(VALUE obj, VALUE kwfreeze); diff --git a/shape.c b/shape.c index 1f08b9fa22fa49..1de89d3f8f2b42 100644 --- a/shape.c +++ b/shape.c @@ -54,9 +54,10 @@ rb_shape_get_shape_by_id_without_assertion(shape_id_t shape_id) } #if !SHAPE_IN_BASIC_FLAGS -static inline shape_id_t -RCLASS_SHAPE_ID(VALUE obj) +shape_id_t +rb_rclass_shape_id(VALUE obj) { + RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); return RCLASS_EXT(obj)->shape_id; } @@ -115,7 +116,9 @@ static rb_shape_t* get_next_shape_internal(rb_shape_t* shape, ID id, VALUE obj, enum shape_type shape_type) { rb_shape_t *res = NULL; - RUBY_ASSERT(SHAPE_FROZEN != (enum shape_type)shape->type); + + RUBY_ASSERT(SHAPE_FROZEN != (enum shape_type)shape->type || RB_TYPE_P(obj, T_MODULE) || RB_TYPE_P(obj, T_CLASS)); + RB_VM_LOCK_ENTER(); { if (rb_shape_lookup_id(shape, id, shape_type)) { diff --git a/shape.h b/shape.h index 1470cdd4aa4af1..8e1bf46ec9cd53 100644 --- a/shape.h +++ b/shape.h @@ -90,6 +90,13 @@ ROBJECT_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) RBASIC_SET_SHAPE_ID(obj, shape_id); } +static inline shape_id_t +RCLASS_SHAPE_ID(VALUE obj) +{ + RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); + return RBASIC_SHAPE_ID(obj); +} + #else static inline shape_id_t @@ -105,6 +112,15 @@ ROBJECT_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) RBASIC(obj)->flags &= SHAPE_FLAG_MASK; RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT); } + +MJIT_SYMBOL_EXPORT_BEGIN +shape_id_t rb_rclass_shape_id(VALUE obj); +MJIT_SYMBOL_EXPORT_END + +static inline shape_id_t RCLASS_SHAPE_ID(VALUE obj) { + return rb_rclass_shape_id(obj); +} + #endif bool rb_shape_root_shape_p(rb_shape_t* shape); @@ -134,6 +150,14 @@ ROBJECT_IV_COUNT(VALUE obj) return ivc; } +static inline uint32_t +RCLASS_IV_COUNT(VALUE obj) +{ + RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE)); + uint32_t ivc = rb_shape_get_shape_by_id(RCLASS_SHAPE_ID(obj))->next_iv_index; + return ivc; +} + rb_shape_t * rb_shape_alloc(ID edge_name, rb_shape_t * parent); rb_shape_t * rb_shape_alloc_with_parent_id(ID edge_name, shape_id_t parent_id); diff --git a/variable.c b/variable.c index 4c5ae2c112b7ff..0f71b2741816d5 100644 --- a/variable.c +++ b/variable.c @@ -111,18 +111,16 @@ rb_namespace_p(VALUE obj) static VALUE classname(VALUE klass, int *permanent) { - st_table *ivtbl; - st_data_t n; - *permanent = 0; if (!RCLASS_EXT(klass)) return Qnil; - if (!(ivtbl = RCLASS_IV_TBL(klass))) return Qnil; - if (st_lookup(ivtbl, (st_data_t)classpath, &n)) { + + VALUE classpathv = rb_ivar_lookup(klass, classpath, Qnil); + if (RTEST(classpathv)) { *permanent = 1; - return (VALUE)n; + return classpathv; } - if (st_lookup(ivtbl, (st_data_t)tmp_classpath, &n)) return (VALUE)n; - return Qnil; + + return rb_ivar_lookup(klass, tmp_classpath, Qnil);; } /* @@ -946,6 +944,8 @@ gen_ivtbl_get_unlocked(VALUE obj, ID id, struct gen_ivtbl **ivtbl) MJIT_FUNC_EXPORTED int rb_gen_ivtbl_get(VALUE obj, ID id, struct gen_ivtbl **ivtbl) { + RUBY_ASSERT(!RB_TYPE_P(obj, T_ICLASS)); + st_data_t data; int r = 0; @@ -1116,54 +1116,6 @@ gen_ivtbl_count(const struct gen_ivtbl *ivtbl) return n; } -static int -lock_st_lookup(st_table *tab, st_data_t key, st_data_t *value) -{ - int r; - RB_VM_LOCK_ENTER(); - { - r = st_lookup(tab, key, value); - } - RB_VM_LOCK_LEAVE(); - return r; -} - -static int -lock_st_delete(st_table *tab, st_data_t *key, st_data_t *value) -{ - int r; - RB_VM_LOCK_ENTER(); - { - r = st_delete(tab, key, value); - } - RB_VM_LOCK_LEAVE(); - return r; -} - -static int -lock_st_is_member(st_table *tab, st_data_t key) -{ - int r; - RB_VM_LOCK_ENTER(); - { - r = st_is_member(tab, key); - } - RB_VM_LOCK_LEAVE(); - return r; -} - -static int -lock_st_insert(st_table *tab, st_data_t key, st_data_t value) -{ - int r; - RB_VM_LOCK_ENTER(); - { - r = st_insert(tab, key, value); - } - RB_VM_LOCK_LEAVE(); - return r; -} - VALUE rb_ivar_lookup(VALUE obj, ID id, VALUE undef) { @@ -1181,21 +1133,39 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) case T_CLASS: case T_MODULE: { - st_data_t val; + bool found; + VALUE val; - if (RCLASS_IV_TBL(obj) && - lock_st_lookup(RCLASS_IV_TBL(obj), (st_data_t)id, &val)) { - if (rb_is_instance_id(id) && - UNLIKELY(!rb_ractor_main_p()) && - !rb_ractor_shareable_p(val)) { - rb_raise(rb_eRactorIsolationError, - "can not get unshareable values from instance variables of classes/modules from non-main Ractors"); + RB_VM_LOCK_ENTER(); + { +#if !SHAPE_IN_BASIC_FLAGS + shape_id = RCLASS_SHAPE_ID(obj); +#endif + + attr_index_t index = 0; + shape = rb_shape_get_shape_by_id(shape_id); + found = rb_shape_get_iv_index(shape, id, &index); + + if (found) { + ivar_list = RCLASS_IVPTR(obj); + RUBY_ASSERT(ivar_list); + + val = ivar_list[index]; + } + else { + val = undef; } - return val; } - else { - return undef; + RB_VM_LOCK_LEAVE(); + + if (found && + rb_is_instance_id(id) && + UNLIKELY(!rb_ractor_main_p()) && + !rb_ractor_shareable_p(val)) { + rb_raise(rb_eRactorIsolationError, + "can not get unshareable values from instance variables of classes/modules from non-main Ractors"); } + return val; } case T_OBJECT: { @@ -1247,19 +1217,25 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) { rb_check_frozen(obj); - VALUE val = Qnil; + VALUE val = undef; attr_index_t index; switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id); - if (RCLASS_IV_TBL(obj)) { - st_data_t id_data = (st_data_t)id, val; - if (lock_st_delete(RCLASS_IV_TBL(obj), &id_data, &val)) { - return (VALUE)val; + + RB_VM_LOCK_ENTER(); + { + rb_shape_t * shape = rb_shape_get_shape(obj); + if (rb_shape_get_iv_index(shape, id, &index)) { + rb_shape_transition_shape_remove_ivar(obj, id, shape); + val = RCLASS_IVPTR(obj)[index]; + RCLASS_IVPTR(obj)[index] = Qundef; } } + RB_VM_LOCK_LEAVE(); + break; case T_OBJECT: { rb_shape_t * shape = rb_shape_get_shape(obj); @@ -1267,7 +1243,6 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) rb_shape_transition_shape_remove_ivar(obj, id, shape); val = ROBJECT_IVPTR(obj)[index]; ROBJECT_IVPTR(obj)[index] = Qundef; - return val; } break; @@ -1281,14 +1256,13 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) rb_gen_ivtbl_get(obj, id, &ivtbl); val = ivtbl->ivptr[index]; ivtbl->ivptr[index] = Qundef; - return val; } break; } } - return undef; + return val; } VALUE @@ -1553,10 +1527,9 @@ ivar_set(VALUE obj, ID id, VALUE val) } case T_CLASS: case T_MODULE: - // TODO: Transition shapes on classes - //rb_shape_transition_shape(obj, id, rb_shape_get_shape_by_id(RCLASS_SHAPE_ID(obj))); IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id); rb_class_ivar_set(obj, id, val); + break; default: generic_ivar_set(obj, id, val); @@ -1587,13 +1560,7 @@ rb_ivar_defined(VALUE obj, ID id) attr_index_t index; if (SPECIAL_CONST_P(obj)) return Qfalse; - switch (BUILTIN_TYPE(obj)) { - case T_CLASS: - case T_MODULE: - return RBOOL(RCLASS_IV_TBL(obj) && lock_st_is_member(RCLASS_IV_TBL(obj), (st_data_t)id)); - default: - return RBOOL(rb_shape_get_iv_index(rb_shape_get_shape(obj), id, &index)); - } + return RBOOL(rb_shape_get_iv_index(rb_shape_get_shape(obj), id, &index)); } typedef int rb_ivar_foreach_callback_func(ID key, VALUE val, st_data_t arg); @@ -1636,6 +1603,15 @@ gen_ivar_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg) iterate_over_shapes_with_callback(shape, ivtbl->ivptr, func, arg); } +static void +class_ivar_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg) +{ + RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); + + rb_shape_t* shape = rb_shape_get_shape(obj); + iterate_over_shapes_with_callback(shape, RCLASS_IVPTR(obj), func, arg); +} + void rb_copy_generic_ivar(VALUE clone, VALUE obj) { @@ -1720,13 +1696,11 @@ rb_ivar_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg) case T_CLASS: case T_MODULE: IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(0); - if (RCLASS_IV_TBL(obj)) { - RB_VM_LOCK_ENTER(); - { - st_foreach_safe(RCLASS_IV_TBL(obj), func, arg); - } - RB_VM_LOCK_LEAVE(); + RB_VM_LOCK_ENTER(); + { + class_ivar_each(obj, func, arg); } + RB_VM_LOCK_LEAVE(); break; default: if (FL_TEST(obj, FL_EXIVAR)) { @@ -1739,8 +1713,6 @@ rb_ivar_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg) st_index_t rb_ivar_count(VALUE obj) { - st_table *tbl; - if (SPECIAL_CONST_P(obj)) return 0; switch (BUILTIN_TYPE(obj)) { @@ -1758,8 +1730,22 @@ rb_ivar_count(VALUE obj) break; case T_CLASS: case T_MODULE: - if ((tbl = RCLASS_IV_TBL(obj)) != 0) { - return tbl->num_entries; + if (rb_shape_get_shape(obj)->next_iv_index > 0) { + st_index_t count = 0; + + RB_VM_LOCK_ENTER(); + { + st_index_t i, num = rb_shape_get_shape(obj)->next_iv_index; + const VALUE *const ivptr = RCLASS_IVPTR(obj); + for (i = count = 0; i < num; ++i) { + if (ivptr[i] != Qundef) { + count++; + } + } + } + RB_VM_LOCK_LEAVE(); + + return count; } break; default: @@ -1879,11 +1865,12 @@ rb_obj_remove_instance_variable(VALUE obj, VALUE name) case T_CLASS: case T_MODULE: IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id); - if (RCLASS_IV_TBL(obj)) { - st_data_t id_data = (st_data_t)id, val; - if (lock_st_delete(RCLASS_IV_TBL(obj), &id_data, &val)) { - return (VALUE)val; - } + rb_shape_t * shape = rb_shape_get_shape(obj); + if (rb_shape_get_iv_index(shape, id, &index)) { + rb_shape_transition_shape_remove_ivar(obj, id, shape); + val = RCLASS_IVPTR(obj)[index]; + RCLASS_IVPTR(obj)[index] = Qundef; + return val; } break; case T_OBJECT: { @@ -2030,8 +2017,22 @@ autoload_data(VALUE mod, ID id) struct st_table *tbl; st_data_t val; + // If we are called with a non-origin ICLASS, fetch the autoload data from + // the original module. + if (RB_TYPE_P(mod, T_ICLASS)) { + if (FL_TEST_RAW(mod, RICLASS_IS_ORIGIN)) { + return 0; + } else { + mod = RBASIC(mod)->klass; + } + } + + RUBY_ASSERT(RB_TYPE_P(mod, T_CLASS) || RB_TYPE_P(mod, T_MODULE)); + // Look up the instance variable table for `autoload`, then index into that table with the given constant name `id`. - if (!st_lookup(RCLASS_IV_TBL(mod), autoload, &val) || !(tbl = check_autoload_table((VALUE)val)) || !st_lookup(tbl, (st_data_t)id, &val)) { + + VALUE tbl_value = rb_ivar_lookup(mod, autoload, 0); + if (!tbl_value || !(tbl = check_autoload_table(tbl_value)) || !st_lookup(tbl, (st_data_t)id, &val)) { return 0; } @@ -2229,23 +2230,14 @@ autoload_feature_lookup_or_create(VALUE feature, struct autoload_data **autoload static struct st_table * autoload_table_lookup_or_create(VALUE module) { - // Get or create an autoload table in the class instance variables: - struct st_table *table = RCLASS_IV_TBL(module); - VALUE autoload_table_value; - - if (table && st_lookup(table, (st_data_t)autoload, &autoload_table_value)) { - return check_autoload_table((VALUE)autoload_table_value); - } - - if (!table) { - table = RCLASS_IV_TBL(module) = st_init_numtable(); + VALUE autoload_table_value = rb_ivar_lookup(module, autoload, 0); + if (autoload_table_value) { + return check_autoload_table(autoload_table_value); + } else { + autoload_table_value = TypedData_Wrap_Struct(0, &autoload_table_type, 0); + rb_class_ivar_set(module, autoload, autoload_table_value); + return (DATA_PTR(autoload_table_value) = st_init_numtable()); } - - autoload_table_value = TypedData_Wrap_Struct(0, &autoload_table_type, 0); - st_add_direct(table, (st_data_t)autoload, (st_data_t)autoload_table_value); - - RB_OBJ_WRITTEN(module, Qnil, autoload_table_value); - return (DATA_PTR(autoload_table_value) = st_init_numtable()); } static VALUE @@ -2314,10 +2306,13 @@ autoload_delete(VALUE module, ID name) { RUBY_ASSERT_CRITICAL_SECTION_ENTER(); - st_data_t value, load = 0, key = name; + st_data_t load = 0, key = name; - if (st_lookup(RCLASS_IV_TBL(module), (st_data_t)autoload, &value)) { - struct st_table *table = check_autoload_table((VALUE)value); + RUBY_ASSERT(RB_TYPE_P(module, T_CLASS) || RB_TYPE_P(module, T_MODULE)); + + VALUE table_value = rb_ivar_lookup(module, autoload, 0); + if (table_value) { + struct st_table *table = check_autoload_table(table_value); st_delete(table, &key, &load); @@ -2342,8 +2337,7 @@ autoload_delete(VALUE module, ID name) // If the autoload table is empty, we can delete it. if (table->num_entries == 0) { - name = autoload; - st_delete(RCLASS_IV_TBL(module), &name, &value); + rb_attr_delete(module, autoload); } } } @@ -3129,10 +3123,7 @@ set_namespace_path_i(ID id, VALUE v, void *payload) return ID_TABLE_CONTINUE; } set_namespace_path(value, build_const_path(parental_path, id)); - if (RCLASS_IV_TBL(value)) { - st_data_t tmp = tmp_classpath; - st_delete(RCLASS_IV_TBL(value), &tmp, 0); - } + rb_attr_delete(value, tmp_classpath); return ID_TABLE_CONTINUE; } @@ -3469,8 +3460,20 @@ original_module(VALUE c) static int cvar_lookup_at(VALUE klass, ID id, st_data_t *v) { - if (!RCLASS_IV_TBL(klass)) return 0; - return st_lookup(RCLASS_IV_TBL(klass), (st_data_t)id, v); + if (RB_TYPE_P(klass, T_ICLASS)) { + if (FL_TEST_RAW(klass, RICLASS_IS_ORIGIN)) { + return 0; + } else { + // check the original module + klass = RBASIC(klass)->klass; + } + } + + VALUE n = rb_ivar_lookup(klass, id, Qundef); + if (n == Qundef) return 0; + + if (v) *v = n; + return 1; } static VALUE @@ -3489,8 +3492,6 @@ static void cvar_overtaken(VALUE front, VALUE target, ID id) { if (front && target != front) { - st_data_t did = (st_data_t)id; - if (original_module(front) != original_module(target)) { rb_raise(rb_eRuntimeError, "class variable % "PRIsVALUE" of %"PRIsVALUE" is overtaken by %"PRIsVALUE"", @@ -3498,7 +3499,7 @@ cvar_overtaken(VALUE front, VALUE target, ID id) rb_class_name(original_module(target))); } if (BUILTIN_TYPE(front) == T_CLASS) { - st_delete(RCLASS_IV_TBL(front), &did, 0); + rb_ivar_delete(front, id, Qundef); } } } @@ -3543,9 +3544,8 @@ find_cvar(VALUE klass, VALUE * front, VALUE * target, ID id) static void check_for_cvar_table(VALUE subclass, VALUE key) { - st_table *tbl = RCLASS_IV_TBL(subclass); - - if (tbl && st_lookup(tbl, key, NULL)) { + // Must not check ivar on ICLASS + if (!RB_TYPE_P(subclass, T_ICLASS) && RTEST(rb_ivar_defined(subclass, key))) { RB_DEBUG_COUNTER_INC(cvar_class_invalidate); ruby_vm_global_cvar_state++; return; @@ -3688,9 +3688,9 @@ mod_cvar_at(VALUE mod, void *data) if (!tbl) { tbl = st_init_numtable(); } - if (RCLASS_IV_TBL(mod)) { - st_foreach_safe(RCLASS_IV_TBL(mod), cv_i, (st_data_t)tbl); - } + mod = original_module(mod); + + rb_ivar_foreach(mod, cv_i, (st_data_t)tbl); return tbl; } @@ -3793,13 +3793,14 @@ VALUE rb_mod_remove_cvar(VALUE mod, VALUE name) { const ID id = id_for_var_message(mod, name, class, "wrong class variable name %1$s"); - st_data_t val, n = id; + st_data_t val; if (!id) { goto not_defined; } rb_check_frozen(mod); - if (RCLASS_IV_TBL(mod) && st_delete(RCLASS_IV_TBL(mod), &n, &val)) { + val = rb_ivar_delete(mod, id, Qundef); + if (val != Qundef) { return (VALUE)val; } if (rb_cvar_defined(mod, id)) { @@ -3834,30 +3835,63 @@ rb_iv_set(VALUE obj, const char *name, VALUE val) int rb_class_ivar_set(VALUE obj, ID key, VALUE value) { - if (!RCLASS_IV_TBL(obj)) { - RCLASS_IV_TBL(obj) = st_init_numtable(); + RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); + int found; + + RB_VM_LOCK_ENTER(); + { + rb_shape_t * shape = rb_shape_get_shape(obj); + attr_index_t idx; + found = rb_shape_get_iv_index(shape, key, &idx); + + if (found) { + // Changing an existing instance variable + RUBY_ASSERT(RCLASS_IVPTR(obj)); + + RCLASS_IVPTR(obj)[idx] = value; + RB_OBJ_WRITTEN(obj, Qundef, value); + } else { + // Creating and setting a new instance variable + + // Move to a shape which fits the new ivar + idx = shape->next_iv_index; + shape = rb_shape_get_next(shape, obj, key); + + // We always allocate a power of two sized IV array. This way we + // only need to realloc when we expand into a new power of two size + if ((idx & (idx - 1)) == 0) { + size_t newsize = idx ? idx * 2 : 1; + REALLOC_N(RCLASS_IVPTR(obj), VALUE, newsize); + } + + RUBY_ASSERT(RCLASS_IVPTR(obj)); + + RB_OBJ_WRITE(obj, &RCLASS_IVPTR(obj)[idx], value); + rb_shape_set_shape(obj, shape); + } } + RB_VM_LOCK_LEAVE(); - st_table *tbl = RCLASS_IV_TBL(obj); - int result = lock_st_insert(tbl, (st_data_t)key, (st_data_t)value); - RB_OBJ_WRITTEN(obj, Qundef, value); - return result; + return found; } static int -tbl_copy_i(st_data_t key, st_data_t value, st_data_t data) -{ - RB_OBJ_WRITTEN((VALUE)data, Qundef, (VALUE)value); +tbl_copy_i(st_data_t key, st_data_t val, st_data_t dest) { + rb_class_ivar_set(dest, key, val); + return ST_CONTINUE; } void rb_iv_tbl_copy(VALUE dst, VALUE src) { - st_table *orig_tbl = RCLASS_IV_TBL(src); - st_table *new_tbl = st_copy(orig_tbl); - st_foreach(new_tbl, tbl_copy_i, (st_data_t)dst); - RCLASS_IV_TBL(dst) = new_tbl; + RUBY_ASSERT(rb_type(dst) == rb_type(src)); + RUBY_ASSERT(RB_TYPE_P(dst, T_CLASS) || RB_TYPE_P(dst, T_MODULE)); + + RUBY_ASSERT(RCLASS_SHAPE_ID(dst) == ROOT_SHAPE_ID); + RUBY_ASSERT(!RCLASS_IVPTR(dst)); + + rb_ivar_foreach(src, tbl_copy_i, dst); } MJIT_FUNC_EXPORTED rb_const_entry_t * diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 2434c5c3c64738..cff4b9138a6ead 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1169,7 +1169,23 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call case T_CLASS: case T_MODULE: { - goto general_path; + if (UNLIKELY(!rb_ractor_main_p())) { + // For two reasons we can only use the fast path on the main + // ractor. + // First, only the main ractor is allowed to set ivars on classes + // and modules. So we can skip locking. + // Second, other ractors need to check the shareability of the + // values returned from the class ivars. + goto general_path; + } + + ivar_list = RCLASS_IVPTR(obj); + +#if !SHAPE_IN_BASIC_FLAGS + shape_id = RCLASS_SHAPE_ID(obj); +#endif + + break; } default: if (FL_TEST_RAW(obj, FL_EXIVAR)) { @@ -1507,15 +1523,13 @@ vm_getclassvariable(const rb_iseq_t *iseq, const rb_control_frame_t *reg_cfp, ID { const rb_cref_t *cref; - if (ic->entry && ic->entry->global_cvar_state == GET_GLOBAL_CVAR_STATE()) { - VALUE v = Qundef; + if (ic->entry && ic->entry->global_cvar_state == GET_GLOBAL_CVAR_STATE() && LIKELY(rb_ractor_main_p())) { RB_DEBUG_COUNTER_INC(cvar_read_inline_hit); - if (st_lookup(RCLASS_IV_TBL(ic->entry->class_value), (st_data_t)id, &v) && - LIKELY(rb_ractor_main_p())) { + VALUE v = rb_ivar_lookup(ic->entry->class_value, id, Qundef); + RUBY_ASSERT(v != Qundef); - return v; - } + return v; } cref = vm_get_cref(GET_EP()); From 2d86e79fe6a8eaea85edb4b9ab59d12228dbd8b9 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 31 Oct 2022 15:38:51 -0700 Subject: [PATCH 077/148] Always lookup IV buffers when iterating Always look up instance variable buffers when iterating. It is possible for the instance variable buffer to change out from under the object during iteration, so we cannot cache the buffer on the stack. In the case of Bug #19095, the transient heap moved the buffer during iteration: ``` Watchpoint 1 hit: old value: 0x0000000107c00df8 new value: 0x00000001032743c0 Process 31720 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = watchpoint 1 frame #0: 0x00000001006e5178 miniruby`rb_obj_transient_heap_evacuate(obj=0x000000010d6b94b0, promote=1) at variable.c:1361:5 1358 } 1359 MEMCPY(new_ptr, old_ptr, VALUE, len); 1360 ROBJECT(obj)->as.heap.ivptr = new_ptr; -> 1361 } 1362 } 1363 #endif 1364 miniruby`rb_obj_transient_heap_evacuate: -> 0x1006e5178 <+328>: b 0x1006e517c ; <+332> at variable.c:1362:1 0x1006e517c <+332>: ldp x29, x30, [sp, #0x50] 0x1006e5180 <+336>: add sp, sp, #0x60 0x1006e5184 <+340>: ret Target 0: (miniruby) stopped. (lldb) bt * thread #1, queue = 'com.apple.main-thread', stop reason = watchpoint 1 * frame #0: 0x00000001006e5178 miniruby`rb_obj_transient_heap_evacuate(obj=0x000000010d6b94b0, promote=1) at variable.c:1361:5 frame #1: 0x00000001006cb150 miniruby`transient_heap_block_evacuate(theap=0x0000000100b196c0, block=0x0000000107c00000) at transient_heap.c:734:17 frame #2: 0x00000001006c854c miniruby`transient_heap_evacuate(dmy=0x0000000000000000) at transient_heap.c:808:17 frame #3: 0x00000001007fe6c0 miniruby`rb_postponed_job_flush(vm=0x0000000104402900) at vm_trace.c:1773:21 frame #4: 0x0000000100637a84 miniruby`rb_threadptr_execute_interrupts(th=0x0000000103803bc0, blocking_timing=0) at thread.c:2316:13 frame #5: 0x000000010078b730 miniruby`rb_vm_check_ints(ec=0x00000001048038d0) at vm_core.h:2025:9 frame #6: 0x00000001006fbd10 miniruby`vm_pop_frame(ec=0x00000001048038d0, cfp=0x0000000104a04440, ep=0x0000000104904a28) at vm_insnhelper.c:422:5 frame #7: 0x00000001006fbca0 miniruby`rb_vm_pop_frame(ec=0x00000001048038d0) at vm_insnhelper.c:431:5 frame #8: 0x00000001007d6420 miniruby`vm_call0_cfunc_with_frame(ec=0x00000001048038d0, calling=0x000000016fdcc6a0, argv=0x0000000000000000) at vm_eval.c:153:9 frame #9: 0x00000001007d44cc miniruby`vm_call0_cfunc(ec=0x00000001048038d0, calling=0x000000016fdcc6a0, argv=0x0000000000000000) at vm_eval.c:164:12 frame #10: 0x0000000100766e80 miniruby`vm_call0_body(ec=0x00000001048038d0, calling=0x000000016fdcc6a0, argv=0x0000000000000000) at vm_eval.c:210:15 frame #11: 0x00000001007d76f0 miniruby`vm_call0_cc(ec=0x00000001048038d0, recv=0x000000010d6b49d8, id=2769, argc=0, argv=0x0000000000000000, cc=0x000000010d6b2e58, kw_splat=0) at vm_eval.c:87:12 frame #12: 0x0000000100769e48 miniruby`rb_funcallv_scope(recv=0x000000010d6b49d8, mid=2769, argc=0, argv=0x0000000000000000, scope=CALL_FCALL) at vm_eval.c:1051:16 frame #13: 0x0000000100760a54 miniruby`rb_funcallv(recv=0x000000010d6b49d8, mid=2769, argc=0, argv=0x0000000000000000) at vm_eval.c:1066:12 frame #14: 0x000000010037513c miniruby`rb_inspect(obj=0x000000010d6b49d8) at object.c:633:34 frame #15: 0x000000010002c950 miniruby`inspect_ary(ary=0x000000010d6b4938, dummy=0x0000000000000000, recur=0) at array.c:3091:13 frame #16: 0x0000000100642020 miniruby`exec_recursive(func=(miniruby`inspect_ary at array.c:3084), obj=0x000000010d6b4938, pairid=0x0000000000000000, arg=0x0000000000000000, outer=0, mid=2769) at thread.c:5177:23 frame #17: 0x00000001006412fc miniruby`rb_exec_recursive(func=(miniruby`inspect_ary at array.c:3084), obj=0x000000010d6b4938, arg=0x0000000000000000) at thread.c:5205:12 frame #18: 0x00000001000127f0 miniruby`rb_ary_inspect(ary=0x000000010d6b4938) at array.c:3117:12 ``` In general though, any calls back out to the interpreter could change the IV buffer, so it's not safe to cache. [Bug #19095] --- variable.c | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/variable.c b/variable.c index 0f71b2741816d5..745ace528f1a5b 100644 --- a/variable.c +++ b/variable.c @@ -1566,22 +1566,41 @@ rb_ivar_defined(VALUE obj, ID id) typedef int rb_ivar_foreach_callback_func(ID key, VALUE val, st_data_t arg); st_data_t rb_st_nth_key(st_table *tab, st_index_t index); +struct iv_itr_data { + VALUE obj; + struct gen_ivtbl * ivtbl; + st_data_t arg; +}; + static void -iterate_over_shapes_with_callback(rb_shape_t *shape, VALUE* iv_list, rb_ivar_foreach_callback_func *callback, st_data_t arg) +iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_func *callback, struct iv_itr_data * itr_data) { switch ((enum shape_type)shape->type) { case SHAPE_ROOT: return; case SHAPE_IVAR: - iterate_over_shapes_with_callback(rb_shape_get_shape_by_id(shape->parent_id), iv_list, callback, arg); + iterate_over_shapes_with_callback(rb_shape_get_shape_by_id(shape->parent_id), callback, itr_data); + VALUE * iv_list; + switch(BUILTIN_TYPE(itr_data->obj)) { + case T_OBJECT: + iv_list = ROBJECT_IVPTR(itr_data->obj); + break; + case T_CLASS: + case T_MODULE: + iv_list = RCLASS_IVPTR(itr_data->obj); + break; + default: + iv_list = itr_data->ivtbl->ivptr; + break; + } VALUE val = iv_list[shape->next_iv_index - 1]; if (val != Qundef) { - callback(shape->edge_name, val, arg); + callback(shape->edge_name, val, itr_data->arg); } return; case SHAPE_IVAR_UNDEF: case SHAPE_FROZEN: - iterate_over_shapes_with_callback(rb_shape_get_shape_by_id(shape->parent_id), iv_list, callback, arg); + iterate_over_shapes_with_callback(rb_shape_get_shape_by_id(shape->parent_id), callback, itr_data); return; } } @@ -1590,7 +1609,10 @@ static void obj_ivar_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg) { rb_shape_t* shape = rb_shape_get_shape(obj); - iterate_over_shapes_with_callback(shape, ROBJECT_IVPTR(obj), func, arg); + struct iv_itr_data itr_data; + itr_data.obj = obj; + itr_data.arg = arg; + iterate_over_shapes_with_callback(shape, func, &itr_data); } static void @@ -1600,7 +1622,11 @@ gen_ivar_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg) struct gen_ivtbl *ivtbl; if (!rb_gen_ivtbl_get(obj, 0, &ivtbl)) return; - iterate_over_shapes_with_callback(shape, ivtbl->ivptr, func, arg); + struct iv_itr_data itr_data; + itr_data.obj = obj; + itr_data.ivtbl = ivtbl; + itr_data.arg = arg; + iterate_over_shapes_with_callback(shape, func, &itr_data); } static void @@ -1609,7 +1635,10 @@ class_ivar_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg) RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)); rb_shape_t* shape = rb_shape_get_shape(obj); - iterate_over_shapes_with_callback(shape, RCLASS_IVPTR(obj), func, arg); + struct iv_itr_data itr_data; + itr_data.obj = obj; + itr_data.arg = arg; + iterate_over_shapes_with_callback(shape, func, &itr_data); } void From 4c59808a464e1d866a8645b6cacd946a05883f9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 15:27:13 +0000 Subject: [PATCH 078/148] [rubygems/rubygems] Bump rb-sys Bumps [rb-sys](https://github.com/oxidize-rb/rb-sys) from 0.9.34 to 0.9.35. - [Release notes](https://github.com/oxidize-rb/rb-sys/releases) - [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.34...v0.9.35) --- updated-dependencies: - dependency-name: rb-sys dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] https://github.com/rubygems/rubygems/commit/73268e7af5 --- .../rust_ruby_example/Cargo.lock | 8 ++++---- .../rust_ruby_example/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) 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 93e561357465da..e7e91de57674cc 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 @@ -153,18 +153,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.34" +version = "0.9.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ff8b3a4d418f3604ac3781aee54a094f3f9d732fb9a2458f73a3937a9ea918" +checksum = "2d2bde30824a18f2e68cd1c8004cec16656764c6efc385bc1c7fb4c904b276a5" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.34" +version = "0.9.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2921dcd727615d4af5a6d39cc3c1c8c9dc8e37bf2b42d44b3e101a6da2bce17" +checksum = "5ff5d3ba92624df9c66bf0d1f0251d96284f08ac9773b7723d370e3f225c1d38" dependencies = [ "bindgen", "linkify", 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 ecd8ea142ab34d..814afe10f72f2d 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 = { version = "0.9.34", features = ["gem"] } +rb-sys = { version = "0.9.35", features = ["gem"] } From 2d3ecc4d284b5c16a98f68fcf3265d3451ffe61c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Nov 2022 09:35:09 +0900 Subject: [PATCH 079/148] Adjust indents [ci skip] --- variable.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/variable.c b/variable.c index 745ace528f1a5b..fd0c30b9b53315 100644 --- a/variable.c +++ b/variable.c @@ -1581,17 +1581,17 @@ iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_fu case SHAPE_IVAR: iterate_over_shapes_with_callback(rb_shape_get_shape_by_id(shape->parent_id), callback, itr_data); VALUE * iv_list; - switch(BUILTIN_TYPE(itr_data->obj)) { - case T_OBJECT: - iv_list = ROBJECT_IVPTR(itr_data->obj); - break; - case T_CLASS: - case T_MODULE: - iv_list = RCLASS_IVPTR(itr_data->obj); - break; - default: - iv_list = itr_data->ivtbl->ivptr; - break; + switch (BUILTIN_TYPE(itr_data->obj)) { + case T_OBJECT: + iv_list = ROBJECT_IVPTR(itr_data->obj); + break; + case T_CLASS: + case T_MODULE: + iv_list = RCLASS_IVPTR(itr_data->obj); + break; + default: + iv_list = itr_data->ivtbl->ivptr; + break; } VALUE val = iv_list[shape->next_iv_index - 1]; if (val != Qundef) { From aa8c6759ee7d740939eab9ee0e94260aa8f4f010 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Nov 2022 11:23:31 +0900 Subject: [PATCH 080/148] vcs.rb: do not reference the constant before assignment --- tool/lib/vcs.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index d96caddbf2290c..857b7928a458a0 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -424,12 +424,12 @@ class GIT < self SAFE_DIRECTORIES ||= begin command = ENV["GIT"] || 'git' - IO.popen(%W"#{command} config --global --get-all safe.directory", &:read).split("\n") + dirs = IO.popen(%W"#{command} config --global --get-all safe.directory", &:read).split("\n") rescue command = nil - [] + dirs = [] ensure - VCS.dump(SAFE_DIRECTORIES, "safe.directory: ") if $DEBUG + VCS.dump(dirs, "safe.directory: ") if $DEBUG COMMAND = command end From 16953867ed8fc951364f05fdf0c9267799e3087a Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 1 Nov 2022 17:10:31 +1300 Subject: [PATCH 081/148] We don't care about actual hostname resolution. (#6652) https://bugs.ruby-lang.org/issues/18380 --- test/fiber/test_address_resolve.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fiber/test_address_resolve.rb b/test/fiber/test_address_resolve.rb index 457221b9b1ef2c..12223bcc06fa8b 100644 --- a/test/fiber/test_address_resolve.rb +++ b/test/fiber/test_address_resolve.rb @@ -269,7 +269,7 @@ def test_socket_getnameinfo_domain_blocking Fiber.set_scheduler scheduler Fiber.schedule do - result = Socket.getnameinfo(["AF_INET", 80, "example.com"], Socket::NI_NUMERICSERV) + result = Socket.getnameinfo(["AF_INET", 80, "example.com"], Socket::NI_NUMERICSERV | Socket::NI_NUMERICHOST) assert_equal(["1.2.3.4", "80"], result) end From f0c8c1e878978341701b0818c19bf383bd9efbae Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Nov 2022 10:51:35 +0900 Subject: [PATCH 082/148] vpath.rb: tweak --vpath option message --- tool/lib/vpath.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/lib/vpath.rb b/tool/lib/vpath.rb index 48ab148405d5b0..a52f840c30a503 100644 --- a/tool/lib/vpath.rb +++ b/tool/lib/vpath.rb @@ -56,7 +56,7 @@ def def_options(opt) opt.on("-I", "--srcdir=DIR", "add a directory to search path") {|dir| @additional << dir } - opt.on("-L", "--vpath=PATH LIST", "add directories to search path") {|dirs| + opt.on("-L", "--vpath=PATH-LIST", "add directories to search path") {|dirs| @additional << [dirs] } opt.on("--path-separator=SEP", /\A(?:\W\z|\.(\W).+)/, "separator for vpath") {|sep, vsep| From 99a79dc40bf20e0e5d588db4f93b69617affa7f3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Nov 2022 10:56:28 +0900 Subject: [PATCH 083/148] colorize.rb: support for NO_COLOR --- tool/lib/colorize.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/lib/colorize.rb b/tool/lib/colorize.rb index 11b878d3182c0e..f11137cfdf22c3 100644 --- a/tool/lib/colorize.rb +++ b/tool/lib/colorize.rb @@ -7,7 +7,7 @@ class Colorize def initialize(color = nil, opts = ((_, color = color, nil)[0] if Hash === color)) @colors = @reset = nil @color = (opts[:color] if opts) - if color or (color == nil && STDOUT.tty?) + if color or (color == nil && STDOUT.tty? && (ENV["NO_COLOR"] || "").empty?) if (%w[smso so].any? {|attr| /\A\e\[.*m\z/ =~ IO.popen("tput #{attr}", "r", :err => IO::NULL, &:read)} rescue nil) @beg = "\e[" colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:\n]*)/)] : {} From a2e7b11f2ae13f96171cb8a5aa6ae3cc75f6f083 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Nov 2022 11:08:47 +0900 Subject: [PATCH 084/148] output.rb: extract from generic_erb.rb - writing to a file or stdout - touching timestamp files - overwriting only if changed - colorizing --- tool/generic_erb.rb | 41 ++++++++------------------------------- tool/lib/output.rb | 47 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 33 deletions(-) create mode 100644 tool/lib/output.rb diff --git a/tool/generic_erb.rb b/tool/generic_erb.rb index 6af995fc139e4b..47bffd830c81dd 100644 --- a/tool/generic_erb.rb +++ b/tool/generic_erb.rb @@ -5,31 +5,23 @@ require 'erb' require 'optparse' -require_relative 'lib/vpath' -require_relative 'lib/colorize' +require_relative 'lib/output' -vpath = VPath.new -timestamp = nil -output = nil -ifchange = nil +out = Output.new source = false -color = nil templates = [] ARGV.options do |o| - o.on('-t', '--timestamp[=PATH]') {|v| timestamp = v || true} o.on('-i', '--input=PATH') {|v| template << v} - o.on('-o', '--output=PATH') {|v| output = v} - o.on('-c', '--[no-]if-change') {|v| ifchange = v} o.on('-x', '--source') {source = true} - o.on('--color') {color = true} - vpath.def_options(o) + out.def_options(o) o.order!(ARGV) templates << (ARGV.shift or abort o.to_s) if templates.empty? end -color = Colorize.new(color) -unchanged = color.pass("unchanged") -updated = color.fail("updated") + +# Used in prelude.c.tmpl and unicode_norm_gen.tmpl +output = out.path +vpath = out.vpath result = templates.map do |template| if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+ @@ -41,21 +33,4 @@ source ? erb.src : proc{erb.result(binding)}.call end result = result.size == 1 ? result[0] : result.join("") -if output - if ifchange and (vpath.open(output, "rb") {|f| f.read} rescue nil) == result - puts "#{output} #{unchanged}" - else - open(output, "wb") {|f| f.print result} - puts "#{output} #{updated}" - end - if timestamp - if timestamp == true - dir, base = File.split(output) - timestamp = File.join(dir, ".time." + base) - end - File.open(timestamp, 'a') {} - File.utime(nil, nil, timestamp) - end -else - print result -end +out.write(result) diff --git a/tool/lib/output.rb b/tool/lib/output.rb new file mode 100644 index 00000000000000..5e0e878322224d --- /dev/null +++ b/tool/lib/output.rb @@ -0,0 +1,47 @@ +require_relative 'vpath' +require_relative 'colorize' + +class Output + attr_reader :path, :vpath + + def initialize + @path = @timestamp = @ifchange = @color = nil + @vpath = VPath.new + end + + def def_options(opt) + opt.on('-o', '--output=PATH') {|v| @path = v} + opt.on('-t', '--timestamp[=PATH]') {|v| @timestamp = v || true} + opt.on('-c', '--[no-]if-change') {|v| @ifchange = v} + opt.on('--color') {@color = true} + @vpath.def_options(opt) + end + + def write(data) + unless @path + $stdout.print data + return true + end + color = Colorize.new(@color) + unchanged = color.pass("unchanged") + updated = color.fail("updated") + + if @ifchange and (@vpath.read(@path, "rb") == data rescue false) + puts "#{@path} #{unchanged}" + written = false + else + File.binwrite(@path, data) + puts "#{@path} #{updated}" + written = true + end + if timestamp = @timestamp + if timestamp == true + dir, base = File.split(@path) + timestamp = File.join(dir, ".time." + base) + end + File.binwrite(timestamp, '') + File.utime(nil, nil, timestamp) + end + written + end +end From 7e6e94262c9c916cb22dc9015be0e99659018c1f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Nov 2022 11:43:22 +0900 Subject: [PATCH 085/148] file2lastrev.rb: rename output as format Also: - format -> time_format - output -> formatter --- tool/file2lastrev.rb | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tool/file2lastrev.rb b/tool/file2lastrev.rb index 94c2b2a9d6c480..68d1b0b4aad910 100755 --- a/tool/file2lastrev.rb +++ b/tool/file2lastrev.rb @@ -11,17 +11,17 @@ Program = $0 -@output = nil -def self.output=(output) - if @output and @output != output +@format = nil +def self.format=(format) + if @format and @format != format raise "you can specify only one of --changed, --revision.h and --doxygen" end - @output = output + @format = format end @suppress_not_found = false @limit = 20 -format = '%Y-%m-%dT%H:%M:%S%z' +time_format = '%Y-%m-%dT%H:%M:%S%z' vcs = nil OptionParser.new {|opts| opts.banner << " paths..." @@ -33,17 +33,17 @@ def self.output=(output) srcdir = path end opts.on("--changed", "changed rev") do - self.output = :changed + self.format = :changed end opts.on("--revision.h", "RUBY_REVISION macro") do - self.output = :revision_h + self.format = :revision_h end opts.on("--doxygen", "Doxygen format") do - self.output = :doxygen + self.format = :doxygen end opts.on("--modified[=FORMAT]", "modified time") do |fmt| - self.output = :modified - format = fmt if fmt + self.format = :modified + time_format = fmt if fmt end opts.on("--limit=NUM", "limit branch name length (#@limit)", Integer) do |n| @limit = n @@ -61,8 +61,8 @@ def self.output=(output) end } -output = - case @output +formatter = + case @format when :changed, nil Proc.new {|last, changed| changed @@ -77,16 +77,16 @@ def self.output=(output) } when :modified Proc.new {|last, changed, modified| - modified.strftime(format) + modified.strftime(time_format) } else - raise "unknown output format `#{@output}'" + raise "unknown output format `#{@format}'" end ok = true (ARGV.empty? ? [nil] : ARGV).each do |arg| begin - puts output[*vcs.get_revisions(arg)] + puts formatter[*vcs.get_revisions(arg)] rescue => e warn "#{File.basename(Program)}: #{e.message}" ok = false From 6bf458eefdac6e03fe4f2989b6cf08ad6a838520 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Nov 2022 12:29:46 +0900 Subject: [PATCH 086/148] file2lastrev.rb: use output.rb for the options --- tool/file2lastrev.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tool/file2lastrev.rb b/tool/file2lastrev.rb index 68d1b0b4aad910..db9e6fec429350 100755 --- a/tool/file2lastrev.rb +++ b/tool/file2lastrev.rb @@ -8,6 +8,7 @@ # this file run with BASERUBY, which may be older than 1.9, so no # require_relative require File.expand_path('../lib/vcs', __FILE__) +require File.expand_path('../lib/output', __FILE__) Program = $0 @@ -20,6 +21,7 @@ def self.format=(format) end @suppress_not_found = false @limit = 20 +@output = Output.new time_format = '%Y-%m-%dT%H:%M:%S%z' vcs = nil @@ -51,6 +53,7 @@ def self.format=(format) opts.on("-q", "--suppress_not_found") do @suppress_not_found = true end + @output.def_options(opts) opts.order! rescue abort "#{File.basename(Program)}: #{$!}\n#{opts}" begin vcs = VCS.detect(srcdir || ".", vcs_options, opts.new) @@ -69,7 +72,7 @@ def self.format=(format) } when :revision_h Proc.new {|last, changed, modified, branch, title| - vcs.revision_header(last, modified, modified, branch, title, limit: @limit) + vcs.revision_header(last, modified, modified, branch, title, limit: @limit).join("\n") } when :doxygen Proc.new {|last, changed| @@ -86,7 +89,7 @@ def self.format=(format) ok = true (ARGV.empty? ? [nil] : ARGV).each do |arg| begin - puts formatter[*vcs.get_revisions(arg)] + @output.write(formatter[*vcs.get_revisions(arg)]+"\n") rescue => e warn "#{File.basename(Program)}: #{e.message}" ok = false From a70f90e1a95cf4dfbaa2485767156997eb958c4b Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 1 Nov 2022 11:39:13 -0400 Subject: [PATCH 087/148] YJIT: Delete redundant ways to make Context Context::new() is the same as Context::default() and Context::new_with_stack_size() was only used in tests. --- yjit/src/codegen.rs | 11 ++++++----- yjit/src/core.rs | 16 ---------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index bd40af9f797312..de0a2dec0fee5a 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -5049,7 +5049,7 @@ fn gen_send_iseq( }; // Create a context for the callee - let mut callee_ctx = Context::new(); // Was DEFAULT_CTX + let mut callee_ctx = Context::default(); // Set the argument types in the callee's context for arg_idx in 0..argc { @@ -6870,7 +6870,7 @@ mod tests { return ( JITState::new(&block), - Context::new(), + Context::default(), Assembler::new(), CodeBlock::new_dummy(256 * 1024), OutlinedCb::wrap(CodeBlock::new_dummy(256 * 1024)), @@ -6913,18 +6913,19 @@ mod tests { asm.compile(&mut cb); assert_eq!(status, KeepCompiling); - assert_eq!(context.diff(&Context::new()), 0); + assert_eq!(context.diff(&Context::default()), 0); assert_eq!(cb.get_write_pos(), 0); } #[test] fn test_gen_pop() { let (mut jit, _, mut asm, _cb, mut ocb) = setup_codegen(); - let mut context = Context::new_with_stack_size(1); + let mut context = Context::default(); + context.stack_push(Type::Fixnum); let status = gen_pop(&mut jit, &mut context, &mut asm, &mut ocb); assert_eq!(status, KeepCompiling); - assert_eq!(context.diff(&Context::new()), 0); + assert_eq!(context.diff(&Context::default()), 0); } #[test] diff --git a/yjit/src/core.rs b/yjit/src/core.rs index 9288a0188b83ad..70a6540b5b2bea 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -1005,22 +1005,6 @@ impl Block { } impl Context { - pub fn new_with_stack_size(size: i16) -> Self { - return Context { - stack_size: size as u16, - sp_offset: size, - chain_depth: 0, - local_types: [Type::Unknown; MAX_LOCAL_TYPES], - temp_types: [Type::Unknown; MAX_TEMP_TYPES], - self_type: Type::Unknown, - temp_mapping: [MapToStack; MAX_TEMP_TYPES], - }; - } - - pub fn new() -> Self { - return Self::new_with_stack_size(0); - } - pub fn get_stack_size(&self) -> u16 { self.stack_size } From cbf15e5cbe30cbca45810ef65224ad1bc19c67b4 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 1 Nov 2022 11:44:55 -0400 Subject: [PATCH 088/148] YJIT: Add an assert to help with Context changes While experimenting I found that it's easy to change Context and forget to also change the copying operation in limit_block_versions(). Add an assert to make sure we substitute a compatible generic context when limiting the number of versions. --- yjit/src/core.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/yjit/src/core.rs b/yjit/src/core.rs index 70a6540b5b2bea..dd28c2361ec38f 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -846,7 +846,12 @@ pub fn limit_block_versions(blockid: BlockId, ctx: &Context) -> Context { generic_ctx.stack_size = ctx.stack_size; generic_ctx.sp_offset = ctx.sp_offset; - // Mutate the incoming context + debug_assert_ne!( + usize::MAX, + ctx.diff(&generic_ctx), + "should substitute a compatible context", + ); + return generic_ctx; } From 0d1e1987d1ca90cf2d2374f576be2633c52a66f4 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 1 Nov 2022 12:05:36 -0700 Subject: [PATCH 089/148] YJIT: Visualize live ranges on register spill (#6651) --- yjit/src/backend/ir.rs | 43 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/yjit/src/backend/ir.rs b/yjit/src/backend/ir.rs index 64e5805d9e0762..0aa087630d5d42 100644 --- a/yjit/src/backend/ir.rs +++ b/yjit/src/backend/ir.rs @@ -932,15 +932,14 @@ impl Assembler // Mutate the pool bitmap to indicate that the register at that index // has been allocated and is live. - fn alloc_reg(pool: &mut u32, regs: &Vec) -> Reg { + fn alloc_reg(pool: &mut u32, regs: &Vec) -> Option { for (index, reg) in regs.iter().enumerate() { if (*pool & (1 << index)) == 0 { *pool |= 1 << index; - return *reg; + return Some(*reg); } } - - unreachable!("Register spill not supported"); + None } // Allocate a specific register @@ -966,6 +965,29 @@ impl Assembler } } + // Dump live registers for register spill debugging. + fn dump_live_regs(insns: Vec, live_ranges: Vec, num_regs: usize, spill_index: usize) { + // Convert live_ranges to live_regs: the number of live registers at each index + let mut live_regs: Vec = vec![]; + let mut end_idxs: Vec = vec![]; + for (cur_idx, &end_idx) in live_ranges.iter().enumerate() { + end_idxs.push(end_idx); + while let Some(end_idx) = end_idxs.iter().position(|&end_idx| cur_idx == end_idx) { + end_idxs.remove(end_idx); + } + live_regs.push(end_idxs.len()); + } + + // Dump insns along with live registers + for (insn_idx, insn) in insns.iter().enumerate() { + print!("{:3} ", if spill_index == insn_idx { "==>" } else { "" }); + for reg in 0..=num_regs { + print!("{:1}", if reg < live_regs[insn_idx] { "|" } else { "" }); + } + println!(" [{:3}] {:?}", insn_idx, insn); + } + } + let live_ranges: Vec = take(&mut self.live_ranges); let mut asm = Assembler::new_with_label_names(take(&mut self.label_names)); let mut iterator = self.into_draining_iter(); @@ -1050,8 +1072,17 @@ impl Assembler let reg = opnd.unwrap_reg(); Some(take_reg(&mut pool, ®s, ®)) }, - _ => { - Some(alloc_reg(&mut pool, ®s)) + _ => match alloc_reg(&mut pool, ®s) { + Some(reg) => Some(reg), + None => { + let mut insns = asm.insns; + insns.push(insn); + for insn in iterator.insns { + insns.push(insn); + } + dump_live_regs(insns, live_ranges, regs.len(), index); + unreachable!("Register spill not supported"); + } } }; } From 70173a72a29474b6fd8fdc629954a95a4b0a3793 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 1 Nov 2022 12:31:24 -0700 Subject: [PATCH 090/148] Ivar copy needs to happen _before_ setting the shape When we copy instance variables, it is possible for the GC to be kicked off. The GC looks at the shape to determine what slots to mark inside the object. If the shape is set too soon, the GC could think that there are more instance variables on the object than there actually are at that moment. --- misc/lldb_rb/rb_base_command.py | 4 ++-- object.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/misc/lldb_rb/rb_base_command.py b/misc/lldb_rb/rb_base_command.py index bf98b6761255bc..de90a1617c2602 100644 --- a/misc/lldb_rb/rb_base_command.py +++ b/misc/lldb_rb/rb_base_command.py @@ -19,8 +19,8 @@ def lldb_init(cls, debugger): imemo_types = target.FindFirstType("enum imemo_type") - for member in imemo_types.GetEnumMembers(): - g[member.GetName()] = member.GetValueAsUnsigned() + #for member in imemo_types.GetEnumMembers(): + # g[member.GetName()] = member.GetValueAsUnsigned() for enum in target.FindFirstGlobalVariable("ruby_dummy_gdb_enums"): enum = enum.GetType() diff --git a/object.c b/object.c index 5f8568e3b4f747..aa337ea2cebd9f 100644 --- a/object.c +++ b/object.c @@ -298,6 +298,10 @@ init_copy(VALUE dest, VALUE obj) rb_copy_generic_ivar(dest, obj); rb_gc_copy_finalizer(dest, obj); + if (RB_TYPE_P(obj, T_OBJECT)) { + rb_obj_copy_ivar(dest, obj); + } + if (!RB_TYPE_P(obj, T_CLASS) && !RB_TYPE_P(obj, T_MODULE)) { rb_shape_t *shape_to_set = rb_shape_get_shape(obj); @@ -310,10 +314,6 @@ init_copy(VALUE dest, VALUE obj) // shape ids are different rb_shape_set_shape(dest, shape_to_set); } - - if (RB_TYPE_P(obj, T_OBJECT)) { - rb_obj_copy_ivar(dest, obj); - } } static VALUE immutable_obj_clone(VALUE obj, VALUE kwfreeze); From 265a96b005748beb1dea9a3de67ae35f96374c97 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Nov 2022 13:32:54 +0900 Subject: [PATCH 091/148] Manage the timestamp for revision.h --- common.mk | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/common.mk b/common.mk index 2bf44b530f336f..1a20e005d25ebe 100644 --- a/common.mk +++ b/common.mk @@ -1228,13 +1228,8 @@ $(BUILTIN_RB_INCS): $(top_srcdir)/tool/mk_builtin_loader.rb $(srcdir)/revision.h: $(REVISION_H) -revision.$(HAVE_BASERUBY:no=tmp):: - $(Q) $(NULLCMD) > $@ -revision.$(HAVE_BASERUBY:yes=tmp):: $(srcdir)/version.h $(tooldir)/file2lastrev.rb $(REVISION_FORCE) - $(Q) $(BASERUBY) $(tooldir)/file2lastrev.rb -q --revision.h --srcdir="$(srcdir)" > $@ - -$(REVISION_H): revision.tmp - -$(Q)$(IFCHANGE) "--timestamp=$@" "$(srcdir)/revision.h" revision.tmp +$(REVISION_H): + $(Q) $(BASERUBY) $(tooldir)/file2lastrev.rb -q --revision.h --srcdir="$(srcdir)" --output=revision.h --timestamp=$@ $(srcdir)/ext/ripper/ripper.c: $(srcdir)/ext/ripper/tools/preproc.rb $(srcdir)/parse.y $(srcdir)/defs/id.def $(srcdir)/ext/ripper/depend $(ECHO) generating $@ From 60f12c7d2e37ebb57193612421ab66d6368eff6e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 2 Nov 2022 11:33:08 +0900 Subject: [PATCH 092/148] Fix infinite loop when out-of-place build --- template/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/Makefile.in b/template/Makefile.in index 9b3f7852170898..666a9773d80ff1 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -381,7 +381,7 @@ install-cross: $(arch)-fake.rb $(RBCONFIG) rbconfig.rb $(arch_hdrdir)/ruby/confi Makefile: $(srcdir)/template/Makefile.in $(srcdir)/enc/Makefile.in -$(MKFILES): config.status $(srcdir)/version.h $(srcdir)/revision.h $(ABI_VERSION_HDR) +$(MKFILES): config.status $(srcdir)/version.h $(REVISION_H) $(ABI_VERSION_HDR) @[ -f $@ ] && mv $@ $@.old MAKE=$(MAKE) $(SHELL) ./config.status $@ @cmp $@ $@.old > /dev/null 2>&1 && echo $@ unchanged && exit 0; \ From ac06951c31c59781951a70788e4b0de2aa3d481c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 2 Nov 2022 11:37:54 +0900 Subject: [PATCH 093/148] file2lastrev.rb: separate options for `Output` and `VPath` So the `--srcdir` option in this file can override the same option in `VPath`. --- tool/file2lastrev.rb | 3 ++- tool/lib/output.rb | 1 + tool/lib/vpath.rb | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tool/file2lastrev.rb b/tool/file2lastrev.rb index db9e6fec429350..a2b639a426e590 100755 --- a/tool/file2lastrev.rb +++ b/tool/file2lastrev.rb @@ -28,11 +28,13 @@ def self.format=(format) OptionParser.new {|opts| opts.banner << " paths..." vcs_options = VCS.define_options(opts) + opts.new {@output.def_options(opts)} srcdir = nil opts.new opts.on("--srcdir=PATH", "use PATH as source directory") do |path| abort "#{File.basename(Program)}: srcdir is already set" if srcdir srcdir = path + @output.vpath.add(srcdir) end opts.on("--changed", "changed rev") do self.format = :changed @@ -53,7 +55,6 @@ def self.format=(format) opts.on("-q", "--suppress_not_found") do @suppress_not_found = true end - @output.def_options(opts) opts.order! rescue abort "#{File.basename(Program)}: #{$!}\n#{opts}" begin vcs = VCS.detect(srcdir || ".", vcs_options, opts.new) diff --git a/tool/lib/output.rb b/tool/lib/output.rb index 5e0e878322224d..584be769748c0b 100644 --- a/tool/lib/output.rb +++ b/tool/lib/output.rb @@ -10,6 +10,7 @@ def initialize end def def_options(opt) + opt.separator(" Output common options:") opt.on('-o', '--output=PATH') {|v| @path = v} opt.on('-t', '--timestamp[=PATH]') {|v| @timestamp = v || true} opt.on('-c', '--[no-]if-change') {|v| @ifchange = v} diff --git a/tool/lib/vpath.rb b/tool/lib/vpath.rb index a52f840c30a503..fa819f32423ffe 100644 --- a/tool/lib/vpath.rb +++ b/tool/lib/vpath.rb @@ -53,6 +53,7 @@ def foreach(file, *args, &block) end def def_options(opt) + opt.separator(" VPath common options:") opt.on("-I", "--srcdir=DIR", "add a directory to search path") {|dir| @additional << dir } @@ -80,6 +81,10 @@ def list @list end + def add(path) + @additional << path + end + def strip(path) prefix = list.map {|dir| Regexp.quote(dir)} path.sub(/\A#{prefix.join('|')}(?:\/|\z)/, '') From 3475b661607e6d2c72460ea381ee856726128259 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 2 Nov 2022 12:18:17 +0900 Subject: [PATCH 094/148] file2lastrev.rb: try to overwrite the found revision.h as before --- tool/file2lastrev.rb | 2 +- tool/lib/output.rb | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tool/file2lastrev.rb b/tool/file2lastrev.rb index a2b639a426e590..48b8a1ae990c1f 100755 --- a/tool/file2lastrev.rb +++ b/tool/file2lastrev.rb @@ -90,7 +90,7 @@ def self.format=(format) ok = true (ARGV.empty? ? [nil] : ARGV).each do |arg| begin - @output.write(formatter[*vcs.get_revisions(arg)]+"\n") + @output.write(formatter[*vcs.get_revisions(arg)]+"\n", overwrite: true) rescue => e warn "#{File.basename(Program)}: #{e.message}" ok = false diff --git a/tool/lib/output.rb b/tool/lib/output.rb index 584be769748c0b..cbaa4b5a3e8c3b 100644 --- a/tool/lib/output.rb +++ b/tool/lib/output.rb @@ -18,7 +18,7 @@ def def_options(opt) @vpath.def_options(opt) end - def write(data) + def write(data, overwrite: false) unless @path $stdout.print data return true @@ -26,13 +26,16 @@ def write(data) color = Colorize.new(@color) unchanged = color.pass("unchanged") updated = color.fail("updated") + outpath = nil - if @ifchange and (@vpath.read(@path, "rb") == data rescue false) - puts "#{@path} #{unchanged}" + if @ifchange and (@vpath.open(@path, "rb") {|f| outpath = f.path; f.read == data} rescue false) + puts "#{outpath} #{unchanged}" written = false else - File.binwrite(@path, data) - puts "#{@path} #{updated}" + unless overwrite and outpath and (File.binwrite(outpath, data) rescue nil) + File.binwrite(outpath = @path, data) + end + puts "#{outpath} #{updated}" written = true end if timestamp = @timestamp From 8e2c70af141239f5142642db35cee11c3d00bd24 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 2 Nov 2022 13:31:42 +0900 Subject: [PATCH 095/148] file2lastrev.rb: changed revision may be `nil` [ci skip] When `--suppress_not_found` option is given, no revision information is available. And remove extraneous newline, when result is empty or ends with a newline. --- tool/file2lastrev.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tool/file2lastrev.rb b/tool/file2lastrev.rb index 48b8a1ae990c1f..1dfb01ab9e5e96 100755 --- a/tool/file2lastrev.rb +++ b/tool/file2lastrev.rb @@ -69,7 +69,7 @@ def self.format=(format) case @format when :changed, nil Proc.new {|last, changed| - changed + changed || "" } when :revision_h Proc.new {|last, changed, modified, branch, title| @@ -90,7 +90,9 @@ def self.format=(format) ok = true (ARGV.empty? ? [nil] : ARGV).each do |arg| begin - @output.write(formatter[*vcs.get_revisions(arg)]+"\n", overwrite: true) + data = formatter[*vcs.get_revisions(arg)] + data.sub!(/(? e warn "#{File.basename(Program)}: #{e.message}" ok = false From d9cf0388599a3234b9f3c06ddd006cd59a58ab8b Mon Sep 17 00:00:00 2001 From: Sampat Badhe Date: Wed, 2 Nov 2022 10:35:54 +0530 Subject: [PATCH 096/148] Update Regexp.timeout doc (#6658) * Correct Regexp.timeout doc, Timeout.timeout= => Regexp.timeout= * add link Regexp Timeout section --- doc/regexp.rdoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/regexp.rdoc b/doc/regexp.rdoc index d623487a98e9ef..92c7ecf66e8d7b 100644 --- a/doc/regexp.rdoc +++ b/doc/regexp.rdoc @@ -28,7 +28,7 @@ Specifically, /st/ requires that the string contains the letter _s_ followed by the letter _t_, so it matches _haystack_, also. Note that any Regexp matching will raise a RuntimeError if timeout is set and -exceeded. See "Timeout" section in detail. +exceeded. See {"Timeout"}[#label-Timeout] section in detail. == \Regexp Interpolation @@ -781,7 +781,7 @@ with a{0,29}: == Timeout -There are two APIs to set timeout. One is Timeout.timeout=, which is +There are two APIs to set timeout. One is Regexp.timeout=, which is process-global configuration of timeout for Regexp matching. Regexp.timeout = 3 From 875b7b3361d6073cfbad6161c595b3640210cb03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 00:17:02 +0000 Subject: [PATCH 097/148] [rubygems/rubygems] Bump rb-sys in /test/rubygems/test_gem_ext_cargo_builder/custom_name Bumps [rb-sys](https://github.com/oxidize-rb/rb-sys) from 0.9.34 to 0.9.35. - [Release notes](https://github.com/oxidize-rb/rb-sys/releases) - [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.34...v0.9.35) --- updated-dependencies: - dependency-name: rb-sys dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] https://github.com/rubygems/rubygems/commit/19feb314cb --- .../test_gem_ext_cargo_builder/custom_name/Cargo.lock | 8 ++++---- .../test_gem_ext_cargo_builder/custom_name/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) 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 7bb26c9f7a0e4b..aa975b1cd02d05 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 @@ -160,18 +160,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.34" +version = "0.9.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ff8b3a4d418f3604ac3781aee54a094f3f9d732fb9a2458f73a3937a9ea918" +checksum = "2d2bde30824a18f2e68cd1c8004cec16656764c6efc385bc1c7fb4c904b276a5" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.34" +version = "0.9.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2921dcd727615d4af5a6d39cc3c1c8c9dc8e37bf2b42d44b3e101a6da2bce17" +checksum = "5ff5d3ba92624df9c66bf0d1f0251d96284f08ac9773b7723d370e3f225c1d38" dependencies = [ "bindgen", "linkify", 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 ce05eb6f84080d..6673f784645b9a 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 = { version = "0.9.34", features = ["gem"] } +rb-sys = { version = "0.9.35", features = ["gem"] } From 4a8cd9e8bca2be1c14310cc13366ec9177a1f74d Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 2 Nov 2022 11:03:21 -0400 Subject: [PATCH 098/148] Use shared flags of the type The ELTS_SHARED flag is generic, so we should prefer to use the flags specific of the type (STR_SHARED for strings and RARRAY_SHARED_FLAG for arrays). --- ext/objspace/objspace_dump.c | 3 ++- internal/string.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index 2917d493310ea9..7c7cae34887f9c 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -14,6 +14,7 @@ #include "gc.h" #include "internal.h" +#include "internal/array.h" #include "internal/hash.h" #include "internal/string.h" #include "internal/sanitizers.h" @@ -456,7 +457,7 @@ dump_object(VALUE obj, struct dump_config *dc) case T_ARRAY: dump_append(dc, ", \"length\":"); dump_append_ld(dc, RARRAY_LEN(obj)); - if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, ELTS_SHARED)) + if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, RARRAY_SHARED_FLAG)) dump_append(dc, ", \"shared\":true"); if (FL_TEST(obj, RARRAY_EMBED_FLAG)) dump_append(dc, ", \"embedded\":true"); diff --git a/internal/string.h b/internal/string.h index 43b716f9b25f4b..12edbff2b12c30 100644 --- a/internal/string.h +++ b/internal/string.h @@ -106,7 +106,7 @@ STR_EMBED_P(VALUE str) static inline bool STR_SHARED_P(VALUE str) { - return FL_ALL_RAW(str, STR_NOEMBED | ELTS_SHARED); + return FL_ALL_RAW(str, STR_NOEMBED | STR_SHARED); } static inline bool From 946bb34fb57b9588b93d8435c9a581d0c7ef0019 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 2 Nov 2022 08:14:31 -0700 Subject: [PATCH 099/148] YJIT: Avoid accumulating freed pages in the payload (#6657) Co-Authored-By: Alan Wu Co-Authored-By: Maxime Chevalier-Boisvert Co-authored-by: Alan Wu Co-authored-by: Maxime Chevalier-Boisvert --- yjit/src/asm/mod.rs | 6 ++++++ yjit/src/core.rs | 35 +++++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/yjit/src/asm/mod.rs b/yjit/src/asm/mod.rs index 7ac3625fbd25f7..6b2dd7da9a381d 100644 --- a/yjit/src/asm/mod.rs +++ b/yjit/src/asm/mod.rs @@ -6,6 +6,8 @@ use std::rc::Rc; use crate::backend::x86_64::JMP_PTR_BYTES; #[cfg(target_arch = "aarch64")] use crate::backend::arm64::JMP_PTR_BYTES; +use crate::core::IseqPayload; +use crate::core::for_each_off_stack_iseq_payload; use crate::core::for_each_on_stack_iseq_payload; use crate::invariants::rb_yjit_tracing_invalidate_all; use crate::stats::incr_counter; @@ -571,6 +573,10 @@ impl CodeBlock { let mut freed_pages: Vec = pages_in_use.iter().enumerate() .filter(|&(_, &in_use)| !in_use).map(|(page, _)| page).collect(); self.free_pages(&freed_pages); + // Avoid accumulating freed pages for future code GC + for_each_off_stack_iseq_payload(|iseq_payload: &mut IseqPayload| { + iseq_payload.pages.clear(); + }); // Append virtual pages in case RubyVM::YJIT.code_gc is manually triggered. let mut virtual_pages: Vec = (self.num_mapped_pages()..self.num_virtual_pages()).collect(); diff --git a/yjit/src/core.rs b/yjit/src/core.rs index dd28c2361ec38f..c0e48e87b2bc20 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -552,20 +552,39 @@ pub fn for_each_iseq(mut callback: F) { unsafe { rb_yjit_for_each_iseq(Some(callback_wrapper), (&mut data) as *mut _ as *mut c_void) }; } +/// Iterate over all on-stack ISEQs +pub fn for_each_on_stack_iseq(mut callback: F) { + unsafe extern "C" fn callback_wrapper(iseq: IseqPtr, data: *mut c_void) { + let callback: &mut &mut dyn FnMut(IseqPtr) -> bool = std::mem::transmute(&mut *data); + callback(iseq); + } + let mut data: &mut dyn FnMut(IseqPtr) = &mut callback; + unsafe { rb_jit_cont_each_iseq(Some(callback_wrapper), (&mut data) as *mut _ as *mut c_void) }; +} + /// Iterate over all on-stack ISEQ payloads -#[cfg(not(test))] pub fn for_each_on_stack_iseq_payload(mut callback: F) { - unsafe extern "C" fn callback_wrapper(iseq: IseqPtr, data: *mut c_void) { - let callback: &mut &mut dyn FnMut(&IseqPayload) -> bool = std::mem::transmute(&mut *data); + for_each_on_stack_iseq(|iseq| { if let Some(iseq_payload) = get_iseq_payload(iseq) { callback(iseq_payload); } - } - let mut data: &mut dyn FnMut(&IseqPayload) = &mut callback; - unsafe { rb_jit_cont_each_iseq(Some(callback_wrapper), (&mut data) as *mut _ as *mut c_void) }; + }); +} + +/// Iterate over all NOT on-stack ISEQ payloads +pub fn for_each_off_stack_iseq_payload(mut callback: F) { + let mut on_stack_iseqs: Vec = vec![]; + for_each_on_stack_iseq(|iseq| { + on_stack_iseqs.push(iseq); + }); + for_each_iseq(|iseq| { + if !on_stack_iseqs.contains(&iseq) { + if let Some(iseq_payload) = get_iseq_payload(iseq) { + callback(iseq_payload); + } + } + }) } -#[cfg(test)] -pub fn for_each_on_stack_iseq_payload(mut _callback: F) {} /// Free the per-iseq payload #[no_mangle] From ee7c031dc46b4e86f889b2f98cb479d79774a446 Mon Sep 17 00:00:00 2001 From: Noah Gibbs Date: Wed, 2 Nov 2022 15:16:26 +0000 Subject: [PATCH 100/148] YJIT: don't show a full crash report if mmap is only out of memory (#6659) --- yjit.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/yjit.c b/yjit.c index d7f369ca2ec568..1694e1edd7a3f0 100644 --- a/yjit.c +++ b/yjit.c @@ -307,6 +307,10 @@ rb_yjit_reserve_addr_space(uint32_t mem_size) // Check that the memory mapping was successful if (mem_block == MAP_FAILED) { perror("ruby: yjit: mmap:"); + if(errno == ENOMEM) { + // No crash report if it's only insufficient memory + exit(EXIT_FAILURE); + } rb_bug("mmap failed"); } From 81e84e0a4d348309d5d38311d283d049ffeeb7a2 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 2 Nov 2022 09:30:48 -0700 Subject: [PATCH 101/148] YJIT: Support invokeblock (#6640) * YJIT: Support invokeblock * Update yjit/src/backend/arm64/mod.rs * Update yjit/src/codegen.rs Co-authored-by: Maxime Chevalier-Boisvert --- yjit.c | 14 ++- yjit.rb | 1 + yjit/bindgen/src/main.rs | 5 +- yjit/src/backend/arm64/mod.rs | 8 +- yjit/src/backend/x86_64/mod.rs | 1 + yjit/src/codegen.rs | 150 ++++++++++++++++++++++++++++----- yjit/src/core.rs | 10 +++ yjit/src/cruby.rs | 20 ++++- yjit/src/cruby_bindings.inc.rs | 11 ++- yjit/src/stats.rs | 8 ++ 10 files changed, 198 insertions(+), 30 deletions(-) diff --git a/yjit.c b/yjit.c index 1694e1edd7a3f0..b943277d61167b 100644 --- a/yjit.c +++ b/yjit.c @@ -626,6 +626,12 @@ rb_get_iseq_body_stack_max(const rb_iseq_t *iseq) return iseq->body->stack_max; } +bool +rb_get_iseq_flags_has_lead(const rb_iseq_t *iseq) +{ + return iseq->body->param.flags.has_lead; +} + bool rb_get_iseq_flags_has_opt(const rb_iseq_t *iseq) { @@ -669,7 +675,13 @@ rb_get_iseq_flags_has_block(const rb_iseq_t *iseq) } bool -rb_get_iseq_flags_has_accepts_no_kwarg(const rb_iseq_t *iseq) +rb_get_iseq_flags_ambiguous_param0(const rb_iseq_t *iseq) +{ + return iseq->body->param.flags.ambiguous_param0; +} + +bool +rb_get_iseq_flags_accepts_no_kwarg(const rb_iseq_t *iseq) { return iseq->body->param.flags.accepts_no_kwarg; } diff --git a/yjit.rb b/yjit.rb index ac49a30e90a728..bb344948ebcc21 100644 --- a/yjit.rb +++ b/yjit.rb @@ -187,6 +187,7 @@ def _print_stats $stderr.puts("***YJIT: Printing YJIT statistics on exit***") print_counters(stats, prefix: 'send_', prompt: 'method call exit reasons: ') + print_counters(stats, prefix: 'invokeblock_', prompt: 'invokeblock exit reasons: ') print_counters(stats, prefix: 'invokesuper_', prompt: 'invokesuper exit reasons: ') print_counters(stats, prefix: 'leave_', prompt: 'leave exit reasons: ') print_counters(stats, prefix: 'gbpp_', prompt: 'getblockparamproxy exit reasons: ') diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 21aaec84cbf95b..167ab2a74f20d1 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -250,6 +250,7 @@ fn main() { .blocklist_type("rb_control_frame_struct") .opaque_type("rb_control_frame_struct") .allowlist_function("rb_vm_bh_to_procval") + .allowlist_function("rb_vm_ep_local_ep") .allowlist_type("vm_special_object_type") .allowlist_var("VM_ENV_DATA_INDEX_SPECVAL") .allowlist_var("VM_ENV_DATA_INDEX_FLAGS") @@ -353,13 +354,15 @@ fn main() { .allowlist_function("rb_get_iseq_body_parent_iseq") .allowlist_function("rb_get_iseq_body_iseq_encoded") .allowlist_function("rb_get_iseq_body_stack_max") + .allowlist_function("rb_get_iseq_flags_has_lead") .allowlist_function("rb_get_iseq_flags_has_opt") .allowlist_function("rb_get_iseq_flags_has_kw") .allowlist_function("rb_get_iseq_flags_has_rest") .allowlist_function("rb_get_iseq_flags_has_post") .allowlist_function("rb_get_iseq_flags_has_kwrest") .allowlist_function("rb_get_iseq_flags_has_block") - .allowlist_function("rb_get_iseq_flags_has_accepts_no_kwarg") + .allowlist_function("rb_get_iseq_flags_ambiguous_param0") + .allowlist_function("rb_get_iseq_flags_accepts_no_kwarg") .allowlist_function("rb_get_iseq_flags_ruby2_keywords") .allowlist_function("rb_get_iseq_body_local_table_size") .allowlist_function("rb_get_iseq_body_param_keyword") diff --git a/yjit/src/backend/arm64/mod.rs b/yjit/src/backend/arm64/mod.rs index 0c784c0beadbe2..ce1dd2e43c8d33 100644 --- a/yjit/src/backend/arm64/mod.rs +++ b/yjit/src/backend/arm64/mod.rs @@ -165,8 +165,8 @@ impl Assembler Opnd::Reg(_) | Opnd::InsnOut { .. } => opnd, Opnd::Mem(_) => split_load_operand(asm, opnd), Opnd::Imm(imm) => { - if imm <= 0 { - asm.load(opnd) + if imm == 0 { + Opnd::Reg(XZR_REG) } else if (dest_num_bits == 64 && BitmaskImmediate::try_from(imm as u64).is_ok()) || (dest_num_bits == 32 && @@ -1352,8 +1352,8 @@ mod tests { asm.test(Opnd::Reg(X0_REG), Opnd::Imm(-7)); asm.compile_with_num_regs(&mut cb, 1); - // Assert that a load and a test instruction were written. - assert_eq!(8, cb.get_write_pos()); + // Assert that a test instruction is written. + assert_eq!(4, cb.get_write_pos()); } #[test] diff --git a/yjit/src/backend/x86_64/mod.rs b/yjit/src/backend/x86_64/mod.rs index ac5ac0fff4aabd..dc5f21221da048 100644 --- a/yjit/src/backend/x86_64/mod.rs +++ b/yjit/src/backend/x86_64/mod.rs @@ -93,6 +93,7 @@ impl Assembler vec![ RAX_REG, RCX_REG, + RDX_REG, ] } diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index de0a2dec0fee5a..9ec6c26f89a9d4 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -4011,6 +4011,7 @@ enum SpecVal { BlockISeq(IseqPtr), BlockParamProxy, PrevEP(*const VALUE), + PrevEPOpnd(Opnd), } struct ControlFrame { @@ -4050,17 +4051,6 @@ fn gen_push_frame( let sp = frame.sp; - let num_locals = frame.local_size; - if num_locals > 0 { - asm.comment("initialize locals"); - - // Initialize local variables to Qnil - for i in 0..num_locals { - let offs = (SIZEOF_VALUE as i32) * (i - num_locals - 3); - asm.store(Opnd::mem(64, sp, offs), Qnil.into()); - } - } - asm.comment("push cme, specval, frame type"); // Write method entry at sp[-3] @@ -4099,9 +4089,25 @@ fn gen_push_frame( let tagged_prev_ep = (prev_ep as usize) | 1; VALUE(tagged_prev_ep).into() } + SpecVal::PrevEPOpnd(ep_opnd) => { + asm.or(ep_opnd, 1.into()) + }, }; asm.store(Opnd::mem(64, sp, SIZEOF_VALUE_I32 * -2), specval); + // Arm requires another register to load the immediate value of Qnil before storing it. + // So doing this after releasing the register for specval to avoid register spill. + let num_locals = frame.local_size; + if num_locals > 0 { + asm.comment("initialize locals"); + + // Initialize local variables to Qnil + for i in 0..num_locals { + let offs = (SIZEOF_VALUE as i32) * (i - num_locals - 3); + asm.store(Opnd::mem(64, sp, offs), Qnil.into()); + } + } + // Write env flags at sp[-1] // sp[-1] = frame_type; asm.store(Opnd::mem(64, sp, SIZEOF_VALUE_I32 * -1), frame.frame_type.into()); @@ -4522,7 +4528,7 @@ fn gen_send_bmethod( } let frame_type = VM_FRAME_MAGIC_BLOCK | VM_FRAME_FLAG_BMETHOD | VM_FRAME_FLAG_LAMBDA; - gen_send_iseq(jit, ctx, asm, ocb, iseq, ci, frame_type, Some(capture.ep), cme, block, flags, argc) + gen_send_iseq(jit, ctx, asm, ocb, iseq, ci, frame_type, Some(capture.ep), cme, block, flags, argc, None) } fn gen_send_iseq( @@ -4538,10 +4544,10 @@ fn gen_send_iseq( block: Option, flags: u32, argc: i32, + captured_opnd: Option, ) -> CodegenStatus { let mut argc = argc; - // Create a side-exit to fall back to the interpreter let side_exit = get_side_exit(jit, ocb, ctx); @@ -4592,7 +4598,7 @@ fn gen_send_iseq( // If we have a method accepting no kwargs (**nil), exit if we have passed // it any kwargs. - if supplying_kws && unsafe { get_iseq_flags_has_accepts_no_kwarg(iseq) } { + if supplying_kws && unsafe { get_iseq_flags_accepts_no_kwarg(iseq) } { gen_counter_incr!(asm, send_iseq_complex_callee); return CantCompile; } @@ -4997,12 +5003,17 @@ fn gen_send_iseq( asm.mov(ctx.stack_opnd(-1), unspec_opnd.into()); } - // Points to the receiver operand on the stack - let recv = ctx.stack_opnd(argc); + // Points to the receiver operand on the stack unless a captured environment is used + let recv = match captured_opnd { + Some(captured_opnd) => asm.load(Opnd::mem(64, captured_opnd, 0)), // captured->self + _ => ctx.stack_opnd(argc), + }; + let captured_self = captured_opnd.is_some(); + let sp_offset = (argc as isize) + if captured_self { 0 } else { 1 }; // Store the updated SP on the current frame (pop arguments and receiver) asm.comment("store caller sp"); - let caller_sp = asm.lea(ctx.sp_opnd((SIZEOF_VALUE as isize) * -((argc as isize) + 1))); + let caller_sp = asm.lea(ctx.sp_opnd((SIZEOF_VALUE as isize) * -sp_offset)); asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP), caller_sp); // Store the next PC in the current frame @@ -5017,6 +5028,9 @@ fn gen_send_iseq( // We've already side-exited if the callee expects a block, so we // ignore any supplied block here SpecVal::PrevEP(prev_ep) + } else if let Some(captured_opnd) = captured_opnd { + let ep_opnd = asm.load(Opnd::mem(64, captured_opnd, SIZEOF_VALUE_I32)); // captured->ep + SpecVal::PrevEPOpnd(ep_opnd) } else if block_arg_type == Some(Type::BlockParamProxy) { SpecVal::BlockParamProxy } else if let Some(block_val) = block { @@ -5058,7 +5072,11 @@ fn gen_send_iseq( callee_ctx.set_local_type(arg_idx.try_into().unwrap(), arg_type); } - let recv_type = ctx.get_opnd_type(StackOpnd(argc.try_into().unwrap())); + let recv_type = if captured_self { + ctx.get_opnd_type(CapturedSelfOpnd) + } else { + ctx.get_opnd_type(StackOpnd(argc.try_into().unwrap())) + }; callee_ctx.upgrade_opnd_type(SelfOpnd, recv_type); // The callee might change locals through Kernel#binding and other means. @@ -5068,7 +5086,7 @@ fn gen_send_iseq( // After the return, sp_offset will be 1. The codegen for leave writes // the return value in case of JIT-to-JIT return. let mut return_ctx = *ctx; - return_ctx.stack_pop((argc + 1).try_into().unwrap()); + return_ctx.stack_pop(sp_offset.try_into().unwrap()); return_ctx.stack_push(Type::Unknown); return_ctx.set_sp_offset(1); return_ctx.reset_chain_depth(); @@ -5317,7 +5335,7 @@ fn gen_send_general( VM_METHOD_TYPE_ISEQ => { let iseq = unsafe { get_def_iseq_ptr((*cme).def) }; let frame_type = VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL; - return gen_send_iseq(jit, ctx, asm, ocb, iseq, ci, frame_type, None, cme, block, flags, argc); + return gen_send_iseq(jit, ctx, asm, ocb, iseq, ci, frame_type, None, cme, block, flags, argc, None); } VM_METHOD_TYPE_CFUNC => { return gen_send_cfunc( @@ -5642,6 +5660,95 @@ fn gen_send( return gen_send_general(jit, ctx, asm, ocb, cd, block); } +fn gen_invokeblock( + jit: &mut JITState, + ctx: &mut Context, + asm: &mut Assembler, + ocb: &mut OutlinedCb, +) -> CodegenStatus { + if !jit_at_current_insn(jit) { + defer_compilation(jit, ctx, asm, ocb); + return EndBlock; + } + + // Get call info + let cd = jit_get_arg(jit, 0).as_ptr(); + let ci = unsafe { get_call_data_ci(cd) }; + let argc: i32 = unsafe { vm_ci_argc(ci) }.try_into().unwrap(); + let flags = unsafe { vm_ci_flag(ci) }; + + // Get block_handler + let cfp = unsafe { get_ec_cfp(jit.ec.unwrap()) }; + let lep = unsafe { rb_vm_ep_local_ep(get_cfp_ep(cfp)) }; + let comptime_handler = unsafe { *lep.offset(VM_ENV_DATA_INDEX_SPECVAL.try_into().unwrap()) }; + + // Handle each block_handler type + if comptime_handler.0 == VM_BLOCK_HANDLER_NONE as usize { // no block given + gen_counter_incr!(asm, invokeblock_none); + CantCompile + } else if comptime_handler.0 & 0x3 == 0x1 { // VM_BH_ISEQ_BLOCK_P + asm.comment("get local EP"); + let ep_opnd = gen_get_lep(jit, asm); + let block_handler_opnd = asm.load( + Opnd::mem(64, ep_opnd, (SIZEOF_VALUE as i32) * (VM_ENV_DATA_INDEX_SPECVAL as i32)) + ); + + asm.comment("guard block_handler type"); + 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()); + + // 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() }; + let comptime_iseq = unsafe { *comptime_captured.code.iseq.as_ref() }; + if argc == 1 && unsafe { get_iseq_flags_has_lead(comptime_iseq) && !get_iseq_flags_ambiguous_param0(comptime_iseq) } { + gen_counter_incr!(asm, invokeblock_iseq_arg0_splat); + return CantCompile; + } + + asm.comment("guard known ISEQ"); + let captured_opnd = asm.and(block_handler_opnd, Opnd::Imm(!0x3)); + let iseq_opnd = asm.load(Opnd::mem(64, captured_opnd, SIZEOF_VALUE_I32 * 2)); + asm.cmp(iseq_opnd, (comptime_iseq as usize).into()); + let block_changed_exit = counted_exit!(ocb, side_exit, invokeblock_iseq_block_changed); + jit_chain_guard( + JCC_JNE, + jit, + ctx, + asm, + ocb, + SEND_MAX_CHAIN_DEPTH, + block_changed_exit, + ); + + gen_send_iseq( + jit, + ctx, + asm, + ocb, + comptime_iseq, + ci, + VM_FRAME_MAGIC_BLOCK, + None, + 0 as _, + None, + flags, + argc, + Some(captured_opnd), + ) + } else if comptime_handler.0 & 0x3 == 0x3 { // VM_BH_IFUNC_P + gen_counter_incr!(asm, invokeblock_ifunc); + CantCompile + } else if comptime_handler.symbol_p() { + gen_counter_incr!(asm, invokeblock_symbol); + CantCompile + } else { // Proc + gen_counter_incr!(asm, invokeblock_proc); + CantCompile + } +} + fn gen_invokesuper( jit: &mut JITState, ctx: &mut Context, @@ -5781,7 +5888,7 @@ fn gen_invokesuper( VM_METHOD_TYPE_ISEQ => { let iseq = unsafe { get_def_iseq_ptr((*cme).def) }; let frame_type = VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL; - gen_send_iseq(jit, ctx, asm, ocb, iseq, ci, frame_type, None, cme, block, ci_flags, argc) + gen_send_iseq(jit, ctx, asm, ocb, iseq, ci, frame_type, None, cme, block, ci_flags, argc, None) } VM_METHOD_TYPE_CFUNC => { gen_send_cfunc(jit, ctx, asm, ocb, ci, cme, block, ptr::null(), ci_flags, argc) @@ -6548,6 +6655,7 @@ fn get_gen_fn(opcode: VALUE) -> Option { YARVINSN_getblockparam => Some(gen_getblockparam), YARVINSN_opt_send_without_block => Some(gen_opt_send_without_block), YARVINSN_send => Some(gen_send), + YARVINSN_invokeblock => Some(gen_invokeblock), YARVINSN_invokesuper => Some(gen_invokesuper), YARVINSN_leave => Some(gen_leave), diff --git a/yjit/src/core.rs b/yjit/src/core.rs index c0e48e87b2bc20..afa7604aaf418f 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -269,6 +269,9 @@ pub enum InsnOpnd { // The value is self SelfOpnd, + // Captured block's self + CapturedSelfOpnd, + // Temporary stack operand with stack index StackOpnd(u16), } @@ -297,6 +300,9 @@ pub struct Context { // Type we track for self self_type: Type, + // Type we track for captured block's self + captured_self_type: Type, + // Mapping of temp stack entries to types we track temp_mapping: [TempMapping; MAX_TEMP_TYPES], } @@ -1160,6 +1166,7 @@ impl Context { pub fn get_opnd_type(&self, opnd: InsnOpnd) -> Type { match opnd { SelfOpnd => self.self_type, + CapturedSelfOpnd => self.captured_self_type, StackOpnd(idx) => { let idx = idx as u16; assert!(idx < self.stack_size); @@ -1201,6 +1208,7 @@ impl Context { match opnd { SelfOpnd => self.self_type.upgrade(opnd_type), + CapturedSelfOpnd => self.self_type.upgrade(opnd_type), StackOpnd(idx) => { let idx = idx as u16; assert!(idx < self.stack_size); @@ -1236,6 +1244,7 @@ impl Context { match opnd { SelfOpnd => (MapToSelf, opnd_type), + CapturedSelfOpnd => unreachable!("not used for captured self"), StackOpnd(idx) => { let idx = idx as u16; assert!(idx < self.stack_size); @@ -1257,6 +1266,7 @@ impl Context { pub fn set_opnd_mapping(&mut self, opnd: InsnOpnd, (mapping, opnd_type): (TempMapping, Type)) { match opnd { SelfOpnd => unreachable!("self always maps to self"), + CapturedSelfOpnd => unreachable!("not used for captured self"), StackOpnd(idx) => { assert!(idx < self.stack_size); let stack_idx = (self.stack_size - 1 - idx) as usize; diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index d8dbdb40193f8d..168443e6f0a8a6 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -162,6 +162,7 @@ pub use rb_iseq_encoded_size as get_iseq_encoded_size; pub use rb_get_iseq_body_local_iseq as get_iseq_body_local_iseq; pub use rb_get_iseq_body_iseq_encoded as get_iseq_body_iseq_encoded; pub use rb_get_iseq_body_stack_max as get_iseq_body_stack_max; +pub use rb_get_iseq_flags_has_lead as get_iseq_flags_has_lead; pub use rb_get_iseq_flags_has_opt as get_iseq_flags_has_opt; pub use rb_get_iseq_flags_has_kw as get_iseq_flags_has_kw; pub use rb_get_iseq_flags_has_rest as get_iseq_flags_has_rest; @@ -169,7 +170,8 @@ pub use rb_get_iseq_flags_ruby2_keywords as get_iseq_flags_ruby2_keywords; pub use rb_get_iseq_flags_has_post as get_iseq_flags_has_post; pub use rb_get_iseq_flags_has_kwrest as get_iseq_flags_has_kwrest; pub use rb_get_iseq_flags_has_block as get_iseq_flags_has_block; -pub use rb_get_iseq_flags_has_accepts_no_kwarg as get_iseq_flags_has_accepts_no_kwarg; +pub use rb_get_iseq_flags_ambiguous_param0 as get_iseq_flags_ambiguous_param0; +pub use rb_get_iseq_flags_accepts_no_kwarg as get_iseq_flags_accepts_no_kwarg; pub use rb_get_iseq_body_local_table_size as get_iseq_body_local_table_size; pub use rb_get_iseq_body_param_keyword as get_iseq_body_param_keyword; pub use rb_get_iseq_body_param_size as get_iseq_body_param_size; @@ -346,13 +348,27 @@ impl VALUE { (cval & mask) == flag } - /// Return true for a static (non-heap) Ruby symbol + /// Return true if the value is a Ruby symbol (RB_SYMBOL_P) + pub fn symbol_p(self) -> bool { + self.static_sym_p() || self.dynamic_sym_p() + } + + /// Return true for a static (non-heap) Ruby symbol (RB_STATIC_SYM_P) pub fn static_sym_p(self) -> bool { let VALUE(cval) = self; let flag = RUBY_SYMBOL_FLAG as usize; (cval & 0xff) == flag } + /// Return true for a dynamic Ruby symbol (RB_DYNAMIC_SYM_P) + fn dynamic_sym_p(self) -> bool { + return if self.special_const_p() { + false + } else { + self.builtin_type() == RUBY_T_SYMBOL + } + } + /// Returns true or false depending on whether the value is nil pub fn nil_p(self) -> bool { self == Qnil diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index da7b332e07f841..3373c6d76e41bc 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -965,6 +965,9 @@ pub const VM_ENV_FLAG_ESCAPED: vm_frame_env_flags = 4; pub const VM_ENV_FLAG_WB_REQUIRED: vm_frame_env_flags = 8; pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16; pub type vm_frame_env_flags = u32; +extern "C" { + pub fn rb_vm_ep_local_ep(ep: *const VALUE) -> *const VALUE; +} extern "C" { pub fn rb_iseq_path(iseq: *const rb_iseq_t) -> VALUE; } @@ -1430,6 +1433,9 @@ extern "C" { extern "C" { pub fn rb_get_iseq_body_stack_max(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint; } +extern "C" { + pub fn rb_get_iseq_flags_has_lead(iseq: *const rb_iseq_t) -> bool; +} extern "C" { pub fn rb_get_iseq_flags_has_opt(iseq: *const rb_iseq_t) -> bool; } @@ -1452,7 +1458,10 @@ extern "C" { pub fn rb_get_iseq_flags_has_block(iseq: *const rb_iseq_t) -> bool; } extern "C" { - pub fn rb_get_iseq_flags_has_accepts_no_kwarg(iseq: *const rb_iseq_t) -> bool; + pub fn rb_get_iseq_flags_ambiguous_param0(iseq: *const rb_iseq_t) -> bool; +} +extern "C" { + pub fn rb_get_iseq_flags_accepts_no_kwarg(iseq: *const rb_iseq_t) -> bool; } extern "C" { pub fn rb_get_iseq_body_param_keyword( diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index e07b475a9f1d6e..b6b2d92b6dbb31 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -217,6 +217,14 @@ make_counters! { invokesuper_me_changed, invokesuper_block, + invokeblock_none, + invokeblock_iseq_arg0_splat, + invokeblock_iseq_block_changed, + invokeblock_iseq_tag_changed, + invokeblock_ifunc, + invokeblock_proc, + invokeblock_symbol, + leave_se_interrupt, leave_interp_return, leave_start_pc_non_zero, From b777408c33b57077f8be09eaed9245eeca0b59c5 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 2 Nov 2022 13:34:02 -0400 Subject: [PATCH 102/148] Fix crash in test runner on timeout When a test worker hangs and timeouts, the test runner crashes with the following stack trace: ruby/tool/lib/test/unit.rb:1747:in `puke': undefined method `backtrace' for Timeout::Error:Class (NoMethodError) from ruby/tool/lib/test/unit.rb:790:in `block in _run_parallel' from ruby/tool/lib/test/unit.rb:788:in `each' This commit adds handling for Timeout::Error and outputs a message. --- tool/lib/test/unit.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tool/lib/test/unit.rb b/tool/lib/test/unit.rb index b2190843c8bc4e..59409f016c85be 100644 --- a/tool/lib/test/unit.rb +++ b/tool/lib/test/unit.rb @@ -787,7 +787,7 @@ def _run_parallel suites, type, result unless rep.empty? rep.each do |r| if r[:error] - puke(*r[:error], Timeout::Error) + puke(*r[:error], Timeout::Error.new) next end r[:report]&.each do |f| @@ -1742,6 +1742,9 @@ def puke klass, meth, e when Test::Unit::AssertionFailedError then @failures += 1 "Failure:\n#{klass}##{meth} [#{location e}]:\n#{e.message}\n" + when Timeout::Error + @errors += 1 + "Timeout:\n#{klass}##{meth}\n" else @errors += 1 bt = Test::filter_backtrace(e.backtrace).join "\n " From 59a6caf83acf32915abf11ae81ca3167d2577b21 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 3 Nov 2022 07:27:11 +0900 Subject: [PATCH 103/148] Run only daily schedule because CodeQL provides a lot of false-positive results for Ruby code --- .github/workflows/codeql-analysis.yml | 34 +++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 85b11e90e72ea2..42f1494b1de475 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,24 +1,24 @@ name: "Code scanning - action" on: - push: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - - '**/.document' - - '**.[1-8]' - - '**.ronn' - pull_request: - paths-ignore: - - 'doc/**' - - '**.md' - - '**.rdoc' - - '**/.document' - - '**.[1-8]' - - '**.ronn' + # push: + # paths-ignore: + # - 'doc/**' + # - '**.md' + # - '**.rdoc' + # - '**/.document' + # - '**.[1-8]' + # - '**.ronn' + # pull_request: + # paths-ignore: + # - 'doc/**' + # - '**.md' + # - '**.rdoc' + # - '**/.document' + # - '**.[1-8]' + # - '**.ronn' schedule: - - cron: '0 12 * * 4' + - cron: '0 12 * * *' concurrency: group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} From c24800f4f71a1971619855065c37adaa766a3dd1 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 2 Nov 2022 22:49:09 -0700 Subject: [PATCH 104/148] [ruby/erb] Fix CI for JRuby https://github.com/ruby/erb/commit/df642335b7 --- lib/erb.gemspec | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/erb.gemspec b/lib/erb.gemspec index e89a358f8a5dd3..2e7e981ff13d0b 100644 --- a/lib/erb.gemspec +++ b/lib/erb.gemspec @@ -27,7 +27,9 @@ Gem::Specification.new do |spec| spec.executables = ['erb'] spec.require_paths = ['lib'] - spec.required_ruby_version = ">= 2.7.0" + if RUBY_ENGINE != 'jruby' + spec.required_ruby_version = '>= 2.7.0' + end spec.add_dependency 'cgi', '>= 0.3.3' end From f667d3afc9096ea32f6f84204529b95099062e86 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 2 Nov 2022 22:53:39 -0700 Subject: [PATCH 105/148] [ruby/erb] Skip tests for truffleruby https://github.com/ruby/erb/commit/65a7c70a00 --- test/erb/test_erb.rb | 2 ++ test/erb/test_erb_command.rb | 3 +++ 2 files changed, 5 insertions(+) diff --git a/test/erb/test_erb.rb b/test/erb/test_erb.rb index fb5e9b611ec150..5be5ed7e0931c5 100644 --- a/test/erb/test_erb.rb +++ b/test/erb/test_erb.rb @@ -236,6 +236,8 @@ def test_explicit_trim_line_with_carriage_return end def test_invalid_trim_mode + pend if RUBY_ENGINE == 'truffleruby' + assert_warning(/#{__FILE__}:#{__LINE__ + 1}/) do @erb.new("", trim_mode: 'abc-def') end diff --git a/test/erb/test_erb_command.rb b/test/erb/test_erb_command.rb index 0baa59ddd59ed9..446efde227934b 100644 --- a/test/erb/test_erb_command.rb +++ b/test/erb/test_erb_command.rb @@ -4,6 +4,7 @@ class TestErbCommand < Test::Unit::TestCase def test_var + pend if RUBY_ENGINE == 'truffleruby' assert_in_out_err(["-I#{File.expand_path('../../lib', __dir__)}", "-w", File.expand_path("../../libexec/erb", __dir__), "var=hoge"], @@ -11,6 +12,7 @@ def test_var end def test_template_file_encoding + pend if RUBY_ENGINE == 'truffleruby' assert_in_out_err(["-I#{File.expand_path('../../lib', __dir__)}", "-w", File.expand_path("../../libexec/erb", __dir__)], "<%=''.encoding.to_s%>", ["UTF-8"]) @@ -18,6 +20,7 @@ def test_template_file_encoding # These interfaces will be removed at Ruby 2.7. def test_deprecated_option + pend if RUBY_ENGINE == 'truffleruby' warnings = [ "warning: -S option of erb command is deprecated. Please do not use this.", /\n.+\/libexec\/erb:\d+: warning: Passing safe_level with the 2nd argument of ERB\.new is deprecated\. Do not use it, and specify other arguments as keyword arguments\.\n/, From 7b6c5f9b5b16d244268f700aa5cd60bdde3e34d6 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 2 Nov 2022 22:56:24 -0700 Subject: [PATCH 106/148] [ruby/erb] Skip a test for JRuby https://github.com/ruby/erb/commit/48a75665ab --- test/erb/test_erb.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/erb/test_erb.rb b/test/erb/test_erb.rb index 5be5ed7e0931c5..424ddae87eb000 100644 --- a/test/erb/test_erb.rb +++ b/test/erb/test_erb.rb @@ -79,6 +79,9 @@ def test_html_escape end def test_concurrent_default_binding + # This test randomly fails with JRuby -- NameError: undefined local variable or method `template2' + pend if RUBY_ENGINE == 'jruby' + template1 = 'one <%= ERB.new(template2).result %>' eval 'template2 = "two"', TOPLEVEL_BINDING From 0468136a1b07d693cf60abd0c5ccd125fc361039 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 2 Nov 2022 15:21:50 -0400 Subject: [PATCH 107/148] Make str_alloc_heap return a STR_NOEMBED string This commit refactors str_alloc_heap to return a string with the STR_NOEMBED flag set. --- string.c | 53 ++++++++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/string.c b/string.c index 1fd82595fe6f76..ca2cb4525eb2c2 100644 --- a/string.c +++ b/string.c @@ -877,30 +877,29 @@ must_not_null(const char *ptr) } } -static inline VALUE -str_alloc(VALUE klass, size_t size) -{ - assert(size > 0); - RVARGC_NEWOBJ_OF(str, struct RString, klass, - T_STRING | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), size); - return (VALUE)str; -} - static inline VALUE str_alloc_embed(VALUE klass, size_t capa) { size_t size = rb_str_embed_size(capa); + assert(size > 0); assert(rb_gc_size_allocatable_p(size)); #if !USE_RVARGC assert(size <= sizeof(struct RString)); #endif - return str_alloc(klass, size); + + RVARGC_NEWOBJ_OF(str, struct RString, klass, + T_STRING | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), size); + + return (VALUE)str; } static inline VALUE str_alloc_heap(VALUE klass) { - return str_alloc(klass, sizeof(struct RString)); + RVARGC_NEWOBJ_OF(str, struct RString, klass, + T_STRING | STR_NOEMBED | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), sizeof(struct RString)); + + return (VALUE)str; } static inline VALUE @@ -937,7 +936,6 @@ str_new0(VALUE klass, const char *ptr, long len, int termlen) * mul_add_mul can be reverted to a simple ALLOC_N. */ RSTRING(str)->as.heap.ptr = rb_xmalloc_mul_add_mul(sizeof(char), len, sizeof(char), termlen); - STR_SET_NOEMBED(str); } if (ptr) { memcpy(RSTRING_PTR(str), ptr, len); @@ -1044,7 +1042,6 @@ str_new_static(VALUE klass, const char *ptr, long len, int encindex) RSTRING(str)->as.heap.len = len; RSTRING(str)->as.heap.ptr = (char *)ptr; RSTRING(str)->as.heap.aux.capa = len; - STR_SET_NOEMBED(str); RBASIC(str)->flags |= STR_NOFREE; } rb_enc_associate_index(str, encindex); @@ -1441,7 +1438,6 @@ heap_str_make_shared(VALUE klass, VALUE orig) assert(!STR_SHARED_P(orig)); VALUE str = str_alloc_heap(klass); - STR_SET_NOEMBED(str); RSTRING(str)->as.heap.len = RSTRING_LEN(orig); RSTRING(str)->as.heap.ptr = RSTRING_PTR(orig); RSTRING(str)->as.heap.aux.capa = RSTRING(orig)->as.heap.aux.capa; @@ -1542,7 +1538,6 @@ rb_str_buf_new(long capa) capa = STR_BUF_MIN_SIZE; } #endif - FL_SET(str, STR_NOEMBED); RSTRING(str)->as.heap.aux.capa = capa; RSTRING(str)->as.heap.ptr = ALLOC_N(char, (size_t)capa + 1); RSTRING(str)->as.heap.ptr[0] = '\0'; @@ -1721,30 +1716,29 @@ str_replace(VALUE str, VALUE str2) return str; } -static inline VALUE -ec_str_alloc(struct rb_execution_context_struct *ec, VALUE klass, size_t size) -{ - assert(size > 0); - RB_RVARGC_EC_NEWOBJ_OF(ec, str, struct RString, klass, - T_STRING | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), size); - return (VALUE)str; -} - static inline VALUE ec_str_alloc_embed(struct rb_execution_context_struct *ec, VALUE klass, size_t capa) { size_t size = rb_str_embed_size(capa); + assert(size > 0); assert(rb_gc_size_allocatable_p(size)); #if !USE_RVARGC assert(size <= sizeof(struct RString)); #endif - return ec_str_alloc(ec, klass, size); + + RB_RVARGC_EC_NEWOBJ_OF(ec, str, struct RString, klass, + T_STRING | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), size); + + return (VALUE)str; } static inline VALUE ec_str_alloc_heap(struct rb_execution_context_struct *ec, VALUE klass) { - return ec_str_alloc(ec, klass, sizeof(struct RString)); + RB_RVARGC_EC_NEWOBJ_OF(ec, str, struct RString, klass, + T_STRING | STR_NOEMBED | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), sizeof(struct RString)); + + return (VALUE)str; } static inline VALUE @@ -1762,6 +1756,7 @@ str_duplicate_setup(VALUE klass, VALUE str, VALUE dup) if (STR_EMBED_P(str)) { long len = RSTRING_EMBED_LEN(str); + assert(STR_EMBED_P(dup)); assert(str_embed_capa(dup) >= len + 1); STR_SET_EMBED_LEN(dup, len); MEMCPY(RSTRING(dup)->as.embed.ary, RSTRING(str)->as.embed.ary, char, len + 1); @@ -1782,6 +1777,7 @@ str_duplicate_setup(VALUE klass, VALUE str, VALUE dup) else if (STR_EMBED_P(root)) { MEMCPY(RSTRING(dup)->as.embed.ary, RSTRING(root)->as.embed.ary, char, RSTRING_EMBED_LEN_MAX + 1); + FL_UNSET(dup, STR_NOEMBED); } #endif else { @@ -1805,7 +1801,7 @@ static inline VALUE ec_str_duplicate(struct rb_execution_context_struct *ec, VALUE klass, VALUE str) { VALUE dup; - if (!USE_RVARGC || FL_TEST(str, STR_NOEMBED)) { + if (FL_TEST(str, STR_NOEMBED)) { dup = ec_str_alloc_heap(ec, klass); } else { @@ -1819,7 +1815,7 @@ static inline VALUE str_duplicate(VALUE klass, VALUE str) { VALUE dup; - if (!USE_RVARGC || FL_TEST(str, STR_NOEMBED)) { + if (FL_TEST(str, STR_NOEMBED)) { dup = str_alloc_heap(klass); } else { @@ -2307,7 +2303,6 @@ rb_str_times(VALUE str, VALUE times) str2 = str_alloc_heap(rb_cString); RSTRING(str2)->as.heap.aux.capa = len; RSTRING(str2)->as.heap.ptr = ZALLOC_N(char, (size_t)len + 1); - STR_SET_NOEMBED(str2); } STR_SET_LEN(str2, len); rb_enc_copy(str2, str); From 68ef97d788cf8bff42d981bda41cd26128220740 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Nov 2022 08:42:28 -0700 Subject: [PATCH 108/148] YJIT: Stop incrementing write_pos if cb.has_dropped_bytes (#6664) Co-Authored-By: Alan Wu Co-authored-by: Alan Wu --- yjit/src/asm/mod.rs | 12 ++++++------ yjit/src/backend/arm64/mod.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/yjit/src/asm/mod.rs b/yjit/src/asm/mod.rs index 6b2dd7da9a381d..1be4488006e101 100644 --- a/yjit/src/asm/mod.rs +++ b/yjit/src/asm/mod.rs @@ -418,12 +418,11 @@ impl CodeBlock { /// Write a single byte at the current position. pub fn write_byte(&mut self, byte: u8) { let write_ptr = self.get_write_ptr(); - if !self.has_capacity(1) || self.mem_block.borrow_mut().write_byte(write_ptr, byte).is_err() { + if self.has_capacity(1) && self.mem_block.borrow_mut().write_byte(write_ptr, byte).is_ok() { + self.write_pos += 1; + } else { self.dropped_bytes = true; } - - // Always advance write_pos since arm64 PadEntryExit needs this to stop the loop. - self.write_pos += 1; } /// Write multiple bytes starting from the current position. @@ -489,10 +488,11 @@ impl CodeBlock { self.label_refs.push(LabelRef { pos: self.write_pos, label_idx, num_bytes, encode }); // Move past however many bytes the instruction takes up - if !self.has_capacity(num_bytes) { + if self.has_capacity(num_bytes) { + self.write_pos += num_bytes; + } else { self.dropped_bytes = true; // retry emitting the Insn after next_page } - self.write_pos += num_bytes; } // Link internal label references diff --git a/yjit/src/backend/arm64/mod.rs b/yjit/src/backend/arm64/mod.rs index ce1dd2e43c8d33..c899f6871f9d1e 100644 --- a/yjit/src/backend/arm64/mod.rs +++ b/yjit/src/backend/arm64/mod.rs @@ -1028,7 +1028,7 @@ impl Assembler } Insn::LiveReg { .. } => (), // just a reg alloc signal, no code Insn::PadInvalPatch => { - while (cb.get_write_pos().saturating_sub(std::cmp::max(start_write_pos, cb.page_start_pos()))) < JMP_PTR_BYTES { + while (cb.get_write_pos().saturating_sub(std::cmp::max(start_write_pos, cb.page_start_pos()))) < JMP_PTR_BYTES && !cb.has_dropped_bytes() { nop(cb); } } From c5d6a483f5f771aa904ea85dad35a368ddf8047a Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 3 Nov 2022 16:32:10 +0000 Subject: [PATCH 109/148] [ruby/irb] Refactor RubyLex and its tests (https://github.com/ruby/irb/pull/427) * Make sure `RubyLex#set_input`'s context is always present in tests In real-world scenarios, the context should always be non-nil: https://github.com/ruby/irb/blob/master/lib/irb.rb#L489 So we should make sure our test setup reflects that. * Make context a required keyword Since in practice, `set_input`'s context should always be non-nil, its parameters should reflect that. And since `RubyLex#check_state` is only called by `#lex` and `#set_input`, both of which now always require context, we can assume its context should be non-nil too. https://github.com/ruby/irb/commit/1aeeb86203 --- lib/irb/ruby-lex.rb | 4 ++-- test/irb/test_ruby_lex.rb | 29 ++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 54ea2a9e7b390a..544392228e00f1 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -48,7 +48,7 @@ def self.compile_with_errors_suppressed(code, line_no: 1) end # io functions - def set_input(io, p = nil, context: nil, &block) + def set_input(io, p = nil, context:, &block) @io = io if @io.respond_to?(:check_termination) @io.check_termination do |code| @@ -216,7 +216,7 @@ def check_state(code, tokens = nil, context: nil) ltype = process_literal_type(tokens) indent = process_nesting_level(tokens) continue = process_continue(tokens) - lvars_code = self.class.generate_local_variables_assign_code(context&.local_variables || []) + lvars_code = self.class.generate_local_variables_assign_code(context.local_variables) code = "#{lvars_code}\n#{code}" if lvars_code code_block_open = check_code_block(code, tokens) [ltype, indent, continue, code_block_open] diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb index 1388d0896204c8..5d66dc5bb012b9 100644 --- a/test/irb/test_ruby_lex.rb +++ b/test/irb/test_ruby_lex.rb @@ -24,13 +24,14 @@ def assert_indenting(lines, correct_space_count, add_new_line) last_line_index = lines.length - 1 byte_pointer = lines.last.length + context = build_context + context.auto_indent_mode = true ruby_lex = RubyLex.new() io = MockIO_AutoIndent.new([lines, last_line_index, byte_pointer, add_new_line]) do |auto_indent| error_message = "Calculated the wrong number of spaces for:\n #{lines.join("\n")}" assert_equal(correct_space_count, auto_indent, error_message) end - ruby_lex.set_input(io) - context = OpenStruct.new(auto_indent_mode: true) + ruby_lex.set_input(io, context: context) ruby_lex.set_auto_indent(context) end @@ -48,11 +49,10 @@ def assert_code_block_open(lines, expected, local_variables: []) def ruby_lex_for_lines(lines, local_variables: []) ruby_lex = RubyLex.new() + + context = build_context(local_variables) io = proc{ lines.join("\n") } - ruby_lex.set_input(io, io) - unless local_variables.empty? - context = OpenStruct.new(local_variables: local_variables) - end + ruby_lex.set_input(io, io, context: context) ruby_lex.lex(context) ruby_lex end @@ -620,7 +620,8 @@ def assert_dynamic_prompt(lines, expected_prompt_list) ruby_lex.set_prompt do |ltype, indent, continue, line_no| '%03d:%01d:%1s:%s ' % [line_no, indent, ltype, continue ? '*' : '>'] end - ruby_lex.set_input(io) + context = build_context + ruby_lex.set_input(io, context: context) end def test_dyanmic_prompt @@ -697,5 +698,19 @@ def test_unterminated_heredoc_string_literal assert_equal('< Date: Thu, 3 Nov 2022 17:12:31 +0000 Subject: [PATCH 110/148] [ruby/irb] Require the entire irb lib in RubyLex test (https://github.com/ruby/irb/pull/428) RubyLex is not designed to be used alone. It's usually used with an IRB context, which requires workspace. So its tests should have access to those components too. https://github.com/ruby/irb/commit/608f261da4 --- test/irb/test_ruby_lex.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb index 5d66dc5bb012b9..3d6455a710be03 100644 --- a/test/irb/test_ruby_lex.rb +++ b/test/irb/test_ruby_lex.rb @@ -1,5 +1,5 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) -require 'irb/ruby-lex' +require 'irb' require 'test/unit' require 'ostruct' From 124f10f56b16e4fa4572b6dffde7487dcf914b26 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Nov 2022 10:33:49 -0700 Subject: [PATCH 111/148] YJIT: Fix a wrong type reference (#6661) * YJIT: Fix a wrong type reference * YJIT: Just remove CapturedSelfOpnd for now --- yjit/src/codegen.rs | 2 +- yjit/src/core.rs | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 9ec6c26f89a9d4..8e36c448235314 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -5073,7 +5073,7 @@ fn gen_send_iseq( } let recv_type = if captured_self { - ctx.get_opnd_type(CapturedSelfOpnd) + Type::Unknown // we don't track the type information of captured->self for now } else { ctx.get_opnd_type(StackOpnd(argc.try_into().unwrap())) }; diff --git a/yjit/src/core.rs b/yjit/src/core.rs index afa7604aaf418f..c0e48e87b2bc20 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -269,9 +269,6 @@ pub enum InsnOpnd { // The value is self SelfOpnd, - // Captured block's self - CapturedSelfOpnd, - // Temporary stack operand with stack index StackOpnd(u16), } @@ -300,9 +297,6 @@ pub struct Context { // Type we track for self self_type: Type, - // Type we track for captured block's self - captured_self_type: Type, - // Mapping of temp stack entries to types we track temp_mapping: [TempMapping; MAX_TEMP_TYPES], } @@ -1166,7 +1160,6 @@ impl Context { pub fn get_opnd_type(&self, opnd: InsnOpnd) -> Type { match opnd { SelfOpnd => self.self_type, - CapturedSelfOpnd => self.captured_self_type, StackOpnd(idx) => { let idx = idx as u16; assert!(idx < self.stack_size); @@ -1208,7 +1201,6 @@ impl Context { match opnd { SelfOpnd => self.self_type.upgrade(opnd_type), - CapturedSelfOpnd => self.self_type.upgrade(opnd_type), StackOpnd(idx) => { let idx = idx as u16; assert!(idx < self.stack_size); @@ -1244,7 +1236,6 @@ impl Context { match opnd { SelfOpnd => (MapToSelf, opnd_type), - CapturedSelfOpnd => unreachable!("not used for captured self"), StackOpnd(idx) => { let idx = idx as u16; assert!(idx < self.stack_size); @@ -1266,7 +1257,6 @@ impl Context { pub fn set_opnd_mapping(&mut self, opnd: InsnOpnd, (mapping, opnd_type): (TempMapping, Type)) { match opnd { SelfOpnd => unreachable!("self always maps to self"), - CapturedSelfOpnd => unreachable!("not used for captured self"), StackOpnd(idx) => { assert!(idx < self.stack_size); let stack_idx = (self.stack_size - 1 - idx) as usize; From 611b5e7f40dd7f0ee00df7981dc60890eeb2f315 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Nov 2022 10:34:46 -0700 Subject: [PATCH 112/148] [ruby/irb] Fix build_context for ruby/ruby CI Co-Authored-By: Stan Lo https://github.com/ruby/irb/commit/d1fe234a9a --- test/irb/test_ruby_lex.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb index 3d6455a710be03..dcc998a24e16a3 100644 --- a/test/irb/test_ruby_lex.rb +++ b/test/irb/test_ruby_lex.rb @@ -702,7 +702,8 @@ def test_unterminated_heredoc_string_literal private def build_context(local_variables = nil) - workspace = IRB::WorkSpace.new + IRB.init_config(nil) + workspace = IRB::WorkSpace.new(TOPLEVEL_BINDING.dup) if local_variables local_variables.each do |n| From ea77aa2fd0899820752df7de6077a3b847a20aee Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Nov 2022 10:41:35 -0700 Subject: [PATCH 113/148] YJIT: Make Code GC metrics available for non-stats builds (#6665) --- NEWS.md | 3 +++ test/ruby/test_yjit.rb | 8 ++++---- yjit/src/asm/mod.rs | 2 -- yjit/src/codegen.rs | 11 ++++++++++- yjit/src/stats.rs | 4 +++- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9156ef7a68b6ec..0367e8bfd0eb1b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -330,6 +330,9 @@ The following deprecated APIs are removed. memory pages until actually utilized by JIT code. * Introduce Code GC that frees all code pages when the memory consumption by JIT code reaches `--yjit-exec-mem-size`. +* `RubyVM::YJIT.runtime_stats` returns Code GC metrics in addition to + existing `inline_code_size` and `outlined_code_size` keys: + `code_gc_count`, `live_page_count`, `freed_page_count`, and `freed_code_size`. ### MJIT diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index 57a60db89b5b7f..1a564889af6963 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -837,7 +837,7 @@ def test_code_gc return :not_compiled2 unless compiles { nil } # should be JITable again code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] - return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count != 2 + return :"code_gc_#{code_gc_count}" if code_gc_count != 2 :ok RUBY @@ -858,7 +858,7 @@ def test_on_stack_code_gc_call return :broken_resume2 if fiber.resume != 0 # The code should be still callable code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] - return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count != 1 + return :"code_gc_#{code_gc_count}" if code_gc_count != 1 :ok RUBY @@ -890,7 +890,7 @@ def test_on_stack_code_gc_twice return :not_paged4 unless add_pages(100) # check everything still works code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] - return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count != 3 + return :"code_gc_#{code_gc_count}" if code_gc_count != 3 :ok RUBY @@ -912,7 +912,7 @@ def test_code_gc_with_many_iseqs return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] - return :"code_gc_#{code_gc_count}" if code_gc_count && code_gc_count == 0 + return :"code_gc_#{code_gc_count}" if code_gc_count == 0 :ok RUBY diff --git a/yjit/src/asm/mod.rs b/yjit/src/asm/mod.rs index 1be4488006e101..3d7de7cd79bed8 100644 --- a/yjit/src/asm/mod.rs +++ b/yjit/src/asm/mod.rs @@ -10,7 +10,6 @@ use crate::core::IseqPayload; use crate::core::for_each_off_stack_iseq_payload; use crate::core::for_each_on_stack_iseq_payload; use crate::invariants::rb_yjit_tracing_invalidate_all; -use crate::stats::incr_counter; use crate::virtualmem::WriteError; #[cfg(feature = "disasm")] @@ -605,7 +604,6 @@ impl CodeBlock { } CodegenGlobals::set_freed_pages(freed_pages); - incr_counter!(code_gc_count); } pub fn inline(&self) -> bool { diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 8e36c448235314..46f5ed64d3dd3e 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -6724,6 +6724,9 @@ pub struct CodegenGlobals { /// Freed page indexes. None if code GC has not been used. freed_pages: Option>, + + /// How many times code GC has been executed. + code_gc_count: usize, } /// For implementing global code invalidation. A position in the inline @@ -6816,6 +6819,7 @@ impl CodegenGlobals { method_codegen_table: HashMap::new(), ocb_pages, freed_pages: None, + code_gc_count: 0, }; // Register the method codegen functions @@ -6961,7 +6965,12 @@ impl CodegenGlobals { } pub fn set_freed_pages(freed_pages: Vec) { - CodegenGlobals::get_instance().freed_pages = Some(freed_pages) + CodegenGlobals::get_instance().freed_pages = Some(freed_pages); + CodegenGlobals::get_instance().code_gc_count += 1; + } + + pub fn get_code_gc_count() -> usize { + CodegenGlobals::get_instance().code_gc_count } } diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index b6b2d92b6dbb31..a60fcaf8364f84 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -261,7 +261,6 @@ make_counters! { compiled_block_count, compilation_failure, freed_iseq_count, - code_gc_count, exit_from_branch_stub, @@ -391,6 +390,9 @@ fn rb_yjit_gen_stats_dict() -> VALUE { // Live pages hash_aset_usize!(hash, "live_page_count", cb.num_mapped_pages() - freed_page_count); + + // Code GC count + hash_aset_usize!(hash, "code_gc_count", CodegenGlobals::get_code_gc_count()); } // If we're not generating stats, the hash is done From 01d7e15757131555291daa2b851a850cfe17cfe7 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Nov 2022 10:42:29 -0700 Subject: [PATCH 114/148] YJIT: Show side_exit count in stats as well (#6666) --- yjit.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/yjit.rb b/yjit.rb index bb344948ebcc21..71cbaf7edb96fb 100644 --- a/yjit.rb +++ b/yjit.rb @@ -231,6 +231,7 @@ def _print_stats $stderr.puts "code_gc_count: " + ("%10d" % stats[:code_gc_count]) $stderr.puts "num_gc_obj_refs: " + ("%10d" % stats[:num_gc_obj_refs]) + $stderr.puts "side_exit_count: " + ("%10d" % side_exits) $stderr.puts "total_exit_count: " + ("%10d" % total_exits) $stderr.puts "total_insns_count: " + ("%10d" % total_insns_count) $stderr.puts "vm_insns_count: " + ("%10d" % stats[:vm_insns_count]) From 56884b64deeeffb6400060523f8b85211ec99d78 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Nov 2022 10:41:57 -0700 Subject: [PATCH 115/148] [ruby/irb] Require rubygems to run the test alone on ruby/ruby `Gem` is not undefined on test-all https://github.com/ruby/irb/commit/08ac803d61 --- test/irb/test_ruby_lex.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb index dcc998a24e16a3..0f7939d2e82861 100644 --- a/test/irb/test_ruby_lex.rb +++ b/test/irb/test_ruby_lex.rb @@ -1,5 +1,6 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'irb' +require 'rubygems' require 'test/unit' require 'ostruct' From 5344618cb758e351a857d7e71eecc6ac475ce8a7 Mon Sep 17 00:00:00 2001 From: Yusuke Nakamura Date: Fri, 4 Nov 2022 01:13:26 +0900 Subject: [PATCH 116/148] [DOC] Fix IO::Buffer#slice rdoc position Before this change, rdoc shows empty in 'slice' method section --- io_buffer.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/io_buffer.c b/io_buffer.c index a0d988e25903fc..cbf51250c4b22d 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -1101,6 +1101,27 @@ io_buffer_validate_range(struct rb_io_buffer *data, size_t offset, size_t length } } +static VALUE +rb_io_buffer_slice(struct rb_io_buffer *data, VALUE self, size_t offset, size_t length) +{ + io_buffer_validate_range(data, offset, length); + + VALUE instance = rb_io_buffer_type_allocate(rb_class_of(self)); + struct rb_io_buffer *slice = NULL; + TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, slice); + + slice->base = (char*)data->base + offset; + slice->size = length; + + // The source should be the root buffer: + if (data->source != Qnil) + slice->source = data->source; + else + slice->source = self; + + return instance; +} + /* * call-seq: slice([offset = 0, [length]]) -> io_buffer * @@ -1157,27 +1178,6 @@ io_buffer_validate_range(struct rb_io_buffer *data, size_t offset, size_t length * string * # => tost */ -static VALUE -rb_io_buffer_slice(struct rb_io_buffer *data, VALUE self, size_t offset, size_t length) -{ - io_buffer_validate_range(data, offset, length); - - VALUE instance = rb_io_buffer_type_allocate(rb_class_of(self)); - struct rb_io_buffer *slice = NULL; - TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, slice); - - slice->base = (char*)data->base + offset; - slice->size = length; - - // The source should be the root buffer: - if (data->source != Qnil) - slice->source = data->source; - else - slice->source = self; - - return instance; -} - static VALUE io_buffer_slice(int argc, VALUE *argv, VALUE self) { From d24ac6d2811e461562e3bb95525399bb9cf464f8 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 3 Nov 2022 16:58:39 -0400 Subject: [PATCH 117/148] Update configure command in building_ruby.md Readers didn't realize that you can use --prefix with --disable-install-doc. --- doc/contributing/building_ruby.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index d2f175f4a9f9c9..ace5fbed37c5e8 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -39,7 +39,7 @@ 5. Optional: If you are frequently building Ruby, disabling documentation will reduce the time it takes to `make`: ``` shell - ../configure --disable-install-doc + ../configure --prefix="${HOME}/.rubies/ruby-master" --disable-install-doc ``` 6. [Run tests](testing_ruby.md) to confirm your build succeeded From a13836e70d9cc2eb569911030cbd735d68b4042c Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Nov 2022 15:09:51 -0700 Subject: [PATCH 118/148] [ruby/irb] Allow non-identifier aliases like Pry's @ and $ (https://github.com/ruby/irb/pull/426) * Allow non-identifier aliases * Move the configuration to IRB.conf * Avoid abusing method lookup for symbol aliases * Add more alias tests * A small optimization * Assume non-nil Context * Load IRB.conf earlier https://github.com/ruby/irb/commit/e23db5132e --- lib/irb.rb | 3 +++ lib/irb/context.rb | 18 ++++++++++++++ lib/irb/init.rb | 2 ++ lib/irb/ruby-lex.rb | 6 +++++ test/irb/test_cmd.rb | 58 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+) diff --git a/lib/irb.rb b/lib/irb.rb index 749f3ee167efc0..57ec9ebaebc9d9 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -426,6 +426,9 @@ class Irb def initialize(workspace = nil, input_method = nil) @context = Context.new(self, workspace, input_method) @context.main.extend ExtendCommandBundle + @context.command_aliases.each do |alias_name, cmd_name| + @context.main.install_alias_method(alias_name, cmd_name) + end @signal_status = :IN_IRB @scanner = RubyLex.new end diff --git a/lib/irb/context.rb b/lib/irb/context.rb index d238da9350b0ef..d1ae2cb605b936 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -149,6 +149,8 @@ def initialize(irb, workspace = nil, input_method = nil) if @newline_before_multiline_output.nil? @newline_before_multiline_output = true end + + @command_aliases = IRB.conf[:COMMAND_ALIASES] end # The top-level workspace, see WorkSpace#main @@ -326,6 +328,9 @@ def main # See IRB@Command+line+options for more command line options. attr_accessor :back_trace_limit + # User-defined IRB command aliases + attr_accessor :command_aliases + # Alias for #use_multiline alias use_multiline? use_multiline # Alias for #use_singleline @@ -477,6 +482,13 @@ def evaluate(line, line_no, exception: nil) # :nodoc: line = "begin ::Kernel.raise _; rescue _.class\n#{line}\n""end" @workspace.local_variable_set(:_, exception) end + + # Transform a non-identifier alias (ex: @, $) + command = line.split(/\s/, 2).first + if original = symbol_alias(command) + line = line.gsub(/\A#{Regexp.escape(command)}/, original.to_s) + end + set_last_value(@workspace.evaluate(self, line, irb_path, line_no)) end @@ -522,5 +534,11 @@ def inspect # :nodoc: def local_variables # :nodoc: workspace.binding.local_variables end + + # Return a command name if it's aliased from the argument and it's not an identifier. + def symbol_alias(command) + return nil if command.match?(/\A\w+\z/) + command_aliases[command.to_sym] + end end end diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 5409528faeb7e7..09099f88b77346 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -158,6 +158,8 @@ def IRB.init_config(ap_path) @CONF[:LC_MESSAGES] = Locale.new @CONF[:AT_EXIT] = [] + + @CONF[:COMMAND_ALIASES] = {} end def IRB.set_measure_callback(type = nil, arg = nil, &block) diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 544392228e00f1..28029bbf4ce6c3 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -65,6 +65,12 @@ def set_input(io, p = nil, context:, &block) false end else + # Accept any single-line input starting with a non-identifier alias (ex: @, $) + command = code.split(/\s/, 2).first + if context.symbol_alias(command) + next true + end + code.gsub!(/\s*\z/, '').concat("\n") ltype, indent, continue, code_block_open = check_state(code, context: context) if ltype or indent > 0 or continue or code_block_open diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 060f70c9cc3aa2..034d825bbc4c33 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -570,6 +570,24 @@ def test_show_source assert_match(%r[/irb\.rb], out) end + def test_show_source_alias + input = TestInputMethod.new([ + "$ 'IRB.conf'\n", + ]) + IRB.init_config(nil) + IRB.conf[:COMMAND_ALIASES] = { :'$' => :show_source } + workspace = IRB::WorkSpace.new(Object.new) + IRB.conf[:VERBOSE] = false + irb = IRB::Irb.new(workspace, input) + IRB.conf[:MAIN_CONTEXT] = irb.context + irb.context.return_format = "=> %s\n" + out, err = capture_output do + irb.eval_input + end + assert_empty err + assert_match(%r[/irb\.rb], out) + end + def test_show_source_end_finder pend if RUBY_ENGINE == 'truffleruby' eval(code = <<-EOS, binding, __FILE__, __LINE__ + 1) @@ -610,5 +628,45 @@ def test_whereami assert_empty err assert_match(/^From: .+ @ line \d+ :\n/, out) end + + def test_whereami_alias + input = TestInputMethod.new([ + "@\n", + ]) + IRB.init_config(nil) + IRB.conf[:COMMAND_ALIASES] = { :'@' => :whereami } + workspace = IRB::WorkSpace.new(Object.new) + irb = IRB::Irb.new(workspace, input) + IRB.conf[:MAIN_CONTEXT] = irb.context + out, err = capture_output do + irb.eval_input + end + assert_empty err + assert_match(/^From: .+ @ line \d+ :\n/, out) + end + + def test_vars_with_aliases + input = TestInputMethod.new([ + "@foo\n", + "$bar\n", + ]) + IRB.init_config(nil) + IRB.conf[:COMMAND_ALIASES] = { + :'@' => :whereami, + :'$' => :show_source, + } + main = Object.new + main.instance_variable_set(:@foo, "foo") + $bar = "bar" + workspace = IRB::WorkSpace.new(main) + irb = IRB::Irb.new(workspace, input) + IRB.conf[:MAIN_CONTEXT] = irb.context + out, err = capture_output do + irb.eval_input + end + assert_empty err + assert_match(/"foo"/, out) + assert_match(/"bar"/, out) + end end end From a9232038119f2401e6c3d3a4946114b1e878afb5 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 3 Nov 2022 22:13:07 +0000 Subject: [PATCH 119/148] [ruby/irb] Provide a base test class and let tests restore encodings conveniently (https://github.com/ruby/irb/pull/429) * Create a base TestIRB::TestCase class * Save/restore encodings for tests that initializes InputMethod classes Because `RelineInputMethod#initializes` calls `set_encoding`, which changes stdio/out/err and Encoding's default encoding values, we need to make sure any test that directly or indirectly (e.g. through Context) initializes `RelineInputMethod` restores encodings. `ReadlineInputMethod` also changes encodings but currently no tests cover it. * Remove unnecessary TestHelper module Since we now have a base TestCase, without_rdoc can just live there. https://github.com/ruby/irb/commit/c2874ec121 --- test/irb/helper.rb | 22 ++++++++++++++++--- test/irb/test_cmd.rb | 15 ++++--------- test/irb/test_color.rb | 5 +++-- test/irb/test_color_printer.rb | 5 +++-- test/irb/test_completion.rb | 5 ++--- test/irb/test_context.rb | 5 +++-- test/irb/test_history.rb | 5 +++-- test/irb/test_init.rb | 5 +++-- test/irb/test_input_method.rb | 22 ++++--------------- test/irb/test_option.rb | 4 ++-- test/irb/test_raise_no_backtrace_exception.rb | 5 +++-- test/irb/test_ruby_lex.rb | 13 +++++++++-- test/irb/test_workspace.rb | 5 +++-- 13 files changed, 63 insertions(+), 53 deletions(-) diff --git a/test/irb/helper.rb b/test/irb/helper.rb index 19c39a4a59483f..15d5dafc57ef9d 100644 --- a/test/irb/helper.rb +++ b/test/irb/helper.rb @@ -1,6 +1,22 @@ -module IRB - module TestHelper - def self.without_rdoc(&block) +require "test/unit" + +module TestIRB + class TestCase < Test::Unit::TestCase + def save_encodings + @default_encoding = [Encoding.default_external, Encoding.default_internal] + @stdio_encodings = [STDIN, STDOUT, STDERR].map {|io| [io.external_encoding, io.internal_encoding] } + end + + def restore_encodings + EnvUtil.suppress_warning do + Encoding.default_external, Encoding.default_internal = *@default_encoding + [STDIN, STDOUT, STDERR].zip(@stdio_encodings) do |io, encs| + io.set_encoding(*encs) + end + end + end + + def without_rdoc(&block) ::Kernel.send(:alias_method, :old_require, :require) ::Kernel.define_method(:require) do |name| diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 034d825bbc4c33..09f4b3bae8ba5d 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -1,12 +1,11 @@ # frozen_string_literal: false -require "test/unit" require "irb" require "irb/extend-command" require_relative "helper" module TestIRB - class ExtendCommand < Test::Unit::TestCase + class ExtendCommand < TestCase class TestInputMethod < ::IRB::InputMethod attr_reader :list, :line_no @@ -46,8 +45,7 @@ def setup @home_backup = ENV["HOME"] ENV["HOME"] = @tmpdir @xdg_config_home_backup = ENV.delete("XDG_CONFIG_HOME") - @default_encoding = [Encoding.default_external, Encoding.default_internal] - @stdio_encodings = [STDIN, STDOUT, STDERR].map {|io| [io.external_encoding, io.internal_encoding] } + save_encodings IRB.instance_variable_get(:@CONF).clear @is_win = (RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/) end @@ -57,12 +55,7 @@ def teardown ENV["HOME"] = @home_backup Dir.chdir(@pwd) FileUtils.rm_rf(@tmpdir) - EnvUtil.suppress_warning { - Encoding.default_external, Encoding.default_internal = *@default_encoding - [STDIN, STDOUT, STDERR].zip(@stdio_encodings) do |io, encs| - io.set_encoding(*encs) - end - } + restore_encodings end def test_irb_info_multiline @@ -450,7 +443,7 @@ def test_help_without_rdoc IRB.conf[:VERBOSE] = false irb = IRB::Irb.new(IRB::WorkSpace.new(self), input) out, _ = capture_output do - IRB::TestHelper.without_rdoc do + without_rdoc do irb.eval_input end end diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb index 02c79e344376f4..1b27afb898de4f 100644 --- a/test/irb/test_color.rb +++ b/test/irb/test_color.rb @@ -1,11 +1,12 @@ # frozen_string_literal: false -require 'test/unit' require 'irb/color' require 'rubygems' require 'stringio' +require_relative "helper" + module TestIRB - class TestColor < Test::Unit::TestCase + class TestColor < TestCase CLEAR = "\e[0m" BOLD = "\e[1m" UNDERLINE = "\e[4m" diff --git a/test/irb/test_color_printer.rb b/test/irb/test_color_printer.rb index 93ec7001465564..79ab050550481b 100644 --- a/test/irb/test_color_printer.rb +++ b/test/irb/test_color_printer.rb @@ -1,11 +1,12 @@ # frozen_string_literal: false -require 'test/unit' require 'irb/color_printer' require 'rubygems' require 'stringio' +require_relative "helper" + module TestIRB - class TestColorPrinter < Test::Unit::TestCase + class TestColorPrinter < TestCase CLEAR = "\e[0m" BOLD = "\e[1m" RED = "\e[31m" diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index e9b422fe7aa405..1ab7dbbb194991 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -1,12 +1,11 @@ # frozen_string_literal: false -require "test/unit" require "pathname" require "irb" require_relative "helper" module TestIRB - class TestCompletion < Test::Unit::TestCase + class TestCompletion < TestCase def setup # make sure require completion candidates are not cached IRB::InputCompletor.class_variable_set(:@@files_from_load_path, nil) @@ -276,7 +275,7 @@ def test_perfect_matching_stops_without_rdoc result = nil out, err = capture_output do - IRB::TestHelper.without_rdoc do + without_rdoc do result = IRB::InputCompletor::PerfectMatchedProc.("String", bind: binding) end end diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 998cdd8591d0f7..e7cc0bab4f4fd8 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -1,11 +1,12 @@ # frozen_string_literal: false -require 'test/unit' require 'tempfile' require 'irb' require 'rubygems' if defined?(Gem) +require_relative "helper" + module TestIRB - class TestContext < Test::Unit::TestCase + class TestContext < TestCase class TestInputMethod < ::IRB::InputMethod attr_reader :list, :line_no diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index fab38074470ed5..b0a07ca29d3e8a 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -1,11 +1,12 @@ # frozen_string_literal: false -require 'test/unit' require 'irb' require 'irb/ext/save-history' require 'readline' +require_relative "helper" + module TestIRB - class TestHistory < Test::Unit::TestCase + class TestHistory < TestCase def setup IRB.conf[:RC_NAME_GENERATOR] = nil end diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 39c5cfa8a0acee..9591de15895f45 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -1,10 +1,11 @@ # frozen_string_literal: false -require "test/unit" require "irb" require "fileutils" +require_relative "helper" + module TestIRB - class TestInit < Test::Unit::TestCase + class TestInit < TestCase def setup # IRBRC is for RVM... @backup_env = %w[HOME XDG_CONFIG_HOME IRBRC].each_with_object({}) do |env, hash| diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index 90d6bf536400e1..fdfb5663905a66 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -1,34 +1,20 @@ # frozen_string_literal: false -require "test/unit" require "irb" require_relative "helper" module TestIRB - class TestRelineInputMethod < Test::Unit::TestCase + class TestRelineInputMethod < TestCase def setup @conf_backup = IRB.conf.dup IRB.conf[:LC_MESSAGES] = IRB::Locale.new - - # RelineInputMethod#initialize calls IRB.set_encoding, which mutates standard input/output's encoding - # so we need to make sure we set them back - @original_io_encodings = { - STDIN => [STDIN.external_encoding, STDIN.internal_encoding], - STDOUT => [STDOUT.external_encoding, STDOUT.internal_encoding], - STDERR => [STDERR.external_encoding, STDERR.internal_encoding], - } - @original_default_encodings = [Encoding.default_external, Encoding.default_internal] + save_encodings end def teardown IRB.conf.replace(@conf_backup) - - @original_io_encodings.each do |io, (external_encoding, internal_encoding)| - io.set_encoding(external_encoding, internal_encoding) - end - - EnvUtil.suppress_warning { Encoding.default_external, Encoding.default_internal = @original_default_encodings } + restore_encodings end def test_initialization @@ -78,7 +64,7 @@ def test_initialization_with_use_autocomplete_but_without_rdoc IRB.conf[:USE_AUTOCOMPLETE] = true - IRB::TestHelper.without_rdoc do + without_rdoc do IRB::RelineInputMethod.new end diff --git a/test/irb/test_option.rb b/test/irb/test_option.rb index aa634c02a22bd1..e334cee6dd39a6 100644 --- a/test/irb/test_option.rb +++ b/test/irb/test_option.rb @@ -1,8 +1,8 @@ # frozen_string_literal: false -require 'test/unit' +require_relative "helper" module TestIRB - class TestOption < Test::Unit::TestCase + class TestOption < TestCase def test_end_of_option bug4117 = '[ruby-core:33574]' bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] diff --git a/test/irb/test_raise_no_backtrace_exception.rb b/test/irb/test_raise_no_backtrace_exception.rb index a532d8b3ec1066..ba8e89c59fb63b 100644 --- a/test/irb/test_raise_no_backtrace_exception.rb +++ b/test/irb/test_raise_no_backtrace_exception.rb @@ -1,8 +1,9 @@ # frozen_string_literal: false -require 'test/unit' + +require_relative "helper" module TestIRB - class TestRaiseNoBacktraceException < Test::Unit::TestCase + class TestRaiseNoBacktraceException < TestCase def test_raise_exception bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : [] assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /Exception: foo/, []) diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb index 0f7939d2e82861..d618b01c31bbf5 100644 --- a/test/irb/test_ruby_lex.rb +++ b/test/irb/test_ruby_lex.rb @@ -1,11 +1,12 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'irb' require 'rubygems' -require 'test/unit' require 'ostruct' +require_relative "helper" + module TestIRB - class TestRubyLex < Test::Unit::TestCase + class TestRubyLex < TestCase Row = Struct.new(:content, :current_line_spaces, :new_line_spaces, :nesting_level) class MockIO_AutoIndent @@ -20,6 +21,14 @@ def auto_indent(&block) end end + def setup + save_encodings + end + + def teardown + restore_encodings + end + def assert_indenting(lines, correct_space_count, add_new_line) lines = lines + [""] if add_new_line last_line_index = lines.length - 1 diff --git a/test/irb/test_workspace.rb b/test/irb/test_workspace.rb index 1a1dc1f49b57a6..9b10c27b89d200 100644 --- a/test/irb/test_workspace.rb +++ b/test/irb/test_workspace.rb @@ -1,13 +1,14 @@ # frozen_string_literal: false -require 'test/unit' require 'tempfile' require 'rubygems' require 'irb' require 'irb/workspace' require 'irb/color' +require_relative "helper" + module TestIRB - class TestWorkSpace < Test::Unit::TestCase + class TestWorkSpace < TestCase def test_code_around_binding IRB.conf[:USE_COLORIZE] = false Tempfile.create('irb') do |f| From 1956fb9b78c1f78649df48282c716b4f74d45ed7 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Nov 2022 17:26:50 -0700 Subject: [PATCH 120/148] Bump benchmark-driver version https://github.com/benchmark-driver/benchmark-driver/pull/75 is useful for quickly benchmarking a single method in CRuby. --- common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 1a20e005d25ebe..ed06e2243166b6 100644 --- a/common.mk +++ b/common.mk @@ -50,7 +50,7 @@ GEM_PATH = GEM_VENDOR = BENCHMARK_DRIVER_GIT_URL = https://github.com/benchmark-driver/benchmark-driver -BENCHMARK_DRIVER_GIT_REF = v0.15.18 +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 From 570dee15a6939f1a3c8cf301202a1c508c2c2a33 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 4 Nov 2022 15:17:49 +0900 Subject: [PATCH 121/148] sync_default_gems.rb: accept log input from other than STDIN --- tool/sync_default_gems.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 7543b4f73ccc7c..d6b20a82bfd28f 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -418,8 +418,8 @@ def sync_default_gems(gem) |rakelib\/.* )\z/mx -def message_filter(repo, sha) - log = STDIN.read +def message_filter(repo, sha, input: ARGF) + log = input.read log.delete!("\r") url = "https://github.com/#{repo}" subject, log = log.split(/\n(?:[\s\t]*(?:\n|\z))/, 2) @@ -659,8 +659,10 @@ def update_default_gems(gem, release: false) end when "--message-filter" ARGV.shift - abort unless ARGV.size == 2 - message_filter(*ARGV) + if ARGV.size < 2 + abort "usage: #{$0} --message-filter repository commit-hash [input...]" + end + message_filter(*ARGV.shift(2)) exit when "rdoc-ref" ARGV.shift From b6d7e98f2540500f072c5cc0f136cae69f80055c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 4 Nov 2022 15:18:36 +0900 Subject: [PATCH 122/148] sync_default_gems.rb: fix the position to insert the original URL Since the regexp had expected an empty line before `Co-Authored-By:` trailer lines, it failed to match when the body has the trailer only. --- tool/sync_default_gems.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index d6b20a82bfd28f..80eabab81c3ce5 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -441,9 +441,10 @@ def message_filter(repo, sha, input: ARGF) end url = "#{url}/commit/#{sha[0,10]}\n" if log and !log.empty? + log.sub!(/(?<=\n)\n+\z/, '') # drop empty lines at the last conv[log] - log.sub!(/\s*(?=(?i:\nCo-authored-by:.*)*\Z)/) { - "\n\n#{url}" + log.sub!(/(?:(\A\s*)|\s*\n)(?=(?i:Co-authored-by:.*)*\Z)/) { + $~.begin(0) ? "#{url}\n" : "\n\n#{url}" } else log = url From dc5d06e9b145f7d5f8c5f7c3757b43f2d68833fd Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 2 Nov 2022 22:28:45 -0700 Subject: [PATCH 123/148] [ruby/erb] Copy CGI.escapeHTML to ERB::Util.html_escape https://github.com/ruby/erb/commit/ac9b219fa9 --- ext/erb/erb.c | 96 ++++++++++++++++++++++++++++++++++++++++++++ ext/erb/extconf.rb | 2 + lib/erb.gemspec | 5 ++- lib/erb.rb | 12 +++++- test/erb/test_erb.rb | 13 ++++++ 5 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 ext/erb/erb.c create mode 100644 ext/erb/extconf.rb diff --git a/ext/erb/erb.c b/ext/erb/erb.c new file mode 100644 index 00000000000000..92cfbd07692d0d --- /dev/null +++ b/ext/erb/erb.c @@ -0,0 +1,96 @@ +#include "ruby.h" +#include "ruby/encoding.h" + +static VALUE rb_cERB, rb_mEscape; + +#define HTML_ESCAPE_MAX_LEN 6 + +static const struct { + uint8_t len; + char str[HTML_ESCAPE_MAX_LEN+1]; +} html_escape_table[UCHAR_MAX+1] = { +#define HTML_ESCAPE(c, str) [c] = {rb_strlen_lit(str), str} + HTML_ESCAPE('\'', "'"), + HTML_ESCAPE('&', "&"), + HTML_ESCAPE('"', """), + HTML_ESCAPE('<', "<"), + HTML_ESCAPE('>', ">"), +#undef HTML_ESCAPE +}; + +static inline void +preserve_original_state(VALUE orig, VALUE dest) +{ + rb_enc_associate(dest, rb_enc_get(orig)); +} + +static inline long +escaped_length(VALUE str) +{ + const long len = RSTRING_LEN(str); + if (len >= LONG_MAX / HTML_ESCAPE_MAX_LEN) { + ruby_malloc_size_overflow(len, HTML_ESCAPE_MAX_LEN); + } + return len * HTML_ESCAPE_MAX_LEN; +} + +static VALUE +optimized_escape_html(VALUE str) +{ + VALUE vbuf; + char *buf = ALLOCV_N(char, vbuf, escaped_length(str)); + const char *cstr = RSTRING_PTR(str); + const char *end = cstr + RSTRING_LEN(str); + + char *dest = buf; + while (cstr < end) { + const unsigned char c = *cstr++; + uint8_t len = html_escape_table[c].len; + if (len) { + memcpy(dest, html_escape_table[c].str, len); + dest += len; + } + else { + *dest++ = c; + } + } + + VALUE escaped; + if (RSTRING_LEN(str) < (dest - buf)) { + escaped = rb_str_new(buf, dest - buf); + preserve_original_state(str, escaped); + } + else { + escaped = rb_str_dup(str); + } + ALLOCV_END(vbuf); + return escaped; +} + +static VALUE +cgiesc_escape_html(VALUE self, VALUE str) +{ + StringValue(str); + + if (rb_enc_str_asciicompat_p(str)) { + return optimized_escape_html(str); + } + else { + return rb_call_super(1, &str); + } +} + +static VALUE +erb_escape_html(VALUE self, VALUE str) +{ + str = rb_funcall(str, rb_intern("to_s"), 0); + return cgiesc_escape_html(self, str); +} + +void +Init_erb(void) +{ + rb_cERB = rb_define_class("ERB", rb_cObject); + rb_mEscape = rb_define_module_under(rb_cERB, "Escape"); + rb_define_method(rb_mEscape, "html_escape", erb_escape_html, 1); +} diff --git a/ext/erb/extconf.rb b/ext/erb/extconf.rb new file mode 100644 index 00000000000000..00a7e92aea10a2 --- /dev/null +++ b/ext/erb/extconf.rb @@ -0,0 +1,2 @@ +require 'mkmf' +create_makefile 'erb' diff --git a/lib/erb.gemspec b/lib/erb.gemspec index 2e7e981ff13d0b..419685c31890c2 100644 --- a/lib/erb.gemspec +++ b/lib/erb.gemspec @@ -27,8 +27,11 @@ Gem::Specification.new do |spec| spec.executables = ['erb'] spec.require_paths = ['lib'] - if RUBY_ENGINE != 'jruby' + if RUBY_ENGINE == 'jruby' + spec.platform = 'java' + else spec.required_ruby_version = '>= 2.7.0' + spec.extensions = ['ext/erb/extconf.rb'] end spec.add_dependency 'cgi', '>= 0.3.3' diff --git a/lib/erb.rb b/lib/erb.rb index 962eeb6963f081..c588ae1a65da87 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -986,7 +986,6 @@ def def_class(superklass=Object, methodname='result') class ERB # A utility module for conversion routines, often handy in HTML generation. module Util - public # # A utility method for escaping HTML tag characters in _s_. # @@ -1002,6 +1001,17 @@ module Util def html_escape(s) CGI.escapeHTML(s.to_s) end + end + + begin + require 'erb.so' + rescue LoadError + else + private_constant :Escape + Util.prepend(Escape) + end + + module Util alias h html_escape module_function :h module_function :html_escape diff --git a/test/erb/test_erb.rb b/test/erb/test_erb.rb index 424ddae87eb000..1db0e55f8ae022 100644 --- a/test/erb/test_erb.rb +++ b/test/erb/test_erb.rb @@ -73,11 +73,24 @@ def test_html_escape assert_equal("", ERB::Util.html_escape("")) assert_equal("abc", ERB::Util.html_escape("abc")) assert_equal("<<", ERB::Util.html_escape("<\<")) + assert_equal("'&"><", ERB::Util.html_escape("'&\"><")) assert_equal("", ERB::Util.html_escape(nil)) assert_equal("123", ERB::Util.html_escape(123)) end + def test_html_escape_to_s + object = Object.new + def object.to_s + "object" + end + assert_equal("object", ERB::Util.html_escape(object)) + end + + def test_html_escape_extension + assert_nil(ERB::Util.method(:html_escape).source_location) + end if RUBY_ENGINE == 'ruby' + def test_concurrent_default_binding # This test randomly fails with JRuby -- NameError: undefined local variable or method `template2' pend if RUBY_ENGINE == 'jruby' From 20efeaddbe246f3b2eaee4f17f54a814777176a8 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Nov 2022 23:26:53 -0700 Subject: [PATCH 124/148] [ruby/erb] Optimize away to_s if it's already T_STRING [Feature #19102]https://github.com/ruby/erb/commit/38c6e182fb --- ext/erb/erb.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/ext/erb/erb.c b/ext/erb/erb.c index 92cfbd07692d0d..9376fa5dcb7e19 100644 --- a/ext/erb/erb.c +++ b/ext/erb/erb.c @@ -68,9 +68,9 @@ optimized_escape_html(VALUE str) } static VALUE -cgiesc_escape_html(VALUE self, VALUE str) +erb_escape_html(VALUE self, VALUE str) { - StringValue(str); + str = rb_convert_type(str, T_STRING, "String", "to_s"); if (rb_enc_str_asciicompat_p(str)) { return optimized_escape_html(str); @@ -80,13 +80,6 @@ cgiesc_escape_html(VALUE self, VALUE str) } } -static VALUE -erb_escape_html(VALUE self, VALUE str) -{ - str = rb_funcall(str, rb_intern("to_s"), 0); - return cgiesc_escape_html(self, str); -} - void Init_erb(void) { From ccf32a5ca4ce29f9019cf8bae9f3c0f69e27bd22 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Nov 2022 23:34:09 -0700 Subject: [PATCH 125/148] [ruby/erb] Do not allocate a new String if not needed [Feature #19102]https://github.com/ruby/erb/commit/ecebf8075c --- ext/erb/erb.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/erb/erb.c b/ext/erb/erb.c index 9376fa5dcb7e19..4adab8ad339d26 100644 --- a/ext/erb/erb.c +++ b/ext/erb/erb.c @@ -55,18 +55,18 @@ optimized_escape_html(VALUE str) } } - VALUE escaped; + VALUE escaped = str; if (RSTRING_LEN(str) < (dest - buf)) { escaped = rb_str_new(buf, dest - buf); preserve_original_state(str, escaped); } - else { - escaped = rb_str_dup(str); - } ALLOCV_END(vbuf); return escaped; } +// ERB::Util.html_escape is different from CGI.escapeHTML in the following two parts: +// * ERB::Util.html_escape converts an argument with #to_s first (only if it's not T_STRING) +// * ERB::Util.html_escape does not allocate a new string when nothing needs to be escaped static VALUE erb_escape_html(VALUE self, VALUE str) { From 2bb89b7f114e4beb3012f63e12e726ae23005e6f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 4 Nov 2022 20:13:48 +1300 Subject: [PATCH 126/148] Lower priority of `POSIX_MADV_DONTNEED`. (#6671) --- cont.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/cont.c b/cont.c index 635faf88092ba4..a85b7fc2d1338a 100644 --- a/cont.c +++ b/cont.c @@ -693,21 +693,36 @@ fiber_pool_stack_free(struct fiber_pool_stack * stack) if (DEBUG) fprintf(stderr, "fiber_pool_stack_free: %p+%"PRIuSIZE" [base=%p, size=%"PRIuSIZE"]\n", base, size, stack->base, stack->size); -#if VM_CHECK_MODE > 0 && defined(MADV_DONTNEED) + // The pages being used by the stack can be returned back to the system. + // That doesn't change the page mapping, but it does allow the system to + // reclaim the physical memory. + // Since we no longer care about the data itself, we don't need to page + // out to disk, since that is costly. Not all systems support that, so + // we try our best to select the most efficient implementation. + // In addition, it's actually slightly desirable to not do anything here, + // but that results in higher memory usage. + +#ifdef __wasi__ + // WebAssembly doesn't support madvise, so we just don't do anything. +#elif VM_CHECK_MODE > 0 && defined(MADV_DONTNEED) // This immediately discards the pages and the memory is reset to zero. madvise(base, size, MADV_DONTNEED); -#elif defined(POSIX_MADV_DONTNEED) - posix_madvise(base, size, POSIX_MADV_DONTNEED); #elif defined(MADV_FREE_REUSABLE) + // Darwin / macOS / iOS. // Acknowledge the kernel down to the task info api we make this // page reusable for future use. // As for MADV_FREE_REUSE below we ensure in the rare occasions the task was not // completed at the time of the call to re-iterate. while (madvise(base, size, MADV_FREE_REUSABLE) == -1 && errno == EAGAIN); #elif defined(MADV_FREE) + // Recent Linux. madvise(base, size, MADV_FREE); #elif defined(MADV_DONTNEED) + // Old Linux. madvise(base, size, MADV_DONTNEED); +#elif defined(POSIX_MADV_DONTNEED) + // Solaris? + posix_madvise(base, size, POSIX_MADV_DONTNEED); #elif defined(_WIN32) VirtualAlloc(base, size, MEM_RESET, PAGE_READWRITE); // Not available in all versions of Windows. From 7e3af23d1d66ec02da74a23cce1262af395064f9 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 4 Nov 2022 16:22:21 +0900 Subject: [PATCH 127/148] sync_default_gems.rb: fix a typo [ci skip] --- tool/sync_default_gems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 80eabab81c3ce5..d6c7f771d71073 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -444,7 +444,7 @@ def message_filter(repo, sha, input: ARGF) log.sub!(/(?<=\n)\n+\z/, '') # drop empty lines at the last conv[log] log.sub!(/(?:(\A\s*)|\s*\n)(?=(?i:Co-authored-by:.*)*\Z)/) { - $~.begin(0) ? "#{url}\n" : "\n\n#{url}" + $~.begin(1) ? "#{url}\n" : "\n\n#{url}" } else log = url From cb18deee7285dde67102c9702796d7eba0046af5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 4 Nov 2022 17:38:28 +0900 Subject: [PATCH 128/148] Substitute from the actual netinet6/in6.h Xcode no longer links the system include files directory to `/usr`. Extract the actual header file path from cpp output. --- ext/socket/extconf.rb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ext/socket/extconf.rb b/ext/socket/extconf.rb index b70a86241497a1..8998bb5c2f2869 100644 --- a/ext/socket/extconf.rb +++ b/ext/socket/extconf.rb @@ -655,12 +655,20 @@ def %(s) s || self end end hdr = "netinet6/in6.h" - if /darwin/ =~ RUBY_PLATFORM and !try_compile(<<"SRC", nil, :werror=>true) + /darwin/ =~ RUBY_PLATFORM and + checking_for("if apple's #{hdr} needs s6_addr patch") {!try_compile(<<"SRC", nil, :werror=>true)} and #include int t(struct in6_addr *addr) {return IN6_IS_ADDR_UNSPECIFIED(addr);} SRC - print "fixing apple's netinet6/in6.h ..."; $stdout.flush - in6 = File.read("/usr/include/#{hdr}") + checking_for("fixing apple's #{hdr}", "%s") do + file = xpopen(%w"clang -include netinet/in.h -E -xc -", in: IO::NULL) do |f| + re = %r[^# *\d+ *"(.*/netinet/in\.h)"] + Logging.message " grep(#{re})\n" + f.read[re, 1] + end + Logging.message "Substitute from #{file}\n" + + in6 = File.read(file) if in6.gsub!(/\*\(const\s+__uint32_t\s+\*\)\(const\s+void\s+\*\)\(&(\(\w+\))->s6_addr\[(\d+)\]\)/) do i, r = $2.to_i.divmod(4) if r.zero? @@ -673,9 +681,9 @@ def %(s) s || self end open(hdr, "w") {|f| f.write(in6)} $distcleanfiles << hdr $distcleandirs << File.dirname(hdr) - puts "done" + "done" else - puts "not needed" + "not needed" end end create_makefile("socket") From ed9d7612172e12f515cb79088efe45267a062276 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 4 Nov 2022 18:19:30 +0900 Subject: [PATCH 129/148] mkconfig.rb: take CPU name from arch flag --- tool/mkconfig.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tool/mkconfig.rb b/tool/mkconfig.rb index 120b90850dc4a2..fb3552240170af 100755 --- a/tool/mkconfig.rb +++ b/tool/mkconfig.rb @@ -124,6 +124,14 @@ if universal platform = val.sub(/universal/, %q[#{arch && universal[/(?:\A|\s)#{Regexp.quote(arch)}=(\S+)/, 1] || RUBY_PLATFORM[/\A[^-]*/]}]) end + when /^target_cpu$/ + if universal + val = 'arch' + end + when /^target$/ + val = '"$(target_cpu)-$(target_vendor)-$(target_os)"' + when /^host(?:_(?:os|vendor|cpu|alias))?$/ + val = %["$(#{name.sub(/^host/, 'target')})"] when /^includedir$/ val = '"$(SDKROOT)"'+val if /darwin/ =~ arch end From b83074dac7a3a1353ac8f897a510cecfccc2f7c6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 4 Nov 2022 20:43:12 +0900 Subject: [PATCH 130/148] [ruby/irb] Suppress "switching inspect mode" messages https://github.com/ruby/irb/commit/ee068d039b --- test/irb/test_cmd.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb index 09f4b3bae8ba5d..2728aa656a8f5f 100644 --- a/test/irb/test_cmd.rb +++ b/test/irb/test_cmd.rb @@ -629,6 +629,7 @@ def test_whereami_alias IRB.init_config(nil) IRB.conf[:COMMAND_ALIASES] = { :'@' => :whereami } workspace = IRB::WorkSpace.new(Object.new) + IRB.conf[:VERBOSE] = false irb = IRB::Irb.new(workspace, input) IRB.conf[:MAIN_CONTEXT] = irb.context out, err = capture_output do @@ -652,6 +653,7 @@ def test_vars_with_aliases main.instance_variable_set(:@foo, "foo") $bar = "bar" workspace = IRB::WorkSpace.new(main) + IRB.conf[:VERBOSE] = false irb = IRB::Irb.new(workspace, input) IRB.conf[:MAIN_CONTEXT] = irb.context out, err = capture_output do From 76a6c5d6d16cd0e55df15d66d53c6d7a9614f3d6 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Thu, 3 Nov 2022 14:59:48 -0400 Subject: [PATCH 131/148] Remove unnecessary branch in `UnboundMethod#bind` Co-authored-by: Michael Herold --- proc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proc.c b/proc.c index 7c76f3647783a0..1f047cd04f37c1 100644 --- a/proc.c +++ b/proc.c @@ -2571,8 +2571,7 @@ convert_umethod_to_method_components(const struct METHOD *data, VALUE recv, VALU VALUE refined_class = rb_refinement_module_get_refined_class(methclass); if (!NIL_P(refined_class)) methclass = refined_class; } - if (!RB_TYPE_P(methclass, T_MODULE) && - methclass != CLASS_OF(recv) && !rb_obj_is_kind_of(recv, methclass)) { + if (!RB_TYPE_P(methclass, T_MODULE) && !rb_obj_is_kind_of(recv, methclass)) { if (FL_TEST(methclass, FL_SINGLETON)) { rb_raise(rb_eTypeError, "singleton method called for a different object"); From 93f364d65e25187ce3c1dd5172a00264804e9380 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 4 Nov 2022 09:02:58 -0400 Subject: [PATCH 132/148] Use RTEST to to check return value rb_obj_is_kind_of returns a Ruby Qtrue or Qfalse. We should use RTEST rather than assuming that Qfalse is 0. --- proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proc.c b/proc.c index 1f047cd04f37c1..a25786c0157251 100644 --- a/proc.c +++ b/proc.c @@ -2571,7 +2571,7 @@ convert_umethod_to_method_components(const struct METHOD *data, VALUE recv, VALU VALUE refined_class = rb_refinement_module_get_refined_class(methclass); if (!NIL_P(refined_class)) methclass = refined_class; } - if (!RB_TYPE_P(methclass, T_MODULE) && !rb_obj_is_kind_of(recv, methclass)) { + if (!RB_TYPE_P(methclass, T_MODULE) && !RTEST(rb_obj_is_kind_of(recv, methclass))) { if (FL_TEST(methclass, FL_SINGLETON)) { rb_raise(rb_eTypeError, "singleton method called for a different object"); From 6b9091097360988dce7b4190fef1602536ddff34 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 5 Nov 2022 00:18:02 +0900 Subject: [PATCH 133/148] Update dependencies --- common.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common.mk b/common.mk index ed06e2243166b6..781c323d95e625 100644 --- a/common.mk +++ b/common.mk @@ -18005,6 +18005,7 @@ yjit.$(OBJEXT): $(top_srcdir)/internal/array.h yjit.$(OBJEXT): $(top_srcdir)/internal/class.h yjit.$(OBJEXT): $(top_srcdir)/internal/compile.h yjit.$(OBJEXT): $(top_srcdir)/internal/compilers.h +yjit.$(OBJEXT): $(top_srcdir)/internal/cont.h yjit.$(OBJEXT): $(top_srcdir)/internal/fixnum.h yjit.$(OBJEXT): $(top_srcdir)/internal/gc.h yjit.$(OBJEXT): $(top_srcdir)/internal/hash.h @@ -18202,6 +18203,7 @@ yjit.$(OBJEXT): {$(VPATH)}probes.h yjit.$(OBJEXT): {$(VPATH)}probes_helper.h yjit.$(OBJEXT): {$(VPATH)}ruby_assert.h yjit.$(OBJEXT): {$(VPATH)}ruby_atomic.h +yjit.$(OBJEXT): {$(VPATH)}shape.h yjit.$(OBJEXT): {$(VPATH)}st.h yjit.$(OBJEXT): {$(VPATH)}subst.h yjit.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h From 6e4b97f1daf2a6e60dcfb5df4136ce5681095849 Mon Sep 17 00:00:00 2001 From: Jemma Issroff Date: Fri, 4 Nov 2022 10:49:00 -0400 Subject: [PATCH 134/148] Increment max_iv_count on class in gc marking, not gc freeing We were previously incrementing the max_iv_count on a class in gc freeing. By the time we free an object though, we're not guaranteed its class is still valid. Instead, we can do this when marking and we're guaranteed the object still knows its class. --- gc.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/gc.c b/gc.c index 75b330ede502d3..f2bccbca16091a 100644 --- a/gc.c +++ b/gc.c @@ -3429,16 +3429,6 @@ obj_free(rb_objspace_t *objspace, VALUE obj) RB_DEBUG_COUNTER_INC(obj_obj_transient); } else { - rb_shape_t *shape = rb_shape_get_shape_by_id(ROBJECT_SHAPE_ID(obj)); - if (shape) { - VALUE klass = RBASIC_CLASS(obj); - - // Increment max_iv_count if applicable, used to determine size pool allocation - uint32_t num_of_ivs = shape->next_iv_index; - if (RCLASS_EXT(klass)->max_iv_count < num_of_ivs) { - RCLASS_EXT(klass)->max_iv_count = num_of_ivs; - } - } xfree(RANY(obj)->as.object.as.heap.ivptr); RB_DEBUG_COUNTER_INC(obj_obj_ptr); } @@ -7281,6 +7271,17 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj) gc_mark(objspace, ptr[i]); } + rb_shape_t *shape = rb_shape_get_shape_by_id(ROBJECT_SHAPE_ID(obj)); + if (shape) { + VALUE klass = RBASIC_CLASS(obj); + + // Increment max_iv_count if applicable, used to determine size pool allocation + uint32_t num_of_ivs = shape->next_iv_index; + if (RCLASS_EXT(klass)->max_iv_count < num_of_ivs) { + RCLASS_EXT(klass)->max_iv_count = num_of_ivs; + } + } + if (LIKELY(during_gc) && ROBJ_TRANSIENT_P(obj)) { rb_transient_heap_mark(obj, ptr); From 13395757fa7fe0abee3a260d5669baec2dc2e0fc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 5 Nov 2022 00:43:35 +0900 Subject: [PATCH 135/148] Update dependencies for bc28acc347eace4d02bbb4b672655216f7dd3a81 --- ext/digest/md5/depend | 1 + ext/digest/sha1/depend | 1 + ext/digest/sha2/depend | 1 + 3 files changed, 3 insertions(+) diff --git a/ext/digest/md5/depend b/ext/digest/md5/depend index 0353e7a40dde30..ea1ceec7fd36cd 100644 --- a/ext/digest/md5/depend +++ b/ext/digest/md5/depend @@ -326,5 +326,6 @@ md5init.o: $(hdrdir)/ruby/subst.h md5init.o: $(srcdir)/../defs.h md5init.o: $(srcdir)/../digest.h md5init.o: md5.h +md5init.o: md5cc.h md5init.o: md5init.c # AUTOGENERATED DEPENDENCIES END diff --git a/ext/digest/sha1/depend b/ext/digest/sha1/depend index a4e454d21470fb..48aaef158b3c39 100644 --- a/ext/digest/sha1/depend +++ b/ext/digest/sha1/depend @@ -326,5 +326,6 @@ sha1init.o: $(hdrdir)/ruby/subst.h sha1init.o: $(srcdir)/../defs.h sha1init.o: $(srcdir)/../digest.h sha1init.o: sha1.h +sha1init.o: sha1cc.h sha1init.o: sha1init.c # AUTOGENERATED DEPENDENCIES END diff --git a/ext/digest/sha2/depend b/ext/digest/sha2/depend index 2fb598aa4827f6..47a859068cce0d 100644 --- a/ext/digest/sha2/depend +++ b/ext/digest/sha2/depend @@ -325,5 +325,6 @@ sha2init.o: $(hdrdir)/ruby/st.h sha2init.o: $(hdrdir)/ruby/subst.h sha2init.o: $(srcdir)/../digest.h sha2init.o: sha2.h +sha2init.o: sha2cc.h sha2init.o: sha2init.c # AUTOGENERATED DEPENDENCIES END From b169d78c882efdb4a3da6077f6d723f65ded6f15 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 4 Nov 2022 09:46:23 -0700 Subject: [PATCH 136/148] [ruby/erb] Avoid using prepend + super for fallback (https://github.com/ruby/erb/pull/28) `prepend` is prioritized more than ActiveSupport's monkey-patch, but the monkey-patch needs to work. https://github.com/ruby/erb/commit/611de5a865 --- ext/erb/erb.c | 12 ++++++++---- lib/erb.rb | 20 +++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/ext/erb/erb.c b/ext/erb/erb.c index 4adab8ad339d26..c90f77f7b12e40 100644 --- a/ext/erb/erb.c +++ b/ext/erb/erb.c @@ -1,7 +1,8 @@ #include "ruby.h" #include "ruby/encoding.h" -static VALUE rb_cERB, rb_mEscape; +static VALUE rb_cERB, rb_mUtil, rb_cCGI; +static ID id_escapeHTML; #define HTML_ESCAPE_MAX_LEN 6 @@ -76,7 +77,7 @@ erb_escape_html(VALUE self, VALUE str) return optimized_escape_html(str); } else { - return rb_call_super(1, &str); + return rb_funcall(rb_cCGI, id_escapeHTML, 1, str); } } @@ -84,6 +85,9 @@ void Init_erb(void) { rb_cERB = rb_define_class("ERB", rb_cObject); - rb_mEscape = rb_define_module_under(rb_cERB, "Escape"); - rb_define_method(rb_mEscape, "html_escape", erb_escape_html, 1); + rb_mUtil = rb_define_module_under(rb_cERB, "Util"); + rb_define_method(rb_mUtil, "html_escape", erb_escape_html, 1); + + rb_cCGI = rb_define_class("CGI", rb_cObject); + id_escapeHTML = rb_intern("escapeHTML"); } diff --git a/lib/erb.rb b/lib/erb.rb index c588ae1a65da87..48dcca71aa6490 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -998,20 +998,14 @@ module Util # # is a > 0 & a < 10? # - def html_escape(s) - CGI.escapeHTML(s.to_s) + begin + # ERB::Util.html_escape + require 'erb.so' + rescue LoadError + def html_escape(s) + CGI.escapeHTML(s.to_s) + end end - end - - begin - require 'erb.so' - rescue LoadError - else - private_constant :Escape - Util.prepend(Escape) - end - - module Util alias h html_escape module_function :h module_function :html_escape From 367c072ac6910e487a7619f61baebe1b4a6cf315 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 4 Nov 2022 17:53:10 +0000 Subject: [PATCH 137/148] [ruby/irb] Silent the noise created when building Context in tests https://github.com/ruby/irb/commit/27e4274b3c --- test/irb/test_ruby_lex.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb index d618b01c31bbf5..2b16e2e9a10b6c 100644 --- a/test/irb/test_ruby_lex.rb +++ b/test/irb/test_ruby_lex.rb @@ -721,6 +721,7 @@ def build_context(local_variables = nil) end end + IRB.conf[:VERBOSE] = false IRB::Context.new(nil, workspace) end end From 6d835901575d58e7db404665801a1c455ee982a8 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 4 Nov 2022 15:36:30 -0400 Subject: [PATCH 138/148] Don't report changed ENV caused by Bundler Bundler's backups changes environment variables starting with BUNDLER_ORIG_. This causes a lot of noise in tests as the leakchecker reports them as changed. --- tool/lib/leakchecker.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tool/lib/leakchecker.rb b/tool/lib/leakchecker.rb index 26d75b92fa87a0..19661455883e1a 100644 --- a/tool/lib/leakchecker.rb +++ b/tool/lib/leakchecker.rb @@ -234,6 +234,9 @@ def check_env(test_name) new_env = find_env return false if old_env == new_env (old_env.keys | new_env.keys).sort.each {|k| + # Don't report changed environment variables caused by Bundler's backups + next if k.start_with?(Bundler::EnvironmentPreserver::BUNDLER_PREFIX) + if old_env.has_key?(k) if new_env.has_key?(k) if old_env[k] != new_env[k] From dd4ae9a475e081f5264c827e8c50b351236a68d2 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Fri, 4 Nov 2022 17:02:04 -0400 Subject: [PATCH 139/148] Auto-enable YJIT build when rustc >= 1.58.0 present (#6662) * Auto-enable YJIT build when rustc >= 1.58.0 present * Try different incantation to have rustc output to stdout only * Add comment, remove whitespace * Try to detect if we are on a platform on which YJIT is supported --- configure.ac | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index 87966fdfcfbfe5..c73b7fb7e1df82 100644 --- a/configure.ac +++ b/configure.ac @@ -3733,10 +3733,40 @@ AS_IF([test x"$MJIT_SUPPORT" = "xyes"], AC_SUBST(MJIT_SUPPORT) +AC_CHECK_TOOL(RUSTC, [rustc], [no]) + +dnl check if we can build YJIT on this target platform +AS_CASE(["$target_cpu-$target_os"], + [arm64-darwin*|aarch64-darwin*|x86_64-darwin*], [ + YJIT_TARGET_OK=yes + ], + [arm64-*linux*|aarch64-*linux*|x86_64-*linux*], [ + YJIT_TARGET_OK=yes + ], + [arm64-*bsd*|aarch64-*bsd*|x86_64-*bsd*], [ + YJIT_TARGET_OK=yes + ], + [YJIT_TARGET_OK=no] +) + +dnl build YJIT in release mode if rustc >= 1.58.0 is present and we are on a supported platform AC_ARG_ENABLE(yjit, - AS_HELP_STRING([--enable-yjit], - [enable experimental in-process JIT compiler that requires Rust build tools [default=no]]), - [YJIT_SUPPORT=$enableval], [YJIT_SUPPORT=no]) + AS_HELP_STRING([--enable-yjit], + [enable experimental in-process JIT compiler that requires Rust build tools [default=no]]), + [YJIT_SUPPORT=$enableval], + [ + AS_IF([test x"$RUSTC" != "xno"], + AS_IF([ echo "fn main() { let x = 1; format!(\"{x}\"); }" | rustc - --emit asm=/dev/null ], + AS_IF([test x"$YJIT_TARGET_OK" != "xno"], + [YJIT_SUPPORT=yes], + [YJIT_SUPPORT=no] + ), + [YJIT_SUPPORT=no] + ), + [YJIT_SUPPORT=no] + ) + ] +) CARGO= CARGO_BUILD_ARGS= @@ -3784,7 +3814,6 @@ AS_CASE(["${YJIT_SUPPORT}"], AC_DEFINE(USE_YJIT, 1) ], [AC_DEFINE(USE_YJIT, 0)]) - dnl These variables end up in ::RbConfig::CONFIG AC_SUBST(YJIT_SUPPORT)dnl what flavor of YJIT the Ruby build includes AC_SUBST(RUSTC)dnl Rust compiler command From f276d5a7fe28ae4fd2934af4befd920bb78cfa9e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 4 Nov 2022 23:24:16 -0700 Subject: [PATCH 140/148] Improve HTML escape benchmarks --- benchmark/cgi_escape_html.yml | 37 +++++++++++++---------------------- benchmark/erb_escape_html.yml | 31 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 benchmark/erb_escape_html.yml diff --git a/benchmark/cgi_escape_html.yml b/benchmark/cgi_escape_html.yml index af6abd08ac7b9c..655be9d7d8fde9 100644 --- a/benchmark/cgi_escape_html.yml +++ b/benchmark/cgi_escape_html.yml @@ -1,32 +1,23 @@ -prelude: require 'cgi/escape' +prelude: | + # frozen_string_literal: true + require 'cgi/escape' benchmark: - - name: escape_html_blank - prelude: str = "" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("") loop_count: 20000000 - - name: escape_html_short_none - prelude: str = "abcde" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("abcde") loop_count: 20000000 - - name: escape_html_short_one - prelude: str = "abcd<" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("abcd<") loop_count: 20000000 - - name: escape_html_short_all - prelude: str = "'&\"<>" - script: CGI.escapeHTML(str) + - script: CGI.escapeHTML("'&\"<>") loop_count: 5000000 - - name: escape_html_long_none - prelude: str = "abcde" * 300 - script: CGI.escapeHTML(str) + - prelude: long_no_escape = "abcde" * 300 + script: CGI.escapeHTML(long_no_escape) loop_count: 1000000 - - name: escape_html_long_all - prelude: str = "'&\"<>" * 10 - script: CGI.escapeHTML(str) + - prelude: long_all_escape = "'&\"<>" * 10 + script: CGI.escapeHTML(long_all_escape) loop_count: 1000000 - - name: escape_html_real - prelude: | # http://example.com/ - str = <<~HTML + - prelude: | # http://example.com/ + example_html = <<~HTML

Example Domain

@@ -36,5 +27,5 @@ benchmark:
HTML - script: CGI.escapeHTML(str) + script: CGI.escapeHTML(example_html) loop_count: 1000000 diff --git a/benchmark/erb_escape_html.yml b/benchmark/erb_escape_html.yml new file mode 100644 index 00000000000000..ca28d756e7ddb6 --- /dev/null +++ b/benchmark/erb_escape_html.yml @@ -0,0 +1,31 @@ +prelude: | + # frozen_string_literal: true + require 'erb' +benchmark: + - script: ERB::Util.html_escape("") + loop_count: 20000000 + - script: ERB::Util.html_escape("abcde") + loop_count: 20000000 + - script: ERB::Util.html_escape("abcd<") + loop_count: 20000000 + - script: ERB::Util.html_escape("'&\"<>") + loop_count: 5000000 + - prelude: long_no_escape = "abcde" * 300 + script: ERB::Util.html_escape(long_no_escape) + loop_count: 1000000 + - prelude: long_all_escape = "'&\"<>" * 10 + script: ERB::Util.html_escape(long_all_escape) + loop_count: 1000000 + - prelude: | # http://example.com/ + example_html = <<~HTML + +
+ + HTML + script: ERB::Util.html_escape(example_html) + loop_count: 1000000 From 419d2fc14d2bedc6d5a7080ee80df8330884ea6c Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 4 Nov 2022 23:58:43 -0700 Subject: [PATCH 141/148] [ruby/erb] Optimize the no-escape case with strpbrk (https://github.com/ruby/erb/pull/29) Typically, strpbrk(3) is optimized pretty well with SIMD instructions. Just using it makes this as fast as a SIMD-based implementation for the no-escape case. Not utilizing this for escaped cases because memory allocation would be a more significant bottleneck for many strings anyway. Also, there'll be some overhead in calling a C function (strpbrk) many times because we're not using SIMD instructions directly. So using strpbrk all the time might not necessarily be faster. --- ext/erb/erb.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ext/erb/erb.c b/ext/erb/erb.c index c90f77f7b12e40..1e4842c793ac3b 100644 --- a/ext/erb/erb.c +++ b/ext/erb/erb.c @@ -38,6 +38,12 @@ escaped_length(VALUE str) static VALUE optimized_escape_html(VALUE str) { + // Optimize the most common, no-escape case with strpbrk(3). Not using it after + // this because calling a C function many times could be slower for some cases. + if (strpbrk(RSTRING_PTR(str), "'&\"<>") == NULL) { + return str; + } + VALUE vbuf; char *buf = ALLOCV_N(char, vbuf, escaped_length(str)); const char *cstr = RSTRING_PTR(str); @@ -56,11 +62,8 @@ optimized_escape_html(VALUE str) } } - VALUE escaped = str; - if (RSTRING_LEN(str) < (dest - buf)) { - escaped = rb_str_new(buf, dest - buf); - preserve_original_state(str, escaped); - } + VALUE escaped = rb_str_new(buf, dest - buf); + preserve_original_state(str, escaped); ALLOCV_END(vbuf); return escaped; } From e8873e01b67629f93ebbd83397f2454e16e0d864 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sat, 5 Nov 2022 00:26:32 -0700 Subject: [PATCH 142/148] [ruby/erb] Use strpbrk only when str is long enough for SIMD This is the same trick used by https://github.com/k0kubun/hescape to choose the best strategy for different scenarios. https://github.com/ruby/erb/commit/af26da2858 --- ext/erb/erb.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ext/erb/erb.c b/ext/erb/erb.c index 1e4842c793ac3b..1c3371d24ede93 100644 --- a/ext/erb/erb.c +++ b/ext/erb/erb.c @@ -38,9 +38,8 @@ escaped_length(VALUE str) static VALUE optimized_escape_html(VALUE str) { - // Optimize the most common, no-escape case with strpbrk(3). Not using it after - // this because calling a C function many times could be slower for some cases. - if (strpbrk(RSTRING_PTR(str), "'&\"<>") == NULL) { + // Use strpbrk to optimize the no-escape case when str is long enough for SIMD. + if (RSTRING_LEN(str) >= 16 && strpbrk(RSTRING_PTR(str), "'&\"<>") == NULL) { return str; } @@ -62,8 +61,11 @@ optimized_escape_html(VALUE str) } } - VALUE escaped = rb_str_new(buf, dest - buf); - preserve_original_state(str, escaped); + VALUE escaped = str; + if (RSTRING_LEN(str) < (dest - buf)) { + escaped = rb_str_new(buf, dest - buf); + preserve_original_state(str, escaped); + } ALLOCV_END(vbuf); return escaped; } From 458d6fb15eedaa142c71f120e3a71b84d0938454 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sat, 5 Nov 2022 00:41:52 -0700 Subject: [PATCH 143/148] [ruby/erb] Optimize away the rb_convert_type call using RB_TYPE_P https://github.com/ruby/erb/commit/12058c3784 --- ext/erb/erb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/erb/erb.c b/ext/erb/erb.c index 1c3371d24ede93..a103fb30e4b6f4 100644 --- a/ext/erb/erb.c +++ b/ext/erb/erb.c @@ -76,7 +76,9 @@ optimized_escape_html(VALUE str) static VALUE erb_escape_html(VALUE self, VALUE str) { - str = rb_convert_type(str, T_STRING, "String", "to_s"); + if (!RB_TYPE_P(str, T_STRING)) { + str = rb_convert_type(str, T_STRING, "String", "to_s"); + } if (rb_enc_str_asciicompat_p(str)) { return optimized_escape_html(str); From 9af344a42129ea193d6dba92d62458c5f3429507 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sat, 5 Nov 2022 01:30:41 -0700 Subject: [PATCH 144/148] [ruby/erb] Revert the strpbrk optimization because it's much slower on M1 https://github.com/ruby/erb/pull/29. It'd be too complicated to switch the implementation based on known optimized platforms / versions. Besides, short strings are the most common usages of this method and SIMD doesn't really help that case. All in all, I can't justify the existence of this code. https://github.com/ruby/erb/commit/30691c8995 --- ext/erb/erb.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ext/erb/erb.c b/ext/erb/erb.c index a103fb30e4b6f4..b72c295a816e91 100644 --- a/ext/erb/erb.c +++ b/ext/erb/erb.c @@ -38,11 +38,6 @@ escaped_length(VALUE str) static VALUE optimized_escape_html(VALUE str) { - // Use strpbrk to optimize the no-escape case when str is long enough for SIMD. - if (RSTRING_LEN(str) >= 16 && strpbrk(RSTRING_PTR(str), "'&\"<>") == NULL) { - return str; - } - VALUE vbuf; char *buf = ALLOCV_N(char, vbuf, escaped_length(str)); const char *cstr = RSTRING_PTR(str); From 267452e6fed5d31dcad3c20970f74eec009dc03f Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Sat, 5 Nov 2022 23:18:32 +0900 Subject: [PATCH 145/148] Sync TRICK 2018 (02-mame) https://github.com/tric/trick2018/pull/5 --- sample/trick2018/02-mame/entry.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sample/trick2018/02-mame/entry.rb b/sample/trick2018/02-mame/entry.rb index cc4ef9cbc4e306..ced791aa3d1414 100644 --- a/sample/trick2018/02-mame/entry.rb +++ b/sample/trick2018/02-mame/entry.rb @@ -1,11 +1,11 @@ '';eval(r=%q(->z{r="'';eval(r=\ -%q(#{r}))[%q`#{z}`]";i=-040;30. +%q(#{r}))[%q`#{z}`]";i=-040;31. times{|n|(15+n%2*15-n/2).times{ r<["t]];};o[1,?\n*8];ex"-}eac 1Hl<1[-1]*2*t=n%2];o[14-n,0)mvk 8M$<4,?\n];15.times{|n|;o[35ie2 From 3a6cdeda89280ade714f158830acee88fb36306d Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 5 Nov 2022 16:10:35 +0000 Subject: [PATCH 146/148] [wasm] Scan machine stack based on `ec->machine.stack_{start,end}` fiber machine stack is placed outside of C stack allocated by wasm-ld, so highest stack address recorded by `rb_wasm_record_stack_base` is invalid when running on non-main fiber. Therefore, we should scan `stack_{start,end}` which always point a valid stack range in any context. --- gc.c | 6 ++++-- thread_none.c | 7 +++++++ wasm/machine.c | 6 +++--- wasm/machine.h | 5 ++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/gc.c b/gc.c index f2bccbca16091a..4adf86bf77e1af 100644 --- a/gc.c +++ b/gc.c @@ -6756,8 +6756,10 @@ mark_current_machine_context(rb_objspace_t *objspace, rb_execution_context_t *ec static void mark_current_machine_context(rb_objspace_t *objspace, rb_execution_context_t *ec) { - rb_wasm_scan_stack(rb_mark_locations); - each_stack_location(objspace, ec, rb_stack_range_tmp[0], rb_stack_range_tmp[1], gc_mark_maybe); + VALUE *stack_start, *stack_end; + SET_STACK_END; + GET_STACK_BOUNDS(stack_start, stack_end, 1); + each_stack_location(objspace, ec, stack_start, stack_end, gc_mark_maybe); rb_wasm_scan_locals(rb_mark_locations); each_stack_location(objspace, ec, rb_stack_range_tmp[0], rb_stack_range_tmp[1], gc_mark_maybe); diff --git a/thread_none.c b/thread_none.c index cf4658e5712751..a82ced684e6897 100644 --- a/thread_none.c +++ b/thread_none.c @@ -15,6 +15,10 @@ #include +#if defined(__wasm__) && !defined(__EMSCRIPTEN__) +# include "wasm/machine.h" +#endif + #define TIME_QUANTUM_MSEC (100) #define TIME_QUANTUM_USEC (TIME_QUANTUM_MSEC * 1000) #define TIME_QUANTUM_NSEC (TIME_QUANTUM_USEC * 1000) @@ -143,6 +147,9 @@ ruby_init_stack(volatile VALUE *addr) static int native_thread_init_stack(rb_thread_t *th) { +#if defined(__wasm__) && !defined(__EMSCRIPTEN__) + th->ec->machine.stack_start = (VALUE *)rb_wasm_stack_get_base(); +#endif return 0; // success } diff --git a/wasm/machine.c b/wasm/machine.c index 238041f93e3c2b..2ca84625029a05 100644 --- a/wasm/machine.c +++ b/wasm/machine.c @@ -49,10 +49,10 @@ rb_wasm_record_stack_base(void) return 0; } -void -_rb_wasm_scan_stack(rb_wasm_scan_func scan, void *current) +void * +rb_wasm_stack_get_base(void) { - scan(current, rb_wasm_stack_base); + return rb_wasm_stack_base; } void * diff --git a/wasm/machine.h b/wasm/machine.h index 4cf7228684cc2f..1a60e51d11efe0 100644 --- a/wasm/machine.h +++ b/wasm/machine.h @@ -8,9 +8,8 @@ typedef void (*rb_wasm_scan_func)(void*, void*); // Used by conservative GC void rb_wasm_scan_locals(rb_wasm_scan_func scan); -// Scan userland C-stack memory space in WebAssembly. Used by conservative GC -#define rb_wasm_scan_stack(scan) _rb_wasm_scan_stack((scan), rb_wasm_get_stack_pointer()) -void _rb_wasm_scan_stack(rb_wasm_scan_func scan, void *current); +// Get base address of userland C-stack memory space in WebAssembly. Used by conservative GC +void *rb_wasm_stack_get_base(void); // Get the current stack pointer From 10fd1d9507a87bc9cdb1592cdb10c7890e52eb30 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 5 Nov 2022 12:48:11 +0900 Subject: [PATCH 147/148] Should use the configured rustc consistently --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index c73b7fb7e1df82..7b0c6014b298de 100644 --- a/configure.ac +++ b/configure.ac @@ -3756,7 +3756,7 @@ AC_ARG_ENABLE(yjit, [YJIT_SUPPORT=$enableval], [ AS_IF([test x"$RUSTC" != "xno"], - AS_IF([ echo "fn main() { let x = 1; format!(\"{x}\"); }" | rustc - --emit asm=/dev/null ], + AS_IF([ echo "fn main() { let x = 1; format!(\"{x}\"); }" | $RUSTC - --emit asm=/dev/null ], AS_IF([test x"$YJIT_TARGET_OK" != "xno"], [YJIT_SUPPORT=yes], [YJIT_SUPPORT=no] From 1454f8f219890b8134f68e868d8cb1d0a9d2aa20 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 5 Nov 2022 22:19:21 +0900 Subject: [PATCH 148/148] Add `--target` option to RUSTC when cross-compiling --- common.mk | 2 ++ configure.ac | 9 +++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/common.mk b/common.mk index 781c323d95e625..aea4d02c5fa1d0 100644 --- a/common.mk +++ b/common.mk @@ -303,6 +303,8 @@ showflags: " LC_ALL = $(LC_ALL)" \ " LC_CTYPE = $(LC_CTYPE)" \ " MFLAGS = $(MFLAGS)" \ + " RUST = $(RUST)" \ + " YJIT_RUSTC_ARGS = $(YJIT_RUSTC_ARGS)" \ $(MESSAGE_END) -@$(CC_VERSION) diff --git a/configure.ac b/configure.ac index 7b0c6014b298de..9e2ba81a5101f9 100644 --- a/configure.ac +++ b/configure.ac @@ -3733,7 +3733,7 @@ AS_IF([test x"$MJIT_SUPPORT" = "xyes"], AC_SUBST(MJIT_SUPPORT) -AC_CHECK_TOOL(RUSTC, [rustc], [no]) +AC_CHECK_PROG(RUSTC, [rustc], [rustc], [no]) dnl no ac_tool_prefix dnl check if we can build YJIT on this target platform AS_CASE(["$target_cpu-$target_os"], @@ -3757,10 +3757,7 @@ AC_ARG_ENABLE(yjit, [ AS_IF([test x"$RUSTC" != "xno"], AS_IF([ echo "fn main() { let x = 1; format!(\"{x}\"); }" | $RUSTC - --emit asm=/dev/null ], - AS_IF([test x"$YJIT_TARGET_OK" != "xno"], - [YJIT_SUPPORT=yes], - [YJIT_SUPPORT=no] - ), + [YJIT_SUPPORT="$YJIT_TARGET_OK"], [YJIT_SUPPORT=no] ), [YJIT_SUPPORT=no] @@ -3776,10 +3773,10 @@ AS_CASE(["${YJIT_SUPPORT}"], AS_IF([test x"$enable_jit_support" = "xno"], AC_MSG_ERROR([--disable-jit-support but --enable-yjit. YJIT requires JIT support]) ) - AC_CHECK_TOOL(RUSTC, [rustc], [no]) AS_IF([test x"$RUSTC" = "xno"], AC_MSG_ERROR([rustc is required. Installation instructions available at https://www.rust-lang.org/tools/install]) ) + AS_IF([test "$cross_compiling" = yes], [RUSTC="$RUSTC --target=$target"]) AS_CASE(["${YJIT_SUPPORT}"], [yes], [