diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index 9345a1f7a465fc..c77b4516d1e737 100644 --- a/.github/workflows/parse_y.yml +++ b/.github/workflows/parse_y.yml @@ -78,7 +78,9 @@ jobs: - run: make - - run: make TESTRUN_SCRIPT='-e "exit !RUBY_DESCRIPTION.include?(%[+PRISM])"' run + - run: make TESTRUN_SCRIPT='-renvutil -v -e "exit EnvUtil.current_parser == %[parse.y]"' run + env: + RUNOPT0: -I$(tooldir)/lib - name: make ${{ matrix.test_task }} run: make -s ${{ matrix.test_task }} RUN_OPTS="$RUN_OPTS" SPECOPTS="$SPECOPTS" diff --git a/NEWS.md b/NEWS.md index bc026cdce8c5f1..288f7d97dedf93 100644 --- a/NEWS.md +++ b/NEWS.md @@ -160,7 +160,7 @@ The following default gems are updated. * io-console 0.8.1 * io-nonblock 0.3.2 * io-wait 0.3.2 -* json 2.13.1 +* json 2.13.2 * optparse 0.7.0.dev.2 * prism 1.4.0 * psych 5.2.6 diff --git a/compile.c b/compile.c index 8d5cb45904c6e7..bda18c1c424ab6 100644 --- a/compile.c +++ b/compile.c @@ -1440,6 +1440,30 @@ new_insn_body(rb_iseq_t *iseq, int line_no, int node_id, enum ruby_vminsn_type i return new_insn_core(iseq, line_no, node_id, insn_id, argc, operands); } +static INSN * +insn_replace_with_operands(rb_iseq_t *iseq, INSN *iobj, enum ruby_vminsn_type insn_id, int argc, ...) +{ + VALUE *operands = 0; + va_list argv; + if (argc > 0) { + int i; + va_start(argv, argc); + operands = compile_data_alloc2(iseq, sizeof(VALUE), argc); + for (i = 0; i < argc; i++) { + VALUE v = va_arg(argv, VALUE); + operands[i] = v; + } + va_end(argv); + } + + iobj->insn_id = insn_id; + iobj->operand_size = argc; + iobj->operands = operands; + iseq_insn_each_markable_object(iobj, iseq_insn_each_object_write_barrier, (VALUE)iseq); + + return iobj; +} + static const struct rb_callinfo * new_callinfo(rb_iseq_t *iseq, ID mid, int argc, unsigned int flag, struct rb_callinfo_kwarg *kw_arg, int has_blockiseq) { @@ -3439,11 +3463,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal VALUE ary = iobj->operands[0]; rb_obj_reveal(ary, rb_cArray); - iobj->insn_id = BIN(opt_ary_freeze); - iobj->operand_size = 2; - iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE)); - iobj->operands[0] = ary; - iobj->operands[1] = (VALUE)ci; + insn_replace_with_operands(iseq, iobj, BIN(opt_ary_freeze), 2, ary, (VALUE)ci); ELEM_REMOVE(next); } } @@ -3465,11 +3485,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal VALUE hash = iobj->operands[0]; rb_obj_reveal(hash, rb_cHash); - iobj->insn_id = BIN(opt_hash_freeze); - iobj->operand_size = 2; - iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE)); - iobj->operands[0] = hash; - iobj->operands[1] = (VALUE)ci; + insn_replace_with_operands(iseq, iobj, BIN(opt_hash_freeze), 2, hash, (VALUE)ci); ELEM_REMOVE(next); } } @@ -3488,11 +3504,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(next, 1); if (vm_ci_simple(ci) && vm_ci_argc(ci) == 0 && blockiseq == NULL && vm_ci_mid(ci) == idFreeze) { - iobj->insn_id = BIN(opt_ary_freeze); - iobj->operand_size = 2; - iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE)); - RB_OBJ_WRITE(iseq, &iobj->operands[0], rb_cArray_empty_frozen); - iobj->operands[1] = (VALUE)ci; + insn_replace_with_operands(iseq, iobj, BIN(opt_ary_freeze), 2, rb_cArray_empty_frozen, (VALUE)ci); ELEM_REMOVE(next); } } @@ -3511,11 +3523,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(next, 1); if (vm_ci_simple(ci) && vm_ci_argc(ci) == 0 && blockiseq == NULL && vm_ci_mid(ci) == idFreeze) { - iobj->insn_id = BIN(opt_hash_freeze); - iobj->operand_size = 2; - iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE)); - RB_OBJ_WRITE(iseq, &iobj->operands[0], rb_cHash_empty_frozen); - iobj->operands[1] = (VALUE)ci; + insn_replace_with_operands(iseq, iobj, BIN(opt_hash_freeze), 2, rb_cHash_empty_frozen, (VALUE)ci); ELEM_REMOVE(next); } } @@ -4109,17 +4117,16 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal static int insn_set_specialized_instruction(rb_iseq_t *iseq, INSN *iobj, int insn_id) { - iobj->insn_id = insn_id; - iobj->operand_size = insn_len(insn_id) - 1; - iobj->insn_info.events |= RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN; - if (insn_id == BIN(opt_neq)) { VALUE original_ci = iobj->operands[0]; - iobj->operand_size = 2; - iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE)); - iobj->operands[0] = (VALUE)new_callinfo(iseq, idEq, 1, 0, NULL, FALSE); - iobj->operands[1] = original_ci; + VALUE new_ci = (VALUE)new_callinfo(iseq, idEq, 1, 0, NULL, FALSE); + insn_replace_with_operands(iseq, iobj, insn_id, 2, new_ci, original_ci); } + else { + iobj->insn_id = insn_id; + iobj->operand_size = insn_len(insn_id) - 1; + } + iobj->insn_info.events |= RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN; return COMPILE_OK; } @@ -4151,12 +4158,7 @@ iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj) if (method != INT2FIX(0)) { VALUE num = iobj->operands[0]; - int operand_len = insn_len(BIN(opt_newarray_send)) - 1; - iobj->insn_id = BIN(opt_newarray_send); - iobj->operands = compile_data_calloc2(iseq, operand_len, sizeof(VALUE)); - iobj->operands[0] = num; - iobj->operands[1] = method; - iobj->operand_size = operand_len; + insn_replace_with_operands(iseq, iobj, BIN(opt_newarray_send), 2, num, method); ELEM_REMOVE(&niobj->link); return COMPILE_OK; } @@ -4168,12 +4170,7 @@ iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj) const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT((INSN *)niobj->link.next, 0); if (vm_ci_simple(ci) && vm_ci_argc(ci) == 1 && vm_ci_mid(ci) == idPack) { VALUE num = iobj->operands[0]; - int operand_len = insn_len(BIN(opt_newarray_send)) - 1; - iobj->insn_id = BIN(opt_newarray_send); - iobj->operands = compile_data_calloc2(iseq, operand_len, sizeof(VALUE)); - iobj->operands[0] = FIXNUM_INC(num, 1); - iobj->operands[1] = INT2FIX(VM_OPT_NEWARRAY_SEND_PACK); - iobj->operand_size = operand_len; + insn_replace_with_operands(iseq, iobj, BIN(opt_newarray_send), 2, FIXNUM_INC(num, 1), INT2FIX(VM_OPT_NEWARRAY_SEND_PACK)); ELEM_REMOVE(&iobj->link); ELEM_REMOVE(niobj->link.next); ELEM_INSERT_NEXT(&niobj->link, &iobj->link); @@ -4191,12 +4188,7 @@ iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj) if (vm_ci_mid(ci) == idPack && vm_ci_argc(ci) == 2 && (kwarg && kwarg->keyword_len == 1 && kwarg->keywords[0] == rb_id2sym(idBuffer))) { VALUE num = iobj->operands[0]; - int operand_len = insn_len(BIN(opt_newarray_send)) - 1; - iobj->insn_id = BIN(opt_newarray_send); - iobj->operands = compile_data_calloc2(iseq, operand_len, sizeof(VALUE)); - iobj->operands[0] = FIXNUM_INC(num, 2); - iobj->operands[1] = INT2FIX(VM_OPT_NEWARRAY_SEND_PACK_BUFFER); - iobj->operand_size = operand_len; + insn_replace_with_operands(iseq, iobj, BIN(opt_newarray_send), 2, FIXNUM_INC(num, 2), INT2FIX(VM_OPT_NEWARRAY_SEND_PACK_BUFFER)); // Remove the "send" insn. ELEM_REMOVE((niobj->link.next)->next); // Remove the modified insn from its original "newarray" position... @@ -4230,11 +4222,7 @@ iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj) if (vm_ci_simple(ci) && vm_ci_argc(ci) == 1 && vm_ci_mid(ci) == idIncludeP) { VALUE num = iobj->operands[0]; INSN *sendins = (INSN *)sendobj; - sendins->insn_id = BIN(opt_newarray_send); - sendins->operand_size = insn_len(sendins->insn_id) - 1; - sendins->operands = compile_data_calloc2(iseq, sendins->operand_size, sizeof(VALUE)); - sendins->operands[0] = FIXNUM_INC(num, 1); - sendins->operands[1] = INT2FIX(VM_OPT_NEWARRAY_SEND_INCLUDE_P); + insn_replace_with_operands(iseq, sendins, BIN(opt_newarray_send), 2, FIXNUM_INC(num, 1), INT2FIX(VM_OPT_NEWARRAY_SEND_INCLUDE_P)); // Remove the original "newarray" insn. ELEM_REMOVE(&iobj->link); return COMPILE_OK; @@ -4272,12 +4260,7 @@ iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj) rb_obj_reveal(ary, rb_cArray); INSN *sendins = (INSN *)sendobj; - sendins->insn_id = BIN(opt_duparray_send); - sendins->operand_size = insn_len(sendins->insn_id) - 1;; - sendins->operands = compile_data_calloc2(iseq, sendins->operand_size, sizeof(VALUE)); - sendins->operands[0] = ary; - sendins->operands[1] = rb_id2sym(idIncludeP); - sendins->operands[2] = INT2FIX(1); + insn_replace_with_operands(iseq, sendins, BIN(opt_duparray_send), 3, ary, rb_id2sym(idIncludeP), INT2FIX(1)); // Remove the duparray insn. ELEM_REMOVE(&iobj->link); diff --git a/debug_counter.h b/debug_counter.h index 9f0649e0ac8e33..8ffce66f0f7b16 100644 --- a/debug_counter.h +++ b/debug_counter.h @@ -305,7 +305,6 @@ RB_DEBUG_COUNTER(obj_imemo_ment) RB_DEBUG_COUNTER(obj_imemo_iseq) RB_DEBUG_COUNTER(obj_imemo_env) RB_DEBUG_COUNTER(obj_imemo_tmpbuf) -RB_DEBUG_COUNTER(obj_imemo_ast) RB_DEBUG_COUNTER(obj_imemo_cref) RB_DEBUG_COUNTER(obj_imemo_svar) RB_DEBUG_COUNTER(obj_imemo_throw_data) diff --git a/ext/-test-/namespace/yay1/yay1.def b/ext/-test-/namespace/yay1/yay1.def index edbae873126604..510fbe7017bd9f 100644 --- a/ext/-test-/namespace/yay1/yay1.def +++ b/ext/-test-/namespace/yay1/yay1.def @@ -1,4 +1,3 @@ -LIBRARY yay1 EXPORTS Init_yay1 yay_value diff --git a/ext/-test-/namespace/yay1/yay1.h b/ext/-test-/namespace/yay1/yay1.h index d68f8b552324b9..c4dade928ad34b 100644 --- a/ext/-test-/namespace/yay1/yay1.h +++ b/ext/-test-/namespace/yay1/yay1.h @@ -1,4 +1,4 @@ #include #include "ruby/internal/dllexport.h" -RUBY_EXTERN VALUE yay_value(void); +RUBY_FUNC_EXPORTED VALUE yay_value(void); diff --git a/ext/-test-/namespace/yay2/yay2.def b/ext/-test-/namespace/yay2/yay2.def index 6bb6011f4b841d..163fc44c04649c 100644 --- a/ext/-test-/namespace/yay2/yay2.def +++ b/ext/-test-/namespace/yay2/yay2.def @@ -1,4 +1,3 @@ -LIBRARY yay2 EXPORTS Init_yay2 yay_value diff --git a/ext/-test-/namespace/yay2/yay2.h b/ext/-test-/namespace/yay2/yay2.h index d68f8b552324b9..c4dade928ad34b 100644 --- a/ext/-test-/namespace/yay2/yay2.h +++ b/ext/-test-/namespace/yay2/yay2.h @@ -1,4 +1,4 @@ #include #include "ruby/internal/dllexport.h" -RUBY_EXTERN VALUE yay_value(void); +RUBY_FUNC_EXPORTED VALUE yay_value(void); diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index 2aef3d7f613888..f9ac3e17a947a5 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.13.1' + VERSION = '2.13.2' end diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 8476c9b74752d1..8b3045fda322c4 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -500,7 +500,6 @@ count_imemo_objects(int argc, VALUE *argv, VALUE self) INIT_IMEMO_TYPE_ID(imemo_ment); INIT_IMEMO_TYPE_ID(imemo_iseq); INIT_IMEMO_TYPE_ID(imemo_tmpbuf); - INIT_IMEMO_TYPE_ID(imemo_ast); INIT_IMEMO_TYPE_ID(imemo_callinfo); INIT_IMEMO_TYPE_ID(imemo_callcache); INIT_IMEMO_TYPE_ID(imemo_constcache); diff --git a/gc/default/default.c b/gc/default/default.c index de3bee15223b19..47cfe3fb3baff3 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -4454,10 +4454,10 @@ rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj) asan_unpoisoning_object(obj) { /* Garbage can live on the stack, so do not mark or pin */ switch (BUILTIN_TYPE(obj)) { - case T_ZOMBIE: - case T_NONE: + case T_ZOMBIE: + case T_NONE: break; - default: + default: gc_mark_and_pin(objspace, obj); break; } diff --git a/hash.c b/hash.c index 6f3ffb78ba2055..7256063d72e079 100644 --- a/hash.c +++ b/hash.c @@ -71,10 +71,6 @@ #define HASH_DEBUG 0 #endif -#if HASH_DEBUG -#include "internal/gc.h" -#endif - #define SET_DEFAULT(hash, ifnone) ( \ FL_UNSET_RAW(hash, RHASH_PROC_DEFAULT), \ RHASH_SET_IFNONE(hash, ifnone)) diff --git a/imemo.c b/imemo.c index 5f24e0230129db..7153689030a7b6 100644 --- a/imemo.c +++ b/imemo.c @@ -16,7 +16,6 @@ rb_imemo_name(enum imemo_type type) // put no default case to get a warning if an imemo type is missing switch (type) { #define IMEMO_NAME(x) case imemo_##x: return #x; - IMEMO_NAME(ast); IMEMO_NAME(callcache); IMEMO_NAME(callinfo); IMEMO_NAME(constcache); @@ -220,10 +219,6 @@ rb_imemo_memsize(VALUE obj) { size_t size = 0; switch (imemo_type(obj)) { - case imemo_ast: - rb_bug("imemo_ast is obsolete"); - - break; case imemo_callcache: break; case imemo_callinfo: @@ -336,10 +331,6 @@ void rb_imemo_mark_and_move(VALUE obj, bool reference_updating) { switch (imemo_type(obj)) { - case imemo_ast: - rb_bug("imemo_ast is obsolete"); - - break; case imemo_callcache: { /* cc is callcache. * @@ -600,10 +591,6 @@ void rb_imemo_free(VALUE obj) { switch (imemo_type(obj)) { - case imemo_ast: - rb_bug("imemo_ast is obsolete"); - - break; case imemo_callcache: RB_DEBUG_COUNTER_INC(obj_imemo_callcache); diff --git a/inits.c b/inits.c index 29087a6306481b..b4e58ea25a1cec 100644 --- a/inits.c +++ b/inits.c @@ -90,7 +90,6 @@ rb_call_builtin_inits(void) #define BUILTIN(n) CALL(builtin_##n) BUILTIN(kernel); BUILTIN(yjit); - // BUILTIN(yjit_hook) is called after rb_yjit_init() BUILTIN(gc); BUILTIN(ractor); BUILTIN(numeric); @@ -109,6 +108,7 @@ rb_call_builtin_inits(void) BUILTIN(nilclass); BUILTIN(marshal); BUILTIN(zjit); + BUILTIN(yjit_hook); Init_builtin_prelude(); } #undef CALL diff --git a/internal/imemo.h b/internal/imemo.h index dcea997ae803a7..0ad00fe6b79d99 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -37,7 +37,6 @@ enum imemo_type { imemo_ment = 6, imemo_iseq = 7, imemo_tmpbuf = 8, - imemo_ast = 9, // Obsolete due to the universal parser imemo_callinfo = 10, imemo_callcache = 11, imemo_constcache = 12, diff --git a/lib/tsort.gemspec b/lib/tsort.gemspec index 8970cbe8261770..4e0ef0507df008 100644 --- a/lib/tsort.gemspec +++ b/lib/tsort.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage + spec.metadata["changelog_uri"] = "#{spec.homepage}/releases" dir, gemspec = File.split(__FILE__) excludes = %W[ diff --git a/ruby.c b/ruby.c index 0d09e7ce61e1ab..cc67b0b25db8f8 100644 --- a/ruby.c +++ b/ruby.c @@ -1833,12 +1833,6 @@ ruby_opt_init(ruby_cmdline_options_t *opt) } #endif -#if USE_YJIT - // Call yjit_hook.rb after rb_yjit_init() to use `RubyVM::YJIT.enabled?` - void Init_builtin_yjit_hook(); - Init_builtin_yjit_hook(); -#endif - rb_namespace_init_done(); ruby_init_prelude(); ruby_set_script_name(opt->script_name); diff --git a/set.c b/set.c index 55d0e626338af5..61f1fd8bc43d1b 100644 --- a/set.c +++ b/set.c @@ -505,6 +505,14 @@ set_i_initialize(int argc, VALUE *argv, VALUE set) } } else { + ID id_size = rb_intern("size"); + if (rb_obj_is_kind_of(other, rb_mEnumerable) && rb_respond_to(other, id_size)) { + VALUE size = rb_funcall(other, id_size, 0); + if (RB_TYPE_P(size, T_FLOAT) && RFLOAT_VALUE(size) == INFINITY) { + rb_raise(rb_eArgError, "cannot initialize Set from an object with infinite size"); + } + } + rb_block_call(other, enum_method_id(other), 0, 0, rb_block_given_p() ? set_initialize_with_block : set_initialize_without_block, set); diff --git a/spec/ruby/command_line/dash_v_spec.rb b/spec/ruby/command_line/dash_v_spec.rb index d30efa37d68c23..b13350404c659c 100644 --- a/spec/ruby/command_line/dash_v_spec.rb +++ b/spec/ruby/command_line/dash_v_spec.rb @@ -6,7 +6,7 @@ describe "when used alone" do it "prints version and ends" do - ruby_exe(nil, args: '-v').sub("+PRISM ", "").should include(RUBY_DESCRIPTION.sub("+PRISM ", "")) + ruby_exe(nil, args: '-v').gsub("+PRISM ", "").should include(RUBY_DESCRIPTION.gsub("+PRISM ", "")) end unless (defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?) || (defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?) || (ENV['RUBY_GC_LIBRARY'] && ENV['RUBY_GC_LIBRARY'].length > 0) || diff --git a/spec/ruby/command_line/rubyopt_spec.rb b/spec/ruby/command_line/rubyopt_spec.rb index e940f912af8f7b..e1163ffcfcc9d7 100644 --- a/spec/ruby/command_line/rubyopt_spec.rb +++ b/spec/ruby/command_line/rubyopt_spec.rb @@ -25,12 +25,12 @@ guard -> { RbConfig::CONFIG["CROSS_COMPILING"] != "yes" } do it "prints the version number for '-v'" do ENV["RUBYOPT"] = '-v' - ruby_exe("").sub("+PRISM ", "").sub(/\+GC(\[\w+\]\s|\s)?/, "")[/\A.*/].should == RUBY_DESCRIPTION.sub("+PRISM ", "").sub(/\+GC(\[\w+\]\s|\s)?/, "") + ruby_exe("")[/\A.*/].gsub(/\s\+(PRISM|GC(\[\w+\])?)(?=\s)/, "").should == RUBY_DESCRIPTION.gsub(/\s\+(PRISM|GC(\[\w+\])?)(?=\s)/, "") end it "ignores whitespace around the option" do ENV["RUBYOPT"] = ' -v ' - ruby_exe("").sub("+PRISM ", "").sub(/\+GC(\[\w+\]\s|\s)?/, "")[/\A.*/].should == RUBY_DESCRIPTION.sub("+PRISM ", "").sub(/\+GC(\[\w+\]\s|\s)?/, "") + ruby_exe("")[/\A.*/].gsub(/\s\+(PRISM|GC(\[\w+\])?)(?=\s)/, "").should == RUBY_DESCRIPTION.gsub(/\s\+(PRISM|GC(\[\w+\])?)(?=\s)/, "") end end diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb index 87e1fd8d26e908..934a470c1e626f 100644 --- a/test/ruby/test_set.rb +++ b/test/ruby/test_set.rb @@ -81,6 +81,20 @@ def test_s_new s = Set.new(ary) { |o| o * 2 } assert_equal([2,4,6], s.sort) + + assert_raise(ArgumentError) { + Set.new((1..)) + } + assert_raise(ArgumentError) { + Set.new((1..), &:succ) + } + assert_raise(ArgumentError) { + Set.new(1.upto(Float::INFINITY)) + } + + assert_raise(ArgumentError) { + Set.new(Object.new) + } end def test_clone diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 0dcdb8e4cb8275..b78d53e682233c 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -819,6 +819,13 @@ def a(n1,n2,n3,n4,n5,n6,n7,n8) = self } end + def test_spilled_param_new_arary + assert_compiles '[:ok]', %q{ + def a(n1,n2,n3,n4,n5,n6,n7,n8) = [n8] + a(0,0,0,0,0,0,0, :ok) + } + end + def test_opt_aref_with assert_compiles ':ok', %q{ def aref_with(hash) = hash["key"] @@ -882,6 +889,48 @@ def test = X end end + def test_constant_invalidation + assert_compiles '123', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path] + class C; end + def test = C + test + test + + C = 123 + test + RUBY + end + + def test_constant_path_invalidation + assert_compiles '["Foo::C", "Foo::C", "Bar::C"]', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path] + module A + module B; end + end + + module Foo + C = "Foo::C" + end + + module Bar + C = "Bar::C" + end + + A::B = Foo + + def test = A::B::C + + result = [] + + result << test + result << test + + A::B = Bar + + result << test + result + RUBY + end + def test_dupn assert_compiles '[[1], [1, 1], :rhs, [nil, :rhs]]', <<~RUBY, insns: [:dupn] def test(array) = (array[1, 2] ||= :rhs) diff --git a/tool/lib/envutil.rb b/tool/lib/envutil.rb index d02329d4f13053..fe166d85145ea7 100644 --- a/tool/lib/envutil.rb +++ b/tool/lib/envutil.rb @@ -226,7 +226,6 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = args = [args] if args.kind_of?(String) # use the same parser as current ruby if args.none? { |arg| arg.start_with?("--parser=") } - current_parser = RUBY_DESCRIPTION =~ /prism/i ? "prism" : "parse.y" args = ["--parser=#{current_parser}"] + args end pid = spawn(child_env, *precommand, rubybin, *args, opt) @@ -276,6 +275,12 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = end module_function :invoke_ruby + def current_parser + features = RUBY_DESCRIPTION[%r{\)\K [-+*/%._0-9a-zA-Z ]*(?=\[[-+*/%._0-9a-zA-Z]+\]\z)}] + features&.split&.include?("+PRISM") ? "prism" : "parse.y" + end + module_function :current_parser + def verbose_warning class << (stderr = "".dup) alias write concat diff --git a/vm_method.c b/vm_method.c index 84d0ed2f9e4c07..fa81d56c74119d 100644 --- a/vm_method.c +++ b/vm_method.c @@ -148,6 +148,7 @@ rb_clear_constant_cache_for_id(ID id) } rb_yjit_constant_state_changed(id); + rb_zjit_constant_state_changed(id); } static void diff --git a/yjit.rb b/yjit.rb index e8ba3cdd28752a..e4fafa729eea75 100644 --- a/yjit.rb +++ b/yjit.rb @@ -64,7 +64,6 @@ def self.enable(stats: false, log: false, mem_size: nil, call_threshold: nil) end at_exit { print_and_dump_stats } if stats - call_yjit_hooks Primitive.rb_yjit_enable(stats, stats != :quiet, log, log != :quiet, mem_size, call_threshold) end diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 7c8ce7bce5c69d..61b6f233269269 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -105,6 +105,9 @@ fn main() { .allowlist_var("SHAPE_ID_NUM_BITS") .allowlist_var("SHAPE_ID_HAS_IVAR_MASK") + // From ruby/internal/eval.h + .allowlist_function("rb_funcall") + // From ruby/internal/intern/object.h .allowlist_function("rb_obj_is_kind_of") .allowlist_function("rb_obj_frozen_p") @@ -269,6 +272,7 @@ fn main() { .allowlist_function("rb_float_new") // From vm_core.h + .allowlist_var("rb_cRubyVM") .allowlist_var("rb_mRubyVMFrozenCore") .allowlist_var("VM_BLOCK_HANDLER_NONE") .allowlist_type("vm_frame_env_flags") @@ -383,6 +387,7 @@ fn main() { .allowlist_function("rb_ivar_defined") .allowlist_function("rb_ivar_get") .allowlist_function("rb_mod_name") + .allowlist_function("rb_const_get") // From internal/vm.h .allowlist_var("rb_vm_insns_count") diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 725a29fa70cf23..f7a08b3b18b537 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -601,9 +601,15 @@ pub fn rust_str_to_ruby(str: &str) -> VALUE { /// Produce a Ruby symbol from a Rust string slice pub fn rust_str_to_sym(str: &str) -> VALUE { + let id = rust_str_to_id(str); + unsafe { rb_id2sym(id) } +} + +/// Produce an ID from a Rust string slice +pub fn rust_str_to_id(str: &str) -> ID { let c_str = CString::new(str).unwrap(); let c_ptr: *const c_char = c_str.as_ptr(); - unsafe { rb_id2sym(rb_intern(c_ptr)) } + unsafe { rb_intern(c_ptr) } } /// Produce an owned Rust String from a C char pointer diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 666f5c4f28d614..eeabbf594df9bb 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -405,7 +405,6 @@ pub const imemo_memo: imemo_type = 5; pub const imemo_ment: imemo_type = 6; pub const imemo_iseq: imemo_type = 7; pub const imemo_tmpbuf: imemo_type = 8; -pub const imemo_ast: imemo_type = 9; pub const imemo_callinfo: imemo_type = 10; pub const imemo_callcache: imemo_type = 11; pub const imemo_constcache: imemo_type = 12; @@ -1019,6 +1018,7 @@ extern "C" { pub fn rb_gc_location(obj: VALUE) -> VALUE; pub fn rb_gc_writebarrier(old: VALUE, young: VALUE); pub fn rb_class_get_superclass(klass: VALUE) -> VALUE; + pub fn rb_funcall(recv: VALUE, mid: ID, n: ::std::os::raw::c_int, ...) -> VALUE; pub static mut rb_mKernel: VALUE; pub static mut rb_cBasicObject: VALUE; pub static mut rb_cArray: VALUE; @@ -1080,6 +1080,7 @@ extern "C" { pub fn rb_ivar_get(obj: VALUE, name: ID) -> VALUE; pub fn rb_ivar_defined(obj: VALUE, name: ID) -> VALUE; pub fn rb_attr_get(obj: VALUE, name: ID) -> VALUE; + pub fn rb_const_get(space: VALUE, name: ID) -> VALUE; pub fn rb_obj_info_dump(obj: VALUE); pub fn rb_class_allocate_instance(klass: VALUE) -> VALUE; pub fn rb_obj_equal(obj1: VALUE, obj2: VALUE) -> VALUE; @@ -1102,6 +1103,7 @@ extern "C" { klass: VALUE, id: ID, ) -> *const rb_callable_method_entry_t; + pub static mut rb_cRubyVM: VALUE; pub static mut rb_mRubyVMFrozenCore: VALUE; pub static mut rb_block_param_proxy: VALUE; pub fn rb_vm_ep_local_ep(ep: *const VALUE) -> *const VALUE; diff --git a/yjit/src/yjit.rs b/yjit/src/yjit.rs index 4b2b5214f4da13..8df1163d64b725 100644 --- a/yjit/src/yjit.rs +++ b/yjit/src/yjit.rs @@ -54,6 +54,12 @@ fn yjit_init() { // TODO: need to make sure that command-line options have been // initialized by CRuby + // Call YJIT hooks before enabling YJIT to avoid compiling the hooks themselves + unsafe { + let yjit = rb_const_get(rb_cRubyVM, rust_str_to_id("YJIT")); + rb_funcall(yjit, rust_str_to_id("call_yjit_hooks"), 0); + } + // Catch panics to avoid UB for unwinding into C frames. // See https://doc.rust-lang.org/nomicon/exception-safety.html let result = std::panic::catch_unwind(|| { diff --git a/yjit_hook.rb b/yjit_hook.rb index 8f0f38aaf1f535..610a7be3303e2c 100644 --- a/yjit_hook.rb +++ b/yjit_hook.rb @@ -1,8 +1,3 @@ -# If YJIT is enabled, load the YJIT-only version of builtin methods -if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled? - RubyVM::YJIT.send(:call_yjit_hooks) -end - # Remove the helper defined in kernel.rb class Module undef :with_yjit diff --git a/zjit.h b/zjit.h index 724ae4abd05b15..e354d4b122d0cf 100644 --- a/zjit.h +++ b/zjit.h @@ -14,6 +14,7 @@ void rb_zjit_profile_enable(const rb_iseq_t *iseq); void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop); void rb_zjit_cme_invalidate(const rb_callable_method_entry_t *cme); void rb_zjit_invalidate_ep_is_bp(const rb_iseq_t *iseq); +void rb_zjit_constant_state_changed(ID id); void rb_zjit_iseq_mark(void *payload); void rb_zjit_iseq_update_references(void *payload); #else @@ -24,6 +25,7 @@ static inline void rb_zjit_profile_enable(const rb_iseq_t *iseq) {} static inline void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop) {} static inline void rb_zjit_cme_invalidate(const rb_callable_method_entry_t *cme) {} static inline void rb_zjit_invalidate_ep_is_bp(const rb_iseq_t *iseq) {} +static inline void rb_zjit_constant_state_changed(ID id) {} #endif // #if USE_YJIT #endif // #ifndef ZJIT_H diff --git a/zjit/src/asm/arm64/opnd.rs b/zjit/src/asm/arm64/opnd.rs index 28422b747652d8..a77958f7e6eeec 100644 --- a/zjit/src/asm/arm64/opnd.rs +++ b/zjit/src/asm/arm64/opnd.rs @@ -119,6 +119,9 @@ pub const X20_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 20 }; pub const X21_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 21 }; pub const X22_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 22 }; +// frame pointer (base pointer) +pub const X29_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 29 }; + // link register pub const X30_REG: A64Reg = A64Reg { num_bits: 64, reg_no: 30 }; diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 88ccad8e091ed1..42dc31c90fd5cc 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -29,6 +29,7 @@ pub const C_ARG_OPNDS: [Opnd; 6] = [ pub const C_RET_REG: Reg = X0_REG; pub const C_RET_OPND: Opnd = Opnd::Reg(X0_REG); pub const NATIVE_STACK_PTR: Opnd = Opnd::Reg(XZR_REG); +pub const NATIVE_BASE_PTR: Opnd = Opnd::Reg(X29_REG); // These constants define the way we work with Arm64's stack pointer. The stack // pointer always needs to be aligned to a 16-byte boundary. @@ -911,18 +912,54 @@ impl Assembler cb.write_byte(0); } }, - Insn::FrameSetup => { + &Insn::FrameSetup { preserved, mut slot_count } => { + const { assert!(SIZEOF_VALUE == 8, "alignment logic relies on SIZEOF_VALUE == 8"); } + // Preserve X29 and set up frame record stp_pre(cb, X29, X30, A64Opnd::new_mem(128, C_SP_REG, -16)); - - // X29 (frame_pointer) = SP mov(cb, X29, C_SP_REG); - }, - Insn::FrameTeardown => { + + for regs in preserved.chunks(2) { + // For the body, store pairs and move SP + if let [reg0, reg1] = regs { + stp_pre(cb, reg1.into(), reg0.into(), A64Opnd::new_mem(128, C_SP_REG, -16)); + } else if let [reg] = regs { + // For overhang, store but don't move SP. Combine movement with + // movement for slots below. + stur(cb, reg.into(), A64Opnd::new_mem(64, C_SP_REG, -8)); + slot_count += 1; + } else { + unreachable!("chunks(2)"); + } + } + // Align slot_count + if slot_count % 2 == 1 { + slot_count += 1 + } + if slot_count > 0 { + let slot_offset = (slot_count * SIZEOF_VALUE) as u64; + // Bail when asked to reserve too many slots in one instruction. + ShiftedImmediate::try_from(slot_offset).ok()?; + sub(cb, C_SP_REG, C_SP_REG, A64Opnd::new_uimm(slot_offset)); + } + } + Insn::FrameTeardown { preserved } => { + // Restore preserved registers below frame pointer. + let mut base_offset = 0; + for regs in preserved.chunks(2) { + if let [reg0, reg1] = regs { + base_offset -= 16; + ldp(cb, reg1.into(), reg0.into(), A64Opnd::new_mem(128, X29, base_offset)); + } else if let [reg] = regs { + ldur(cb, reg.into(), A64Opnd::new_mem(64, X29, base_offset - 8)); + } else { + unreachable!("chunks(2)"); + } + } + // SP = X29 (frame pointer) mov(cb, C_SP_REG, X29); - ldp_post(cb, X29, X30, A64Opnd::new_mem(128, C_SP_REG, 16)); - }, + } Insn::Add { left, right, out } => { // Usually, we issue ADDS, so you could branch on overflow, but ADDS with // out=31 refers to out=XZR, which discards the sum. So, instead of ADDS @@ -1482,11 +1519,73 @@ mod tests { fn test_emit_frame() { let (mut asm, mut cb) = setup_asm(); - asm.frame_setup(); - asm.frame_teardown(); + asm.frame_setup(&[], 0); + asm.frame_teardown(&[]); asm.compile_with_num_regs(&mut cb, 0); } + #[test] + fn frame_setup_and_teardown() { + const THREE_REGS: &'static [Opnd] = &[Opnd::Reg(X19_REG), Opnd::Reg(X20_REG), Opnd::Reg(X21_REG)]; + // Test 3 preserved regs (odd), odd slot_count + { + let (mut asm, mut cb) = setup_asm(); + asm.frame_setup(THREE_REGS, 3); + asm.frame_teardown(THREE_REGS); + asm.compile_with_num_regs(&mut cb, 0); + assert_disasm!(cb, "fd7bbfa9fd030091f44fbfa9f5831ff8ff8300d1b44f7fa9b5835ef8bf030091fd7bc1a8", " + 0x0: stp x29, x30, [sp, #-0x10]! + 0x4: mov x29, sp + 0x8: stp x20, x19, [sp, #-0x10]! + 0xc: stur x21, [sp, #-8] + 0x10: sub sp, sp, #0x20 + 0x14: ldp x20, x19, [x29, #-0x10] + 0x18: ldur x21, [x29, #-0x18] + 0x1c: mov sp, x29 + 0x20: ldp x29, x30, [sp], #0x10 + "); + } + + // Test 3 preserved regs (odd), even slot_count + { + let (mut asm, mut cb) = setup_asm(); + asm.frame_setup(THREE_REGS, 4); + asm.frame_teardown(THREE_REGS); + asm.compile_with_num_regs(&mut cb, 0); + assert_disasm!(cb, "fd7bbfa9fd030091f44fbfa9f5831ff8ffc300d1b44f7fa9b5835ef8bf030091fd7bc1a8", " + 0x0: stp x29, x30, [sp, #-0x10]! + 0x4: mov x29, sp + 0x8: stp x20, x19, [sp, #-0x10]! + 0xc: stur x21, [sp, #-8] + 0x10: sub sp, sp, #0x30 + 0x14: ldp x20, x19, [x29, #-0x10] + 0x18: ldur x21, [x29, #-0x18] + 0x1c: mov sp, x29 + 0x20: ldp x29, x30, [sp], #0x10 + "); + } + + // Test 4 preserved regs (even), odd slot_count + { + static FOUR_REGS: &'static [Opnd] = &[Opnd::Reg(X19_REG), Opnd::Reg(X20_REG), Opnd::Reg(X21_REG), Opnd::Reg(X22_REG)]; + let (mut asm, mut cb) = setup_asm(); + asm.frame_setup(FOUR_REGS, 3); + asm.frame_teardown(FOUR_REGS); + asm.compile_with_num_regs(&mut cb, 0); + assert_disasm!(cb, "fd7bbfa9fd030091f44fbfa9f657bfa9ff8300d1b44f7fa9b6577ea9bf030091fd7bc1a8", " + 0x0: stp x29, x30, [sp, #-0x10]! + 0x4: mov x29, sp + 0x8: stp x20, x19, [sp, #-0x10]! + 0xc: stp x22, x21, [sp, #-0x10]! + 0x10: sub sp, sp, #0x20 + 0x14: ldp x20, x19, [x29, #-0x10] + 0x18: ldp x22, x21, [x29, #-0x20] + 0x1c: mov sp, x29 + 0x20: ldp x29, x30, [sp], #0x10 + "); + } + } + #[test] fn test_emit_je_fits_into_bcond() { let (mut asm, mut cb) = setup_asm(); diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 7bac210bee6689..36e783bd4e658a 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -12,10 +12,12 @@ use crate::asm::{CodeBlock, Label}; pub use crate::backend::current::{ Reg, EC, CFP, SP, - NATIVE_STACK_PTR, + NATIVE_STACK_PTR, NATIVE_BASE_PTR, C_ARG_OPNDS, C_RET_REG, C_RET_OPND, }; +pub static JIT_PRESERVED_REGS: &'static [Opnd] = &[CFP, SP, EC]; + // Memory operand base #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum MemBase @@ -291,8 +293,6 @@ pub enum Target context: Option, /// We use this to enrich asm comments. reason: SideExitReason, - /// The number of bytes we need to adjust the C stack pointer by. - c_stack_bytes: usize, /// Some if the side exit should write this label. We use it for patch points. label: Option