From 8c7f4db893af4c9e40eab5810dc58737dc9010bb Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 3 Mar 2026 08:16:42 -0500 Subject: [PATCH 01/10] [ruby/prism] Fix modifier rescue pattern matching [Bug #21713] https://github.com/ruby/prism/commit/02c944c055 --- prism/prism.c | 13 +++++++++++++ ..._predicate_after_rescue_with_dot_method_call.txt | 1 + .../match_predicate_after_rescue_with_opreator.txt | 1 + ...h_required_after_rescue_with_dot_method_call.txt | 1 + .../match_required_after_rescue_with_opreator.txt | 1 + test/prism/errors/rescue_pattern.txt | 4 ++++ 6 files changed, 21 insertions(+) create mode 100644 test/prism/errors/rescue_pattern.txt diff --git a/prism/prism.c b/prism/prism.c index c644c947538c4e..ebd86b01fe089b 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -21740,6 +21740,19 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc return node; } break; + case PM_RESCUE_MODIFIER_NODE: + // A rescue modifier whose handler is a one-liner pattern match + // (=> or in) produces a statement. That means it cannot be + // extended by operators above the modifier level. + if (pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_MODIFIER) { + pm_rescue_modifier_node_t *cast = (pm_rescue_modifier_node_t *) node; + pm_node_t *rescue_expression = cast->rescue_expression; + + if (PM_NODE_TYPE_P(rescue_expression, PM_MATCH_REQUIRED_NODE) || PM_NODE_TYPE_P(rescue_expression, PM_MATCH_PREDICATE_NODE)) { + return node; + } + } + break; default: break; } diff --git a/test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt b/test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt index fead8aaf234069..f599dc476bd607 100644 --- a/test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt +++ b/test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt @@ -1,3 +1,4 @@ 'a' rescue 2 in 3.upcase ^ unexpected '.', expecting end-of-input + ^ unexpected '.', ignoring it diff --git a/test/prism/errors/match_predicate_after_rescue_with_opreator.txt b/test/prism/errors/match_predicate_after_rescue_with_opreator.txt index b2363a544d0c75..44a4ba848823a9 100644 --- a/test/prism/errors/match_predicate_after_rescue_with_opreator.txt +++ b/test/prism/errors/match_predicate_after_rescue_with_opreator.txt @@ -1,3 +1,4 @@ 1 rescue 2 in 3 << 4 ^~ unexpected <<, expecting end-of-input + ^~ unexpected <<, ignoring it diff --git a/test/prism/errors/match_required_after_rescue_with_dot_method_call.txt b/test/prism/errors/match_required_after_rescue_with_dot_method_call.txt index d72d72ce606d48..abcfaf094d30b2 100644 --- a/test/prism/errors/match_required_after_rescue_with_dot_method_call.txt +++ b/test/prism/errors/match_required_after_rescue_with_dot_method_call.txt @@ -1,3 +1,4 @@ 1 rescue 2 => 3.inspect ^ unexpected '.', expecting end-of-input + ^ unexpected '.', ignoring it diff --git a/test/prism/errors/match_required_after_rescue_with_opreator.txt b/test/prism/errors/match_required_after_rescue_with_opreator.txt index 903e2ccc8e6dc4..5e6387ca4d33ef 100644 --- a/test/prism/errors/match_required_after_rescue_with_opreator.txt +++ b/test/prism/errors/match_required_after_rescue_with_opreator.txt @@ -1,3 +1,4 @@ 1 rescue 2 => 3 ** 4 ^~ unexpected '**', expecting end-of-input + ^~ unexpected '**', ignoring it diff --git a/test/prism/errors/rescue_pattern.txt b/test/prism/errors/rescue_pattern.txt new file mode 100644 index 00000000000000..c85feb27bdf11b --- /dev/null +++ b/test/prism/errors/rescue_pattern.txt @@ -0,0 +1,4 @@ +a rescue b => c in d + ^~ unexpected 'in', expecting end-of-input + ^~ unexpected 'in', ignoring it + From c128106e60f1a65243b3782a0f14a7a644b7ba6f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 3 Mar 2026 21:24:12 +0900 Subject: [PATCH 02/10] Adjust variable type for rb_block_given_p() --- hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hash.c b/hash.c index c09c7b26b5414a..773df7e78d8c7f 100644 --- a/hash.c +++ b/hash.c @@ -5332,7 +5332,7 @@ static VALUE env_fetch(int argc, VALUE *argv, VALUE _) { VALUE key; - long block_given; + int block_given; const char *nam; VALUE env; From 7a3940e8b60a3f0b62db05210947ab841a029ca3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 3 Mar 2026 21:25:06 +0900 Subject: [PATCH 03/10] Unify rb_node_list_new and rb_node_list_new2 The former is the specialized case of the latter. --- parse.y | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/parse.y b/parse.y index ab301d6034acf4..6813b396332b00 100644 --- a/parse.y +++ b/parse.y @@ -11708,16 +11708,10 @@ rb_node_match3_new(struct parser_params *p, NODE *nd_recv, NODE *nd_value, const return n; } -/* TODO: Use union for NODE_LIST2 */ static rb_node_list_t * rb_node_list_new(struct parser_params *p, NODE *nd_head, const YYLTYPE *loc) { - rb_node_list_t *n = NODE_NEWNODE(NODE_LIST, rb_node_list_t, loc); - n->nd_head = nd_head; - n->as.nd_alen = 1; - n->nd_next = 0; - - return n; + return rb_node_list_new2(p, nd_head, 1, 0, loc); } static rb_node_list_t * From e30b5a06dc0a43c35ca9545c661784dcdb5c6806 Mon Sep 17 00:00:00 2001 From: Jeff Zhang Date: Tue, 3 Mar 2026 11:19:28 -0500 Subject: [PATCH 04/10] ZJIT: Add diff tool for running benchmarks between git refs (#16160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For https://github.com/Shopify/ruby/issues/822 Adds a small script for automatically running benchmarks between two git refs. Example output (doubled an increment to `inline_cfunc_optimized_send_count` to illustrate stat diffs): ``` $ ./zjit_diff --before master I, [2026-03-03T10:24:52.691660 #48554] INFO -- : Existing worktree found at /var/folders/l_/p20zlp5j3wx76vr9ggzgzcmr0000gn/T/ruby-zjit-before HEAD is now at 7a3940e8b6 Unify rb_node_list_new and rb_node_list_new2 I, [2026-03-03T10:24:52.729815 #48554] INFO -- : Found existing build for master, skipping build I, [2026-03-03T10:24:52.729881 #48554] INFO -- : Existing worktree found at /var/folders/l_/p20zlp5j3wx76vr9ggzgzcmr0000gn/T/ruby-zjit-after HEAD is now at 3859f12966 dup I, [2026-03-03T10:24:52.801287 #48554] INFO -- : Found existing build for HEAD, skipping build I, [2026-03-03T10:24:52.801354 #48554] INFO -- : Running benchmarks I, [2026-03-03T10:24:52.801404 #48554] INFO -- : ruby-bench already cloned, pulling from upstream <... benchmark run logs ...> before: ruby 4.1.0dev (2026-03-03T14:05:00Z master 7a3940e8b6) +ZJIT dev +PRISM [arm64-darwin25] last_commit=Unify rb_node_list_new and rb_node_list_new2 after: ruby 4.1.0dev (2026-03-03T15:17:20Z :detached: 3859f12966) +ZJIT dev +PRISM [arm64-darwin25] ---------- ------------- ------------- ------------- ------------ bench before (ms) after (ms) after 1st itr before/after lobsters 747.9 ± 2.9% 757.7 ± 2.4% 0.983 0.987 railsbench 1183.4 ± 1.9% 1214.5 ± 2.4% 1.001 0.974 (*) ---------- ------------- ------------- ------------- ------------ Legend: - after 1st itr: ratio of before/after time for the first benchmarking iteration. - before/after: ratio of before/after time. Higher is better for after. Above 1 represents a speedup. - ***: p < 0.001, **: p < 0.01, *: p < 0.05 (Welch's t-test) Output: data/zjit_diff.json ================================================================================ ZJIT Stats Comparison ================================================================================ before (baseline): ruby 4.1.0dev (2026-03-03T14:05:00Z master 7a3940e8b6) +ZJIT dev +PRISM [arm64-darwin25] last_commit=Unify rb_node_list_new and rb_node_list_new2 after: ruby 4.1.0dev (2026-03-03T15:17:20Z :detached: 3859f12966) +ZJIT dev +PRISM [arm64-darwin25] BENCHMARK TIMINGS (lower is better) -------------------------------------------------------------------------------- lobsters: before avg: 0.748s min: 0.706s ★ (baseline) after avg: 0.758s min: 0.735s +1.3% (slower) railsbench: before avg: 1.183s min: 1.158s ★ (baseline) after avg: 1.214s min: 1.182s +2.6% (slower) MEMORY USAGE -------------------------------------------------------------------------------- lobsters: before maxrss: 548.3MB zjit_mem: 68.6MB after maxrss: 520.8MB zjit_mem: 68.3MB railsbench: before maxrss: 208.3MB zjit_mem: 29.3MB after maxrss: 205.7MB zjit_mem: 29.3MB SEND COUNTERS (showing differences > 5.0%) -------------------------------------------------------------------------------- lobsters: inline_cfunc_optimized_send_count 64,066,387 ( 37.3%) → 105,498,619 ( 61.4%) ▲ +64.7% optimized_send_count 136,557,743 ( 79.5%) → 177,989,922 (103.6%) ▲ +30.3% send_count 171,791,578 (100.0%) → 213,223,697 (124.1%) ▲ +24.1% railsbench: inline_cfunc_optimized_send_count 130,638,873 ( 36.3%) → 225,643,322 ( 62.8%) ▲ +72.7% optimized_send_count 306,690,230 ( 85.3%) → 401,694,689 (111.8%) ▲ +31.0% send_count 359,393,477 (100.0%) → 454,397,936 (126.4%) ▲ +26.4% SUMMARY COUNTERS (showing differences > 5.0%) -------------------------------------------------------------------------------- railsbench: invalidation_time 7ms → 7ms ▲ +5.3% COMPILE HIR PASS TIMINGS (showing differences > 5.0%) -------------------------------------------------------------------------------- lobsters: eliminate_dead_code_time 245ms → 232ms ▼ -5.3% ``` --- tool/zjit_diff.rb | 272 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100755 tool/zjit_diff.rb diff --git a/tool/zjit_diff.rb b/tool/zjit_diff.rb new file mode 100755 index 00000000000000..4f8f74d20f6225 --- /dev/null +++ b/tool/zjit_diff.rb @@ -0,0 +1,272 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'fileutils' +require 'optparse' +require 'tmpdir' +require 'logger' +require 'digest' +require 'shellwords' + +GitRef = Struct.new(:ref, :commit_hash) + +RUBIES_DIR = File.join(Dir.home, '.zjit-diff') +BEFORE_NAME = 'ruby-zjit-before' +AFTER_NAME = 'ruby-zjit-after' + +LOG = Logger.new($stderr) + +def macos? + Gem::Platform.local == 'darwin' +end + +class CommandRunner + def initialize(quiet: false) + @quiet = quiet + end + + def cmd(*args, **options) + options[:out] ||= @quiet ? File::NULL : $stderr + options = options.merge(exception: true) + system(*args, **options) + end +end + +class ZJITDiff + DATA_FILENAME = File.join('data', 'zjit_diff') + RUBY_BENCH_REPO_URL = 'https://github.com/ruby/ruby-bench.git' + + def initialize(before_hash:, after_hash:, runner:, options:) + @before_hash = before_hash + @after_hash = after_hash + @runner = runner + @options = options + end + + def bench! + LOG.info('Running benchmarks') + ruby_bench_path = @options[:bench_path] || setup_ruby_bench + run_benchmarks(ruby_bench_path) + end + + private + + def run_benchmarks(ruby_bench_path) + Dir.chdir(ruby_bench_path) do + @runner.cmd({ 'RUBIES_DIR' => RUBIES_DIR }, + './run_benchmarks.rb', + '--chruby', + "before::#{@before_hash} --zjit-stats;after::#{@after_hash} --zjit-stats", + '--out-name', + DATA_FILENAME, + *@options[:bench_args], + *@options[:name_filters]) + + @runner.cmd('./misc/zjit_diff.rb', "#{DATA_FILENAME}.json", out: $stdout) + end + end + + def setup_ruby_bench + path = File.join(Dir.tmpdir, 'ruby-bench') + if Dir.exist?(path) + LOG.info('ruby-bench already cloned, pulling from upstream') + Dir.chdir(path) do + @runner.cmd('git', 'pull') + end + else + LOG.info("ruby-bench not cloned yet, cloning repository to #{path}") + @runner.cmd('git', 'clone', RUBY_BENCH_REPO_URL, path) + end + path + end +end + +class RubyWorktree + attr_reader :hash + + BREW_REQUIRED_PACKAGES = %w[openssl readline libyaml].freeze + + def initialize(name:, ref:, runner:, force_rebuild: false) + @path = File.join(Dir.tmpdir, name) + @ref = ref + @force_rebuild = force_rebuild + @runner = runner + @hash = nil + + setup_worktree + end + + def build! + Dir.chdir(@path) do + configure_cmd_args = ['--enable-zjit=dev', '--disable-install-doc'] + if macos? + brew_prefixes = BREW_REQUIRED_PACKAGES.map do |pkg| + `brew --prefix #{pkg}`.strip + end + configure_cmd_args << "--with-opt-dir=#{brew_prefixes.join(':')}" + end + configure_cmd_hash = Digest::MD5.hexdigest(configure_cmd_args.join('')) + + build_cmd_args = ['-j', 'miniruby'] + build_cmd_hash = Digest::MD5.hexdigest(build_cmd_args.join('')) + + @hash = "#{configure_cmd_hash}-#{build_cmd_hash}-#{@ref.commit_hash}" + prefix = File.join(RUBIES_DIR, @hash) + + if Dir.exist?(prefix) && !@force_rebuild + LOG.info("Found existing build for #{@ref.ref}, skipping build") + return + end + + @runner.cmd('./autogen.sh') + + cmd = [ + './configure', + *configure_cmd_args, + "--prefix=#{prefix}" + ] + + @runner.cmd(*cmd) + @runner.cmd('make', *build_cmd_args) + @runner.cmd('make', 'install') + end + end + + private + + def setup_worktree + if Dir.exist?(@path) + LOG.info("Existing worktree found at #{@path}") + Dir.chdir(@path) do + @runner.cmd('git', 'checkout', @ref.commit_hash) + end + else + LOG.info("Creating worktree for ref '#{@ref.ref}' at #{@path}") + @runner.cmd('git', 'worktree', 'add', '--detach', @path, @ref.commit_hash) + end + end +end + +def clean! + [BEFORE_NAME, AFTER_NAME].each do |name| + path = File.join(Dir.tmpdir, name) + if Dir.exist?(path) + LOG.info("Removing worktree at #{path}") + system('git', 'worktree', 'remove', '--force', path) + end + end + + if Dir.exist?(RUBIES_DIR) + LOG.info("Removing ruby installations from #{RUBIES_DIR}") + FileUtils.rm_rf(RUBIES_DIR) + end + + bench_path = File.join(Dir.tmpdir, 'ruby-bench') + return unless Dir.exist?(bench_path) + + LOG.info("Removing ruby-bench clone at #{bench_path}") + FileUtils.rm_rf(bench_path) +end + +def parse_ref(ref) + out = `git rev-parse --verify #{ref}` + return nil unless $?.success? + + GitRef.new(ref: ref, commit_hash: out.strip) +end + +DEFAULT_BENCHMARKS = %w[lobsters railsbench].freeze + +options = {} + +subtext = <<~HELP + Subcommands: + bench : Run benchmarks + clean : Clean temporary files created by benchmarks + See '#{$PROGRAM_NAME} COMMAND --help' for more information on a specific command. +HELP + +top_level = OptionParser.new do |opts| + opts.banner = "Usage: #{$PROGRAM_NAME} [options]" + opts.separator('') + opts.separator(subtext) +end + +subcommands = { + 'bench' => OptionParser.new do |opts| + opts.banner = "Usage: #{$PROGRAM_NAME} [options] " + + opts.on('--before REF', 'Git ref for ruby (before)') do |ref| + git_ref = parse_ref ref + if git_ref.nil? + warn "Error: '#{ref}' is not a valid git ref" + exit 1 + end + + options[:before] = git_ref + end + + opts.on('--after REF', 'Git ref for ruby (after)') do |ref| + git_ref = parse_ref ref + if git_ref.nil? + warn "Error: '#{ref}' is not a valid git ref" + exit 1 + end + + options[:after] = git_ref + end + + opts.on('--bench-path PATH', + 'Path to an existing ruby-bench repository clone ' \ + '(if not specified, ruby-bench will be cloned automatically to a temporary directory)') do |path| + options[:bench_path] = path + end + + opts.on('--bench-args ARGS', 'Args to pass to ruby-bench') do |bench_args| + options[:bench_args] = bench_args.shellsplit + end + + opts.on('--force-rebuild', + 'Force building ruby again instead of using even if existing builds exist in the cache at ~/.diffs') do + options[:force_rebuild] = true + end + + opts.on('--quiet', 'Silence output of commands except for benchmark result') do + options[:quiet] = true + end + + opts.separator('') + opts.separator('If no benchmarks are specified, the benchmarks that will be run are:') + opts.separator(DEFAULT_BENCHMARKS.join(', ')) + end, + 'clean' => OptionParser.new do |opts| + end +} + +top_level.order! +command = ARGV.shift +subcommands[command].order! + +case command +when 'bench' + options[:name_filters] = ARGV.empty? ? DEFAULT_BENCHMARKS : ARGV + options[:after] ||= parse_ref('HEAD') + + runner = CommandRunner.new(quiet: options[:quiet]) + + before = RubyWorktree.new(name: BEFORE_NAME, + ref: options[:before], + runner: runner, + force_rebuild: options[:force_rebuild]) + before.build! + after = RubyWorktree.new(name: AFTER_NAME, + ref: options[:after], + runner: runner, + force_rebuild: options[:force_rebuild]) + after.build! + + zjit_diff = ZJITDiff.new(runner: runner, before_hash: before.hash, after_hash: after.hash, options: options) + zjit_diff.bench! +when 'clean' + clean! +end From b2ff380d5ba0c966b3bfcd2fd05afd0934bfd84a Mon Sep 17 00:00:00 2001 From: ndossche Date: Tue, 24 Feb 2026 23:12:19 +0100 Subject: [PATCH 05/10] [ruby/openssl] Fix memory leak if ossl_bn_new() fails When that call fails, the `bn` BIGNUM is never freed in asn1integer_to_num(). To solve this, use rb_protect(). Example Valgrind report: ``` 32 (24 direct, 8 indirect) bytes in 1 blocks are definitely lost in loss record 11,113 of 25,910 malloc (at /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) CRYPTO_zalloc (at /usr/lib/x86_64-linux-gnu/libcrypto.so.3) BN_new (at /usr/lib/x86_64-linux-gnu/libcrypto.so.3) BN_bin2bn (at /usr/lib/x86_64-linux-gnu/libcrypto.so.3) *asn1integer_to_num (ossl_asn1.c:136) *asn1integer_to_num_i (ossl_asn1.c:165) rb_protect (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) *decode_int (ossl_asn1.c:356) *int_ossl_asn1_decode0_prim (ossl_asn1.c:777) *ossl_asn1_decode0 (ossl_asn1.c:936) *ossl_asn1_decode_all (ossl_asn1.c:1058) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_catch_obj (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_yield (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_ary_each (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_yield (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_ary_each (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_yield (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_ary_each (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_catch_obj (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_invoke_proc (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_proc_call_kw (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) ``` https://github.com/ruby/openssl/commit/309c55d755 --- ext/openssl/ossl_asn1.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 71a87f04636c98..18fa8edeb5777e 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -130,15 +130,17 @@ asn1integer_to_num(const ASN1_INTEGER *ai) if (!ai) { ossl_raise(rb_eTypeError, "ASN1_INTEGER is NULL!"); } + + num = ossl_bn_new(BN_value_one()); + bn = GetBNPtr(num); + if (ASN1_STRING_type(ai) == V_ASN1_ENUMERATED) - bn = ASN1_ENUMERATED_to_BN(ai, NULL); + bn = ASN1_ENUMERATED_to_BN(ai, bn); else - bn = ASN1_INTEGER_to_BN(ai, NULL); + bn = ASN1_INTEGER_to_BN(ai, bn); if (!bn) ossl_raise(eOSSLError, NULL); - num = ossl_bn_new(bn); - BN_free(bn); return num; } From f46eb48b261a9289a463a29fbceaecf37ee860dd Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Tue, 3 Mar 2026 14:28:03 +0000 Subject: [PATCH 06/10] Expand the Shape heap index mask I think we have spare bits here, so let's expand them out and see what happens. This will allow us to have more than 7 size pools in the future --- shape.h | 20 ++++++++++---------- yjit/src/cruby_bindings.inc.rs | 2 +- zjit/src/cruby_bindings.inc.rs | 12 ++++++------ 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/shape.h b/shape.h index 96c78f2bc1a356..ad9b148247b32e 100644 --- a/shape.h +++ b/shape.h @@ -14,33 +14,33 @@ STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_ #define SHAPE_BUFFER_SIZE (1 << SHAPE_ID_OFFSET_NUM_BITS) #define SHAPE_ID_OFFSET_MASK (SHAPE_BUFFER_SIZE - 1) -#define SHAPE_ID_HEAP_INDEX_BITS 3 +#define SHAPE_ID_HEAP_INDEX_BITS 5 #define SHAPE_ID_HEAP_INDEX_MAX ((1 << SHAPE_ID_HEAP_INDEX_BITS) - 1) +#define SHAPE_ID_HEAP_INDEX_OFFSET SHAPE_ID_OFFSET_NUM_BITS #define SHAPE_ID_FL_USHIFT (SHAPE_ID_OFFSET_NUM_BITS + SHAPE_ID_HEAP_INDEX_BITS) -#define SHAPE_ID_HEAP_INDEX_OFFSET SHAPE_ID_FL_USHIFT // shape_id_t bits: // 0-18 SHAPE_ID_OFFSET_MASK // index in rb_shape_tree.shape_list. Allow to access `rb_shape_t *`. -// 19-21 SHAPE_ID_HEAP_INDEX_MASK +// 19-23 SHAPE_ID_HEAP_INDEX_MASK // index in rb_shape_tree.capacities. Allow to access slot size. // Always 0 except for T_OBJECT. -// 22 SHAPE_ID_FL_FROZEN +// 24 SHAPE_ID_FL_FROZEN // Whether the object is frozen or not. -// 23 SHAPE_ID_FL_HAS_OBJECT_ID +// 25 SHAPE_ID_FL_HAS_OBJECT_ID // Whether the object has an `SHAPE_OBJ_ID` transition. -// 24 SHAPE_ID_FL_TOO_COMPLEX +// 26 SHAPE_ID_FL_TOO_COMPLEX // The object is backed by a `st_table`. enum shape_id_fl_type { #define RBIMPL_SHAPE_ID_FL(n) (1<<(SHAPE_ID_FL_USHIFT+n)) - SHAPE_ID_HEAP_INDEX_MASK = RBIMPL_SHAPE_ID_FL(0) | RBIMPL_SHAPE_ID_FL(1) | RBIMPL_SHAPE_ID_FL(2), + SHAPE_ID_HEAP_INDEX_MASK = ((1 << SHAPE_ID_HEAP_INDEX_BITS) - 1) << SHAPE_ID_HEAP_INDEX_OFFSET, - SHAPE_ID_FL_FROZEN = RBIMPL_SHAPE_ID_FL(3), - SHAPE_ID_FL_HAS_OBJECT_ID = RBIMPL_SHAPE_ID_FL(4), - SHAPE_ID_FL_TOO_COMPLEX = RBIMPL_SHAPE_ID_FL(5), + SHAPE_ID_FL_FROZEN = RBIMPL_SHAPE_ID_FL(0), + SHAPE_ID_FL_HAS_OBJECT_ID = RBIMPL_SHAPE_ID_FL(1), + SHAPE_ID_FL_TOO_COMPLEX = RBIMPL_SHAPE_ID_FL(2), SHAPE_ID_FL_NON_CANONICAL_MASK = SHAPE_ID_FL_FROZEN | SHAPE_ID_FL_HAS_OBJECT_ID, SHAPE_ID_FLAGS_MASK = SHAPE_ID_HEAP_INDEX_MASK | SHAPE_ID_FL_NON_CANONICAL_MASK | SHAPE_ID_FL_TOO_COMPLEX, diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 88919ac8748f5d..f0371c65908c54 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -635,7 +635,7 @@ pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16; pub type vm_frame_env_flags = u32; pub type attr_index_t = u16; pub type shape_id_t = u32; -pub const SHAPE_ID_HAS_IVAR_MASK: shape_id_mask = 134742014; +pub const SHAPE_ID_HAS_IVAR_MASK: shape_id_mask = 67633150; pub type shape_id_mask = u32; #[repr(C)] pub struct rb_cvar_class_tbl_entry { diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 5bac9fdf19d1c7..35e714bd73cd72 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1464,12 +1464,12 @@ pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16; pub type vm_frame_env_flags = u32; pub type attr_index_t = u16; pub type shape_id_t = u32; -pub const SHAPE_ID_HEAP_INDEX_MASK: shape_id_fl_type = 29360128; -pub const SHAPE_ID_FL_FROZEN: shape_id_fl_type = 33554432; -pub const SHAPE_ID_FL_HAS_OBJECT_ID: shape_id_fl_type = 67108864; -pub const SHAPE_ID_FL_TOO_COMPLEX: shape_id_fl_type = 134217728; -pub const SHAPE_ID_FL_NON_CANONICAL_MASK: shape_id_fl_type = 100663296; -pub const SHAPE_ID_FLAGS_MASK: shape_id_fl_type = 264241152; +pub const SHAPE_ID_HEAP_INDEX_MASK: shape_id_fl_type = 16252928; +pub const SHAPE_ID_FL_FROZEN: shape_id_fl_type = 16777216; +pub const SHAPE_ID_FL_HAS_OBJECT_ID: shape_id_fl_type = 33554432; +pub const SHAPE_ID_FL_TOO_COMPLEX: shape_id_fl_type = 67108864; +pub const SHAPE_ID_FL_NON_CANONICAL_MASK: shape_id_fl_type = 50331648; +pub const SHAPE_ID_FLAGS_MASK: shape_id_fl_type = 133693440; pub type shape_id_fl_type = u32; #[repr(C)] pub struct rb_cvar_class_tbl_entry { From 6b6ceb97db47b7f7e6a91c21b8f5147ba5f751be Mon Sep 17 00:00:00 2001 From: Andrew Konchin Date: Tue, 3 Mar 2026 15:27:53 +0200 Subject: [PATCH 07/10] Update to ruby/mspec@a2587d9 --- spec/mspec/lib/mspec/commands/mspec-tag.rb | 1 + spec/mspec/lib/mspec/runner/formatters/base.rb | 2 +- spec/mspec/lib/mspec/runner/mspec.rb | 1 + spec/mspec/tool/remove_old_guards.rb | 9 ++++++++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/spec/mspec/lib/mspec/commands/mspec-tag.rb b/spec/mspec/lib/mspec/commands/mspec-tag.rb index 8b1cb8380949cd..9ce9f048c63b6d 100644 --- a/spec/mspec/lib/mspec/commands/mspec-tag.rb +++ b/spec/mspec/lib/mspec/commands/mspec-tag.rb @@ -112,6 +112,7 @@ def register MSpec.register_mode :pretend MSpec.register_mode :unguarded config[:formatter] = false + config[:xtags] = [] else raise ArgumentError, "No recognized action given" end diff --git a/spec/mspec/lib/mspec/runner/formatters/base.rb b/spec/mspec/lib/mspec/runner/formatters/base.rb index e3b5bb23e0bfd0..882e15c8c222bd 100644 --- a/spec/mspec/lib/mspec/runner/formatters/base.rb +++ b/spec/mspec/lib/mspec/runner/formatters/base.rb @@ -46,7 +46,7 @@ def register LeakCheckerAction.new.register end - if ENV['CHECK_LEAKS'] || ENV['CHECK_CONSTANT_LEAKS'] + if (ENV['CHECK_LEAKS'] || ENV['CHECK_CONSTANT_LEAKS']) && ENV['CHECK_CONSTANT_LEAKS'] != 'false' save = ENV['CHECK_LEAKS'] == 'save' || ENV['CHECK_CONSTANT_LEAKS'] == 'save' ConstantsLeakCheckerAction.new(save).register end diff --git a/spec/mspec/lib/mspec/runner/mspec.rb b/spec/mspec/lib/mspec/runner/mspec.rb index 97c3f365bc73d7..0e016c67a7544b 100644 --- a/spec/mspec/lib/mspec/runner/mspec.rb +++ b/spec/mspec/lib/mspec/runner/mspec.rb @@ -365,6 +365,7 @@ def self.make_tag_dir(path) # Writes each tag in +tags+ to the tag file. Overwrites the # tag file if it exists. def self.write_tags(tags) + return delete_tags if tags.empty? file = tags_file make_tag_dir(file) File.open(file, "w:utf-8") do |f| diff --git a/spec/mspec/tool/remove_old_guards.rb b/spec/mspec/tool/remove_old_guards.rb index 75c48e058433e7..bc5612c78dde9c 100755 --- a/spec/mspec/tool/remove_old_guards.rb +++ b/spec/mspec/tool/remove_old_guards.rb @@ -31,6 +31,12 @@ def each_spec_file(&block) Dir["*/**/*.rb"].each(&block) end +def each_file(&block) + Dir["**/*"].each { |path| + yield path if File.file?(path) + } +end + def remove_guards(guard, keep) each_spec_file do |file| contents = File.binread(file) @@ -109,7 +115,7 @@ def remove_unused_shared_specs end def search(regexp) - each_spec_file do |file| + each_file do |file| contents = File.binread(file) if contents =~ regexp puts file @@ -136,3 +142,4 @@ def search(regexp) puts "Search:" search(/(["'])#{version}\1/) search(/^\s*#.+#{version}/) +search(/RUBY_VERSION_IS_#{version.tr('.', '_')}/) From a88e2abb42687c038694b81df31c9baf296ddeeb Mon Sep 17 00:00:00 2001 From: Andrew Konchin Date: Tue, 3 Mar 2026 15:27:56 +0200 Subject: [PATCH 08/10] Update to ruby/spec@af627d6 --- spec/ruby/README.md | 4 +- spec/ruby/command_line/error_message_spec.rb | 5 + spec/ruby/core/data/fixtures/classes.rb | 7 ++ spec/ruby/core/data/initialize_spec.rb | 32 ++++++ .../ruby/core/encoding/locale_charmap_spec.rb | 74 ++++++------- spec/ruby/core/fiber/raise_spec.rb | 12 +- spec/ruby/core/fiber/storage_spec.rb | 18 ++- spec/ruby/core/hash/new_spec.rb | 1 + spec/ruby/core/integer/shared/exponent.rb | 22 +++- spec/ruby/core/io/buffer/and_spec.rb | 62 +++++++++++ spec/ruby/core/io/buffer/not_spec.rb | 37 +++++++ spec/ruby/core/io/buffer/or_spec.rb | 62 +++++++++++ spec/ruby/core/io/buffer/xor_spec.rb | 62 +++++++++++ spec/ruby/core/io/select_spec.rb | 12 +- spec/ruby/core/kernel/raise_spec.rb | 104 +++--------------- spec/ruby/core/kernel/require_spec.rb | 30 +++-- spec/ruby/core/kernel/shared/sprintf.rb | 21 +++- spec/ruby/core/module/autoload_spec.rb | 6 +- spec/ruby/core/mutex/lock_spec.rb | 66 ++++++++++- .../core/refinement/import_methods_spec.rb | 20 ++++ spec/ruby/core/regexp/linear_time_spec.rb | 53 ++++++++- spec/ruby/core/string/shared/encode.rb | 16 +++ spec/ruby/core/string/split_spec.rb | 22 ++-- spec/ruby/core/string/to_f_spec.rb | 10 +- spec/ruby/core/struct/fixtures/classes.rb | 1 + spec/ruby/core/struct/initialize_spec.rb | 23 ++++ spec/ruby/core/thread/raise_spec.rb | 7 ++ spec/ruby/core/time/new_spec.rb | 52 ++++----- spec/ruby/language/ensure_spec.rb | 16 +-- spec/ruby/language/fixtures/super.rb | 2 +- spec/ruby/language/keyword_arguments_spec.rb | 14 +++ spec/ruby/language/lambda_spec.rb | 36 ++++++ spec/ruby/language/method_spec.rb | 10 ++ spec/ruby/language/predefined_spec.rb | 1 + spec/ruby/language/proc_spec.rb | 18 +++ spec/ruby/language/singleton_class_spec.rb | 2 +- spec/ruby/language/string_spec.rb | 6 + spec/ruby/language/super_spec.rb | 14 +++ spec/ruby/language/yield_spec.rb | 6 + .../cgi/htmlextension/multipart_form_spec.rb | 2 +- spec/ruby/library/pathname/glob_spec.rb | 2 +- .../socket/addrinfo/initialize_spec.rb | 36 +++--- spec/ruby/optional/capi/encoding_spec.rb | 18 ++- spec/ruby/optional/capi/ext/encoding_spec.c | 6 + spec/ruby/optional/capi/ext/io_spec.c | 24 ++-- spec/ruby/optional/capi/ext/rubyspec.h | 4 - spec/ruby/optional/capi/ext/string_spec.c | 34 +++++- spec/ruby/optional/capi/ext/struct_spec.c | 4 - spec/ruby/optional/capi/ext/util_spec.c | 4 - spec/ruby/optional/capi/io_spec.rb | 66 +++++++++-- spec/ruby/optional/capi/string_spec.rb | 59 +++++++++- spec/ruby/optional/capi/util_spec.rb | 19 +++- spec/ruby/shared/kernel/raise.rb | 68 +++++++----- spec/ruby/shared/process/fork.rb | 23 ++-- 54 files changed, 1022 insertions(+), 313 deletions(-) create mode 100644 spec/ruby/core/io/buffer/and_spec.rb create mode 100644 spec/ruby/core/io/buffer/not_spec.rb create mode 100644 spec/ruby/core/io/buffer/or_spec.rb create mode 100644 spec/ruby/core/io/buffer/xor_spec.rb diff --git a/spec/ruby/README.md b/spec/ruby/README.md index 14a0068346fe3d..b259a97e21c95f 100644 --- a/spec/ruby/README.md +++ b/spec/ruby/README.md @@ -30,8 +30,8 @@ ruby/spec is known to be tested in these implementations for every commit: * [Opal](https://github.com/opal/opal/tree/master/spec) * [Artichoke](https://github.com/artichoke/spec/tree/artichoke-vendor) -ruby/spec describes the behavior of Ruby 3.2 and more recent Ruby versions. -More precisely, every latest stable MRI release should [pass](https://github.com/ruby/spec/actions/workflows/ci.yml) all specs of ruby/spec (3.2.x, 3.3.x, etc), and those are tested in CI. +ruby/spec describes the behavior of Ruby 3.3 and more recent Ruby versions. +More precisely, every latest stable MRI release should [pass](https://github.com/ruby/spec/actions/workflows/ci.yml) all specs of ruby/spec (3.3.x, 3.4.x, etc), and those are tested in CI. ### Synchronization with Ruby Implementations diff --git a/spec/ruby/command_line/error_message_spec.rb b/spec/ruby/command_line/error_message_spec.rb index 02150f30ce012d..5bed905cf634a6 100644 --- a/spec/ruby/command_line/error_message_spec.rb +++ b/spec/ruby/command_line/error_message_spec.rb @@ -8,4 +8,9 @@ out = ruby_exe("end #syntax error", args: "2> #{File::NULL}", exit_status: 1) out.chomp.should.empty? end + + it "is not modified with extra escaping of control characters and backslashes" do + out = ruby_exe('raise "\e[31mRed\e[0m error\\\\message"', args: "2>&1", exit_status: 1) + out.chomp.should include("\e[31mRed\e[0m error\\message") + end end diff --git a/spec/ruby/core/data/fixtures/classes.rb b/spec/ruby/core/data/fixtures/classes.rb index 650c0b2a623fdd..147293ee45ba6f 100644 --- a/spec/ruby/core/data/fixtures/classes.rb +++ b/spec/ruby/core/data/fixtures/classes.rb @@ -1,6 +1,7 @@ module DataSpecs if Data.respond_to?(:define) Measure = Data.define(:amount, :unit) + Single = Data.define(:value) class MeasureWithOverriddenName < Measure def self.name @@ -8,6 +9,12 @@ def self.name end end + class SingleWithOverriddenName < Single + def self.name + "A" + end + end + class DataSubclass < Data; end MeasureSubclass = Class.new(Measure) do diff --git a/spec/ruby/core/data/initialize_spec.rb b/spec/ruby/core/data/initialize_spec.rb index 010c73b91b8fea..9c3f97ec86720d 100644 --- a/spec/ruby/core/data/initialize_spec.rb +++ b/spec/ruby/core/data/initialize_spec.rb @@ -2,6 +2,16 @@ require_relative 'fixtures/classes' describe "Data#initialize" do + context "with no members" do + ruby_bug "#21819", ""..."4.0.1" do + it "is frozen" do + data = Data.define + + data.new.should.frozen? + end + end + end + it "accepts positional arguments" do data = DataSpecs::Measure.new(42, "km") @@ -37,6 +47,17 @@ data.unit.should == "km" end + it "accepts positional arguments with empty keyword arguments" do + data = DataSpecs::Single.new(42, **{}) + + data.value.should == 42 + + data = DataSpecs::Measure.new(42, "km", **{}) + + data.amount.should == 42 + data.unit.should == "km" + end + it "raises ArgumentError if no arguments are given" do -> { DataSpecs::Measure.new @@ -111,6 +132,17 @@ def initialize(*, **) ScratchPad.recorded.should == [:initialize, [], {amount: 42, unit: "m"}] end + it "accepts positional arguments with empty keyword arguments" do + data = DataSpecs::SingleWithOverriddenName.new(42, **{}) + + data.value.should == 42 + + data = DataSpecs::MeasureWithOverriddenName.new(42, "km", **{}) + + data.amount.should == 42 + data.unit.should == "km" + end + # See https://github.com/ruby/psych/pull/765 it "can be deserialized by calling Data.instance_method(:initialize)" do d1 = DataSpecs::Area.new(width: 2, height: 3) diff --git a/spec/ruby/core/encoding/locale_charmap_spec.rb b/spec/ruby/core/encoding/locale_charmap_spec.rb index 8143b9083a8cc1..345a7b7093705e 100644 --- a/spec/ruby/core/encoding/locale_charmap_spec.rb +++ b/spec/ruby/core/encoding/locale_charmap_spec.rb @@ -5,52 +5,52 @@ Encoding.locale_charmap.should be_an_instance_of(String) end - # FIXME: Get this working on Windows - platform_is :linux do - platform_is_not :android do - it "returns a value based on the LC_ALL environment variable" do - old_lc_all = ENV['LC_ALL'] - ENV['LC_ALL'] = 'C' - ruby_exe("print Encoding.locale_charmap").should == 'ANSI_X3.4-1968' - ENV['LC_ALL'] = old_lc_all + describe "when setting LC_ALL=C" do + before :each do + @old_lc_all = ENV['LC_ALL'] + end + + after :each do + ENV['LC_ALL'] = @old_lc_all + end + + # FIXME: Get this working on Windows + platform_is :linux do + platform_is_not :android do + it "returns a value based on the LC_ALL environment variable" do + ENV['LC_ALL'] = 'C' + ruby_exe("print Encoding.locale_charmap").should == 'ANSI_X3.4-1968' + end end end - end - platform_is :freebsd, :openbsd, :darwin do - it "returns a value based on the LC_ALL environment variable" do - old_lc_all = ENV['LC_ALL'] - ENV['LC_ALL'] = 'C' - ruby_exe("print Encoding.locale_charmap").should == 'US-ASCII' - ENV['LC_ALL'] = old_lc_all + platform_is :freebsd, :openbsd, :darwin do + it "returns a value based on the LC_ALL environment variable" do + ENV['LC_ALL'] = 'C' + ruby_exe("print Encoding.locale_charmap").should == 'US-ASCII' + end end - end - platform_is :netbsd do - it "returns a value based on the LC_ALL environment variable" do - old_lc_all = ENV['LC_ALL'] - ENV['LC_ALL'] = 'C' - ruby_exe("print Encoding.locale_charmap").should == '646' - ENV['LC_ALL'] = old_lc_all + platform_is :netbsd do + it "returns a value based on the LC_ALL environment variable" do + ENV['LC_ALL'] = 'C' + ruby_exe("print Encoding.locale_charmap").should == '646' + end end - end - platform_is :android do - it "always returns UTF-8" do - old_lc_all = ENV['LC_ALL'] - ENV['LC_ALL'] = 'C' - ruby_exe("print Encoding.locale_charmap").should == 'UTF-8' - ENV['LC_ALL'] = old_lc_all + platform_is :android do + it "always returns UTF-8" do + ENV['LC_ALL'] = 'C' + ruby_exe("print Encoding.locale_charmap").should == 'UTF-8' + end end - end - platform_is :bsd, :darwin, :linux do - it "is unaffected by assigning to ENV['LC_ALL'] in the same process" do - old_charmap = Encoding.locale_charmap - old_lc_all = ENV['LC_ALL'] - ENV['LC_ALL'] = 'C' - Encoding.locale_charmap.should == old_charmap - ENV['LC_ALL'] = old_lc_all + platform_is :bsd, :darwin, :linux do + it "is unaffected by assigning to ENV['LC_ALL'] in the same process" do + old_charmap = Encoding.locale_charmap + ENV['LC_ALL'] = 'C' + Encoding.locale_charmap.should == old_charmap + end end end end diff --git a/spec/ruby/core/fiber/raise_spec.rb b/spec/ruby/core/fiber/raise_spec.rb index 896f760290a37b..51d64bba549900 100644 --- a/spec/ruby/core/fiber/raise_spec.rb +++ b/spec/ruby/core/fiber/raise_spec.rb @@ -3,11 +3,16 @@ require_relative '../../shared/kernel/raise' describe "Fiber#raise" do + it "is a public method" do + Fiber.public_instance_methods.should include(:raise) + end + it_behaves_like :kernel_raise, :raise, FiberSpecs::NewFiberToRaise it_behaves_like :kernel_raise_across_contexts, :raise, FiberSpecs::NewFiberToRaise -end + ruby_version_is "4.0" do + it_behaves_like :kernel_raise_with_cause, :raise, FiberSpecs::NewFiberToRaise + end -describe "Fiber#raise" do it 'raises RuntimeError by default' do -> { FiberSpecs::NewFiberToRaise.raise }.should raise_error(RuntimeError) end @@ -126,10 +131,7 @@ end.should raise_error(RuntimeError, "Expected error") end end -end - -describe "Fiber#raise" do it "transfers and raises on a transferring fiber" do root = Fiber.current fiber = Fiber.new { root.transfer } diff --git a/spec/ruby/core/fiber/storage_spec.rb b/spec/ruby/core/fiber/storage_spec.rb index 6ffc13ee283bec..def8af46cec414 100644 --- a/spec/ruby/core/fiber/storage_spec.rb +++ b/spec/ruby/core/fiber/storage_spec.rb @@ -84,17 +84,15 @@ Fiber.new { Fiber[:life] }.resume.should be_nil end - ruby_version_is "3.2.3" do - it "can use dynamically defined keys" do - key = :"#{self.class.name}#.#{self.object_id}" - Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42 - end + it "can use dynamically defined keys" do + key = :"#{self.class.name}#.#{self.object_id}" + Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42 + end - it "can't use invalid keys" do - invalid_keys = [Object.new, 12] - invalid_keys.each do |key| - -> { Fiber[key] }.should raise_error(TypeError) - end + it "can't use invalid keys" do + invalid_keys = [Object.new, 12] + invalid_keys.each do |key| + -> { Fiber[key] }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/hash/new_spec.rb b/spec/ruby/core/hash/new_spec.rb index 8de44ec9411deb..ca43646ef4b9f1 100644 --- a/spec/ruby/core/hash/new_spec.rb +++ b/spec/ruby/core/hash/new_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +# Actually these are specs of Hash#initialize, there is no Hash.new it's just Class.new. describe "Hash.new" do it "creates an empty Hash if passed no arguments" do Hash.new.should == {} diff --git a/spec/ruby/core/integer/shared/exponent.rb b/spec/ruby/core/integer/shared/exponent.rb index 5ef6d686d809d4..d30c54fd3195d5 100644 --- a/spec/ruby/core/integer/shared/exponent.rb +++ b/spec/ruby/core/integer/shared/exponent.rb @@ -57,7 +57,15 @@ end ruby_version_is "3.4" do - it "raises an ArgumentError when the number is too big" do + it "returns an Integer for results larger than the old 32MB limit" do + # 2 ** 40000000 requires 40000001 bits + # This exceeds the old 32MB limit but is within the new 16GB limit + result = 2.send(@method, 40000000) + result.should.is_a?(Integer) + result.bit_length.should == 40000001 + end + + it "raises an ArgumentError when the result size exceeds the limit" do -> { 100000000.send(@method, 1000000000) }.should raise_error(ArgumentError) end end @@ -128,7 +136,17 @@ end ruby_version_is "3.4" do - it "does not switch to a Float when the values is too big" do + it "returns an Integer for large Bignum results exceeding the old limit" do + # (2 ** 70) ** 500000 requires 35000001 bits + # This exceeds the old 32MB limit but is within the new 16GB limit + bignum_base = 2 ** 70 + result = bignum_base.send(@method, 500000) + result.should.is_a?(Integer) + result.bit_length.should == 35000001 + end + + it "raises an ArgumentError when Bignum result exceeds the limit" do + # @bignum ** @bignum would require enormous memory -> { @bignum.send(@method, @bignum) }.should raise_error(ArgumentError) diff --git a/spec/ruby/core/io/buffer/and_spec.rb b/spec/ruby/core/io/buffer/and_spec.rb new file mode 100644 index 00000000000000..1f3611cfa9d8da --- /dev/null +++ b/spec/ruby/core/io/buffer/and_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../../spec_helper' + +describe :io_buffer_and, shared: true do + it "applies the argument buffer as an AND bit mask across the whole buffer" do + IO::Buffer.for(+"12345") do |buffer| + IO::Buffer.for(+"\xF8\x8F") do |mask| + result = buffer.send(@method, mask) + result.get_string.should == "\x30\x02\x30\x04\x30".b + result.free + end + end + end + + it "ignores extra parts of mask if it is longer than source buffer" do + IO::Buffer.for(+"12345") do |buffer| + IO::Buffer.for(+"\xF8\x8F\x00\x00\x00\xFF\xFF") do |mask| + result = buffer.send(@method, mask) + result.get_string.should == "\x30\x02\x00\x00\x00".b + result.free + end + end + end + + it "raises TypeError if mask is not an IO::Buffer" do + IO::Buffer.for(+"12345") do |buffer| + -> { buffer.send(@method, "\xF8\x8F") }.should raise_error(TypeError, "wrong argument type String (expected IO::Buffer)") + -> { buffer.send(@method, 0xF8) }.should raise_error(TypeError, "wrong argument type Integer (expected IO::Buffer)") + -> { buffer.send(@method, nil) }.should raise_error(TypeError, "wrong argument type nil (expected IO::Buffer)") + end + end +end + +describe "IO::Buffer#&" do + it_behaves_like :io_buffer_and, :& + + it "creates a new internal buffer of the same size" do + IO::Buffer.for(+"12345") do |buffer| + IO::Buffer.for(+"\xF8\x8F") do |mask| + result = buffer & mask + result.should_not.equal? buffer + result.should.internal? + result.size.should == buffer.size + result.free + buffer.get_string.should == "12345".b + end + end + end +end + +describe "IO::Buffer#and!" do + it_behaves_like :io_buffer_and, :and! + + it "modifies the buffer in place" do + IO::Buffer.for(+"12345") do |buffer| + IO::Buffer.for(+"\xF8\x8F") do |mask| + result = buffer.and!(mask) + result.should.equal? buffer + result.should.external? + end + end + end +end diff --git a/spec/ruby/core/io/buffer/not_spec.rb b/spec/ruby/core/io/buffer/not_spec.rb new file mode 100644 index 00000000000000..4737a30bde66ff --- /dev/null +++ b/spec/ruby/core/io/buffer/not_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../../spec_helper' + +describe :io_buffer_not, shared: true do + it "inverts every bit of the buffer" do + IO::Buffer.for(+"12345") do |buffer| + result = buffer.send(@method) + result.get_string.should == "\xCE\xCD\xCC\xCB\xCA".b + result.free + end + end +end + +describe "IO::Buffer#~" do + it_behaves_like :io_buffer_not, :~ + + it "creates a new internal buffer of the same size" do + IO::Buffer.for(+"12345") do |buffer| + result = ~buffer + result.should_not.equal? buffer + result.should.internal? + result.size.should == buffer.size + result.free + end + end +end + +describe "IO::Buffer#not!" do + it_behaves_like :io_buffer_not, :not! + + it "modifies the buffer in place" do + IO::Buffer.for(+"12345") do |buffer| + result = buffer.not! + result.should.equal? buffer + result.should.external? + end + end +end diff --git a/spec/ruby/core/io/buffer/or_spec.rb b/spec/ruby/core/io/buffer/or_spec.rb new file mode 100644 index 00000000000000..1c9e52a4cac2d4 --- /dev/null +++ b/spec/ruby/core/io/buffer/or_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../../spec_helper' + +describe :io_buffer_or, shared: true do + it "applies the argument buffer as an OR bit mask across the whole buffer" do + IO::Buffer.for(+"12345") do |buffer| + IO::Buffer.for(+"\xF8\x8F") do |mask| + result = buffer.send(@method, mask) + result.get_string.should == "\xF9\xBF\xFB\xBF\xFD".b + result.free + end + end + end + + it "ignores extra parts of mask if it is longer than source buffer" do + IO::Buffer.for(+"12345") do |buffer| + IO::Buffer.for(+"\xF8\x8F\x00\x00\x00\xFF\xFF") do |mask| + result = buffer.send(@method, mask) + result.get_string.should == "\xF9\xBF345".b + result.free + end + end + end + + it "raises TypeError if mask is not an IO::Buffer" do + IO::Buffer.for(+"12345") do |buffer| + -> { buffer.send(@method, "\xF8\x8F") }.should raise_error(TypeError, "wrong argument type String (expected IO::Buffer)") + -> { buffer.send(@method, 0xF8) }.should raise_error(TypeError, "wrong argument type Integer (expected IO::Buffer)") + -> { buffer.send(@method, nil) }.should raise_error(TypeError, "wrong argument type nil (expected IO::Buffer)") + end + end +end + +describe "IO::Buffer#|" do + it_behaves_like :io_buffer_or, :| + + it "creates a new internal buffer of the same size" do + IO::Buffer.for(+"12345") do |buffer| + IO::Buffer.for(+"\xF8\x8F") do |mask| + result = buffer | mask + result.should_not.equal? buffer + result.should.internal? + result.size.should == buffer.size + result.free + buffer.get_string.should == "12345".b + end + end + end +end + +describe "IO::Buffer#or!" do + it_behaves_like :io_buffer_or, :or! + + it "modifies the buffer in place" do + IO::Buffer.for(+"12345") do |buffer| + IO::Buffer.for(+"\xF8\x8F") do |mask| + result = buffer.or!(mask) + result.should.equal? buffer + result.should.external? + end + end + end +end diff --git a/spec/ruby/core/io/buffer/xor_spec.rb b/spec/ruby/core/io/buffer/xor_spec.rb new file mode 100644 index 00000000000000..637f7519d54a00 --- /dev/null +++ b/spec/ruby/core/io/buffer/xor_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../../spec_helper' + +describe :io_buffer_xor, shared: true do + it "applies the argument buffer as an XOR bit mask across the whole buffer" do + IO::Buffer.for(+"12345") do |buffer| + IO::Buffer.for(+"\xF8\x8F") do |mask| + result = buffer.send(@method, mask) + result.get_string.should == "\xC9\xBD\xCB\xBB\xCD".b + result.free + end + end + end + + it "ignores extra parts of mask if it is longer than source buffer" do + IO::Buffer.for(+"12345") do |buffer| + IO::Buffer.for(+"\xF8\x8F\x00\x00\x00\xFF\xFF") do |mask| + result = buffer.send(@method, mask) + result.get_string.should == "\xC9\xBD345".b + result.free + end + end + end + + it "raises TypeError if mask is not an IO::Buffer" do + IO::Buffer.for(+"12345") do |buffer| + -> { buffer.send(@method, "\xF8\x8F") }.should raise_error(TypeError, "wrong argument type String (expected IO::Buffer)") + -> { buffer.send(@method, 0xF8) }.should raise_error(TypeError, "wrong argument type Integer (expected IO::Buffer)") + -> { buffer.send(@method, nil) }.should raise_error(TypeError, "wrong argument type nil (expected IO::Buffer)") + end + end +end + +describe "IO::Buffer#^" do + it_behaves_like :io_buffer_xor, :^ + + it "creates a new internal buffer of the same size" do + IO::Buffer.for(+"12345") do |buffer| + IO::Buffer.for(+"\xF8\x8F") do |mask| + result = buffer ^ mask + result.should_not.equal? buffer + result.should.internal? + result.size.should == buffer.size + result.free + buffer.get_string.should == "12345".b + end + end + end +end + +describe "IO::Buffer#xor!" do + it_behaves_like :io_buffer_xor, :xor! + + it "modifies the buffer in place" do + IO::Buffer.for(+"12345") do |buffer| + IO::Buffer.for(+"\xF8\x8F") do |mask| + result = buffer.xor!(mask) + result.should.equal? buffer + result.should.external? + end + end + end +end diff --git a/spec/ruby/core/io/select_spec.rb b/spec/ruby/core/io/select_spec.rb index 9fdb7e12c932db..8e0b89c053548f 100644 --- a/spec/ruby/core/io/select_spec.rb +++ b/spec/ruby/core/io/select_spec.rb @@ -112,7 +112,17 @@ end it "raises an ArgumentError when passed a negative timeout" do - -> { IO.select(nil, nil, nil, -5)}.should raise_error(ArgumentError) + -> { IO.select(nil, nil, nil, -5)}.should raise_error(ArgumentError, "time interval must not be negative") + end + + ruby_version_is "4.0" do + it "raises an ArgumentError when passed negative infinity as timeout" do + -> { IO.select(nil, nil, nil, -Float::INFINITY)}.should raise_error(ArgumentError, "time interval must not be negative") + end + end + + it "raises an RangeError when passed NaN as timeout" do + -> { IO.select(nil, nil, nil, Float::NAN)}.should raise_error(RangeError, "NaN out of Time range") end describe "returns the available descriptors when the file descriptor" do diff --git a/spec/ruby/core/kernel/raise_spec.rb b/spec/ruby/core/kernel/raise_spec.rb index fcd011d4e62e88..d08303cd830b2f 100644 --- a/spec/ruby/core/kernel/raise_spec.rb +++ b/spec/ruby/core/kernel/raise_spec.rb @@ -4,9 +4,19 @@ describe "Kernel#raise" do it "is a private method" do - Kernel.should have_private_instance_method(:raise) + Kernel.private_instance_methods.should include(:raise) end + # Shared specs expect a public #raise method. + public_raiser = Object.new + class << public_raiser + public :raise + end + it_behaves_like :kernel_raise, :raise, public_raiser + it_behaves_like :kernel_raise_with_cause, :raise, public_raiser +end + +describe "Kernel#raise with previously rescued exception" do it "re-raises the previously rescued exception if no exception is specified" do ScratchPad.record nil @@ -28,87 +38,6 @@ ScratchPad.recorded.should be_nil end - it "accepts a cause keyword argument that sets the cause" do - cause = StandardError.new - -> { raise("error", cause: cause) }.should raise_error(RuntimeError) { |e| e.cause.should == cause } - end - - it "accepts a cause keyword argument that overrides the last exception" do - begin - raise "first raise" - rescue => ignored - cause = StandardError.new - -> { raise("error", cause: cause) }.should raise_error(RuntimeError) { |e| e.cause.should == cause } - end - end - - it "raises an ArgumentError when only cause is given" do - cause = StandardError.new - -> { raise(cause: cause) }.should raise_error(ArgumentError, "only cause is given with no arguments") - end - - it "raises an ArgumentError when only cause is given even if it has nil value" do - -> { raise(cause: nil) }.should raise_error(ArgumentError, "only cause is given with no arguments") - end - - it "raises a TypeError when given cause is not an instance of Exception" do - -> { raise "message", cause: Object.new }.should raise_error(TypeError, "exception object expected") - end - - it "doesn't raise a TypeError when given cause is nil" do - -> { raise "message", cause: nil }.should raise_error(RuntimeError, "message") - end - - it "allows cause equal an exception" do - e = RuntimeError.new("message") - -> { raise e, cause: e }.should raise_error(e) - end - - it "doesn't set given cause when it equals an exception" do - e = RuntimeError.new("message") - - begin - raise e, cause: e - rescue - end - - e.cause.should == nil - end - - it "raises ArgumentError when exception is part of the cause chain" do - -> { - begin - raise "Error 1" - rescue => e1 - begin - raise "Error 2" - rescue => e2 - begin - raise "Error 3" - rescue => e3 - raise e1, cause: e3 - end - end - end - }.should raise_error(ArgumentError, "circular causes") - end - - it "re-raises a rescued exception" do - -> do - begin - raise StandardError, "aaa" - rescue Exception - begin - raise ArgumentError - rescue ArgumentError - end - - # should raise StandardError "aaa" - raise - end - end.should raise_error(StandardError, "aaa") - end - it "re-raises a previously rescued exception without overwriting the cause" do begin begin @@ -283,10 +212,11 @@ end end -describe "Kernel#raise" do - it_behaves_like :kernel_raise, :raise, Kernel -end - describe "Kernel.raise" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.singleton_class.should.public_method_defined?(:raise) + end + + it_behaves_like :kernel_raise, :raise, Kernel + it_behaves_like :kernel_raise_with_cause, :raise, Kernel end diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb index da2f48fb61500b..c609809df55e92 100644 --- a/spec/ruby/core/kernel/require_spec.rb +++ b/spec/ruby/core/kernel/require_spec.rb @@ -16,28 +16,34 @@ Kernel.should have_private_instance_method(:require) end - provided = %w[complex enumerator fiber rational thread ruby2_keywords] - ruby_version_is "4.0" do - provided << "set" - provided << "pathname" - end - ruby_version_is "4.1" do - provided << "monitor" - end + it "provided features are already required" do + provided = %w[complex enumerator fiber rational thread ruby2_keywords] + ruby_version_is "4.0" do + provided += %w[set pathname] + end + ruby_version_is "4.1" do + provided += %w[monitor] + end - it "#{provided.join(', ')} are already required" do out = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems --disable-did-you-mean') features = out.lines.map { |line| File.basename(line.chomp, '.*') } - # Ignore CRuby internals - features -= %w[encdb transdb windows_1252 windows_31] + # Ignore engine-specific internals + case RUBY_ENGINE + when "jruby" + features -= %w[java util] + else + features -= %w[encdb transdb windows_1252 windows_31] + end features.reject! { |feature| feature.end_with?('-fake') } features.sort.should == provided.sort requires = provided ruby_version_is "4.0" do - requires = requires.map { |f| f == "pathname" ? "pathname.so" : f } + if RUBY_ENGINE != "jruby" + requires = requires.map { |f| f == "pathname" ? "pathname.so" : f } + end end ruby_version_is "4.1" do diff --git a/spec/ruby/core/kernel/shared/sprintf.rb b/spec/ruby/core/kernel/shared/sprintf.rb index 5ffe118035d683..e18747e6b851cc 100644 --- a/spec/ruby/core/kernel/shared/sprintf.rb +++ b/spec/ruby/core/kernel/shared/sprintf.rb @@ -934,10 +934,27 @@ def obj.to_str }.should raise_error(ArgumentError) end - it "raises KeyError when there is no matching key" do + it "respects Hash#default when there is no set key" do + @method.call("%{foo}", Hash.new(123)).should == "123" + @method.call("%{foo}", Hash.new { 123 }).should == "123" + end + + it "raises KeyError when Hash#default returns nil" do -> { @method.call("%{foo}", {}) - }.should raise_error(KeyError) + }.should raise_error(KeyError, 'key{foo} not found') + + -> { + @method.call("%{foo}", Hash.new(nil)) + }.should raise_error(KeyError, 'key{foo} not found') + + -> { + @method.call("%{foo}", Hash.new { nil }) + }.should raise_error(KeyError, 'key{foo} not found') + end + + it "accepts a nil value for an existing key" do + @method.call("%{foo}", { foo: nil }).should == "" end it "converts value to String with to_s" do diff --git a/spec/ruby/core/module/autoload_spec.rb b/spec/ruby/core/module/autoload_spec.rb index 625d945686af4b..87b1d520555cd7 100644 --- a/spec/ruby/core/module/autoload_spec.rb +++ b/spec/ruby/core/module/autoload_spec.rb @@ -948,7 +948,7 @@ class ModuleSpecs::Autoload::Z < ModuleSpecs::Autoload::ZZ begin Object.const_get(mod_name).foo - rescue NoMethodError + rescue NameError, NoMethodError # rubocop:disable Lint/ShadowedException barrier.disable! break false end @@ -956,7 +956,7 @@ class ModuleSpecs::Autoload::Z < ModuleSpecs::Autoload::ZZ end end - # check that no thread got a NoMethodError because of partially loaded module + # check that no thread got a NameError or NoMethodError because of partially loaded module threads.all? {|t| t.value}.should be_true # check that the autoloaded file was evaled exactly once @@ -965,6 +965,8 @@ class ModuleSpecs::Autoload::Z < ModuleSpecs::Autoload::ZZ mod_names.each do |mod_name| Object.send(:remove_const, mod_name) end + ensure + threads.each(&:join) if threads end it "raises a NameError in each thread if the constant is not set" do diff --git a/spec/ruby/core/mutex/lock_spec.rb b/spec/ruby/core/mutex/lock_spec.rb index e9d33f5fd940d9..c1501e8ec26865 100644 --- a/spec/ruby/core/mutex/lock_spec.rb +++ b/spec/ruby/core/mutex/lock_spec.rb @@ -20,11 +20,73 @@ # Unable to find a specific ticket but behavior change may be # related to this ML thread. - it "raises a ThreadError when used recursively" do + it "raises a deadlock ThreadError when used recursively" do m = Mutex.new m.lock -> { m.lock - }.should raise_error(ThreadError) + }.should raise_error(ThreadError, /deadlock/) + end + + it "raises a deadlock ThreadError when multiple fibers from the same thread try to lock" do + m = Mutex.new + + m.lock + f0 = Fiber.new do + m.lock + end + -> { f0.resume }.should raise_error(ThreadError, /deadlock/) + + m.unlock + f1 = Fiber.new do + m.lock + Fiber.yield + end + f2 = Fiber.new do + m.lock + end + f1.resume + -> { f2.resume }.should raise_error(ThreadError, /deadlock/) + end + + it "does not raise deadlock if a fiber's attempt to lock was interrupted" do + lock = Mutex.new + main = Thread.current + + t2 = nil + t1 = Thread.new do + loop do + # interrupt fiber below looping on synchronize + sleep 0.01 + t2.raise if t2 + end + end + + # loop ten times to try to handle the interrupt during synchronize + t2 = Thread.new do + 10.times do + Fiber.new do + begin + loop { lock.synchronize {} } + rescue RuntimeError + end + end.resume + + Fiber.new do + -> do + lock.synchronize {} + end.should_not raise_error(ThreadError) + end.resume + rescue RuntimeError + retry + end + end + t2.join + ensure + t1.kill rescue nil + t2.kill rescue nil + + t1.join + t2.join end end diff --git a/spec/ruby/core/refinement/import_methods_spec.rb b/spec/ruby/core/refinement/import_methods_spec.rb index 13c0b1004cdfea..dc3e0ea31d38d6 100644 --- a/spec/ruby/core/refinement/import_methods_spec.rb +++ b/spec/ruby/core/refinement/import_methods_spec.rb @@ -242,6 +242,26 @@ def foo(number) end end + it "correctly sets owner as the refinement module" do + str_utils = Module.new do + def indent(level) + " " * level + self + end + end + + refinement = Module.new do + refine String do + import_methods str_utils + end + end + + Module.new do + using refinement + + String.instance_method(:indent).owner.should == refinement.refinements.first + end + end + context "when methods are not defined in Ruby code" do it "raises ArgumentError" do Module.new do diff --git a/spec/ruby/core/regexp/linear_time_spec.rb b/spec/ruby/core/regexp/linear_time_spec.rb index 2f3f81ed207236..f70021dfed647f 100644 --- a/spec/ruby/core/regexp/linear_time_spec.rb +++ b/spec/ruby/core/regexp/linear_time_spec.rb @@ -25,7 +25,56 @@ }.should complain(/warning: flags ignored/) end - it "returns true for positive lookarounds" do - Regexp.linear_time?(/(?:(?=a*)a)*/).should == true + it "returns true for positive lookahead" do + Regexp.linear_time?(/a*(?:(?=a*)a)*b/).should == true + end + + it "returns true for positive lookbehind" do + Regexp.linear_time?(/a*(?:(?<=a)a*)*b/).should == true + end + + it "returns true for negative lookbehind" do + Regexp.linear_time?(/a*(?:(? { uses_regexp_caching } do + it "returns true for negative lookahead" do + Regexp.linear_time?(/a*(?:(?!a*)a*)*b/).should == true + end + + it "returns true for atomic groups" do + Regexp.linear_time?(/a*(?:(?>a)a*)*b/).should == true + end + + it "returns true for possessive quantifiers" do + Regexp.linear_time?(/a*(?:(?:a)?+a*)*b/).should == true + end + + it "returns true for positive lookbehind with capture group" do + Regexp.linear_time?(/.(?<=(a))/).should == true + end + end + + # The following specs should not be relied upon, + # they are here only to illustrate differences between Regexp engines. + guard -> { uses_dfa_regexp_engine } do + it "returns true for non-recursive subexpression call" do + Regexp.linear_time?(/(?a){0}\g/).should == true + end + + it "returns true for positive lookahead with capture group" do + Regexp.linear_time?(/x+(?=(a))/).should == true + end end end diff --git a/spec/ruby/core/string/shared/encode.rb b/spec/ruby/core/string/shared/encode.rb index 9466308886ee9d..51a117c90ed564 100644 --- a/spec/ruby/core/string/shared/encode.rb +++ b/spec/ruby/core/string/shared/encode.rb @@ -72,6 +72,14 @@ "abc".send(@method, "xyz") end.should raise_error(Encoding::ConverterNotFoundError) end + + it "raises an Encoding::UndefinedConversionError when a character cannot be represented in the destination encoding" do + # U+0100 (Ā) is valid UTF-8 but not representable in windows-1252 + str = "test\u0100".force_encoding('utf-8') + -> { + str.send(@method, Encoding::Windows_1252) + }.should raise_error(Encoding::UndefinedConversionError) + end end describe "when passed options" do @@ -142,6 +150,14 @@ "ab#{xFF}c".send(@method, Encoding::ISO_8859_1, invalid: :replace).should == "ab?c" end + it "raises UndefinedConversionError for characters not representable in destination encoding with only invalid: :replace" do + # U+0100 (Ā) is valid UTF-8 but not representable in windows-1252 + str = "test\u0100".force_encoding('utf-8') + -> { + str.send(@method, Encoding::Windows_1252, invalid: :replace, replace: "") + }.should raise_error(Encoding::UndefinedConversionError) + end + it "calls #to_hash to convert the options object" do options = mock("string encode options") options.should_receive(:to_hash).and_return({ undef: :replace }) diff --git a/spec/ruby/core/string/split_spec.rb b/spec/ruby/core/string/split_spec.rb index 3c6d1864d1797e..cada1a6789353d 100644 --- a/spec/ruby/core/string/split_spec.rb +++ b/spec/ruby/core/string/split_spec.rb @@ -119,21 +119,21 @@ $; = old_fs end end + end - context "when $; is not nil" do - before do - suppress_warning do - @old_value, $; = $;, 'foobar' - end + context "when $; is not nil" do + before do + suppress_warning do + @old_value, $; = $;, 'foobar' end + end - after do - $; = @old_value - end + after do + $; = @old_value + end - it "warns" do - -> { "".split }.should complain(/warning: \$; is set to non-nil value/) - end + it "warns" do + -> { "".split }.should complain(/warning: \$; is set to non-nil value/) end end diff --git a/spec/ruby/core/string/to_f_spec.rb b/spec/ruby/core/string/to_f_spec.rb index abfd2517b69327..520a797af97764 100644 --- a/spec/ruby/core/string/to_f_spec.rb +++ b/spec/ruby/core/string/to_f_spec.rb @@ -120,12 +120,10 @@ "\3771.2".b.to_f.should == 0 end - ruby_version_is "3.2.3" do - it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do - -> { - '1.2'.encode("UTF-16").to_f - }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16") - end + it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do + -> { + '1.2'.encode("UTF-16").to_f + }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16") end it "allows String representation without a fractional part" do diff --git a/spec/ruby/core/struct/fixtures/classes.rb b/spec/ruby/core/struct/fixtures/classes.rb index 7b80b814efc909..675d403abde28f 100644 --- a/spec/ruby/core/struct/fixtures/classes.rb +++ b/spec/ruby/core/struct/fixtures/classes.rb @@ -3,6 +3,7 @@ module StructClasses class Apple < Struct; end Ruby = Struct.new(:version, :platform) + Single = Struct.new(:value) Car = Struct.new(:make, :model, :year) diff --git a/spec/ruby/core/struct/initialize_spec.rb b/spec/ruby/core/struct/initialize_spec.rb index 06055594d5b18d..6a541d7c9edb3b 100644 --- a/spec/ruby/core/struct/initialize_spec.rb +++ b/spec/ruby/core/struct/initialize_spec.rb @@ -48,4 +48,27 @@ positional_args.version.should == keyword_args.version positional_args.platform.should == keyword_args.platform end + + it "accepts positional arguments with empty keyword arguments" do + data = StructClasses::Single.new(42, **{}) + + data.value.should == 42 + + data = StructClasses::Ruby.new("3.2", "OS", **{}) + + data.version.should == "3.2" + data.platform.should == "OS" + end + + it "can be called via delegated ... from a prepended module" do + wrapper = Module.new do + def initialize(...) + super(...) + end + end + + klass = Class.new(Struct.new(:a)) { prepend wrapper } + s = klass.new("x") + s.a.should == "x" + end end diff --git a/spec/ruby/core/thread/raise_spec.rb b/spec/ruby/core/thread/raise_spec.rb index b473eabd42c492..996b07b1b81de6 100644 --- a/spec/ruby/core/thread/raise_spec.rb +++ b/spec/ruby/core/thread/raise_spec.rb @@ -3,8 +3,15 @@ require_relative '../../shared/kernel/raise' describe "Thread#raise" do + it "is a public method" do + Thread.public_instance_methods.should include(:raise) + end + it_behaves_like :kernel_raise, :raise, ThreadSpecs::NewThreadToRaise it_behaves_like :kernel_raise_across_contexts, :raise, ThreadSpecs::NewThreadToRaise + ruby_version_is "4.0" do + it_behaves_like :kernel_raise_with_cause, :raise, ThreadSpecs::NewThreadToRaise + end it "ignores dead threads and returns nil" do t = Thread.new { :dead } diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb index f3b5d0142044b4..e4e54e16a39fe7 100644 --- a/spec/ruby/core/time/new_spec.rb +++ b/spec/ruby/core/time/new_spec.rb @@ -570,18 +570,16 @@ def obj.to_int; 3; end }.should raise_error(ArgumentError, /missing min part: 00 |can't parse:/) end - ruby_version_is "3.2.3" do - it "raises ArgumentError if the time part is missing" do - -> { - Time.new("2020-12-25") - }.should raise_error(ArgumentError, /no time information|can't parse:/) - end + it "raises ArgumentError if the time part is missing" do + -> { + Time.new("2020-12-25") + }.should raise_error(ArgumentError, /no time information|can't parse:/) + end - it "raises ArgumentError if day is missing" do - -> { - Time.new("2020-12") - }.should raise_error(ArgumentError, /no time information|can't parse:/) - end + it "raises ArgumentError if day is missing" do + -> { + Time.new("2020-12") + }.should raise_error(ArgumentError, /no time information|can't parse:/) end it "raises ArgumentError if subsecond is missing after dot" do @@ -720,24 +718,22 @@ def obj.to_int; 3; end }.should raise_error(ArgumentError, /can't parse.+ abc/) end - ruby_version_is "3.2.3" do - it "raises ArgumentError when there are leading space characters" do - -> { Time.new(" 2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("\t2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("\n2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("\v2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("\f2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("\r2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) - end + it "raises ArgumentError when there are leading space characters" do + -> { Time.new(" 2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\t2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\n2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\v2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\f2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\r2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + end - it "raises ArgumentError when there are trailing whitespaces" do - -> { Time.new("2020-12-02 00:00:00 ") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("2020-12-02 00:00:00\t") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("2020-12-02 00:00:00\n") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("2020-12-02 00:00:00\v") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("2020-12-02 00:00:00\f") }.should raise_error(ArgumentError, /can't parse/) - -> { Time.new("2020-12-02 00:00:00\r") }.should raise_error(ArgumentError, /can't parse/) - end + it "raises ArgumentError when there are trailing whitespaces" do + -> { Time.new("2020-12-02 00:00:00 ") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\t") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\n") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\v") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\f") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\r") }.should raise_error(ArgumentError, /can't parse/) end end end diff --git a/spec/ruby/language/ensure_spec.rb b/spec/ruby/language/ensure_spec.rb index b76292c0075aeb..1b44457f6a051f 100644 --- a/spec/ruby/language/ensure_spec.rb +++ b/spec/ruby/language/ensure_spec.rb @@ -6,7 +6,7 @@ ScratchPad.record [] end - it "is executed when an exception is raised in it's corresponding begin block" do + it "is executed when an exception is raised in its corresponding begin block" do -> { begin ScratchPad << :begin @@ -19,7 +19,7 @@ ScratchPad.recorded.should == [:begin, :ensure] end - it "is executed when an exception is raised and rescued in it's corresponding begin block" do + it "is executed when an exception is raised and rescued in its corresponding begin block" do begin ScratchPad << :begin raise "An exception occurred!" @@ -32,7 +32,7 @@ ScratchPad.recorded.should == [:begin, :rescue, :ensure] end - it "is executed even when a symbol is thrown in it's corresponding begin block" do + it "is executed even when a symbol is thrown in its corresponding begin block" do catch(:symbol) do begin ScratchPad << :begin @@ -47,7 +47,7 @@ ScratchPad.recorded.should == [:begin, :ensure] end - it "is executed when nothing is raised or thrown in it's corresponding begin block" do + it "is executed when nothing is raised or thrown in its corresponding begin block" do begin ScratchPad << :begin rescue @@ -256,7 +256,7 @@ class EnsureInClassExample ScratchPad.record [] end - it "is executed when an exception is raised in it's corresponding begin block" do + it "is executed when an exception is raised in its corresponding begin block" do -> { eval(<<-ruby).call lambda do @@ -271,7 +271,7 @@ class EnsureInClassExample ScratchPad.recorded.should == [:begin, :ensure] end - it "is executed when an exception is raised and rescued in it's corresponding begin block" do + it "is executed when an exception is raised and rescued in its corresponding begin block" do eval(<<-ruby).call lambda do ScratchPad << :begin @@ -286,7 +286,7 @@ class EnsureInClassExample ScratchPad.recorded.should == [:begin, :rescue, :ensure] end - it "is executed even when a symbol is thrown in it's corresponding begin block" do + it "is executed even when a symbol is thrown in its corresponding begin block" do catch(:symbol) do eval(<<-ruby).call lambda do @@ -303,7 +303,7 @@ class EnsureInClassExample ScratchPad.recorded.should == [:begin, :ensure] end - it "is executed when nothing is raised or thrown in it's corresponding begin block" do + it "is executed when nothing is raised or thrown in its corresponding begin block" do eval(<<-ruby).call lambda do ScratchPad << :begin diff --git a/spec/ruby/language/fixtures/super.rb b/spec/ruby/language/fixtures/super.rb index c5bdcf0e402780..b6d4218b03446e 100644 --- a/spec/ruby/language/fixtures/super.rb +++ b/spec/ruby/language/fixtures/super.rb @@ -266,7 +266,7 @@ def name # Use this so that we can see collect all supers that we see. # One bug that arises is that we call Alias2#name from Alias2#name - # as it's superclass. In that case, either we get a runaway recursion + # as its superclass. In that case, either we get a runaway recursion # super OR we get the return value being [:alias2, :alias2, :alias1] # rather than [:alias2, :alias1]. # diff --git a/spec/ruby/language/keyword_arguments_spec.rb b/spec/ruby/language/keyword_arguments_spec.rb index c51c3bc656d4e9..d769839bdaa2a5 100644 --- a/spec/ruby/language/keyword_arguments_spec.rb +++ b/spec/ruby/language/keyword_arguments_spec.rb @@ -86,6 +86,20 @@ def m(*a) m(*[], 42, **{}).should == [42] end + context "marked as ruby2_keywords_hash" do + it "is not copied when passed as a positional argument" do + h = Hash.ruby2_keywords_hash(a:1) + + def bar(a) + a + end + + h2 = bar(h) + h2.should equal(h) + Hash.ruby2_keywords_hash?(h).should == true + end + end + context "**" do it "copies a non-empty Hash for a method taking (*args)" do def m(*args) diff --git a/spec/ruby/language/lambda_spec.rb b/spec/ruby/language/lambda_spec.rb index ed5a1c69e84b98..8a73b51d2cff2b 100644 --- a/spec/ruby/language/lambda_spec.rb +++ b/spec/ruby/language/lambda_spec.rb @@ -286,6 +286,24 @@ def a; 1; end end end end + + evaluate <<-ruby do + @a = -> (**nil) { :ok } + ruby + + @a.call().should == :ok + -> { @a.call(a: 1) }.should raise_error(ArgumentError, 'no keywords accepted') + -> { @a.call(**{a: 1}) }.should raise_error(ArgumentError, 'no keywords accepted') + -> { @a.call("a" => 1) }.should raise_error(ArgumentError, 'no keywords accepted') + end + + evaluate <<-ruby do + @a = -> (a, **nil) { a } + ruby + + @a.call({a: 1}).should == {a: 1} + -> { @a.call(a: 1) }.should raise_error(ArgumentError, 'no keywords accepted') + end end describe "A lambda expression 'lambda { ... }'" do @@ -583,5 +601,23 @@ def m2() yield end result = @a.(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{})) result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l] end + + evaluate <<-ruby do + @a = lambda { |**nil| :ok } + ruby + + @a.call().should == :ok + -> { @a.call(a: 1) }.should raise_error(ArgumentError, 'no keywords accepted') + -> { @a.call(**{a: 1}) }.should raise_error(ArgumentError, 'no keywords accepted') + -> { @a.call("a" => 1) }.should raise_error(ArgumentError, 'no keywords accepted') + end + + evaluate <<-ruby do + @a = lambda { |a, **nil| a } + ruby + + @a.call({a: 1}).should == {a: 1} + -> { @a.call(a: 1) }.should raise_error(ArgumentError, 'no keywords accepted') + end end end diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb index dd93703d9f4fe6..00a087b33730bf 100644 --- a/spec/ruby/language/method_spec.rb +++ b/spec/ruby/language/method_spec.rb @@ -1100,6 +1100,16 @@ def m(a, b=1, *c, d, e:, f: 2, g:, **k, &l) result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l] end + evaluate <<-ruby do + def m(**nil); :ok; end; + ruby + + m().should == :ok + -> { m(a: 1) }.should raise_error(ArgumentError, 'no keywords accepted') + -> { m(**{a: 1}) }.should raise_error(ArgumentError, 'no keywords accepted') + -> { m("a" => 1) }.should raise_error(ArgumentError, 'no keywords accepted') + end + evaluate <<-ruby do def m(a, **nil); a end; ruby diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb index fc1667a38fe8d0..948ad2647528ac 100644 --- a/spec/ruby/language/predefined_spec.rb +++ b/spec/ruby/language/predefined_spec.rb @@ -720,6 +720,7 @@ def foo $/.should equal(str) end end + it "can be assigned nil" do $/ = nil $/.should be_nil diff --git a/spec/ruby/language/proc_spec.rb b/spec/ruby/language/proc_spec.rb index ca9a13aa615e2e..f26f82b015588a 100644 --- a/spec/ruby/language/proc_spec.rb +++ b/spec/ruby/language/proc_spec.rb @@ -246,4 +246,22 @@ -> { p.call() }.should raise_error(ArgumentError) end end + + evaluate <<-ruby do + @p = proc { |**nil| :ok } + ruby + + @p.call().should == :ok + -> { @p.call(a: 1) }.should raise_error(ArgumentError, 'no keywords accepted') + -> { @p.call(**{a: 1}) }.should raise_error(ArgumentError, 'no keywords accepted') + -> { @p.call("a" => 1) }.should raise_error(ArgumentError, 'no keywords accepted') + end + + evaluate <<-ruby do + @p = proc { |a, **nil| a } + ruby + + @p.call({a: 1}).should == {a: 1} + -> { @p.call(a: 1) }.should raise_error(ArgumentError, 'no keywords accepted') + end end diff --git a/spec/ruby/language/singleton_class_spec.rb b/spec/ruby/language/singleton_class_spec.rb index 45e1f7f3ad5936..958bf464a7c9c0 100644 --- a/spec/ruby/language/singleton_class_spec.rb +++ b/spec/ruby/language/singleton_class_spec.rb @@ -60,7 +60,7 @@ ClassSpecs::H.singleton_class.singleton_class end - it "for BasicObject has Class as it's superclass" do + it "for BasicObject has Class as its superclass" do BasicObject.singleton_class.superclass.should == Class end diff --git a/spec/ruby/language/string_spec.rb b/spec/ruby/language/string_spec.rb index f287731bed518e..050ffaa4e777a9 100644 --- a/spec/ruby/language/string_spec.rb +++ b/spec/ruby/language/string_spec.rb @@ -133,6 +133,12 @@ class << obj "#{obj}".should == '42' end + it "raise NoMethodError when #to_s is not defined for the object" do + obj = BasicObject.new + + -> { "#{obj}" }.should raise_error(NoMethodError) + end + it "uses an internal representation when #to_s doesn't return a String" do obj = mock('to_s') obj.stub!(:to_s).and_return(42) diff --git a/spec/ruby/language/super_spec.rb b/spec/ruby/language/super_spec.rb index 7d9e896d8b1199..e205fae13cedda 100644 --- a/spec/ruby/language/super_spec.rb +++ b/spec/ruby/language/super_spec.rb @@ -461,4 +461,18 @@ def obj.foobar(array) @all.foo('a', b: 'b').should == [['a'], {b: 'b'}] end end + + it "works in method definitions using **nil" do + parent = Class.new do + def m(*args, **kwargs) + [args, kwargs] + end + end + child = Class.new(parent) do + def m(*args, **nil) + super + end + end + child.new.m(1, 2).should == [[1, 2], {}] + end end diff --git a/spec/ruby/language/yield_spec.rb b/spec/ruby/language/yield_spec.rb index e125cf8e738bea..3b1313914f2ede 100644 --- a/spec/ruby/language/yield_spec.rb +++ b/spec/ruby/language/yield_spec.rb @@ -48,6 +48,12 @@ it "passes a single, multi-value Array" do @y.s([1, 2, 3]) { |*a| a }.should == [[1, 2, 3]] end + + describe "with optional argument" do + it "does not destructure a single array argument" do + @y.s([1, 2, 3]) { |a = 99| a }.should == [1, 2, 3] + end + end end describe "yielding to a lambda" do diff --git a/spec/ruby/library/cgi/htmlextension/multipart_form_spec.rb b/spec/ruby/library/cgi/htmlextension/multipart_form_spec.rb index ee1c45b84e267e..4b56a7abfe34a2 100644 --- a/spec/ruby/library/cgi/htmlextension/multipart_form_spec.rb +++ b/spec/ruby/library/cgi/htmlextension/multipart_form_spec.rb @@ -11,7 +11,7 @@ end describe "when passed no arguments" do - it "returns a 'form'-element with it's enctype set to multipart" do + it "returns a 'form'-element with its enctype set to multipart" do output = @html.multipart_form output.should equal_element("FORM", { "ENCTYPE" => "multipart/form-data", "METHOD" => "post" }, "") end diff --git a/spec/ruby/library/pathname/glob_spec.rb b/spec/ruby/library/pathname/glob_spec.rb index de322bab476341..6249d0ae021bd6 100644 --- a/spec/ruby/library/pathname/glob_spec.rb +++ b/spec/ruby/library/pathname/glob_spec.rb @@ -41,7 +41,7 @@ it "raises an ArgumentError when supplied a keyword argument other than :base" do -> { Pathname.glob('*i*.rb', foo: @dir + 'lib') - }.should raise_error(ArgumentError, /unknown keyword: :?foo/) + }.should raise_error(ArgumentError, "unknown keyword: :foo") end it "does not raise an ArgumentError when supplied a flag and :base keyword argument" do diff --git a/spec/ruby/library/socket/addrinfo/initialize_spec.rb b/spec/ruby/library/socket/addrinfo/initialize_spec.rb index c556bd758b925a..931984b6515b43 100644 --- a/spec/ruby/library/socket/addrinfo/initialize_spec.rb +++ b/spec/ruby/library/socket/addrinfo/initialize_spec.rb @@ -53,11 +53,11 @@ @addrinfo.ip_port.should == 25 end - it "returns the specified family" do + it "returns the specified pfamily" do @addrinfo.pfamily.should == Socket::PF_INET6 end - it "returns the specified family" do + it "returns the specified afamily" do @addrinfo.afamily.should == Socket::AF_INET6 end @@ -83,11 +83,11 @@ @addrinfo.ip_port.should == 25 end - it "returns the specified family" do + it "returns the specified pfamily" do @addrinfo.pfamily.should == Socket::PF_INET6 end - it "returns the specified family" do + it "returns the specified afamily" do @addrinfo.afamily.should == Socket::AF_INET6 end @@ -113,11 +113,11 @@ @addrinfo.ip_port.should == 25 end - it "returns the specified family" do + it "returns the specified pfamily" do @addrinfo.pfamily.should == Socket::PF_INET6 end - it "returns the specified family" do + it "returns the specified afamily" do @addrinfo.afamily.should == Socket::AF_INET6 end @@ -147,11 +147,11 @@ @addrinfo.ip_port.should == 46102 end - it "returns the specified family" do + it "returns the specified pfamily" do @addrinfo.pfamily.should == Socket::PF_INET end - it "returns the specified family" do + it "returns the specified afamily" do @addrinfo.afamily.should == Socket::AF_INET end @@ -217,11 +217,11 @@ @addrinfo.ip_port.should == 46102 end - it "returns the specified family" do + it "returns the specified pfamily" do @addrinfo.pfamily.should == Socket::PF_INET end - it "returns the specified family" do + it "returns the specified afamily" do @addrinfo.afamily.should == Socket::AF_INET end @@ -247,11 +247,11 @@ @addrinfo.ip_port.should == 46102 end - it "returns the specified family" do + it "returns the specified pfamily" do @addrinfo.pfamily.should == Socket::PF_INET end - it "returns the specified family" do + it "returns the specified afamily" do @addrinfo.afamily.should == Socket::AF_INET end @@ -311,11 +311,11 @@ @addrinfo.ip_port.should == 46102 end - it "returns the specified family" do + it "returns the specified pfamily" do @addrinfo.pfamily.should == Socket::PF_INET end - it "returns the specified family" do + it "returns the specified afamily" do @addrinfo.afamily.should == Socket::AF_INET end @@ -514,13 +514,13 @@ @sockaddr = Socket.sockaddr_in(80, '127.0.0.1') end - it 'returns an Addrinfo with the specified family' do + it 'returns an Addrinfo with the specified pfamily for :PF_INET' do addr = Addrinfo.new(@sockaddr, :PF_INET) addr.pfamily.should == Socket::PF_INET end - it 'returns an Addrinfo with the specified family' do + it 'returns an Addrinfo with the specified pfamily for :INET' do addr = Addrinfo.new(@sockaddr, :INET) addr.pfamily.should == Socket::PF_INET @@ -544,13 +544,13 @@ @sockaddr = Socket.sockaddr_in(80, '127.0.0.1') end - it 'returns an Addrinfo with the specified family' do + it 'returns an Addrinfo with the specified pfamily for PF_INET' do addr = Addrinfo.new(@sockaddr, 'PF_INET') addr.pfamily.should == Socket::PF_INET end - it 'returns an Addrinfo with the specified family' do + it 'returns an Addrinfo with the specified pfamily for INET' do addr = Addrinfo.new(@sockaddr, 'INET') addr.pfamily.should == Socket::PF_INET diff --git a/spec/ruby/optional/capi/encoding_spec.rb b/spec/ruby/optional/capi/encoding_spec.rb index 734b5f125381db..260ebc88a437cc 100644 --- a/spec/ruby/optional/capi/encoding_spec.rb +++ b/spec/ruby/optional/capi/encoding_spec.rb @@ -452,7 +452,7 @@ describe "rb_enc_compatible" do it "returns 0 if the encodings of the Strings are not compatible" do a = [0xff].pack('C').force_encoding "binary" - b = "\u3042".encode("utf-8") + b = "あ" @s.rb_enc_compatible(a, b).should == 0 end @@ -461,11 +461,25 @@ # Encoding.compatible? it "returns the same value as Encoding.compatible? if the Strings have a compatible encoding" do a = "abc".force_encoding("us-ascii") - b = "\u3042".encode("utf-8") + b = "あ" @s.rb_enc_compatible(a, b).should == Encoding.compatible?(a, b) end end + describe "rb_enc_check" do + it "returns the compatible encoding of the two Strings" do + a = "abc".force_encoding("us-ascii") + b = "あ" + @s.rb_enc_check(a, b).should == Encoding::UTF_8 + end + + it "raises Encoding::CompatibilityError if the encodings are not compatible" do + a = [0xff].pack('C').b + b = "あ" + -> { @s.rb_enc_check(a, b) }.should raise_error(Encoding::CompatibilityError) + end + end + describe "rb_enc_copy" do before :each do @obj = "rb_enc_copy".encode(Encoding::US_ASCII) diff --git a/spec/ruby/optional/capi/ext/encoding_spec.c b/spec/ruby/optional/capi/ext/encoding_spec.c index 98d4e2e3b772c8..2038a5d4a85e50 100644 --- a/spec/ruby/optional/capi/ext/encoding_spec.c +++ b/spec/ruby/optional/capi/ext/encoding_spec.c @@ -91,6 +91,11 @@ static VALUE encoding_spec_rb_enc_compatible(VALUE self, VALUE a, VALUE b) { return rb_enc_from_encoding(enc); } +static VALUE encoding_spec_rb_enc_check(VALUE self, VALUE a, VALUE b) { + rb_encoding* enc = rb_enc_check(a, b); + return rb_enc_from_encoding(enc); +} + static VALUE encoding_spec_rb_enc_copy(VALUE self, VALUE dest, VALUE src) { rb_enc_copy(dest, src); return dest; @@ -353,6 +358,7 @@ void Init_encoding_spec(void) { rb_define_method(cls, "rb_enc_associate", encoding_spec_rb_enc_associate, 2); rb_define_method(cls, "rb_enc_associate_index", encoding_spec_rb_enc_associate_index, 2); rb_define_method(cls, "rb_enc_compatible", encoding_spec_rb_enc_compatible, 2); + rb_define_method(cls, "rb_enc_check", encoding_spec_rb_enc_check, 2); rb_define_method(cls, "rb_enc_copy", encoding_spec_rb_enc_copy, 2); rb_define_method(cls, "rb_enc_codelen", encoding_spec_rb_enc_codelen, 2); rb_define_method(cls, "rb_enc_strlen", encoding_spec_rb_enc_strlen, 3); diff --git a/spec/ruby/optional/capi/ext/io_spec.c b/spec/ruby/optional/capi/ext/io_spec.c index f3ede15729bde2..fe31cffb495241 100644 --- a/spec/ruby/optional/capi/ext/io_spec.c +++ b/spec/ruby/optional/capi/ext/io_spec.c @@ -287,6 +287,18 @@ VALUE io_spec_rb_cloexec_open(VALUE self, VALUE path, VALUE flags, VALUE mode) { return rb_funcall(rb_cIO, rb_intern("for_fd"), 1, INT2FIX(fd)); } +VALUE io_spec_rb_cloexec_dup(VALUE self, VALUE io) { + int fd = io_spec_get_fd(io); + int new_fd = rb_cloexec_dup(fd); + return rb_funcall(rb_cIO, rb_intern("for_fd"), 1, INT2FIX(new_fd)); +} + +VALUE io_spec_rb_cloexec_fcntl_dupfd(VALUE self, VALUE io, VALUE minfd) { + int fd = io_spec_get_fd(io); + int new_fd = rb_cloexec_fcntl_dupfd(fd, FIX2INT(minfd)); + return rb_funcall(rb_cIO, rb_intern("for_fd"), 1, INT2FIX(new_fd)); +} + VALUE io_spec_rb_io_close(VALUE self, VALUE io) { return rb_io_close(io); } @@ -319,13 +331,7 @@ static VALUE io_spec_errno_set(VALUE self, VALUE val) { VALUE io_spec_mode_sync_flag(VALUE self, VALUE io) { int mode; -#ifdef RUBY_VERSION_IS_3_3 mode = rb_io_mode(io); -#else - rb_io_t *fp; - GetOpenFile(io, fp); - mode = fp->mode; -#endif if (mode & FMODE_SYNC) { return Qtrue; } else { @@ -333,7 +339,6 @@ VALUE io_spec_mode_sync_flag(VALUE self, VALUE io) { } } -#if defined(RUBY_VERSION_IS_3_3) || defined(TRUFFLERUBY) static VALUE io_spec_rb_io_mode(VALUE self, VALUE io) { return INT2FIX(rb_io_mode(io)); } @@ -360,7 +365,6 @@ static VALUE io_spec_rb_io_open_descriptor(VALUE self, VALUE klass, VALUE descri static VALUE io_spec_rb_io_open_descriptor_without_encoding(VALUE self, VALUE klass, VALUE descriptor, VALUE mode, VALUE path, VALUE timeout) { return rb_io_open_descriptor(klass, FIX2INT(descriptor), FIX2INT(mode), path, timeout, NULL); } -#endif void Init_io_spec(void) { VALUE cls = rb_define_class("CApiIOSpecs", rb_cObject); @@ -391,9 +395,10 @@ void Init_io_spec(void) { rb_define_method(cls, "rb_io_binmode", io_spec_rb_io_binmode, 1); rb_define_method(cls, "rb_fd_fix_cloexec", io_spec_rb_fd_fix_cloexec, 1); rb_define_method(cls, "rb_cloexec_open", io_spec_rb_cloexec_open, 3); + rb_define_method(cls, "rb_cloexec_dup", io_spec_rb_cloexec_dup, 1); + rb_define_method(cls, "rb_cloexec_fcntl_dupfd", io_spec_rb_cloexec_fcntl_dupfd, 2); rb_define_method(cls, "errno=", io_spec_errno_set, 1); rb_define_method(cls, "rb_io_mode_sync_flag", io_spec_mode_sync_flag, 1); -#if defined(RUBY_VERSION_IS_3_3) || defined(TRUFFLERUBY) rb_define_method(cls, "rb_io_mode", io_spec_rb_io_mode, 1); rb_define_method(cls, "rb_io_path", io_spec_rb_io_path, 1); rb_define_method(cls, "rb_io_closed_p", io_spec_rb_io_closed_p, 1); @@ -404,7 +409,6 @@ void Init_io_spec(void) { rb_define_const(cls, "FMODE_BINMODE", INT2FIX(FMODE_BINMODE)); rb_define_const(cls, "FMODE_TEXTMODE", INT2FIX(FMODE_TEXTMODE)); rb_define_const(cls, "ECONV_UNIVERSAL_NEWLINE_DECORATOR", INT2FIX(ECONV_UNIVERSAL_NEWLINE_DECORATOR)); -#endif } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/rubyspec.h b/spec/ruby/optional/capi/ext/rubyspec.h index 6c4bea5da0e124..7107bead90ebff 100644 --- a/spec/ruby/optional/capi/ext/rubyspec.h +++ b/spec/ruby/optional/capi/ext/rubyspec.h @@ -43,8 +43,4 @@ #define RUBY_VERSION_IS_3_4 #endif -#if RUBY_VERSION_SINCE(3, 3) -#define RUBY_VERSION_IS_3_3 -#endif - #endif diff --git a/spec/ruby/optional/capi/ext/string_spec.c b/spec/ruby/optional/capi/ext/string_spec.c index 74aa9e56e816fe..f41d6fa1737057 100644 --- a/spec/ruby/optional/capi/ext/string_spec.c +++ b/spec/ruby/optional/capi/ext/string_spec.c @@ -296,6 +296,26 @@ VALUE string_spec_rb_str_substr(VALUE self, VALUE str, VALUE beg, VALUE len) { return rb_str_substr(str, FIX2INT(beg), FIX2INT(len)); } +VALUE string_spec_rb_str_subpos(VALUE self, VALUE str, VALUE beg) { + char* original = RSTRING_PTR(str); + char* end = RSTRING_END(str); + long len = rb_str_strlen(str); + char *p = rb_str_subpos(str, FIX2LONG(beg), &len); + if (p == NULL) { + return Qnil; + } + + if (p >= original && p <= end) { + return rb_ary_new_from_args(2, LONG2FIX(p - RSTRING_PTR(str)), LONG2FIX(len)); + } else { + rb_raise(rb_eRuntimeError, "the returned pointer is not inside the original string buffer"); + } +} + +VALUE string_spec_rb_str_sublen(VALUE self, VALUE str, VALUE pos) { + return LONG2FIX(rb_str_sublen(str, FIX2LONG(pos))); +} + VALUE string_spec_rb_str_to_str(VALUE self, VALUE arg) { return rb_str_to_str(arg); } @@ -308,6 +328,11 @@ VALUE string_spec_RSTRING_LENINT(VALUE self, VALUE str) { return INT2FIX(RSTRING_LENINT(str)); } +VALUE string_spec_RSTRING_PTR(VALUE self, VALUE str) { + char* ptr = RSTRING_PTR(str); + return LONG2FIX((long)ptr); +} + VALUE string_spec_RSTRING_PTR_iterate(VALUE self, VALUE str) { int i; char* ptr; @@ -393,6 +418,7 @@ VALUE string_spec_RSTRING_PTR_read(VALUE self, VALUE str, VALUE path) { if (read(fd, buffer, 30) < 0) { rb_syserr_fail(errno, "read"); } + rb_str_set_len(str, 30); rb_str_modify_expand(str, 53); rb_ary_push(capacities, SIZET2NUM(rb_str_capacity(str))); @@ -531,7 +557,10 @@ static VALUE string_spec_rb_str_modify(VALUE self, VALUE str) { } static VALUE string_spec_rb_utf8_str_new_static(VALUE self) { - return rb_utf8_str_new_static("nokogiri", 8); + const char* literal = "nokogiri"; + return rb_ary_new_from_args(2, + rb_utf8_str_new_static("nokogiri", 8), + LONG2FIX((long)literal)); } static VALUE string_spec_rb_utf8_str_new(VALUE self) { @@ -645,9 +674,12 @@ void Init_string_spec(void) { rb_define_method(cls, "rb_str_split", string_spec_rb_str_split, 1); rb_define_method(cls, "rb_str_subseq", string_spec_rb_str_subseq, 3); rb_define_method(cls, "rb_str_substr", string_spec_rb_str_substr, 3); + rb_define_method(cls, "rb_str_subpos", string_spec_rb_str_subpos, 2); + rb_define_method(cls, "rb_str_sublen", string_spec_rb_str_sublen, 2); rb_define_method(cls, "rb_str_to_str", string_spec_rb_str_to_str, 1); rb_define_method(cls, "RSTRING_LEN", string_spec_RSTRING_LEN, 1); rb_define_method(cls, "RSTRING_LENINT", string_spec_RSTRING_LENINT, 1); + rb_define_method(cls, "RSTRING_PTR", string_spec_RSTRING_PTR, 1); rb_define_method(cls, "RSTRING_PTR_iterate", string_spec_RSTRING_PTR_iterate, 1); rb_define_method(cls, "RSTRING_PTR_iterate_uint32", string_spec_RSTRING_PTR_iterate_uint32, 1); rb_define_method(cls, "RSTRING_PTR_short_memcpy", string_spec_RSTRING_PTR_short_memcpy, 1); diff --git a/spec/ruby/optional/capi/ext/struct_spec.c b/spec/ruby/optional/capi/ext/struct_spec.c index 756cfca8dd37bf..1c669d153ec9b7 100644 --- a/spec/ruby/optional/capi/ext/struct_spec.c +++ b/spec/ruby/optional/capi/ext/struct_spec.c @@ -66,7 +66,6 @@ static VALUE struct_spec_rb_struct_initialize(VALUE self, VALUE st, VALUE values return rb_struct_initialize(st, values); } -#if defined(RUBY_VERSION_IS_3_3) /* Only allow setting three attributes, should be sufficient for testing. */ static VALUE struct_spec_rb_data_define(VALUE self, VALUE superclass, VALUE attr1, VALUE attr2, VALUE attr3) { @@ -81,7 +80,6 @@ static VALUE struct_spec_rb_data_define(VALUE self, VALUE superclass, return rb_data_define(superclass, a1, a2, a3, NULL); } -#endif void Init_struct_spec(void) { VALUE cls = rb_define_class("CApiStructSpecs", rb_cObject); @@ -95,9 +93,7 @@ void Init_struct_spec(void) { rb_define_method(cls, "rb_struct_new", struct_spec_rb_struct_new, 4); rb_define_method(cls, "rb_struct_size", struct_spec_rb_struct_size, 1); rb_define_method(cls, "rb_struct_initialize", struct_spec_rb_struct_initialize, 2); -#if defined(RUBY_VERSION_IS_3_3) rb_define_method(cls, "rb_data_define", struct_spec_rb_data_define, 4); -#endif } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/util_spec.c b/spec/ruby/optional/capi/ext/util_spec.c index b5bde420d212dc..043da99ace5f91 100644 --- a/spec/ruby/optional/capi/ext/util_spec.c +++ b/spec/ruby/optional/capi/ext/util_spec.c @@ -20,15 +20,11 @@ VALUE util_spec_rb_scan_args(VALUE self, VALUE argv, VALUE fmt, VALUE expected, a1 = a2 = a3 = a4 = a5 = a6 = INT2FIX(-1); -#ifdef RB_SCAN_ARGS_KEYWORDS if (*RSTRING_PTR(fmt) == 'k') { result = rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, args, RSTRING_PTR(fmt)+1, &a1, &a2, &a3, &a4, &a5, &a6); } else { -#endif result = rb_scan_args(argc, args, RSTRING_PTR(fmt), &a1, &a2, &a3, &a4, &a5, &a6); -#ifdef RB_SCAN_ARGS_KEYWORDS } -#endif switch(NUM2INT(expected)) { case 6: diff --git a/spec/ruby/optional/capi/io_spec.rb b/spec/ruby/optional/capi/io_spec.rb index dc4ac3e3744ce8..94874c7ae2bd27 100644 --- a/spec/ruby/optional/capi/io_spec.rb +++ b/spec/ruby/optional/capi/io_spec.rb @@ -15,7 +15,7 @@ end after :each do - @io.close unless @io.closed? + @io.close rm_r @name end @@ -118,7 +118,7 @@ end after :each do - @io.close unless @io.closed? + @io.close rm_r @name end @@ -213,9 +213,9 @@ end after :each do - @r_io.close unless @r_io.closed? - @w_io.close unless @w_io.closed? - @rw_io.close unless @rw_io.closed? + @r_io.close + @w_io.close + @rw_io.close rm_r @name end @@ -678,7 +678,7 @@ def path.to_str; "a.txt"; end end after :each do - @io.close unless @io.closed? + @io.close rm_r @name end @@ -699,7 +699,7 @@ def path.to_str; "a.txt"; end end after :each do - @io.close unless @io.nil? || @io.closed? + @io.close if @io rm_r @name end @@ -709,6 +709,58 @@ def path.to_str; "a.txt"; end end end +describe "rb_cloexec_dup" do + before :each do + @o = CApiIOSpecs.new + @name = tmp("c_api_rb_io_specs") + touch @name + + @io = new_io @name, "r" + @dup = nil + end + + after :each do + @dup.close if @dup + @io.close + rm_r @name + end + + it "duplicates a file descriptor and sets close_on_exec" do + @dup = @o.rb_cloexec_dup(@io) + @dup.should.close_on_exec? + @dup.fileno.should_not == @io.fileno + end +end + +describe "rb_cloexec_fcntl_dupfd" do + before :each do + @o = CApiIOSpecs.new + @name = tmp("c_api_rb_io_specs") + touch @name + + @io = new_io @name, "r" + @dup = nil + end + + after :each do + @dup.close if @dup + @io.close + rm_r @name + end + + it "duplicates a file descriptor and sets close_on_exec" do + @dup = @o.rb_cloexec_fcntl_dupfd(@io, 3) + @dup.close_on_exec?.should be_true + @dup.fileno.should_not == @io.fileno + end + + it "returns a file descriptor greater than or equal to minfd" do + @dup = @o.rb_cloexec_fcntl_dupfd(@io, 100) + @dup.fileno.should >= 100 + @dup.close_on_exec?.should be_true + end +end + describe "rb_io_t modes flags" do before :each do @o = CApiIOSpecs.new diff --git a/spec/ruby/optional/capi/string_spec.rb b/spec/ruby/optional/capi/string_spec.rb index 889f0a6cfe5d51..6861366dd3e033 100644 --- a/spec/ruby/optional/capi/string_spec.rb +++ b/spec/ruby/optional/capi/string_spec.rb @@ -536,6 +536,61 @@ def inspect end end + describe "rb_str_sublen" do + it "returns the character length for a given byte offset in an ASCII string" do + @s.rb_str_sublen("hello", 3).should == 3 + end + + it "returns the character length for a given byte offset in a multibyte string" do + # "hëllo" where 'ë' is 2 bytes in UTF-8, total 6 bytes + str = "hëllo" + @s.rb_str_sublen(str, 3).should == 2 + end + + it "returns 0 for byte offset 0" do + @s.rb_str_sublen("hello", 0).should == 0 + end + + it "returns the full character length for the total byte length" do + str = "hëllo" + @s.rb_str_sublen(str, str.bytesize).should == str.length + end + end + + describe "rb_str_subpos" do + it "returns [byte_offset, byte_length] for a valid character offset in an ASCII string" do + @s.rb_str_subpos("hello", 1).should == [1, 4] + end + + it "returns [byte_offset, byte_length] for a valid character offset in a multibyte string" do + # "hëllo" where 'ë' is 2 bytes in UTF-8 + str = "hëllo" + @s.rb_str_subpos(str, 0).should == [0, 6] + @s.rb_str_subpos(str, 1).should == [1, 5] + @s.rb_str_subpos(str, 2).should == [3, 3] + end + + it "returns [0, byte_length] for offset 0" do + @s.rb_str_subpos("hello", 0).should == [0, 5] + end + + it "returns nil for a negative offset that is out of range" do + @s.rb_str_subpos("hello", -6).should be_nil + end + + it "returns the correct position for a negative offset" do + @s.rb_str_subpos("hello", -2).should == [3, 2] + end + + it "returns [byte_length, 0] when offset equals string length" do + @s.rb_str_subpos("hello", 5).should == [5, 0] + end + + it "returns nil when offset is beyond string length" do + @s.rb_str_subpos("hello", 6).should be_nil + end + end + describe "rb_str_to_str" do it "calls #to_str to coerce the value to a String" do @s.rb_str_to_str("foo").should == "foo" @@ -1175,9 +1230,11 @@ def inspect describe "rb_utf8_str_new_static" do it "returns a UTF-8 string of the correct characters and length" do - str = @s.rb_utf8_str_new_static + str, ptr = @s.rb_utf8_str_new_static str.should == "nokogiri" str.encoding.should == Encoding::UTF_8 + + @s.RSTRING_PTR(str).should == ptr end end diff --git a/spec/ruby/optional/capi/util_spec.rb b/spec/ruby/optional/capi/util_spec.rb index 6cf064bf973b36..31754af0510b34 100644 --- a/spec/ruby/optional/capi/util_spec.rb +++ b/spec/ruby/optional/capi/util_spec.rb @@ -21,11 +21,11 @@ end it "raises an ArgumentError if there are insufficient arguments" do - -> { @o.rb_scan_args([1, 2], "3", 0, @acc) }.should raise_error(ArgumentError) + -> { @o.rb_scan_args([1, 2], "3", 0, @acc) }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 3)") end it "raises an ArgumentError if there are too many arguments" do - -> { @o.rb_scan_args([1, 2, 3, 4], "3", 0, @acc) }.should raise_error(ArgumentError) + -> { @o.rb_scan_args([1, 2, 3, 4], "3", 0, @acc) }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 3)") end it "assigns the required and optional arguments scanned" do @@ -117,8 +117,15 @@ it "rejects the use of nil as a hash" do -> { - @o.rb_scan_args([1, nil], "1:", 2, @acc).should == 1 - }.should raise_error(ArgumentError) + @o.rb_scan_args([1, nil], "1:", 2, @acc) + }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 1)") + ScratchPad.recorded.should == [] + end + + it "rejects the use of of a non-Hash as keywords" do + -> { + @o.rb_scan_args([42], ":", 1, @acc) + }.should raise_error(ArgumentError, "wrong number of arguments (given 1, expected 0)") ScratchPad.recorded.should == [] end @@ -186,7 +193,7 @@ it "raises an error if a required argument is not in the hash" do h = { :a => 7, :c => 12, :b => 5 } - -> { @o.rb_get_kwargs(h, [:b, :d], 2, 0) }.should raise_error(ArgumentError, /missing keyword: :?d/) + -> { @o.rb_get_kwargs(h, [:b, :d], 2, 0) }.should raise_error(ArgumentError, "missing keyword: :d") h.should == {:a => 7, :c => 12} end @@ -198,7 +205,7 @@ it "raises an error if there are additional arguments and optional is positive" do h = { :a => 7, :c => 12, :b => 5 } - -> { @o.rb_get_kwargs(h, [:b, :a], 2, 0) }.should raise_error(ArgumentError, /unknown keyword: :?c/) + -> { @o.rb_get_kwargs(h, [:b, :a], 2, 0) }.should raise_error(ArgumentError, "unknown keyword: :c") h.should == {:c => 12} end diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb index 2be06ea797aa6d..c15eb926bc7b95 100644 --- a/spec/ruby/shared/kernel/raise.rb +++ b/spec/ruby/shared/kernel/raise.rb @@ -140,11 +140,35 @@ def e.exception } end end +end - ruby_version_is "4.0" do - it "allows cause keyword argument" do +describe :kernel_raise_with_cause, shared: true do + context "without cause keyword argument" do + it "sets cause to nil when there is no previous exception" do + -> do + @object.raise("error without a cause") + end.should raise_error(RuntimeError, "error without a cause") do |error| + error.cause.should == nil + end + end + + it "supports automatic cause chaining from a previous exception" do + -> do + begin + raise StandardError,"first error" + rescue + @object.raise("second error") + end + end.should raise_error(RuntimeError, "second error") do |error| + error.cause.should be_kind_of(StandardError) + error.cause.message.should == "first error" + end + end + end + + context "with cause keyword argument" do + it "allows setting exception's cause" do cause = StandardError.new("original error") - result = nil -> do @object.raise("new error", cause: cause) @@ -153,6 +177,14 @@ def e.exception end end + it "allows setting cause to nil" do + -> do + @object.raise("error without a cause", cause: nil) + end.should raise_error(RuntimeError, "error without a cause") do |error| + error.cause.should == nil + end + end + it "raises an ArgumentError when only cause is given" do cause = StandardError.new("cause") -> do @@ -166,7 +198,7 @@ def e.exception end.should raise_error(ArgumentError, "only cause is given with no arguments") end - it "raises a TypeError when given cause is not an instance of Exception" do + it "raises a TypeError when given cause is not an instance of Exception or nil" do cause = Object.new -> do @object.raise("message", cause: cause) @@ -175,7 +207,6 @@ def e.exception it "doesn't set given cause when it equals the raised exception" do cause = StandardError.new("cause") - result = nil -> do @object.raise(cause, cause: cause) @@ -185,18 +216,7 @@ def e.exception end end - it "accepts cause equal an exception" do - error = RuntimeError.new("message") - result = nil - - -> do - @object.raise(error, cause: error) - end.should raise_error(RuntimeError, "message") do |e| - e.cause.should == nil - end - end - - it "rejects circular causes" do + it "raises ArgumentError when cause creates a circular reference" do -> { begin raise "Error 1" @@ -216,7 +236,6 @@ def e.exception it "supports exception class with message and cause" do cause = StandardError.new("cause message") - result = nil -> do @object.raise(ArgumentError, "argument error message", cause: cause) @@ -230,7 +249,6 @@ def e.exception it "supports exception class with message, backtrace and cause" do cause = StandardError.new("cause message") backtrace = ["line1", "line2"] - result = nil -> do @object.raise(ArgumentError, "argument error message", backtrace, cause: cause) @@ -242,21 +260,21 @@ def e.exception end end - it "supports automatic cause chaining" do + it "supports cause: exception, overriding previous exception" do + custom_error = StandardError.new("custom error") -> do begin raise "first error" rescue - # No explicit cause - should chain automatically: - @object.raise("second error") + @object.raise("second error", cause: custom_error) end end.should raise_error(RuntimeError, "second error") do |error| - error.cause.should be_kind_of(RuntimeError) - error.cause.message.should == "first error" + error.cause.should be_kind_of(StandardError) + error.cause.message.should == "custom error" end end - it "supports cause: nil to prevent automatic cause chaining" do + it "supports cause: nil, discarding previous exception" do -> do begin raise "first error" diff --git a/spec/ruby/shared/process/fork.rb b/spec/ruby/shared/process/fork.rb index 8dbb3d0da43db6..35e712572f105f 100644 --- a/spec/ruby/shared/process/fork.rb +++ b/spec/ruby/shared/process/fork.rb @@ -74,16 +74,19 @@ it "marks threads from the parent as killed" do t = Thread.new { sleep } - pid = @object.fork { - touch(@file) do |f| - f.write Thread.current.alive? - f.write t.alive? - end - Process.exit! - } - Process.waitpid(pid) - t.kill - t.join + begin + pid = @object.fork { + touch(@file) do |f| + f.write Thread.current.alive? + f.write t.alive? + end + Process.exit! + } + Process.waitpid(pid) + ensure + t.kill + t.join + end File.read(@file).should == "truefalse" end end From f9c51ac5ea42382a22fb8a2cc5d7aeb78e77c962 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 3 Mar 2026 18:01:30 -0500 Subject: [PATCH 09/10] ZJIT: Fix Class type system bug and constant-fold IsA (#15268) Fix a some bugs in the type lattice around classes and user-defined classes. Refine types more precisely to subclasses of object. With that fix, add const-folding support for IsA. Co-authored-by: John Hawthorn Co-authored-by: Luke Gruber --- zjit/src/hir.rs | 18 ++ zjit/src/hir/opt_tests.rs | 495 ++++++++++++++++++++---------- zjit/src/hir/tests.rs | 22 +- zjit/src/hir_type/gen_hir_type.rb | 18 +- zjit/src/hir_type/hir_type.inc.rs | 28 +- zjit/src/hir_type/mod.rs | 15 +- 6 files changed, 409 insertions(+), 187 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index a88eee26d9101f..7487f8c3e3d26e 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4679,6 +4679,24 @@ impl Function { // Don't bother re-inferring the type of str; we already know it. continue; } + Insn::IsA { val, class } => 'is_a: { + let class_type = self.type_of(class); + if !class_type.is_subtype(types::Class) { + break 'is_a insn_id; + } + let Some(class_value) = class_type.ruby_object() else { + break 'is_a insn_id; + }; + let val_type = self.type_of(val); + let the_class = Type::from_class_inexact(class_value); + if val_type.is_subtype(the_class) { + self.new_insn(Insn::Const { val: Const::Value(Qtrue) }) + } else if !val_type.could_be(the_class) { + self.new_insn(Insn::Const { val: Const::Value(Qfalse) }) + } else { + insn_id + } + } Insn::FixnumAdd { left, right, .. } => { self.fold_fixnum_bop(insn_id, left, right, |l, r| match (l, r) { (Some(l), Some(r)) => l.checked_add(r), diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 53f9e4a138f019..16298db6a28871 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -957,7 +957,7 @@ mod hir_opt_tests { custom.count "); assert_eq!(VALUE::fixnum_from_usize(2), result); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:13: bb1(): EntryPoint interpreter @@ -973,7 +973,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(CustomEq@0x1008) PatchPoint MethodRedefined(CustomEq@0x1008, !=@0x1010, cme:0x1018) - v30:HeapObject[class_exact:CustomEq] = GuardType v10, HeapObject[class_exact:CustomEq] + v30:ObjectSubclass[class_exact:CustomEq] = GuardType v10, ObjectSubclass[class_exact:CustomEq] v31:BoolExact = CCallWithFrame v30, :BasicObject#!=@0x1040, v10 v21:NilClass = Const Value(nil) CheckInterrupts @@ -1133,7 +1133,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v20:BasicObject = SendDirect v19, 0x1038, :foo (0x1048) CheckInterrupts Return v20 @@ -1162,7 +1162,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, baz@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v22:Fixnum[1] = Const Value(1) CheckInterrupts @@ -1191,7 +1191,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, baz@0x1008, cme:0x1010) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v20 @@ -1230,7 +1230,7 @@ mod hir_opt_tests { v11:Fixnum[1] = Const Value(1) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v22:BasicObject = SendDirect v21, 0x1038, :foo (0x1048), v11 CheckInterrupts Return v22 @@ -1282,7 +1282,7 @@ mod hir_opt_tests { def test(o) = o.bar { |x| x } test C.new; test C.new "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:7: bb1(): EntryPoint interpreter @@ -1298,7 +1298,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, bar@0x1010, cme:0x1018) - v26:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v26:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] v27:BasicObject = CCallWithFrame v26, :Enumerable#bar@0x1040, block=0x1048 v16:CPtr = GetEP 0 v17:BasicObject = LoadField v16, :o@0x1050 @@ -1392,7 +1392,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v20:BasicObject = SendDirect v19, 0x1038, :foo (0x1048) CheckInterrupts Return v20 @@ -1421,7 +1421,7 @@ mod hir_opt_tests { v11:Fixnum[3] = Const Value(3) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, Integer@0x1008, cme:0x1010) - v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v22:BasicObject = SendDirect v21, 0x1038, :Integer (0x1048), v11 CheckInterrupts Return v22 @@ -1452,7 +1452,7 @@ mod hir_opt_tests { v13:Fixnum[2] = Const Value(2) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v23:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v24:BasicObject = SendDirect v23, 0x1038, :foo (0x1048), v11, v13 CheckInterrupts Return v24 @@ -1483,11 +1483,11 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v24:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v24:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v25:BasicObject = SendDirect v24, 0x1038, :foo (0x1048) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, bar@0x1050, cme:0x1058) - v28:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v28:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v29:BasicObject = SendDirect v28, 0x1038, :bar (0x1048) CheckInterrupts Return v29 @@ -1514,7 +1514,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v20:BasicObject = SendDirect v19, 0x1038, :foo (0x1048) CheckInterrupts Return v20 @@ -1542,7 +1542,7 @@ mod hir_opt_tests { v11:Fixnum[3] = Const Value(3) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v22:BasicObject = SendDirect v21, 0x1038, :foo (0x1048), v11 CheckInterrupts Return v22 @@ -1571,7 +1571,7 @@ mod hir_opt_tests { v13:Fixnum[4] = Const Value(4) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v23:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v24:BasicObject = SendDirect v23, 0x1038, :foo (0x1048), v11, v13 CheckInterrupts Return v24 @@ -1599,14 +1599,14 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) - v45:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v45:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v46:BasicObject = SendDirect v45, 0x1038, :target (0x1048) v14:Fixnum[10] = Const Value(10) v16:Fixnum[20] = Const Value(20) v18:Fixnum[30] = Const Value(30) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) - v49:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v49:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v50:BasicObject = SendDirect v49, 0x1038, :target (0x1048), v14, v16, v18 v24:Fixnum[10] = Const Value(10) v26:Fixnum[20] = Const Value(20) @@ -1643,7 +1643,7 @@ mod hir_opt_tests { v12:StringExact = StringCopy v11 PatchPoint NoSingletonClass(Object@0x1008) PatchPoint MethodRedefined(Object@0x1008, puts@0x1010, cme:0x1018) - v23:HeapObject[class_exact*:Object@VALUE(0x1008)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1008)] + v23:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] v24:BasicObject = CCallVariadic v23, :Kernel#puts@0x1040, v12 CheckInterrupts Return v24 @@ -3325,7 +3325,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v21:CPtr = GetEP 0 v22:BoolExact = IsBlockGiven v21 IncrCounter inline_cfunc_optimized_send_count @@ -3353,7 +3353,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v21:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts @@ -3383,7 +3383,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) - v24:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v24:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_cfunc_optimized_send_count v15:Fixnum[5] = Const Value(5) CheckInterrupts @@ -3499,7 +3499,7 @@ mod hir_opt_tests { test c "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:6: bb1(): EntryPoint interpreter @@ -3515,7 +3515,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) - v23:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] v24:BasicObject = SendDirect v23, 0x1040, :foo (0x1050) CheckInterrupts Return v24 @@ -3531,7 +3531,7 @@ mod hir_opt_tests { test "); assert_eq!(VALUE::fixnum_from_usize(3), result); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:3: bb1(): EntryPoint interpreter @@ -3546,7 +3546,7 @@ mod hir_opt_tests { v13:Fixnum[2] = Const Value(2) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v24:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v24:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v25:BasicObject = SendDirect v24, 0x1038, :foo (0x1048), v11, v13 CheckInterrupts Return v25 @@ -3565,7 +3565,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:4: bb1(): EntryPoint interpreter @@ -3581,7 +3581,7 @@ mod hir_opt_tests { v13:Fixnum[1] = Const Value(1) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v33:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v8, HeapObject[class_exact*:Object@VALUE(0x1000)] + v33:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v8, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v19:CPtr = GetEP 0 v20:BasicObject = LoadField v19, :a@0x1038 @@ -3667,7 +3667,7 @@ mod hir_opt_tests { v13:Fixnum[2] = Const Value(2) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v23:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v24:BasicObject = SendDirect v23, 0x1038, :foo (0x1048), v11, v13 CheckInterrupts Return v24 @@ -3698,7 +3698,7 @@ mod hir_opt_tests { v15:Fixnum[2] = Const Value(2) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v26:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v26:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v27:BasicObject = SendDirect v26, 0x1038, :foo (0x1048), v13, v15, v11 CheckInterrupts Return v27 @@ -3729,7 +3729,7 @@ mod hir_opt_tests { v15:Fixnum[1] = Const Value(1) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v26:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v26:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v27:BasicObject = SendDirect v26, 0x1038, :foo (0x1048), v11, v15, v13 CheckInterrupts Return v27 @@ -3759,7 +3759,7 @@ mod hir_opt_tests { v13:Fixnum[2] = Const Value(2) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v23:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v24:BasicObject = SendDirect v23, 0x1038, :foo (0x1048), v11, v13 CheckInterrupts Return v24 @@ -3790,7 +3790,7 @@ mod hir_opt_tests { v15:Fixnum[4] = Const Value(4) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v38:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v38:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v39:BasicObject = SendDirect v38, 0x1038, :foo (0x1048), v11, v13, v15 v20:Fixnum[1] = Const Value(1) v22:Fixnum[2] = Const Value(2) @@ -3798,7 +3798,7 @@ mod hir_opt_tests { v26:Fixnum[3] = Const Value(3) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v43:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v43:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v44:BasicObject = SendDirect v43, 0x1038, :foo (0x1048), v20, v22, v26, v24 v30:ArrayExact = NewArray v39, v44 CheckInterrupts @@ -3830,7 +3830,7 @@ mod hir_opt_tests { v34:Fixnum[4] = Const Value(4) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v38:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v38:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v39:BasicObject = SendDirect v38, 0x1038, :foo (0x1048), v11, v13, v34 v18:Fixnum[1] = Const Value(1) v20:Fixnum[2] = Const Value(2) @@ -3838,7 +3838,7 @@ mod hir_opt_tests { v24:Fixnum[30] = Const Value(30) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v43:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v43:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v44:BasicObject = SendDirect v43, 0x1038, :foo (0x1048), v18, v20, v24, v22 v28:ArrayExact = NewArray v39, v44 CheckInterrupts @@ -3868,7 +3868,7 @@ mod hir_opt_tests { v11:Fixnum[6] = Const Value(6) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) - v49:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v49:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v50:BasicObject = SendDirect v49, 0x1038, :target (0x1048), v11 v16:Fixnum[10] = Const Value(10) v18:Fixnum[20] = Const Value(20) @@ -3876,7 +3876,7 @@ mod hir_opt_tests { v22:Fixnum[6] = Const Value(6) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) - v53:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v53:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v54:BasicObject = SendDirect v53, 0x1038, :target (0x1048), v16, v18, v20, v22 v27:Fixnum[10] = Const Value(10) v29:Fixnum[20] = Const Value(20) @@ -3913,7 +3913,7 @@ mod hir_opt_tests { v11:Fixnum[2] = Const Value(2) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v22:BasicObject = SendDirect v21, 0x1038, :foo (0x1048), v11 CheckInterrupts Return v22 @@ -4023,7 +4023,7 @@ mod hir_opt_tests { v17:Fixnum[1] = Const Value(1) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v22:BasicObject = SendDirect v21, 0x1038, :foo (0x1048), v17 CheckInterrupts Return v22 @@ -4277,7 +4277,7 @@ mod hir_opt_tests { v43:Class[C@0x1008] = Const Value(VALUE(0x1008)) v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(C@0x1008, new@0x1009, cme:0x1010) - v46:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) + v46:ObjectSubclass[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040) v50:NilClass = Const Value(nil) @@ -4316,7 +4316,7 @@ mod hir_opt_tests { v12:NilClass = Const Value(nil) v15:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(C@0x1008, new@0x1009, cme:0x1010) - v49:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) + v49:ObjectSubclass[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040) v52:BasicObject = SendDirect v49, 0x1068, :initialize (0x1078), v15 @@ -4557,7 +4557,7 @@ mod hir_opt_tests { eval(" def test(a,b) = [a,b].length "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:2: bb1(): EntryPoint interpreter @@ -4589,7 +4589,7 @@ mod hir_opt_tests { eval(" def test(a,b) = [a,b].size "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:2: bb1(): EntryPoint interpreter @@ -4621,7 +4621,7 @@ mod hir_opt_tests { eval(" def test(&block) = tap(&block) "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:2: bb1(): EntryPoint interpreter @@ -4640,7 +4640,7 @@ mod hir_opt_tests { v17:CInt64 = GuardNoBitsSet v16, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v18:CInt64 = LoadField v15, :_env_data_index_specval@0x1002 v19:CInt64 = GuardAnyBitSet v18, CUInt64(1) - v20:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) + v20:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) v22:BasicObject = Send v9, 0x1001, :tap, v20 # SendFallbackReason: Uncategorized(send) CheckInterrupts Return v22 @@ -4827,7 +4827,7 @@ mod hir_opt_tests { end test p "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:4: bb1(): EntryPoint interpreter @@ -4844,7 +4844,7 @@ mod hir_opt_tests { v15:Fixnum[1] = Const Value(1) PatchPoint NoSingletonClass(Proc@0x1008) PatchPoint MethodRedefined(Proc@0x1008, call@0x1010, cme:0x1018) - v25:HeapObject[class_exact:Proc] = GuardType v10, HeapObject[class_exact:Proc] + v25:ObjectSubclass[class_exact:Proc] = GuardType v10, ObjectSubclass[class_exact:Proc] v26:BasicObject = InvokeProc v25, v15 CheckInterrupts Return v26 @@ -4860,7 +4860,7 @@ mod hir_opt_tests { end test p "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:4: bb1(): EntryPoint interpreter @@ -4877,7 +4877,7 @@ mod hir_opt_tests { v15:Fixnum[2] = Const Value(2) PatchPoint NoSingletonClass(Proc@0x1008) PatchPoint MethodRedefined(Proc@0x1008, []@0x1010, cme:0x1018) - v26:HeapObject[class_exact:Proc] = GuardType v10, HeapObject[class_exact:Proc] + v26:ObjectSubclass[class_exact:Proc] = GuardType v10, ObjectSubclass[class_exact:Proc] v27:BasicObject = InvokeProc v26, v15 CheckInterrupts Return v27 @@ -4893,7 +4893,7 @@ mod hir_opt_tests { end test p "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:4: bb1(): EntryPoint interpreter @@ -4910,7 +4910,7 @@ mod hir_opt_tests { v15:Fixnum[3] = Const Value(3) PatchPoint NoSingletonClass(Proc@0x1008) PatchPoint MethodRedefined(Proc@0x1008, yield@0x1010, cme:0x1018) - v25:HeapObject[class_exact:Proc] = GuardType v10, HeapObject[class_exact:Proc] + v25:ObjectSubclass[class_exact:Proc] = GuardType v10, ObjectSubclass[class_exact:Proc] v26:BasicObject = InvokeProc v25, v15 CheckInterrupts Return v26 @@ -4926,7 +4926,7 @@ mod hir_opt_tests { end test p "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:4: bb1(): EntryPoint interpreter @@ -4943,7 +4943,7 @@ mod hir_opt_tests { v15:Fixnum[1] = Const Value(1) PatchPoint NoSingletonClass(Proc@0x1008) PatchPoint MethodRedefined(Proc@0x1008, ===@0x1010, cme:0x1018) - v25:HeapObject[class_exact:Proc] = GuardType v10, HeapObject[class_exact:Proc] + v25:ObjectSubclass[class_exact:Proc] = GuardType v10, ObjectSubclass[class_exact:Proc] v26:BasicObject = InvokeProc v25, v15 CheckInterrupts Return v26 @@ -6289,11 +6289,11 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, zero@0x1008, cme:0x1010) - v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v23:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v31:StaticSymbol[:b] = Const Value(VALUE(0x1038)) PatchPoint MethodRedefined(Object@0x1000, one@0x1040, cme:0x1048) - v28:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v28:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count CheckInterrupts Return v31 @@ -6909,7 +6909,7 @@ mod hir_opt_tests { test(C.new, C.new) "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:3: bb1(): EntryPoint interpreter @@ -6927,7 +6927,7 @@ mod hir_opt_tests { bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, ==@0x1010, cme:0x1018) - v29:HeapObject[class_exact:C] = GuardType v12, HeapObject[class_exact:C] + v29:ObjectSubclass[class_exact:C] = GuardType v12, ObjectSubclass[class_exact:C] v30:CBool = IsBitEqual v29, v13 v31:BoolExact = BoxBool v30 IncrCounter inline_cfunc_optimized_send_count @@ -7024,7 +7024,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v22:NilClass = Const Value(nil) CheckInterrupts @@ -7047,7 +7047,7 @@ mod hir_opt_tests { test O test O "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:10: bb1(): EntryPoint interpreter @@ -7063,7 +7063,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) - v23:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] v26:CShape = LoadField v23, :_shape_id@0x1040 v27:CShape[0x1041] = GuardBitEquals v26, CShape(0x1041) v28:BasicObject = LoadField v23, :@foo@0x1042 @@ -7090,7 +7090,7 @@ mod hir_opt_tests { test O test O "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:13: bb1(): EntryPoint interpreter @@ -7106,7 +7106,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) - v23:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] v26:CShape = LoadField v23, :_shape_id@0x1040 v27:CShape[0x1041] = GuardBitEquals v26, CShape(0x1041) v28:CPtr = LoadField v23, :_as_heap@0x1042 @@ -7439,7 +7439,7 @@ mod hir_opt_tests { test O1 test O2 "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:20: bb1(): EntryPoint interpreter @@ -7453,21 +7453,21 @@ mod hir_opt_tests { v7:BasicObject = LoadArg :o@1 Jump bb3(v6, v7) bb3(v9:BasicObject, v10:BasicObject): - v15:CBool = HasType v10, HeapObject[class_exact:C] + v15:CBool = HasType v10, ObjectSubclass[class_exact:C] IfTrue v15, bb5(v9, v10, v10) - v24:CBool = HasType v10, HeapObject[class_exact:C] + v24:CBool = HasType v10, ObjectSubclass[class_exact:C] IfTrue v24, bb6(v9, v10, v10) v33:BasicObject = Send v10, :foo # SendFallbackReason: SendWithoutBlock: polymorphic fallback Jump bb4(v9, v10, v33) bb5(v16:BasicObject, v17:BasicObject, v18:BasicObject): - v20:HeapObject[class_exact:C] = RefineType v18, HeapObject[class_exact:C] + v20:ObjectSubclass[class_exact:C] = RefineType v18, ObjectSubclass[class_exact:C] PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) IncrCounter getivar_fallback_not_monomorphic v46:BasicObject = GetIvar v20, :@foo Jump bb4(v16, v17, v46) bb6(v25:BasicObject, v26:BasicObject, v27:BasicObject): - v29:HeapObject[class_exact:C] = RefineType v27, HeapObject[class_exact:C] + v29:ObjectSubclass[class_exact:C] = RefineType v27, ObjectSubclass[class_exact:C] PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) IncrCounter getivar_fallback_not_monomorphic @@ -7495,7 +7495,7 @@ mod hir_opt_tests { def test(o) = o.foo test obj "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:12: bb1(): EntryPoint interpreter @@ -7511,7 +7511,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) - v23:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] IncrCounter getivar_fallback_too_complex v24:BasicObject = GetIvar v23, :@foo CheckInterrupts @@ -7596,7 +7596,7 @@ mod hir_opt_tests { def test(&block) = [].map(&block) test { |x| x }; test { |x| x } "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:2: bb1(): EntryPoint interpreter @@ -7616,7 +7616,7 @@ mod hir_opt_tests { v18:CInt64 = GuardNoBitsSet v17, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v19:CInt64 = LoadField v16, :_env_data_index_specval@0x1002 v20:CInt64 = GuardAnyBitSet v19, CUInt64(1) - v21:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) + v21:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) IncrCounter complex_arg_pass_caller_blockarg v23:BasicObject = Send v14, 0x1001, :map, v21 # SendFallbackReason: Complex argument passing CheckInterrupts @@ -7685,7 +7685,7 @@ mod hir_opt_tests { v14:CInt64 = GuardNoBitsSet v13, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v15:CInt64 = LoadField v12, :_env_data_index_specval@0x1001 v16:CInt64 = GuardAnyBitSet v15, CUInt64(1) - v17:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) + v17:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) IncrCounter complex_arg_pass_caller_blockarg v19:BasicObject = Send v10, 0x1000, :map, v17 # SendFallbackReason: Complex argument passing CheckInterrupts @@ -7704,7 +7704,7 @@ mod hir_opt_tests { test; test "#); assert_eq!(VALUE::fixnum_from_usize(2), result); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:6: bb1(): EntryPoint interpreter @@ -7717,7 +7717,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] v21:BasicObject = SendDirect v20, 0x1038, :foo (0x1048) CheckInterrupts Return v21 @@ -7749,7 +7749,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, O) - v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) v25:CShape = LoadField v20, :_shape_id@0x1048 @@ -7785,7 +7785,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, O) - v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) v25:CShape = LoadField v20, :_shape_id@0x1048 @@ -7807,7 +7807,7 @@ mod hir_opt_tests { test C.new test C.new "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:6: bb1(): EntryPoint interpreter @@ -7823,7 +7823,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) - v23:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] v26:CShape = LoadField v23, :_shape_id@0x1040 v27:CShape[0x1041] = GuardBitEquals v26, CShape(0x1041) v28:NilClass = Const Value(nil) @@ -7843,7 +7843,7 @@ mod hir_opt_tests { test C.new test C.new "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:6: bb1(): EntryPoint interpreter @@ -7859,7 +7859,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) - v23:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] v26:CShape = LoadField v23, :_shape_id@0x1040 v27:CShape[0x1041] = GuardBitEquals v26, CShape(0x1041) v28:NilClass = Const Value(nil) @@ -7879,7 +7879,7 @@ mod hir_opt_tests { test C.new test C.new "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:6: bb1(): EntryPoint interpreter @@ -7895,7 +7895,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): v17:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1008, foo=@0x1010, cme:0x1018) - v28:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v28:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] v31:CShape = LoadField v28, :_shape_id@0x1040 v32:CShape[0x1041] = GuardBitEquals v31, CShape(0x1041) StoreField v28, :@foo@0x1042, v17 @@ -7918,7 +7918,7 @@ mod hir_opt_tests { test C.new test C.new "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:6: bb1(): EntryPoint interpreter @@ -7934,7 +7934,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): v17:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1008, foo=@0x1010, cme:0x1018) - v28:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v28:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] v31:CShape = LoadField v28, :_shape_id@0x1040 v32:CShape[0x1041] = GuardBitEquals v31, CShape(0x1041) StoreField v28, :@foo@0x1042, v17 @@ -7954,7 +7954,7 @@ mod hir_opt_tests { test C.new test C.new "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:3: bb1(): EntryPoint interpreter @@ -7970,7 +7970,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) - v23:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] v24:BasicObject = LoadField v23, :foo@0x1040 CheckInterrupts Return v24 @@ -7985,7 +7985,7 @@ mod hir_opt_tests { test C.new test C.new "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:3: bb1(): EntryPoint interpreter @@ -8001,7 +8001,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) - v23:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] v24:CPtr = LoadField v23, :_as_heap@0x1040 v25:BasicObject = LoadField v24, :foo@0x1041 CheckInterrupts @@ -8020,7 +8020,7 @@ mod hir_opt_tests { test C.new test C.new "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:4: bb1(): EntryPoint interpreter @@ -8036,7 +8036,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) - v27:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v27:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] v19:Fixnum[5] = Const Value(5) CheckInterrupts Return v19 @@ -8052,7 +8052,7 @@ mod hir_opt_tests { test C.new, value test C.new, value "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:3: bb1(): EntryPoint interpreter @@ -8070,7 +8070,7 @@ mod hir_opt_tests { bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo=@0x1010, cme:0x1018) - v31:HeapObject[class_exact:C] = GuardType v12, HeapObject[class_exact:C] + v31:ObjectSubclass[class_exact:C] = GuardType v12, ObjectSubclass[class_exact:C] v32:CUInt64 = LoadField v31, :_rbasic_flags@0x1040 v33:CUInt64 = GuardNoBitsSet v32, RUBY_FL_FREEZE=CUInt64(2048) StoreField v31, :foo=@0x1041, v13 @@ -8089,7 +8089,7 @@ mod hir_opt_tests { test C.new, value test C.new, value "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:3: bb1(): EntryPoint interpreter @@ -8107,7 +8107,7 @@ mod hir_opt_tests { bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo=@0x1010, cme:0x1018) - v31:HeapObject[class_exact:C] = GuardType v12, HeapObject[class_exact:C] + v31:ObjectSubclass[class_exact:C] = GuardType v12, ObjectSubclass[class_exact:C] v32:CUInt64 = LoadField v31, :_rbasic_flags@0x1040 v33:CUInt64 = GuardNoBitsSet v32, RUBY_FL_FREEZE=CUInt64(2048) v34:CPtr = LoadField v31, :_as_heap@0x1041 @@ -8313,7 +8313,7 @@ mod hir_opt_tests { def test(x) = x.to_s test (2**65) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:2: bb1(): EntryPoint interpreter @@ -8328,7 +8328,7 @@ mod hir_opt_tests { Jump bb3(v6, v7) bb3(v9:BasicObject, v10:BasicObject): PatchPoint MethodRedefined(Integer@0x1008, to_s@0x1010, cme:0x1018) - v23:Integer = GuardType v10, Integer + v23:Bignum = GuardType v10, Bignum v24:StringExact = CCallVariadic v23, :Integer#to_s@0x1040 CheckInterrupts Return v24 @@ -9149,7 +9149,7 @@ mod hir_opt_tests { test([]) "); assert_contains_opcode("test", YARVINSN_opt_length); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:2: bb1(): EntryPoint interpreter @@ -9181,7 +9181,7 @@ mod hir_opt_tests { test([]) "); assert_contains_opcode("test", YARVINSN_opt_size); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:2: bb1(): EntryPoint interpreter @@ -9549,7 +9549,7 @@ mod hir_opt_tests { test(4 << 70) "); assert_contains_opcode("test", YARVINSN_opt_succ); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:2: bb1(): EntryPoint interpreter @@ -9564,7 +9564,7 @@ mod hir_opt_tests { Jump bb3(v6, v7) bb3(v9:BasicObject, v10:BasicObject): PatchPoint MethodRedefined(Integer@0x1008, succ@0x1010, cme:0x1018) - v24:Integer = GuardType v10, Integer + v24:Bignum = GuardType v10, Bignum v25:BasicObject = CCallWithFrame v24, :Integer#succ@0x1040 CheckInterrupts Return v25 @@ -10150,7 +10150,7 @@ mod hir_opt_tests { def test(x, y) = x ^ y test(4 << 70, 1) "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:2: bb1(): EntryPoint interpreter @@ -10167,7 +10167,7 @@ mod hir_opt_tests { Jump bb3(v7, v8, v9) bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject): PatchPoint MethodRedefined(Integer@0x1008, ^@0x1010, cme:0x1018) - v27:Integer = GuardType v12, Integer + v27:Bignum = GuardType v12, Bignum v28:BasicObject = CCallWithFrame v27, :Integer#^@0x1040, v13 CheckInterrupts Return v28 @@ -10327,7 +10327,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo) test(C.new) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:5: bb1(): EntryPoint interpreter @@ -10344,7 +10344,7 @@ mod hir_opt_tests { v15:StaticSymbol[:foo] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, respond_to?@0x1018, cme:0x1020) - v26:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v26:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] PatchPoint MethodRedefined(C@0x1010, foo@0x1048, cme:0x1050) v30:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count @@ -10361,7 +10361,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo) test(C.new) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:4: bb1(): EntryPoint interpreter @@ -10378,7 +10378,7 @@ mod hir_opt_tests { v15:StaticSymbol[:foo] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, respond_to?@0x1018, cme:0x1020) - v26:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v26:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] PatchPoint MethodRedefined(C@0x1010, respond_to_missing?@0x1048, cme:0x1050) PatchPoint MethodRedefined(C@0x1010, foo@0x1078, cme:0x1080) v32:FalseClass = Const Value(false) @@ -10398,7 +10398,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo) test(C.new) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:6: bb1(): EntryPoint interpreter @@ -10415,7 +10415,7 @@ mod hir_opt_tests { v15:StaticSymbol[:foo] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, respond_to?@0x1018, cme:0x1020) - v26:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v26:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] PatchPoint MethodRedefined(C@0x1010, foo@0x1048, cme:0x1050) v30:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count @@ -10434,7 +10434,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo, false) test(C.new) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:6: bb1(): EntryPoint interpreter @@ -10452,7 +10452,7 @@ mod hir_opt_tests { v17:FalseClass = Const Value(false) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, respond_to?@0x1018, cme:0x1020) - v28:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v28:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] PatchPoint MethodRedefined(C@0x1010, foo@0x1048, cme:0x1050) v32:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count @@ -10471,7 +10471,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo, nil) test(C.new) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:6: bb1(): EntryPoint interpreter @@ -10489,7 +10489,7 @@ mod hir_opt_tests { v17:NilClass = Const Value(nil) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, respond_to?@0x1018, cme:0x1020) - v28:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v28:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] PatchPoint MethodRedefined(C@0x1010, foo@0x1048, cme:0x1050) v32:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count @@ -10508,7 +10508,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo, true) test(C.new) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:6: bb1(): EntryPoint interpreter @@ -10526,7 +10526,7 @@ mod hir_opt_tests { v17:TrueClass = Const Value(true) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, respond_to?@0x1018, cme:0x1020) - v28:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v28:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] PatchPoint MethodRedefined(C@0x1010, foo@0x1048, cme:0x1050) v32:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count @@ -10544,7 +10544,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo, 4) test(C.new) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:5: bb1(): EntryPoint interpreter @@ -10562,7 +10562,7 @@ mod hir_opt_tests { v17:Fixnum[4] = Const Value(4) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, respond_to?@0x1018, cme:0x1020) - v28:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v28:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] PatchPoint MethodRedefined(C@0x1010, foo@0x1048, cme:0x1050) v32:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count @@ -10580,7 +10580,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo, nil) test(C.new) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:5: bb1(): EntryPoint interpreter @@ -10598,7 +10598,7 @@ mod hir_opt_tests { v17:NilClass = Const Value(nil) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, respond_to?@0x1018, cme:0x1020) - v28:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v28:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] PatchPoint MethodRedefined(C@0x1010, foo@0x1048, cme:0x1050) v32:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count @@ -10615,7 +10615,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo) test(C.new) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:4: bb1(): EntryPoint interpreter @@ -10632,7 +10632,7 @@ mod hir_opt_tests { v15:StaticSymbol[:foo] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, respond_to?@0x1018, cme:0x1020) - v26:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v26:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] PatchPoint MethodRedefined(C@0x1010, respond_to_missing?@0x1048, cme:0x1050) PatchPoint MethodRedefined(C@0x1010, foo@0x1078, cme:0x1080) v32:FalseClass = Const Value(false) @@ -10653,7 +10653,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo) test(C.new) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:7: bb1(): EntryPoint interpreter @@ -10670,7 +10670,7 @@ mod hir_opt_tests { v15:StaticSymbol[:foo] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, respond_to?@0x1018, cme:0x1020) - v26:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v26:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] v27:BasicObject = CCallVariadic v26, :Kernel#respond_to?@0x1048, v15 CheckInterrupts Return v27 @@ -10697,7 +10697,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count CheckInterrupts Return v19 @@ -10725,7 +10725,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v22:StringExact[VALUE(0x1038)] = Const Value(VALUE(0x1038)) CheckInterrupts @@ -10753,7 +10753,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v22:NilClass = Const Value(nil) CheckInterrupts @@ -10781,7 +10781,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v22:TrueClass = Const Value(true) CheckInterrupts @@ -10809,7 +10809,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v22:FalseClass = Const Value(false) CheckInterrupts @@ -10837,7 +10837,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v22:Fixnum[0] = Const Value(0) CheckInterrupts @@ -10865,7 +10865,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v22:Fixnum[1] = Const Value(1) CheckInterrupts @@ -10894,7 +10894,7 @@ mod hir_opt_tests { v11:Fixnum[3] = Const Value(3) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v21:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count CheckInterrupts Return v11 @@ -10924,7 +10924,7 @@ mod hir_opt_tests { v15:Fixnum[3] = Const Value(3) PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v25:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v25:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count CheckInterrupts Return v15 @@ -11043,7 +11043,7 @@ mod hir_opt_tests { end test "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:4: bb1(): EntryPoint interpreter @@ -11056,7 +11056,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v23:Fixnum[123] = Const Value(123) CheckInterrupts @@ -11074,7 +11074,7 @@ mod hir_opt_tests { end test "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:4: bb1(): EntryPoint interpreter @@ -11087,7 +11087,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v23:Fixnum[123] = Const Value(123) CheckInterrupts @@ -11105,7 +11105,7 @@ mod hir_opt_tests { end test "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:4: bb1(): EntryPoint interpreter @@ -11118,7 +11118,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) - v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v23:Fixnum[123] = Const Value(123) CheckInterrupts @@ -11827,6 +11827,161 @@ mod hir_opt_tests { "); } + #[test] + fn test_fold_is_a_true() { + eval(r#" + def test = 5.is_a?(Integer) + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v10:Fixnum[5] = Const Value(5) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Integer) + v22:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(Integer@0x1008, is_a?@0x1009, cme:0x1010) + v27:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_fold_is_a_false() { + eval(r#" + def test = 5.is_a?(String) + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v10:Fixnum[5] = Const Value(5) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, String) + v22:Class[String@0x1008] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(Integer@0x1010, is_a?@0x1018, cme:0x1020) + v27:FalseClass = Const Value(false) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v27 + "); + } + + #[test] + fn test_is_a_array_subclass_folds_to_true() { + eval(r#" + class C < Array; end + O = C.new + def test = O.is_a?(Array) + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, O) + v22:ArraySubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint StableConstantNames(0x1010, Array) + v25:Class[Array@0x1018] = Const Value(VALUE(0x1018)) + PatchPoint NoSingletonClass(C@0x1020) + PatchPoint MethodRedefined(C@0x1020, is_a?@0x1028, cme:0x1030) + v31:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_is_a_user_defined_class_folds_to_true() { + eval(r#" + class C; end + O = C.new + def test = O.is_a?(C) + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:4: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, O) + v22:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint StableConstantNames(0x1010, C) + v25:Class[C@0x1018] = Const Value(VALUE(0x1018)) + PatchPoint NoSingletonClass(C@0x1018) + PatchPoint MethodRedefined(C@0x1018, is_a?@0x1019, cme:0x1020) + v31:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v31 + "); + } + + #[test] + fn test_is_a_symbol_folds_to_true() { + eval(r#" + O = :my_static_symbol + def test = O.is_a?(Symbol) + test + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, O) + v22:StaticSymbol[:my_static_symbol] = Const Value(VALUE(0x1008)) + PatchPoint StableConstantNames(0x1010, Symbol) + v25:Class[Symbol@0x1018] = Const Value(VALUE(0x1018)) + PatchPoint MethodRedefined(Symbol@0x1018, is_a?@0x1019, cme:0x1020) + v30:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v30 + "); + } + #[test] fn counting_complex_feature_use_for_fallback() { eval(" @@ -11946,7 +12101,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(C@0x1000) PatchPoint MethodRedefined(C@0x1000, class@0x1008, cme:0x1010) - v43:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] + v43:ObjectSubclass[class_exact:C] = GuardType v6, ObjectSubclass[class_exact:C] IncrCounter inline_iseq_optimized_send_count v47:Class[C@0x1000] = Const Value(VALUE(0x1000)) IncrCounter inline_cfunc_optimized_send_count @@ -11971,7 +12126,7 @@ mod hir_opt_tests { def test(o) = o.class.name test(C.new) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:3: bb1(): EntryPoint interpreter @@ -11987,7 +12142,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, class@0x1010, cme:0x1018) - v25:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v25:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] IncrCounter inline_iseq_optimized_send_count v29:Class[C@0x1008] = Const Value(VALUE(0x1008)) IncrCounter inline_cfunc_optimized_send_count @@ -12007,7 +12162,7 @@ mod hir_opt_tests { def test(o) = o.class test(C.new) "#); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:3: bb1(): EntryPoint interpreter @@ -12023,7 +12178,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, class@0x1010, cme:0x1018) - v23:HeapObject[class_exact:C] = GuardType v10, HeapObject[class_exact:C] + v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] IncrCounter inline_iseq_optimized_send_count v27:Class[C@0x1008] = Const Value(VALUE(0x1008)) IncrCounter inline_cfunc_optimized_send_count @@ -12078,7 +12233,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, class@0x1008, cme:0x1010) - v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] + v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v23:Class[Object@0x1038] = Const Value(VALUE(0x1038)) IncrCounter inline_cfunc_optimized_send_count @@ -12180,7 +12335,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_OBJ) - v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozen@0x1010) PatchPoint MethodRedefined(TestFrozen@0x1010, a@0x1018, cme:0x1020) v29:Fixnum[1] = Const Value(1) @@ -12221,7 +12376,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, MULTI_FROZEN) - v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestMultiIvars@0x1010) PatchPoint MethodRedefined(TestMultiIvars@0x1010, b@0x1018, cme:0x1020) v29:Fixnum[20] = Const Value(20) @@ -12260,7 +12415,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_STR) - v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozenStr@0x1010) PatchPoint MethodRedefined(TestFrozenStr@0x1010, name@0x1018, cme:0x1020) v29:StringExact[VALUE(0x1048)] = Const Value(VALUE(0x1048)) @@ -12299,7 +12454,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_NIL) - v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozenNil@0x1010) PatchPoint MethodRedefined(TestFrozenNil@0x1010, value@0x1018, cme:0x1020) v29:NilClass = Const Value(nil) @@ -12338,7 +12493,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, UNFROZEN_OBJ) - v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestUnfrozen@0x1010) PatchPoint MethodRedefined(TestUnfrozen@0x1010, a@0x1018, cme:0x1020) v25:CShape = LoadField v20, :_shape_id@0x1048 @@ -12379,7 +12534,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_READER) - v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestAttrReader@0x1010) PatchPoint MethodRedefined(TestAttrReader@0x1010, value@0x1018, cme:0x1020) v29:Fixnum[42] = Const Value(42) @@ -12418,7 +12573,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_SYM) - v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozenSym@0x1010) PatchPoint MethodRedefined(TestFrozenSym@0x1010, sym@0x1018, cme:0x1020) v29:StaticSymbol[:hello] = Const Value(VALUE(0x1048)) @@ -12457,7 +12612,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_TRUE) - v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozenBool@0x1010) PatchPoint MethodRedefined(TestFrozenBool@0x1010, flag@0x1018, cme:0x1020) v29:TrueClass = Const Value(true) @@ -12482,7 +12637,7 @@ mod hir_opt_tests { test o test o "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:9: bb1(): EntryPoint interpreter @@ -12498,7 +12653,7 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(TestDynamic@0x1008) PatchPoint MethodRedefined(TestDynamic@0x1008, val@0x1010, cme:0x1018) - v23:HeapObject[class_exact:TestDynamic] = GuardType v10, HeapObject[class_exact:TestDynamic] + v23:ObjectSubclass[class_exact:TestDynamic] = GuardType v10, ObjectSubclass[class_exact:TestDynamic] v26:CShape = LoadField v23, :_shape_id@0x1040 v27:CShape[0x1041] = GuardBitEquals v26, CShape(0x1041) v28:BasicObject = LoadField v23, :@val@0x1042 @@ -12538,12 +12693,12 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, NESTED_FROZEN) - v27:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v27:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestNestedAccess@0x1010) PatchPoint MethodRedefined(TestNestedAccess@0x1010, x@0x1018, cme:0x1020) v52:Fixnum[100] = Const Value(100) PatchPoint StableConstantNames(0x1048, NESTED_FROZEN) - v33:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v33:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(TestNestedAccess@0x1010, y@0x1050, cme:0x1058) v54:Fixnum[200] = Const Value(200) PatchPoint MethodRedefined(Integer@0x1080, +@0x1088, cme:0x1090) @@ -12607,7 +12762,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(C@0x1000) PatchPoint MethodRedefined(C@0x1000, secret@0x1008, cme:0x1010) - v19:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] + v19:ObjectSubclass[class_exact:C] = GuardType v6, ObjectSubclass[class_exact:C] IncrCounter inline_iseq_optimized_send_count v22:Fixnum[42] = Const Value(42) CheckInterrupts @@ -12638,7 +12793,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Obj) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v21:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v12:BasicObject = Send v21, :secret # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL CheckInterrupts Return v12 @@ -12751,7 +12906,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint NoSingletonClass(C@0x1000) PatchPoint MethodRedefined(C@0x1000, secret@0x1008, cme:0x1010) - v19:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] + v19:ObjectSubclass[class_exact:C] = GuardType v6, ObjectSubclass[class_exact:C] IncrCounter inline_iseq_optimized_send_count v22:Fixnum[42] = Const Value(42) CheckInterrupts @@ -12782,7 +12937,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Obj) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v21:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v12:BasicObject = Send v21, :secret # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL CheckInterrupts Return v12 @@ -13191,7 +13346,7 @@ mod hir_opt_tests { assert!(hir.contains("InvokeSuper "), "Expected unoptimized InvokeSuper but got:\n{hir}"); assert!(!hir.contains("SendDirect"), "Should not optimize to SendDirect for explicit blockarg:\n{hir}"); - assert_snapshot!(hir, @" + assert_snapshot!(hir, @r" fn foo@:10: bb1(): EntryPoint interpreter @@ -13209,7 +13364,7 @@ mod hir_opt_tests { bb3(v11:BasicObject, v12:BasicObject, v13:NilClass): PatchPoint NoSingletonClass(B@0x1008) PatchPoint MethodRedefined(B@0x1008, proc@0x1010, cme:0x1018) - v39:HeapObject[class_exact:B] = GuardType v11, HeapObject[class_exact:B] + v39:ObjectSubclass[class_exact:B] = GuardType v11, ObjectSubclass[class_exact:B] v40:BasicObject = CCallWithFrame v39, :Kernel#proc@0x1040, block=0x1048 v19:CPtr = GetEP 0 v20:BasicObject = LoadField v19, :blk@0x1050 @@ -13389,7 +13544,7 @@ mod hir_opt_tests { test C.new; test D.new; test C.new; test D.new "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:11: bb1(): EntryPoint interpreter @@ -13403,9 +13558,9 @@ mod hir_opt_tests { v7:BasicObject = LoadArg :o@1 Jump bb3(v6, v7) bb3(v9:BasicObject, v10:BasicObject): - v15:CBool = HasType v10, HeapObject[class_exact:C] + v15:CBool = HasType v10, ObjectSubclass[class_exact:C] IfTrue v15, bb5(v9, v10, v10) - v24:CBool = HasType v10, HeapObject[class_exact:D] + v24:CBool = HasType v10, ObjectSubclass[class_exact:D] IfTrue v24, bb6(v9, v10, v10) v33:BasicObject = Send v10, :foo # SendFallbackReason: SendWithoutBlock: polymorphic fallback Jump bb4(v9, v10, v33) @@ -13444,7 +13599,7 @@ mod hir_opt_tests { test C.new; test 3; test C.new; test 4 "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:5: bb1(): EntryPoint interpreter @@ -13458,14 +13613,14 @@ mod hir_opt_tests { v7:BasicObject = LoadArg :o@1 Jump bb3(v6, v7) bb3(v9:BasicObject, v10:BasicObject): - v15:CBool = HasType v10, HeapObject[class_exact:C] + v15:CBool = HasType v10, ObjectSubclass[class_exact:C] IfTrue v15, bb5(v9, v10, v10) v24:CBool = HasType v10, Fixnum IfTrue v24, bb6(v9, v10, v10) v33:BasicObject = Send v10, :itself # SendFallbackReason: SendWithoutBlock: polymorphic fallback Jump bb4(v9, v10, v33) bb5(v16:BasicObject, v17:BasicObject, v18:BasicObject): - v20:HeapObject[class_exact:C] = RefineType v18, HeapObject[class_exact:C] + v20:ObjectSubclass[class_exact:C] = RefineType v18, ObjectSubclass[class_exact:C] PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, itself@0x1010, cme:0x1018) IncrCounter inline_cfunc_optimized_send_count @@ -13561,7 +13716,7 @@ mod hir_opt_tests { test_ep_escape([1], lambda { }) { |x| } } "); - assert_snapshot!(hir_string("test_ep_escape"), @" + assert_snapshot!(hir_string("test_ep_escape"), @r" fn test_ep_escape@:3: bb1(): EntryPoint interpreter @@ -13619,7 +13774,7 @@ mod hir_opt_tests { v71:Falsy = RefineType v60, Falsy PatchPoint NoSingletonClass(Object@0x1020) PatchPoint MethodRedefined(Object@0x1020, lambda@0x1028, cme:0x1030) - v119:HeapObject[class_exact*:Object@VALUE(0x1020)] = GuardType v58, HeapObject[class_exact*:Object@VALUE(0x1020)] + v119:ObjectSubclass[class_exact*:Object@VALUE(0x1020)] = GuardType v58, ObjectSubclass[class_exact*:Object@VALUE(0x1020)] v120:BasicObject = CCallWithFrame v119, :Kernel#lambda@0x1058, block=0x1060 v75:CPtr = GetEP 0 v76:BasicObject = LoadField v75, :list@0x1001 diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 2e4a457a47e131..358cae1f6d053c 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -123,7 +123,7 @@ mod snapshot_tests { v23:Any = Snapshot FrameState { pc: 0x1008, stack: [v6, v13, v15, v11], locals: [] } PatchPoint NoSingletonClass(Object@0x1010) PatchPoint MethodRedefined(Object@0x1010, foo@0x1018, cme:0x1020) - v26:HeapObject[class_exact*:Object@VALUE(0x1010)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1010)] + v26:ObjectSubclass[class_exact*:Object@VALUE(0x1010)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1010)] v27:BasicObject = SendDirect v26, 0x1048, :foo (0x1058), v13, v15, v11 v18:Any = Snapshot FrameState { pc: 0x1060, stack: [v27], locals: [] } PatchPoint NoTracePoint @@ -160,7 +160,7 @@ mod snapshot_tests { v14:Any = Snapshot FrameState { pc: 0x1008, stack: [v6, v11, v13], locals: [] } PatchPoint NoSingletonClass(Object@0x1010) PatchPoint MethodRedefined(Object@0x1010, foo@0x1018, cme:0x1020) - v23:HeapObject[class_exact*:Object@VALUE(0x1010)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1010)] + v23:ObjectSubclass[class_exact*:Object@VALUE(0x1010)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1010)] v24:BasicObject = SendDirect v23, 0x1048, :foo (0x1058), v11, v13 v16:Any = Snapshot FrameState { pc: 0x1060, stack: [v24], locals: [] } PatchPoint NoTracePoint @@ -2310,7 +2310,7 @@ pub mod hir_build_tests { eval(" def test(a, ...) = foo(a, ...) "); - assert_snapshot!(hir_string("test"), @" + assert_snapshot!(hir_string("test"), @r" fn test@:2: bb1(): EntryPoint interpreter @@ -2339,7 +2339,7 @@ pub mod hir_build_tests { v36:CInt64 = GuardNoBitsSet v35, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v37:CInt64 = LoadField v34, :_env_data_index_specval@0x1005 v38:CInt64 = GuardAnyBitSet v37, CUInt64(1) - v39:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) + v39:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) SideExit SplatKwNotProfiled "); } @@ -3232,7 +3232,7 @@ pub mod hir_build_tests { v21:CInt64 = GuardNoBitsSet v20, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v22:CInt64 = LoadField v19, :_env_data_index_specval@0x1003 v23:CInt64 = GuardAnyBitSet v22, CUInt64(1) - v24:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) + v24:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) SideExit SplatKwNotProfiled "); } @@ -3311,7 +3311,7 @@ pub mod hir_build_tests { v21:CInt64 = GuardNoBitsSet v20, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v22:CInt64 = LoadField v19, :_env_data_index_specval@0x1003 v23:CInt64 = GuardAnyBitSet v22, CUInt64(1) - v24:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) + v24:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) v26:HashExact = GuardType v12, HashExact v28:BasicObject = Send v11, 0x1002, :foo, v26, v24 # SendFallbackReason: Uncategorized(send) CheckInterrupts @@ -3348,7 +3348,7 @@ pub mod hir_build_tests { v21:CInt64 = GuardNoBitsSet v20, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v22:CInt64 = LoadField v19, :_env_data_index_specval@0x1003 v23:CInt64 = GuardAnyBitSet v22, CUInt64(1) - v24:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) + v24:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) v26:HashExact = GuardType v12, HashExact v28:BasicObject = Send v11, 0x1002, :foo, v26, v24 # SendFallbackReason: Uncategorized(send) CheckInterrupts @@ -3431,7 +3431,7 @@ pub mod hir_build_tests { v21:CInt64 = GuardNoBitsSet v20, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v22:CInt64 = LoadField v19, :_env_data_index_specval@0x1003 v23:CInt64 = GuardAnyBitSet v22, CUInt64(1) - v24:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) + v24:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) SideExit SplatKwNotNilOrHash "); } @@ -4084,7 +4084,7 @@ pub mod hir_build_tests { let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Dir", "open")); assert!(iseq_contains_opcode(iseq, YARVINSN_opt_invokebuiltin_delegate), "iseq Dir.open does not contain invokebuiltin"); let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @" + assert_snapshot!(hir_string_function(&function), @r" fn open@: bb1(): EntryPoint interpreter @@ -4114,12 +4114,12 @@ pub mod hir_build_tests { v35:CInt64 = GuardNoBitsSet v34, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v36:CInt64 = LoadField v33, :_env_data_index_specval@0x1005 v37:CInt64 = GuardAnyBitSet v36, CUInt64(1) - v38:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) + v38:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008)) CheckInterrupts v41:CBool[true] = Test v38 v42 = RefineType v38, Falsy IfFalse v41, bb4(v18, v19, v20, v21, v22, v27) - v44:HeapObject[BlockParamProxy] = RefineType v38, Truthy + v44:ObjectSubclass[BlockParamProxy] = RefineType v38, Truthy v48:BasicObject = InvokeBlock, v27 # SendFallbackReason: Uncategorized(invokeblock) v51:BasicObject = InvokeBuiltin dir_s_close, v18, v27 CheckInterrupts diff --git a/zjit/src/hir_type/gen_hir_type.rb b/zjit/src/hir_type/gen_hir_type.rb index f952a8b71561fe..5934dcb2d0ea41 100644 --- a/zjit/src/hir_type/gen_hir_type.rb +++ b/zjit/src/hir_type/gen_hir_type.rb @@ -63,6 +63,11 @@ def to_graphviz type, f "BasicObjectExact" => "rb_cBasicObject", } +$subclass_c_names = { + "ObjectSubclass" => "rb_cObject", + "BasicObjectSubclass" => "rb_cBasicObject", +} + $inexact_c_names = { "Object" => "rb_cObject", "BasicObject" => "rb_cBasicObject", @@ -76,7 +81,8 @@ def base_type name, c_name: nil subclass = type.subtype(name+"Subclass") if c_name $exact_c_names[exact.name] = c_name - $inexact_c_names[subclass.name] = c_name + $subclass_c_names[subclass.name] = c_name + $inexact_c_names[type.name] = c_name end $builtin_exact << exact.name $subclass << subclass.name @@ -88,6 +94,8 @@ def base_type name, c_name: nil def final_type name, base: $object, c_name: nil if c_name $exact_c_names[name] = c_name + $subclass_c_names[name] = c_name + $inexact_c_names[name] = c_name end type = base.subtype name $builtin_exact << type.name @@ -217,7 +225,13 @@ def add_union name, type_names puts " (bits::#{type_name}, &raw const crate::cruby::#{c_name})," } puts " ];" -$inexact_c_names = $inexact_c_names.to_a.sort_by {|name, _| $bits[name]}.to_h +$subclass_c_names = $subclass_c_names.to_a.sort_by {|name, _| $numeric_bits[name.sub("Subclass", "")]}.to_h +puts " pub const SubclassBitsAndClass: [(u64, *const VALUE); #{$subclass_c_names.size}] = [" +$subclass_c_names.each {|type_name, c_name| + puts " (bits::#{type_name}, &raw const crate::cruby::#{c_name})," +} +puts " ];" +$inexact_c_names = $inexact_c_names.to_a.sort_by {|name, _| $numeric_bits[name]}.to_h puts " pub const InexactBitsAndClass: [(u64, *const VALUE); #{$inexact_c_names.size}] = [" $inexact_c_names.each {|type_name, c_name| puts " (bits::#{type_name}, &raw const crate::cruby::#{c_name})," diff --git a/zjit/src/hir_type/hir_type.inc.rs b/zjit/src/hir_type/hir_type.inc.rs index 886b4b54dd2811..99697deb82a23b 100644 --- a/zjit/src/hir_type/hir_type.inc.rs +++ b/zjit/src/hir_type/hir_type.inc.rs @@ -247,15 +247,41 @@ pub mod types { (bits::TrueClass, &raw const crate::cruby::rb_cTrueClass), (bits::FalseClass, &raw const crate::cruby::rb_cFalseClass), ]; - pub const InexactBitsAndClass: [(u64, *const VALUE); 10] = [ + pub const SubclassBitsAndClass: [(u64, *const VALUE); 17] = [ (bits::ArraySubclass, &raw const crate::cruby::rb_cArray), + (bits::Class, &raw const crate::cruby::rb_cClass), + (bits::FalseClass, &raw const crate::cruby::rb_cFalseClass), + (bits::Integer, &raw const crate::cruby::rb_cInteger), (bits::HashSubclass, &raw const crate::cruby::rb_cHash), + (bits::Float, &raw const crate::cruby::rb_cFloat), (bits::ModuleSubclass, &raw const crate::cruby::rb_cModule), + (bits::NilClass, &raw const crate::cruby::rb_cNilClass), (bits::NumericSubclass, &raw const crate::cruby::rb_cNumeric), (bits::RangeSubclass, &raw const crate::cruby::rb_cRange), (bits::RegexpSubclass, &raw const crate::cruby::rb_cRegexp), (bits::SetSubclass, &raw const crate::cruby::rb_cSet), + (bits::Symbol, &raw const crate::cruby::rb_cSymbol), (bits::StringSubclass, &raw const crate::cruby::rb_cString), + (bits::TrueClass, &raw const crate::cruby::rb_cTrueClass), + (bits::ObjectSubclass, &raw const crate::cruby::rb_cObject), + (bits::BasicObjectSubclass, &raw const crate::cruby::rb_cBasicObject), + ]; + pub const InexactBitsAndClass: [(u64, *const VALUE); 17] = [ + (bits::Array, &raw const crate::cruby::rb_cArray), + (bits::Class, &raw const crate::cruby::rb_cClass), + (bits::FalseClass, &raw const crate::cruby::rb_cFalseClass), + (bits::Integer, &raw const crate::cruby::rb_cInteger), + (bits::Hash, &raw const crate::cruby::rb_cHash), + (bits::Float, &raw const crate::cruby::rb_cFloat), + (bits::Module, &raw const crate::cruby::rb_cModule), + (bits::NilClass, &raw const crate::cruby::rb_cNilClass), + (bits::Numeric, &raw const crate::cruby::rb_cNumeric), + (bits::Range, &raw const crate::cruby::rb_cRange), + (bits::Regexp, &raw const crate::cruby::rb_cRegexp), + (bits::Set, &raw const crate::cruby::rb_cSet), + (bits::Symbol, &raw const crate::cruby::rb_cSymbol), + (bits::String, &raw const crate::cruby::rb_cString), + (bits::TrueClass, &raw const crate::cruby::rb_cTrueClass), (bits::Object, &raw const crate::cruby::rb_cObject), (bits::BasicObject, &raw const crate::cruby::rb_cBasicObject), ]; diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index cc6a208bcd413e..e1e2c1a8104d51 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -190,7 +190,7 @@ impl Type { } fn bits_from_subclass(class: VALUE) -> Option { - types::InexactBitsAndClass + types::SubclassBitsAndClass .iter() .find(|&(_, class_object)| class.is_subclass_of(unsafe { **class_object }) == ClassRelationship::Subclass) // Can't be an immediate if it's a subclass. @@ -274,7 +274,8 @@ impl Type { else if val.is_nil() { types::NilClass } else if val.is_true() { types::TrueClass } else if val.is_false() { types::FalseClass } - else { Self::from_class(val.class()) } + // TODO(max): Revisit and maybe specialize to *not* an immediate + else { Self::from_class(val.class()).intersection(types::HeapBasicObject) } } pub fn from_class(class: VALUE) -> Type { @@ -288,6 +289,14 @@ impl Type { get_class_name(class)) } + pub fn from_class_inexact(class: VALUE) -> Type { + let bits = types::InexactBitsAndClass + .iter() + .find(|&(_, class_object)| class.is_subclass_of(unsafe { **class_object }) == ClassRelationship::Subclass) + .unwrap_or_else(|| panic!("Class {} is not a subclass of BasicObject! Don't know what to do.", get_class_name(class))).0; + Type { bits, spec: Specialization::Type(class) } + } + /// Private. Only for creating type globals. const fn from_bits(bits: u64) -> Type { Type { @@ -801,7 +810,7 @@ mod tests { assert_bit_equal(Type::from_class(unsafe { rb_cTrueClass }), types::TrueClass); assert_bit_equal(Type::from_class(unsafe { rb_cFalseClass }), types::FalseClass); let c_class = define_class("C", unsafe { rb_cObject }); - assert_bit_equal(Type::from_class(c_class), Type { bits: bits::HeapObject, spec: Specialization::TypeExact(c_class) }); + assert_bit_equal(Type::from_class(c_class), Type { bits: bits::ObjectSubclass, spec: Specialization::TypeExact(c_class) }); }); } From 2be717ae7f3a8ca9063d3971bce541dec005a767 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 25 Feb 2026 11:55:12 -0800 Subject: [PATCH 10/10] Shrink struct rb_callinfo to 32 bytes This shouldn't do anything right now under the default GC, but in the future (or now on MMTK?) this would allow them to be allocated from a smaller size pool. --- vm_callinfo.h | 8 ++++---- yjit/src/cruby_bindings.inc.rs | 4 ++-- zjit/src/cruby_bindings.inc.rs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vm_callinfo.h b/vm_callinfo.h index 3df4b4eb0e9500..9f147522815d50 100644 --- a/vm_callinfo.h +++ b/vm_callinfo.h @@ -66,8 +66,8 @@ struct rb_callinfo { VALUE flags; const struct rb_callinfo_kwarg *kwarg; VALUE mid; - VALUE flag; - VALUE argc; + unsigned int flag; + unsigned int argc; }; #if !defined(USE_EMBED_CI) || (USE_EMBED_CI+0) @@ -146,7 +146,7 @@ vm_ci_flag(const struct rb_callinfo *ci) return (unsigned int)((((VALUE)ci) >> CI_EMBED_FLAG_SHFT) & CI_EMBED_FLAG_MASK); } else { - return (unsigned int)ci->flag; + return ci->flag; } } @@ -157,7 +157,7 @@ vm_ci_argc(const struct rb_callinfo *ci) return (unsigned int)((((VALUE)ci) >> CI_EMBED_ARGC_SHFT) & CI_EMBED_ARGC_MASK); } else { - return (unsigned int)ci->argc; + return ci->argc; } } diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index f0371c65908c54..32ca5137305339 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -671,8 +671,8 @@ pub struct rb_callinfo { pub flags: VALUE, pub kwarg: *const rb_callinfo_kwarg, pub mid: VALUE, - pub flag: VALUE, - pub argc: VALUE, + pub flag: ::std::os::raw::c_uint, + pub argc: ::std::os::raw::c_uint, } #[repr(C)] #[derive(Debug, Copy, Clone)] diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 35e714bd73cd72..baf39100f48d71 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -1505,8 +1505,8 @@ pub struct rb_callinfo { pub flags: VALUE, pub kwarg: *const rb_callinfo_kwarg, pub mid: VALUE, - pub flag: VALUE, - pub argc: VALUE, + pub flag: ::std::os::raw::c_uint, + pub argc: ::std::os::raw::c_uint, } #[repr(C)] #[derive(Debug, Copy, Clone)]