ref: https://bugs.ruby-lang.org/issues/222201f473ed633 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.
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]
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>
`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`.
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
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
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]
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].
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]
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>
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).
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).
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
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
* 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>
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>