1590 Commits
Author SHA1 Message Date
Samuel Williams be08f56e60 Preserve line events across jump optimization. (GH-18122) [Backport #22218] 2026-08-19 10:18:19 -07:00
viralpraxis faa5caf1b7 Optimize DCE [Backport #22220]
ref: https://bugs.ruby-lang.org/issues/22220

1f473ed633 split the walk into a counting pass and a deletion pass -- but
the counting pass only stops at `leave`, so it scans to the end of the
ISEQ on every call.

`opt_new` calls in here once per `Klass.new`, making
`aws-sdk-ec2's` ~16k code-generated `Class.new` take ~30s to compile.

The solution is to merge the passes back into one and defer decision instead.
2026-08-19 10:17:56 -07:00
viralpraxis e36708fa0b [Bug #22181] Fix branch-peephole-optimization label & insn ID collisions 2026-07-09 15:01:52 -07:00
Luke Gruber aad9ef5ce4 CI shape comparison needs to take into account # of kwargs given
If number of keyword arguments are not taken into account when comparing CI
shapes, invalid bindings happen:

```
class Foo
  def bar(a: nil, b: nil)
    p [:body, a: a, b: b]
  end
end

foo = Foo.new

foo.bar(a: 1, b: 2)   #=> [:body, {a: 1, b: 2}] # prime CC
foo.bar(99, a: 0)     #=> [:body, {a: 99, b: nil}] # BAD!
```

Also, set max keywords to 2**16-1 so we don't grow the cc_entries_entry struct.

Fixes [Bug #22099]
2026-07-09 14:31:36 -07:00
viralpraxis e4c52b4159 [Bug #22126] Fix partial DCE with loop back-edges
The old `remove_unreachable_chunk` walked the dead code once and tried
to do two things at the same time -- count label references inside that
chunk and figure out what exactly to delete.

That breaks for loops -- the loop label comes before the jump back to it;
on the first walk, the label looks like it has no references inside the
chunk yet, so the optimizer thinks something outside the dead code still
needs it. It stops there and deletes only the setup code before the loop,
leaving the loop body behind.

I've changed the algorithm to use two passes to mitigate the issue;
I believe the performance overhead is neglible

```shell
$ ruby --parser parse.y -e 'if []; else; a => [*, 42, *]; end'
$ ruby --parser prism -e   'if []; else; a => [*, 42, *]; end'
-- raw disasm--------
   trace: 1
   0000 putnil                                                           (   1)
   0001 leave                                                            (   1)
 <L018> [sp: 1, unremovable: 0, refcnt: 1]
   0002 dup                                                              (   1)
   0003 topn                 2                                           (   1)
   0005 opt_le               <calldata:<=, 1>                            (   1)
   0007 branchunless         <L021>                                      (   1)
   0009 topn                 3                                           (   1)
   0011 topn                 1                                           (   1)
   0013 opt_aref             <calldata:[], 1>                            (   1)
   0015 putobject            42                                          (   1)
   0017 dupn                 2                                           (   1)
   0019 checkmatch           2                                           (   1)
   0021 dup                                                              (   1)
   0022 branchif             <L023>                                      (   1)
   0024 putspecialobject     1                                           (   1)
   0026 putobject            "%p === %p does not return true"            (   1)
   0028 topn                 3                                           (   1)
   0030 topn                 5                                           (   1)
   0032 opt_send_without_block <calldata:core#sprintf, 3>                (   1)
   0034 setn                 10                                          (   1)
   0036 putobject            false                                       (   1)
   0038 setn                 12                                          (   1)
   0040 pop                                                              (   1)
   0041 pop                                                              (   1)
 <L023> [sp: 4, unremovable: 0, refcnt: 1]
   0042 setn                 2                                           (   1)
   0044 pop                                                              (   1)
   0045 pop                                                              (   1)
   0046 branchif             <L020>                                      (   1)
   0048 putobject_INT2FIX_1_                                             (   1)
   0049 opt_plus             <calldata:+, 1>                             (   1)
   0051 jump                 <L018>                                      (   1)
 <L021> [sp: 1, unremovable: 0, refcnt: 1]
*  0053 adjuststack          3                                           (   1)
   0055 putspecialobject     1                                           (   1)
   0057 putobject            "%p does not match to find pattern"         (   1)
   0059 topn                 2                                           (   1)
   0061 opt_send_without_block <calldata:core#sprintf, 2>                (   1)
   0063 setn                 4                                           (   1)
   0065 putobject            false                                       (   1)
   0067 setn                 6                                           (   1)
   0069 pop                                                              (   1)
   0070 pop                                                              (   1)
   0071 jump                 <L012>                                      (   1)
 <L020> [sp: 1, unremovable: 0, refcnt: 1]
   0073 adjuststack          3                                           (   1)
   0075 pop                                                              (   1)
   0076 jump                 <L003>                                      (   1)
 <L012> [sp: -1, unremovable: 0, refcnt: 1]
   0078 pop                                                              (   1)
   0079 putspecialobject     1                                           (   1)
   0081 topn                 4                                           (   1)
   0083 branchif             <L024>                                      (   1)
   0085 putobject            NoMatchingPatternError                      (   1)
   0087 putspecialobject     1                                           (   1)
   0089 putobject            "%p: %s"                                    (   1)
   0091 topn                 4                                           (   1)
   0093 topn                 7                                           (   1)
   0095 opt_send_without_block <calldata:core#sprintf, 3>                (   1)
   0097 opt_send_without_block <calldata:core#raise, 2>                  (   1)
   0099 jump                 <L025>                                      (   1)
 <L024> [sp: -1, unremovable: 0, refcnt: 1]
   0101 putobject            NoMatchingPatternKeyError                   (   1)
   0103 putspecialobject     1                                           (   1)
   0105 putobject            "%p: %s"                                    (   1)
   0107 topn                 4                                           (   1)
   0109 topn                 7                                           (   1)
   0111 opt_send_without_block <calldata:core#sprintf, 3>                (   1)
   0113 topn                 7                                           (   1)
   0115 topn                 9                                           (   1)
   0117 opt_send_without_block <calldata:new, 3>                         (   1)
   0119 opt_send_without_block <calldata:core#raise, 1>                  (   1)
 <L025> [sp: -1, unremovable: 0, refcnt: 1]
   0121 adjuststack          7                                           (   1)
   0123 putnil                                                           (   1)
   0124 leave                                                            (   1)
 <L003> [sp: -1, unremovable: 0, refcnt: 1]
   0125 adjuststack          6                                           (   1)
   0127 putnil                                                           (   1)
   0128 leave                                                            (   1)
---------------------
-e:1: argument stack underflow (-2)
-e: compile error (SyntaxError)
```

Reproducible on every version since `3.4.0-preview2`
(`docker run -e ALL_RUBY_SHOW_DUP=yes -e ALL_RUBY_SINCE=3.4 --rm rubylang/all-ruby ./all-ruby -We 'if []; else; a => [*, 42, *]; end'`)

There seem to be another issue related to Prism's DCE & loops:

```shell
$ ruby --parser parse.y --dump insn -e 'if []; else; while true; end; end'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,33)>
0000 putnil                                                           (   1)[Li]
0001 leave
$ ruby --parser prism --dump insn -e 'if []; else; while true; end; end'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,33)>
0000 newarray                               0                         (   1)[Li]
0002 branchunless                           11
0004 putnil
0005 leave
0006 pop
0007 jump                                   11
0009 putnil
0010 pop
0011 jump                                   11
0013 putnil
0014 leave
```

the reason `branchunless` is not eliminated is that prism does not emit
`NODE_ZLIST` like `parse.y` does and instead emits `PM_ARRAY_NODE-0`
which is not treated as a always-truthy value by the peephole-optmizer.
2026-06-26 14:34:36 -07:00
Nobuyoshi Nakada d6e6d3e9a7 [Bug #22127] Check the dynamic regexp compilation results 2026-06-26 14:29:52 -07:00
Shugo MaedaandClaude Opus 4.8 1940cd5622 compile.c: fix dupstring-of-Regexp from folded /o regexp
The peephole optimizer folds "dupstring str; toregexp" (and the
dupchilledstring / putobject-string variants) into a single
literal-push by compiling the Regexp at compile time and replacing the
operand with it.  It replaced the operand but kept the original opcode,
so a dupstring/dupchilledstring instruction was left holding a Regexp.
At run time dupstring resurrects its operand as a String
(rb_ec_str_resurrect), reading the Regexp as if it were a string and
crashing ("heap_idx_for_size: allocation size too large").

This is reachable from a /o regexp whose interpolation folds to a
constant, e.g. `def m; /#{"a"}/o; end`: the once body is compiled via
the dynamic-regexp path (dupstring + toregexp) and then folded, unlike
a plain /#{"a"}/ which is pushed directly with putobject.

Set the opcode to putobject when folding, matching what a static regexp
literal emits.

Fixes [Bug #22103]. (Backport)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 12:02:10 -07:00
Luke Gruber f6daed5818 Fix use-after-poison in compile.c and prism_compile.c
Prevent GC from accidentally collecting

(Backport 30ec9c089e) AFAIK no ruby ticket for this bug.
2026-06-12 10:21:32 -07:00
Takashi Kokubun b18acb27c3 merge revision(s) 7f4f3c8ee7: [Backport #22004]
[PATCH] [Bug #22004] Fix short-circuited loop conditions
2026-05-11 13:45:55 -07:00
Nobuyoshi Nakada 3636277dc5 Add NUM2PTR and PTR2NUM macros
These macros have been defined here and there, so collect them.
2025-12-10 12:09:50 +09:00
yui-knk 056997cbcd Remove needless ruby2_keywords field from struct rb_args_info
`ruby2_keywords` is set only to be `0` in parse.y.
However `args->ruby2_keywords` is initialized with `0` by `MEMZERO`
in `rb_node_args_new` function and `body->param.flags.ruby2_keywords`
is initialized with `0` by `ZALLOC` in `rb_iseq_constant_body_alloc` function,
so `args->ruby2_keywords` does nothing for `body->param.flags.ruby2_keywords`.
2025-12-09 09:09:00 +08:00
Peter Zhu bacd35626c Remove dead IBF_OBJECT_INTERNAL 2025-11-15 13:02:15 -08:00
Koichi Sasada bc00c4468e use SET_SHAREABLE
to adopt strict shareable rule.

* (basically) shareable objects only refer shareable objects
* (exception) shareable objects can refere unshareable objects
  but should not leak reference to unshareable objects to Ruby world
2025-10-23 13:08:26 +09:00
viralpraxis 7587e92910 [Bug #21644] compile.c: fix newrange INSN peephole optimization for chilled string
ref: https://bugs.ruby-lang.org/issues/21644

```shell
$ ruby -v -e '("a" || "b").."c"'
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
-e:1: warning: possibly useless use of .. in void context
-e:1: [BUG] Stack consistency error (sp: 7, bp: 6)
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0002 p:0013 s:0007 e:000005 EVAL   -e:1 [FINISH]
c:0001 p:0000 s:0003 E:001920 DUMMY  [FINISH]

-- Ruby level backtrace information ----------------------------------------
-e:1:in '<main>'

-- Threading information ---------------------------------------------------
Total ractor count: 1
Ruby thread count for this ractor: 1

-- C level backtrace information -------------------------------------------
ruby/3.4.7/lib/libruby.so.3.4(rb_print_backtrace+0x8) [0x78aa9573c882] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_dump.c:823
ruby/3.4.7/lib/libruby.so.3.4(rb_vm_bugreport) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_dump.c:1155
ruby/3.4.7/lib/libruby.so.3.4(rb_bug_without_die_internal+0x6b) [0x78aa9544c62f] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/error.c:1097
ruby/3.4.7/lib/libruby.so.3.4(rb_bug) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/error.c:1115
ruby/3.4.7/lib/libruby.so.3.4(vm_stack_consistency_error+0x1f) [0x78aa9544f091] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_insnhelper.c:6523
ruby/3.4.7/lib/libruby.so.3.4(vm_get_cref) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/insns.def:1134
ruby/3.4.7/lib/libruby.so.3.4(vm_setclassvariable) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_insnhelper.c:1630
ruby/3.4.7/lib/libruby.so.3.4(vm_setclassvariable) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm_insnhelper.c:1627
ruby/3.4.7/lib/libruby.so.3.4(vm_exec_core) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/insns.def:253
ruby/3.4.7/lib/libruby.so.3.4(vm_exec_loop+0xa) [0x78aa95724959] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm.c:2622
ruby/3.4.7/lib/libruby.so.3.4(rb_vm_exec) /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/vm.c:2598
ruby/3.4.7/lib/libruby.so.3.4(rb_ec_exec_node+0xa5) [0x78aa95525695] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/eval.c:281
ruby/3.4.7/lib/libruby.so.3.4(ruby_run_node+0x83) [0x78aa95529333] /tmp/ruby-build.20251010151551.31019.jR04SY/ruby-3.4.7/eval.c:319
ruby/3.4.7/bin/ruby(rb_main+0x21) [0x59d86f5e0186] ./main.c:43
ruby/3.4.7/bin/ruby(main) ./main.c:68
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_call_main+0x7a) [0x78aa9502a1ca] ../sysdeps/nptl/libc_start_call_main.h:58
/lib/x86_64-linux-gnu/libc.so.6(call_init+0x0) [0x78aa9502a28b] ../csu/libc-start.c:360
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main_impl) ../csu/libc-start.c:347
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main) (null):0
[0x59d86f5e01d5]
```

The optimization in question:

https://github.com/ruby/ruby/blob/957c832db137e67289e93dfd9fd9e915b1f2fc87/compile.c\#L3453-L3480

Before entering the `newrange` optimization, the iseq looks like this:

```
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,17)>
0000 putchilledstring                       "a"                       (   1)[Li]
0002 dup
0003 branchif                               8
0005 pop
0006 putchilledstring                       "b"
0008 putchilledstring                       "c"
0010 newrange                               0
0012 leave
```

So the optimization constructs a new range using the wrong operands (`"b"` and `"c"` instead of `"a"` and `"c"`).

I tried to fix this by checking whether the two previous instructions are labeled.
2025-10-20 14:17:36 +09:00
Peter Zhu 07b59eee6a Fix memory leak when load_from_binary raises
ibf_load_code will leak memory allocated for the code if an exception is
raised. The following script reproduces the leak:

    bin = RubyVM::InstructionSequence.of(1.method(:abs)).to_binary

    10.times do
      100_000.times do
        RubyVM::InstructionSequence.load_from_binary(bin)
      rescue ArgumentError
      end
      puts `ps -o rss= -p #{$$}`
    end

Before:

    18004
    23380
    28756
    34260
    39892
    45396
    50772
    55892
    61012
    66132

After:

    12536
    12920
    13304
    13688
    14072
    14456
    14840
    15352
    15608
    15864
2025-10-10 19:24:55 -04:00
Alan Wu 50393d1ada IBF: Remove unnecessary and potentially UB pointer cast
[Bug #21569]
2025-09-25 16:03:15 -04:00
Aleksey Maximov 354d47ae5b IBF: Avoid unaligned load on 32 bit platforms
[Bug #21569]
2025-09-25 16:03:15 -04:00
Koichi Sasada 5b65e76a7d fix lvar_state dump size
`ibf_dump_write()` should consider the size of the element.
2025-09-24 10:51:02 +09:00
Koichi Sasada 55b1ba3bf2 Ractor.shareable_proc
call-seq:
  Ractor.sharable_proc(self: nil){} -> sharable proc

It returns shareable Proc object. The Proc object is
shareable and the self in a block will be replaced with
the value passed via `self:` keyword.

In a shareable Proc, the outer variables should
* (1) refer shareable objects
* (2) be not be overwritten

```ruby
  a = 42
  Ractor.shareable_proc{ p a }
  #=> OK

  b = 43
  Ractor.shareable_proc{ p b; b = 44 }
  #=> Ractor::IsolationError because 'b' is reassigned in the block.

  c = 44
  Ractor.shareable_proc{ p c }
  #=> Ractor::IsolationError because 'c' will be reassigned outside of the block.
  c = 45

  d = 45
  d = 46 if cond
  Ractor.shareable_proc{ p d }
  #=> Ractor::IsolationError because 'd' was reassigned outside of the block.
```

The last `d`'s case can be relaxed in a future version.

The above check will be done in a static analysis at compile time,
so the reflection feature such as `Binding#local_varaible_set`
can not be detected.

```ruby
  e = 42
  shpr = Ractor.shareable_proc{ p e } #=> OK
  binding.local_variable_set(:e, 43)
  shpr.call #=> 42 (returns captured timing value)
```

Ractor.sharaeble_lambda is also introduced.
[Feature #21550]
[Feature #21557]
2025-09-24 03:59:03 +09:00
Yusuke Endoh 2ccb2de677 Make RubyVM::AST.of return a parent node of NODE_SCOPE
This change makes `RubyVM::AST.of` and `.node_id_for_backtrace_location`
return a parent node of NODE_SCOPE (such as NODE_DEFN) instead of the
NODE_SCOPE node itself.
(In future, we may remove NODE_SCOPE, which is a bit hacky AST node.)

This is preparation for [Feature #21543].
2025-08-28 12:44:04 +09:00
Aaron Patterson fb6e3a8000 Remove opt_aref_with and opt_aset_with
When these instructions were introduced it was common to read from a
hash with mutable string literals.  However, these days, I think these
instructions are fairly rare.

I tested this with the lobsters benchmark, and saw no difference in
speed.  In order to be sure, I tracked down every use of this
instruction in the lobsters benchmark, and there were only 4 places
where it was used.

Additionally, this patch fixes a case where "chilled strings" should
emit a warning but they don't.

```ruby
class Foo
  def self.[](x)= x.gsub!(/hello/, "hi")
end

Foo["hello world"]
```

Removing these instructions shows this warning:

```
> ./miniruby -vw test.rb
ruby 3.5.0dev (2025-08-25T21:36:50Z rm-opt_aref_with dca08e286c) +PRISM [arm64-darwin24]
test.rb:2: warning: literal string will be frozen in the future (run with --debug-frozen-string-literal for more information)
```

[Feature #21553]
2025-08-26 13:02:17 -07:00
John HawthornandAaron Patterson 5ca71364ff Avoid GC while operands in inconsistent state
compile_data_calloc2 may run GC (though because it allocates from an
arena this is rare in practice). When this happened when resizing
operands there was a risk of seeing the insn in an inconsistent state.

To solve this we need to make any allocations before we start modifying
the instrucitons. This refactors the code to use a new
insn_replace_with_operands() function that allocates the new operands
array before modifying the instruction object.

Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
2025-07-28 09:44:47 -07:00
Nobuyoshi Nakada 63f6f87e86 Add underflow check 2025-06-29 01:30:09 +09:00
Erik Berlin 49c138c18b Check dump size in ibf_dump_write 2025-06-28 09:45:18 +09:00
Erik Berlin 5e9be99ef5 Fix loop variable type in compile.c 2025-06-26 13:28:24 +09:00
John Hawthorn 4044188266 Fix load catch table write barrier
I tried fixing this in 521b2fcba4, but
re-running wbcheck with the stricter WBCHECK_VERIFY_AFTER_WB, revealed
that this write barrier was fired too early, before the object was
actually written to the parent.

This solves the issue by writing the table to the parent immediately
(and using calloc so that we don't mark random garbage).
2025-06-24 13:08:46 -07:00
Jean Boussier 45a2c95d0f Reduce exposure of FL_FREEZE
The `FL_FREEZE` flag is redundant with `SHAPE_ID_FL_FROZEN`, so
ideally it should be eliminated in favor of the later.

Doing so would eliminate the risk of desync between the two, but
also solve the problem of the frozen status being global in namespace
context (See Bug #21330).
2025-06-24 11:29:39 +01:00
Jeremy Evans 353fa6f0ba Avoid allocation for positional splat for literal array keyword argument
If all nodes in the array are safe, then it is safe to avoid
allocation for the positional splat:

```ruby
m(*a, kw: [:a])   # Safe
m(*a, kw: [meth]) # Unsafe
```

This avoids an unnecessary allocation in a Rails method call.
Details: https://github.com/rails/rails/pull/54949/files#r2052645431
2025-06-22 06:43:13 +09:00
Nobuyoshi Nakada ef2b26cc3e struct iseq_catch_table is packed 2025-06-19 13:17:23 +09:00
John Hawthorn 521b2fcba4 Add missing write barrier for hash on iseq
Found by wbcheck
2025-06-18 10:18:10 -07:00
John Hawthorn 1bfd6493c0 Add write barrier to rb_cArray_empty_frozen
Found by wbcheck
2025-06-18 10:18:10 -07:00
John Hawthorn d5adf85116 Add write barrier to rb_cHash_empty_frozen
Found by wbcheck
2025-06-18 10:18:10 -07:00
John Hawthorn 99de389071 Use write barriers when loading catch table
Found by wbcheck
2025-06-18 10:18:10 -07:00
John Hawthorn 61230f531d Add missing write barriers to ibf_load
Found by wbcheck
2025-06-18 10:18:10 -07:00
John Hawthorn db5724894f Fix a missing write barrier to mandatory_only_iseq
Found by wbcheck
2025-06-18 10:18:10 -07:00
Nobuyoshi Nakada a60bf9e693 * adjust indent 2025-06-17 12:30:18 +09:00
Nobuyoshi Nakada 7ddc5e6187 Suppress overflow warning at ALLOC_N in iseq_set_local_table
`xmalloc2` checks the overflow of arguments multiplication.
2025-06-04 15:31:40 +09:00
Jean Boussier ff222ac27a compile.c: Handle anonymous variables in outer_variable_cmp
[Bug #21370]
2025-06-02 13:41:28 +02:00
Nobuyoshi Nakada 2e3f81838c Align styles [ci skip] 2025-05-15 17:48:40 +09:00
Jean Boussier 3ec7bfff2e Use a set_table for rb_vm_struct.unused_block_warning_table
Now that we have a hash-set implementation we can use that
instead of a hash-table with a static value.
2025-04-27 11:59:28 +02:00
Aaron Patterson f15d85f086 Add parse.y implementation 2025-04-25 13:46:05 -07:00
0d6263bd41 Fix coverage measurement for negative line numbers
Fixes [Bug #21220]

Co-Authored-By: Mike Bourgeous <mike@mikebourgeous.com>
Co-Authored-By: Jean Boussier <jean.boussier@gmail.com>
2025-04-09 23:45:54 +09:00
Jean Boussier 4aa74b605c compile.c: avoid allocating 0 length call_data
if `body->ci_size` is `0`, there's no point allocating 0B,
it just wastes an entry in the allocator.
2025-04-03 16:57:57 +02:00
Aaron Patterson 62cc3464d9 Remove leading nop from block when we don't need it
Blocks insert a leading `nop` instruction in order to execute a "block
call" tracepoint. Block compilation unconditionally inserts a leading
`nop` plus a label after the instruction:

  https://github.com/ruby/ruby/blob/641f15b1c6bd8921407a1f045573d3b0605f00d3/prism_compile.c#L6867-L6869

This `nop` instruction is used entirely for firing the block entry
tracepoint.  The label exists so that the block can contain a loop but
the block entry tracepoint is executed only once.

For example, the following code is an infinite loop, but should only
execute the b_call tracepoint once:

```ruby
-> { redo }.call
```

Previous to this commit, we would eliminate the `nop` instruction, but
only if there were no other jump instructions inside the block.  This
means that the following code would still contain a leading `nop` even
though the label following the `nop` is unused:

```ruby
-> { nil if bar }
```

```
== disasm: #<ISeq:block in <main>@test.rb:1 (1,2)-(1,17)> (catch: FALSE)
0000 nop                                                              (   1)[Bc]
0001 putself                                [Li]
0002 opt_send_without_block                 <calldata!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0004 branchunless                           8
0006 putnil
0007 leave                                  [Br]
0008 putnil
0009 leave                                  [Br]
```

This commit checks to see if the label inserted after the `nop` is
actually a jump target.  If it's not a jump target, then we should be
safe to eliminate the leading `nop`:

```
> build-master/miniruby --dump=insns test.rb
== disasm: #<ISeq:<main>@test.rb:1 (1,0)-(1,17)>
0000 putspecialobject                       1                         (   1)[Li]
0002 send                                   <calldata!mid:lambda, argc:0, FCALL>, block in <main>
0005 leave

== disasm: #<ISeq:block in <main>@test.rb:1 (1,2)-(1,17)>
0000 putself                                                          (   1)[LiBc]
0001 opt_send_without_block                 <calldata!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 branchunless                           7
0005 putnil
0006 leave                                  [Br]
0007 putnil
0008 leave                                  [Br]
```

We have a test for b_call tracepoints that use `redo` here:

  https://github.com/ruby/ruby/blob/aebf96f371c8d874398e0041b798892e545fa206/test/ruby/test_settracefunc.rb#L1728-L1736
2025-03-20 11:49:13 -07:00
Eileen M. UchitelleandPeter Zhu c85dffeee2 Avoid pinning storage_head in iseq_mark_and_move (#12880)
* Avoid pinning `storage_head` in `iseq_mark_and_move`

This refactor changes the behavior of `iseq_mark_and_move` to avoid
pinning the `storage_head`. Previously pinning was required because they
could be gc'd during `iseq_set_sequence` it would be possible to end
up with a half build array of instructions. However, in order to
implement a moving immix algorithm we can't pin these objects so this
rafactoring changes the code to mark and move. To accomplish this, it was
required to add `iseq_size`, `iseq_encoded`, and the `mark_bits` union
to the `iseq_compile_data` struct. In addition `iseq_compile_data` sets
a bool for whether there is a single or list of mark bits. While this
change is needed for moving immix, it should be better for Ruby's GC
as well.

* Don't allocate mark_offset_bits for one word

If only one word is needed, we don't need to allocate mark_offset_bits
and can instead directly write to it.

---------

Co-authored-by: Peter Zhu <peter@peterzhu.ca>
2025-03-17 10:42:48 -04:00
Jeremy EvansandNobuyoshi Nakada e0d600ec19 Avoid opt_aset_with optimization inside multiple assignment
Previously, since the opt_aset_with optimization was introduced,
use of the opt_aset_with optimization inside multiple assignment
would result in a segfault or incorrect instructions.

Fixes [Bug #21012]

Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com>
2025-01-08 08:49:51 -08:00
Nobuyoshi Nakada b81db531b2 [Bug #21011] nd_value is NULL in massign 2025-01-08 13:26:14 +09:00
ydah 76759003f5 Introduce macro for creating child iseqs with callbacks
Use macro like `NEW_ISEQ` and `NEW_CHILD_ISEQ`.
2025-01-08 10:46:23 +09:00
ydah b3de2c334e Use ISEQ_BODY(iseq) instead of iseq->body
trivial change that unifies the format for access.
2025-01-07 07:18:49 +09:00
ydah 7124c34f51 Remove unused FIXNUM_OR macro from compile.c
This PR remove unused FIXNUM_OR macro from compile.c

before:
```
❯ git grep 'FIXNUM_OR'
compile.c:#define FIXNUM_OR(n, i) ((n)|INT2FIX(i))
```

after:
```
❯ git grep 'FIXNUM_OR'

```
2025-01-04 18:04:42 +09:00