diff --git a/.github/actions/capiext/action.yml b/.github/actions/capiext/action.yml index c17069f97dae20..147c5387b3183e 100644 --- a/.github/actions/capiext/action.yml +++ b/.github/actions/capiext/action.yml @@ -50,24 +50,34 @@ runs: touch spec/ruby/optional/capi/ext/*.$DLEXT [ ! -f spec/ruby/optional/capi/ext/\*.$DLEXT ] ${{ inputs.make }} SPECOPTS=optional/capi test-spec - rm -f spec/ruby/optional/capi/ext/*.c env: DLEXT: ${{ steps.config.outputs.DLEXT }} working-directory: ${{ inputs.builddir }} if: ${{ steps.cache.outputs.cache-hit }} - - name: Save CAPI extensions - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 - with: - path: ${{ inputs.builddir }}/spec/ruby/optional/capi/ext/ - key: ${{ steps.config.outputs.key }} + - name: Strip CAPI extensions + id: strip + shell: bash + run: | + rm -f spec/ruby/optional/capi/ext/*.c + [ "$DLEXT" = bundle ] || # separated to .dSYM directories + strip spec/ruby/optional/capi/ext/*.$DLEXT + env: + DLEXT: ${{ steps.config.outputs.DLEXT }} + working-directory: ${{ inputs.builddir }} if: >- ${{true - && steps.cache.outcome == 'success' && ! steps.cache.outputs.cache-hit && github.ref_name == 'master' }} + - name: Save CAPI extensions + uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: ${{ inputs.builddir }}/spec/ruby/optional/capi/ext/ + key: ${{ steps.config.outputs.key }} + if: ${{ steps.strip.outcome == 'success' }} + - shell: bash run: | echo "::error::Change from ${prev} detected; bump up ABI version" diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 6249418a9abafb..4358e4c3a7e8ce 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -40,9 +40,9 @@ jobs: - test_task: check os: ubuntu-24.04 extra_checks: [capi] - # ubuntu-24.04-arm jobs don't start on ruby/ruby as of 2025-09-04 - #- test_task: check - # os: ubuntu-24.04-arm + - test_task: check + os: ubuntu-24.04-arm + extra_checks: [capi] fail-fast: false env: diff --git a/NEWS.md b/NEWS.md index 7157700816c3e8..7534539a21d2c9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -63,7 +63,7 @@ Note: We're only listing outstanding class updates. * IO - * `IO.select` accepts +Float::INFINITY+ as a timeout argument. + * `IO.select` accepts `Float::INFINITY` as a timeout argument. [[Feature #20610]] * Math diff --git a/imemo.c b/imemo.c index 1bef7a71a69e77..74eae5678f3532 100644 --- a/imemo.c +++ b/imemo.c @@ -48,6 +48,15 @@ rb_imemo_new(enum imemo_type type, VALUE v0, size_t size) return (VALUE)obj; } +VALUE +rb_imemo_tmpbuf_new(void) +{ + VALUE flags = T_IMEMO | (imemo_tmpbuf << FL_USHIFT); + NEWOBJ_OF(obj, rb_imemo_tmpbuf_t, 0, flags, sizeof(rb_imemo_tmpbuf_t), NULL); + + return (VALUE)obj; +} + void * rb_alloc_tmp_buffer_with_count(volatile VALUE *store, size_t size, size_t cnt) { @@ -85,17 +94,6 @@ rb_free_tmp_buffer(volatile VALUE *store) } } -rb_imemo_tmpbuf_t * -rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt) -{ - rb_imemo_tmpbuf_t *tmpbuf = (rb_imemo_tmpbuf_t *)rb_imemo_tmpbuf_new(); - tmpbuf->ptr = buf; - tmpbuf->next = old_heap; - tmpbuf->cnt = cnt; - - return tmpbuf; -} - static VALUE imemo_fields_new(VALUE owner, size_t capa) { @@ -469,9 +467,7 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) const rb_imemo_tmpbuf_t *m = (const rb_imemo_tmpbuf_t *)obj; if (!reference_updating) { - do { - rb_gc_mark_locations(m->ptr, m->ptr + m->cnt); - } while ((m = m->next) != NULL); + rb_gc_mark_locations(m->ptr, m->ptr + m->cnt); } break; diff --git a/internal/imemo.h b/internal/imemo.h index 3673190809e86a..eee3dd71854a8e 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -94,7 +94,6 @@ struct vm_ifunc { struct rb_imemo_tmpbuf_struct { VALUE flags; VALUE *ptr; /* malloc'ed buffer */ - struct rb_imemo_tmpbuf_struct *next; /* next imemo */ size_t cnt; /* buffer size in VALUE */ }; @@ -132,7 +131,7 @@ struct MEMO { #ifndef RUBY_RUBYPARSER_H typedef struct rb_imemo_tmpbuf_struct rb_imemo_tmpbuf_t; #endif -rb_imemo_tmpbuf_t *rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt); +VALUE rb_imemo_tmpbuf_new(void); struct vm_ifunc *rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int max_argc); static inline enum imemo_type imemo_type(VALUE imemo); static inline int imemo_type_p(VALUE imemo, enum imemo_type imemo_type); @@ -198,12 +197,6 @@ rb_vm_ifunc_proc_new(rb_block_call_func_t func, const void *data) return rb_vm_ifunc_new(func, data, 0, UNLIMITED_ARGUMENTS); } -static inline VALUE -rb_imemo_tmpbuf_new(void) -{ - return rb_imemo_new(imemo_tmpbuf, 0, sizeof(rb_imemo_tmpbuf_t)); -} - static inline void * RB_IMEMO_TMPBUF_PTR(VALUE v) { diff --git a/lib/erb.rb b/lib/erb.rb index d4f43e0772341b..f3f2ae3f471811 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -189,7 +189,17 @@ # # When you call method #result, # the method executes the code and removes the entire execution tag -# (generating no text in the result). +# (generating no text in the result): +# +# ``` +# ERB.new('foo <% Dir.chdir("C:/") %> bar').result # => "foo bar" +# ``` +# +# Whitespace before and after the embedded code is optional: +# +# ``` +# ERB.new('foo <%Dir.chdir("C:/")%> bar').result # => "foo bar" +# ``` # # You can interleave text with execution tags to form a control structure # such as a conditional, a loop, or a `case` statements. @@ -261,7 +271,7 @@ # # #### Shorthand Format for Execution Tags # -# You can give `trim_mode: '%'` to enable a shorthand format for execution tags; +# You can use keyword argument `trim_mode: '%'` to enable a shorthand format for execution tags; # this example uses the shorthand format `% _code_` instead of `<% _code_ %>`: # # ``` @@ -283,6 +293,90 @@ # Note that in the shorthand format, the character `'%'` must be the first character in the code line # (no leading whitespace). # +# #### Suppressing Unwanted Blank Lines +# +# With keyword argument `trim_mode` not given, +# all blank lines go into the result: +# +# ``` +# s = < +# <%= RUBY_VERSION %> +# <% end %> +# EOT +# ERB.new(s).result.lines.each {|line| puts line.inspect } +# "\n" +# "3.4.5\n" +# "\n" +# ``` +# +# You can give `trim_mode: '-'`, you can suppress each blank line +# whose source line ends with `-%>` (instead of `%>`): +# +# ``` +# s = < +# <%= RUBY_VERSION %> +# <% end -%> +# EOT +# ERB.new(s, trim_mode: '-').result.lines.each {|line| puts line.inspect } +# "3.4.5\n" +# ``` +# +# It is an error to use the trailing `'-%>'` notation without `trim_mode: '-'`: +# +# ``` +# ERB.new(s).result.lines.each {|line| puts line.inspect } # Raises SyntaxError. +# ``` +# +# #### Suppressing Unwanted Newlines +# +# Consider this input string: +# +# ``` +# s = < +# <%= RUBY_VERSION %> +# foo <% RUBY_VERSION %> +# foo <%= RUBY_VERSION %> +# EOT +# ``` +# +# With keyword argument `trim_mode` not given, all newlines go into the result: +# +# ``` +# ERB.new(s).result.lines.each {|line| puts line.inspect } +# "\n" +# "3.4.5\n" +# "foo \n" +# "foo 3.4.5\n" +# ``` +# +# You can give `trim_mode: '>'` to suppress the trailing newline +# for each line that ends with `'%<'` (regardless of its beginning): +# +# ``` +# ERB.new(s, trim_mode: '>').result.lines.each {|line| puts line.inspect } +# "3.4.5foo foo 3.4.5" +# ``` +# +# You can give `trim_mode: '<>'` to suppress the trailing newline +# for each line that both begins with `'<%'` and ends with `'%<'`: +# +# ``` +# ERB.new(s, trim_mode: '<>').result.lines.each {|line| puts line.inspect } +# "3.4.5foo \n" +# "foo 3.4.5\n" +# ``` +# +# #### Combining Trim Modes +# +# You can combine certain trim modes: +# +# - `'%-'`: Enable shorthand and omit each blank line ending with `'%>'`. +# - `'%>'`: Enable shorthand and omit newline for each line ending with `'%>'`. +# - `'%<>'`: Enable shorthand and omit newline for each line starting with `'<%'` and ending with `'%>'`. +# # ### Comment Tags # # You can embed a comment in a template using a *comment tag*; @@ -537,19 +631,19 @@ def self.version # # **Keyword Argument `trim_mode`** # - # When keyword argument `trim_mode` has a string value, - # that value may be one of: + # You can use keyword argument `trim_mode: '%'` + # to enable the [shorthand format][shorthand format] for execution tags. + # + # This value allows [blank line control][blank line control]: # - # - `'%'`: Enable [shorthand format][shorthand format] for execution tags. # - `'-'`: Omit each blank line ending with `'%>'`. + # + # Other values allow [newline control][newline control]: + # # - `'>'`: Omit newline for each line ending with `'%>'`. # - `'<>'`: Omit newline for each line starting with `'<%'` and ending with `'%>'`. # - # The value may also be certain combinations of the above. - # - # - `'%-'`: Enable shorthand and omit each blank line ending with `'%>'`. - # - `'%>'`: Enable shorthand and omit newline for each line ending with `'%>'`. - # - `'%<>'`: Enable shorthand and omit newline for each line starting with `'<%'` and ending with `'%>'`. + # You can also [combine trim modes][combine trim modes]. # # **Keyword Argument `eoutvar`** # @@ -578,9 +672,12 @@ def self.version # However, their values, if given, are handled thus: # # - `safe_level`: ignored. - # - `legacy_trim_mode: overrides keyword argument `trim_mode`. - # - `legacy_eoutvar: overrides keyword argument `eoutvar`. + # - `legacy_trim_mode`: overrides keyword argument `trim_mode`. + # - `legacy_eoutvar`: overrides keyword argument `eoutvar`. # + # [blank line control]: rdoc-ref:ERB@Suppressing+Unwanted+Blank+Lines + # [combine trim modes]: rdoc-ref:ERB@Combining+Trim+Modes + # [newline control]: rdoc-ref:ERB@Suppressing+Unwanted+Newlines # [shorthand format]: rdoc-ref:ERB@Shorthand+Format+for+Execution+Tags # def initialize(str, safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eoutvar=NOT_GIVEN, trim_mode: nil, eoutvar: '_erbout') diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index ac538a2e97ae43..2ca7da0bf2a5d5 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -152,7 +152,7 @@ def visit_assoc_splat_node(node) # ^^ # ``` def visit_back_reference_read_node(node) - s(node, :back_ref, node.name.name.delete_prefix("$").to_sym) + s(node, :back_ref, node.name.to_s.delete_prefix("$").to_sym) end # ``` diff --git a/numeric.c b/numeric.c index 89cff8a730fc9c..de5b02aaf9eb41 100644 --- a/numeric.c +++ b/numeric.c @@ -6455,7 +6455,7 @@ Init_Numeric(void) * * If the platform supports denormalized numbers, * there are numbers between zero and Float::MIN. - * 0.0.next_float returns the smallest positive floating point number + * +0.0.next_float+ returns the smallest positive floating point number * including denormalized numbers. */ rb_define_const(rb_cFloat, "MIN", DBL2NUM(DBL_MIN)); diff --git a/prism/prism.c b/prism/prism.c index 06419d13789e45..2e202c37456ea5 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -18491,20 +18491,28 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b return (pm_node_t *) node; } case PM_TOKEN_CHARACTER_LITERAL: { - parser_lex(parser); - - pm_token_t opening = parser->previous; - opening.type = PM_TOKEN_STRING_BEGIN; - opening.end = opening.start + 1; - - pm_token_t content = parser->previous; - content.type = PM_TOKEN_STRING_CONTENT; - content.start = content.start + 1; - pm_token_t closing = not_provided(parser); - pm_node_t *node = (pm_node_t *) pm_string_node_create_current_string(parser, &opening, &content, &closing); + pm_node_t *node = (pm_node_t *) pm_string_node_create_current_string( + parser, + &(pm_token_t) { + .type = PM_TOKEN_STRING_BEGIN, + .start = parser->current.start, + .end = parser->current.start + 1 + }, + &(pm_token_t) { + .type = PM_TOKEN_STRING_CONTENT, + .start = parser->current.start + 1, + .end = parser->current.end + }, + &closing + ); + pm_node_flag_set(node, parse_unescaped_encoding(parser)); + // Skip past the character literal here, since now we have handled + // parser->explicit_encoding correctly. + parser_lex(parser); + // Characters can be followed by strings in which case they are // automatically concatenated. if (match1(parser, PM_TOKEN_STRING_BEGIN)) { diff --git a/spec/ruby/optional/capi/regexp_spec.rb b/spec/ruby/optional/capi/regexp_spec.rb index 4b130ff66a067c..49ac79f5c48637 100644 --- a/spec/ruby/optional/capi/regexp_spec.rb +++ b/spec/ruby/optional/capi/regexp_spec.rb @@ -110,7 +110,7 @@ end end - describe "rb_memicmp" do + describe "rb_memcicmp" do it "returns 0 for identical strings" do @p.rb_memcicmp('Hello', 'Hello').should == 0 end diff --git a/test/prism/fixtures/character_literal.txt b/test/prism/fixtures/character_literal.txt new file mode 100644 index 00000000000000..920332123f13ac --- /dev/null +++ b/test/prism/fixtures/character_literal.txt @@ -0,0 +1,2 @@ +# encoding: Windows-31J +p ?\u3042"" diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index bcaed7979150bc..b21ad81391ed1e 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -16,6 +16,7 @@ module Prism class RubyParserTest < TestCase todos = [ + "character_literal.txt", "encoding_euc_jp.txt", "regex_char_width.txt", "seattlerb/masgn_colon3.txt", diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 87d8b06ece2a8d..42d10490c5cee6 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -439,6 +439,37 @@ def entry = [test(1), test(3, 4)] }, call_threshold: 2 end + def test_send_nil_block_arg + assert_compiles 'false', %q{ + def test = block_given? + def entry = test(&nil) + test + } + end + + def test_send_symbol_block_arg + assert_compiles '["1", "2"]', %q{ + def test = [1, 2].map(&:to_s) + test + } + end + + def test_send_splat + assert_runs '[1, 2]', %q{ + def test(a, b) = [a, b] + def entry(arr) = test(*arr) + entry([1, 2]) + } + end + + def test_send_kwarg + assert_runs '[1, 2]', %q{ + def test(a:, b:) = [a, b] + def entry = test(a: 1, b: 2) + entry + } + end + def test_forwardable_iseq assert_compiles '1', %q{ def test(...) = 1 diff --git a/zjit.rb b/zjit.rb index d6f7a4069267b8..44bce453fff827 100644 --- a/zjit.rb +++ b/zjit.rb @@ -40,7 +40,6 @@ def stats_string stats = self.stats # Show exit reasons, ordered by the typical amount of exits for the prefix at the time - print_counters_with_prefix(prefix: 'unhandled_call_', prompt: 'unhandled call types', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index c67bf8c9ba01e8..5f96d0718a9d9f 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -6,7 +6,7 @@ use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_ use crate::hir::SideExitReason; use crate::options::{debug, get_option}; use crate::cruby::VALUE; -use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_call_type, exit_counter_ptr_for_opcode, CompileError}; +use crate::stats::{exit_counter_ptr, exit_counter_ptr_for_opcode, CompileError}; use crate::virtualmem::CodePtr; use crate::asm::{CodeBlock, Label}; @@ -1601,11 +1601,6 @@ impl Assembler self.load_into(SCRATCH_OPND, Opnd::const_ptr(exit_counter_ptr_for_opcode(opcode))); self.incr_counter_with_reg(Opnd::mem(64, SCRATCH_OPND, 0), 1.into(), C_RET_OPND); } - if let SideExitReason::UnhandledCallType(call_type) = reason { - asm_comment!(self, "increment an unknown call type counter"); - self.load_into(SCRATCH_OPND, Opnd::const_ptr(exit_counter_ptr_for_call_type(call_type))); - self.incr_counter_with_reg(Opnd::mem(64, SCRATCH_OPND, 0), 1.into(), C_RET_OPND); - } } asm_comment!(self, "exit to the interpreter"); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 3726d8ec0e44d5..1a268e62da97f7 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -447,10 +447,10 @@ impl PtrPrintMap { #[derive(Debug, Clone, Copy)] pub enum SideExitReason { UnknownNewarraySend(vm_opt_newarray_send_type), - UnhandledCallType(CallType), UnknownSpecialVariable(u64), UnhandledHIRInsn(InsnId), UnhandledYARVInsn(u32), + UnhandledCallType(CallType), FixnumAddOverflow, FixnumSubOverflow, FixnumMultOverflow, @@ -2968,7 +2968,8 @@ fn compute_bytecode_info(iseq: *const rb_iseq_t) -> BytecodeInfo { #[derive(Debug, PartialEq, Clone, Copy)] pub enum CallType { - BlockArg, + Splat, + Kwarg, Tailcall, } @@ -2986,9 +2987,10 @@ fn num_locals(iseq: *const rb_iseq_t) -> usize { } /// If we can't handle the type of send (yet), bail out. -fn unknown_call_type(flag: u32) -> Result<(), CallType> { - if (flag & VM_CALL_ARGS_BLOCKARG) != 0 { return Err(CallType::BlockArg); } - if (flag & VM_CALL_TAILCALL) != 0 { return Err(CallType::Tailcall); } +fn unhandled_call_type(flags: u32) -> Result<(), CallType> { + if (flags & VM_CALL_ARGS_SPLAT) != 0 { return Err(CallType::Splat); } + if (flags & VM_CALL_KWARG) != 0 { return Err(CallType::Kwarg); } + if (flags & VM_CALL_TAILCALL) != 0 { return Err(CallType::Tailcall); } Ok(()) } @@ -3501,8 +3503,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // NB: opt_neq has two cd; get_arg(0) is for eq and get_arg(1) is for neq let cd: *const rb_call_data = get_arg(pc, 1).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { - // Unknown call type; side-exit into the interpreter + let flags = unsafe { rb_vm_ci_flag(call_info) }; + if let Err(call_type) = unhandled_call_type(flags) { + // Can't handle the call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block @@ -3522,8 +3525,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // NB: these instructions have the recv for the call at get_arg(0) let cd: *const rb_call_data = get_arg(pc, 1).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { - // Unknown call type; side-exit into the interpreter + let flags = unsafe { rb_vm_ci_flag(call_info) }; + if let Err(call_type) = unhandled_call_type(flags) { + // Can't handle the call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block @@ -3580,8 +3584,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { YARVINSN_opt_send_without_block => { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { - // Unknown call type; side-exit into the interpreter + let flags = unsafe { rb_vm_ci_flag(call_info) }; + if let Err(call_type) = unhandled_call_type(flags) { + // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block @@ -3598,15 +3603,17 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let blockiseq: IseqPtr = get_arg(pc, 1).as_iseq(); let call_info = unsafe { rb_get_call_data_ci(cd) }; - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { - // Unknown call type; side-exit into the interpreter + let flags = unsafe { rb_vm_ci_flag(call_info) }; + if let Err(call_type) = unhandled_call_type(flags) { + // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; + let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0; - let args = state.stack_pop_n(argc as usize)?; + let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?; let recv = state.stack_pop()?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); let send = fun.push_insn(block, Insn::Send { recv, cd, blockiseq, args, state: exit_id }); @@ -3624,14 +3631,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { YARVINSN_invokesuper => { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) } & !VM_CALL_SUPER & !VM_CALL_ZSUPER) { - // Unknown call type; side-exit into the interpreter + let flags = unsafe { rb_vm_ci_flag(call_info) }; + if let Err(call_type) = unhandled_call_type(flags) { + // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; - let args = state.stack_pop_n(argc as usize)?; + let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0; + let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?; let recv = state.stack_pop()?; let blockiseq: IseqPtr = get_arg(pc, 1).as_ptr(); let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); @@ -3652,14 +3661,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { YARVINSN_invokeblock => { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); let call_info = unsafe { rb_get_call_data_ci(cd) }; - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { - // Unknown call type; side-exit into the interpreter + let flags = unsafe { rb_vm_ci_flag(call_info) }; + if let Err(call_type) = unhandled_call_type(flags) { + // Can't handle tailcall; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; - let args = state.stack_pop_n(argc as usize)?; + let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0; + let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?; let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); let result = fun.push_insn(block, Insn::InvokeBlock { cd, args, state: exit_id }); state.stack_push(result); @@ -3767,11 +3778,6 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } YARVINSN_objtostring => { let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); - let call_info = unsafe { rb_get_call_data_ci(cd) }; - - if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { - panic!("objtostring should not have unknown call type {call_type:?}"); - } let argc = unsafe { vm_ci_argc((*cd).ci) }; assert_eq!(0, argc, "objtostring should not have args"); @@ -5108,21 +5114,22 @@ mod tests { fn test@:2: bb0(v0:BasicObject, v1:BasicObject): v6:ArrayExact = ToArray v1 - v8:BasicObject = SendWithoutBlock v0, :foo, v6 - CheckInterrupts - Return v8 + SideExit UnhandledCallType(Splat) "); } #[test] - fn test_cant_compile_block_arg() { + fn test_compile_block_arg() { eval(" def test(a) = foo(&a) "); assert_snapshot!(hir_string("test"), @r" fn test@:2: bb0(v0:BasicObject, v1:BasicObject): - SideExit UnhandledCallType(BlockArg) + v6:BasicObject = Send v0, 0x1000, :foo, v1 + v7:BasicObject = GetLocal l0, EP@3 + CheckInterrupts + Return v6 "); } @@ -5135,9 +5142,7 @@ mod tests { fn test@:2: bb0(v0:BasicObject, v1:BasicObject): v5:Fixnum[1] = Const Value(1) - v7:BasicObject = SendWithoutBlock v0, :foo, v5 - CheckInterrupts - Return v7 + SideExit UnhandledCallType(Kwarg) "); } @@ -5194,7 +5199,9 @@ mod tests { fn test@:2: bb0(v0:BasicObject): v4:NilClass = Const Value(nil) - SideExit UnhandledCallType(BlockArg) + v6:BasicObject = InvokeSuper v0, 0x1000, v4 + CheckInterrupts + Return v6 "); } @@ -5257,9 +5264,7 @@ mod tests { v6:ArrayExact = ToNewArray v1 v7:Fixnum[1] = Const Value(1) ArrayPush v6, v7 - v11:BasicObject = SendWithoutBlock v0, :foo, v6 - CheckInterrupts - Return v11 + SideExit UnhandledCallType(Splat) "); } @@ -7855,9 +7860,7 @@ mod opt_tests { fn test@:3: bb0(v0:BasicObject): v4:Fixnum[1] = Const Value(1) - v6:BasicObject = SendWithoutBlock v0, :foo, v4 - CheckInterrupts - Return v6 + SideExit UnhandledCallType(Kwarg) "); } @@ -7873,9 +7876,7 @@ mod opt_tests { fn test@:3: bb0(v0:BasicObject): v4:Fixnum[1] = Const Value(1) - v6:BasicObject = SendWithoutBlock v0, :foo, v4 - CheckInterrupts - Return v6 + SideExit UnhandledCallType(Kwarg) "); } @@ -8074,7 +8075,10 @@ mod opt_tests { fn test@:2: bb0(v0:BasicObject, v1:BasicObject): v6:BasicObject = GetBlockParamProxy l0 - SideExit UnhandledCallType(BlockArg) + v8:BasicObject = Send v0, 0x1000, :tap, v6 + v9:BasicObject = GetLocal l0, EP@3 + CheckInterrupts + Return v8 "); } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 8a7073dd977c94..0eb2b8687bdd48 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -92,7 +92,9 @@ make_counters! { // exit_: Side exits reasons exit_compile_error, exit_unknown_newarray_send, - exit_unhandled_call_type, + exit_unhandled_tailcall, + exit_unhandled_splat, + exit_unhandled_kwarg, exit_unknown_special_variable, exit_unhandled_hir_insn, exit_unhandled_yarv_insn, @@ -162,17 +164,6 @@ pub fn exit_counter_ptr_for_opcode(opcode: u32) -> *mut u64 { unsafe { exit_counters.get_unchecked_mut(opcode as usize) } } -/// Return a raw pointer to the exit counter for a given call type -pub fn exit_counter_ptr_for_call_type(call_type: crate::hir::CallType) -> *mut u64 { - use crate::hir::CallType::*; - use crate::stats::Counter::*; - let counter = match call_type { - BlockArg => unhandled_call_block_arg, - Tailcall => unhandled_call_tailcall, - }; - counter_ptr(counter) -} - /// Reason why ZJIT failed to produce any JIT code #[derive(Clone, Debug, PartialEq)] pub enum CompileError { @@ -206,10 +197,13 @@ pub fn exit_counter_for_compile_error(compile_error: &CompileError) -> Counter { pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 { use crate::hir::SideExitReason::*; + use crate::hir::CallType::*; use crate::stats::Counter::*; let counter = match reason { UnknownNewarraySend(_) => exit_unknown_newarray_send, - UnhandledCallType(_) => exit_unhandled_call_type, + UnhandledCallType(Tailcall) => exit_unhandled_tailcall, + UnhandledCallType(Splat) => exit_unhandled_splat, + UnhandledCallType(Kwarg) => exit_unhandled_kwarg, UnknownSpecialVariable(_) => exit_unknown_special_variable, UnhandledHIRInsn(_) => exit_unhandled_hir_insn, UnhandledYARVInsn(_) => exit_unhandled_yarv_insn,