Age | Commit message (Collapse) | Author |
|
This allow checking if an object has ivars with just a shape_id
mask.
Notes:
Merged: https://github.com/ruby/ruby/pull/13606
|
|
Notes:
Merged: https://github.com/ruby/ruby/pull/13596
|
|
Notes:
Merged: https://github.com/ruby/ruby/pull/13524
|
|
Instead it's now a `shape_id` flag.
This allows to check if an object is complex without having
to chase the `rb_shape_t` pointer.
Notes:
Merged: https://github.com/ruby/ruby/pull/13511
|
|
Followup: https://github.com/ruby/ruby/pull/13341 / [Feature #21353]
Even thought `shape_id_t` has been make 32bits, we were still limited
to use only the lower 16 bits because they had to fit alongside `attr_index_t`
inside a `uintptr_t` in inline caches.
By enlarging inline caches we can unlock the full 32bits on all
platforms, allowing to use these extra bits for tagging.
Notes:
Merged: https://github.com/ruby/ruby/pull/13500
|
|
Notes:
Merged: https://github.com/ruby/ruby/pull/13450
|
|
Further reduce exposure of `rb_shape_t`.
Notes:
Merged: https://github.com/ruby/ruby/pull/13450
|
|
```
# frozen_string_ltieral: true
hash["literal"] = value
```
Notes:
Merged: https://github.com/ruby/ruby/pull/13342
|
|
Notes:
Merged-By: k0kubun <[email protected]>
|
|
As well as `RB_OBJ_SHAPE_ID` -> `rb_obj_shape_id`
and `RSHAPE` is now a simple alias for `rb_shape_lookup`.
I tried to turn all these into `static inline` but I'm having
trouble with `RUBY_EXTERN rb_shape_tree_t *rb_shape_tree_ptr;`
not being exposed as I'd expect.
Notes:
Merged: https://github.com/ruby/ruby/pull/13283
|
|
And `rb_shape_get_shape` -> `RB_OBJ_SHAPE`.
Notes:
Merged: https://github.com/ruby/ruby/pull/13283
|
|
Also rename it, and change parameters to be consistent with
other transition functions.
Notes:
Merged: https://github.com/ruby/ruby/pull/13283
|
|
Notes:
Merged: https://github.com/ruby/ruby/pull/13283
|
|
Also refactor checks for `->type == SHAPE_OBJ_TOO_COMPLEX`.
Notes:
Merged: https://github.com/ruby/ruby/pull/13159
|
|
Ivars will longer be the only thing stored inline
via shapes, so keeping the `iv_index` and `ivptr` names
would be confusing.
Instance variables won't be the only thing stored inline
via shapes, so keeping the `ivptr` name would be confusing.
`field` encompass anything that can be stored in a VALUE array.
Similarly, `gen_ivtbl` becomes `gen_fields_tbl`.
Notes:
Merged: https://github.com/ruby/ruby/pull/13159
|
|
Notes:
Merged-By: k0kubun <[email protected]>
|
|
This commit inlines instructions for Class#new. To make this work, we
added a new YARV instructions, `opt_new`. `opt_new` checks whether or
not the `new` method is the default allocator method. If it is, it
allocates the object, and pushes the instance on the stack. If not, the
instruction jumps to the "slow path" method call instructions.
Old instructions:
```
> ruby --dump=insns -e'Object.new'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)>
0000 opt_getconstant_path <ic:0 Object> ( 1)[Li]
0002 opt_send_without_block <calldata!mid:new, argc:0, ARGS_SIMPLE>
0004 leave
```
New instructions:
```
> ./miniruby --dump=insns -e'Object.new'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)>
0000 opt_getconstant_path <ic:0 Object> ( 1)[Li]
0002 putnil
0003 swap
0004 opt_new <calldata!mid:new, argc:0, ARGS_SIMPLE>, 11
0007 opt_send_without_block <calldata!mid:initialize, argc:0, FCALL|ARGS_SIMPLE>
0009 jump 14
0011 opt_send_without_block <calldata!mid:new, argc:0, ARGS_SIMPLE>
0013 swap
0014 pop
0015 leave
```
This commit speeds up basic object allocation (`Foo.new`) by 60%, but
classes that take keyword parameters see an even bigger benefit because
no hash is allocated when instantiating the object (3x to 6x faster).
Here is an example that uses `Hash.new(capacity: 0)`:
```
> hyperfine "ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'" "./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'"
Benchmark 1: ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'
Time (mean ± σ): 1.082 s ± 0.004 s [User: 1.074 s, System: 0.008 s]
Range (min … max): 1.076 s … 1.088 s 10 runs
Benchmark 2: ./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'
Time (mean ± σ): 627.9 ms ± 3.5 ms [User: 622.7 ms, System: 4.8 ms]
Range (min … max): 622.7 ms … 633.2 ms 10 runs
Summary
./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end' ran
1.72 ± 0.01 times faster than ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'
```
This commit changes the backtrace for `initialize`:
```
aaron@tc ~/g/ruby (inline-new)> cat test.rb
class Foo
def initialize
puts caller
end
end
def hello
Foo.new
end
hello
aaron@tc ~/g/ruby (inline-new)> ruby -v test.rb
ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [arm64-darwin24]
test.rb:8:in 'Class#new'
test.rb:8:in 'Object#hello'
test.rb:11:in '<main>'
aaron@tc ~/g/ruby (inline-new)> ./miniruby -v test.rb
ruby 3.5.0dev (2025-03-28T23:59:40Z inline-new c4157884e4) +PRISM [arm64-darwin24]
test.rb:8:in 'Object#hello'
test.rb:11:in '<main>'
```
It also increases memory usage for calls to `new` by 122 bytes:
```
aaron@tc ~/g/ruby (inline-new)> cat test.rb
require "objspace"
class Foo
def initialize
puts caller
end
end
def hello
Foo.new
end
puts ObjectSpace.memsize_of(RubyVM::InstructionSequence.of(method(:hello)))
aaron@tc ~/g/ruby (inline-new)> make runruby
RUBY_ON_BUG='gdb -x ./.gdbinit -p' ./miniruby -I./lib -I. -I.ext/common ./tool/runruby.rb --extout=.ext -- --disable-gems ./test.rb
656
aaron@tc ~/g/ruby (inline-new)> ruby -v test.rb
ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [arm64-darwin24]
544
```
Thanks to @ko1 for coming up with this idea!
Co-Authored-By: John Hawthorn <[email protected]>
|
|
|
|
* YJIT: Fix indentation [ci skip]
Fixes: cdf33ed5f37f9649c482c3ba1d245f0d80ac01ce
* YJIT: Initialize locals in ISeqs defined with `...`
Previously, callers of forwardable ISeqs moved the stack pointer up
without writing to the stack. If there happens to be a stale value in
the area skipped over, it could crash due to "try to mark T_NONE". Also,
the uninitialized local variables were observable through `binding`.
Initialize the locals to nil.
[Bug #21021]
Notes:
Merged-By: maximecb <[email protected]>
|
|
It's in gen_send_general(), so nothing specifically to do with iseqs.
Notes:
Merged: https://github.com/ruby/ruby/pull/12550
|
|
Evident with the crash reported in [Bug #20997], the C replacement
codegen functions aren't authored to handle block arguments (nor
should they because the extra code from the complexity defeats
optimization). Filter sites with VM_CALL_ARGS_BLOCKARG.
Notes:
Merged: https://github.com/ruby/ruby/pull/12536
|
|
Previously, the code for dropping surplus arguments when yielding
into blocks erroneously attempted to drop keyword arguments when there
is in fact no surplus arguments. Fix the condition and test that
supplying the exact number of keyword arguments as require compiles
without fallback.
Notes:
Merged: https://github.com/ruby/ruby/pull/12499
|
|
Notes:
Merged-By: maximecb <[email protected]>
|
|
A good amount of call sites always pass nil as block argument, but the
nil doesn't show up in the context. Put a runtime guard for those
cases to handle it. Particular relevant for the `ruby-lsp` benchmark in
`yjit-bench`. Up to a 2% speedup across headline benchmarks.
Co-authored-by: Takashi Kokubun <[email protected]>
Co-authored-by: Maxime Chevalier-Boisvert <[email protected]>
Co-authored-by: Aaron Patterson <[email protected]>
Co-authored-by: Kevin Menard <[email protected]>
Co-authored-by: Randy Stauner <[email protected]>
Notes:
Merged-By: maximecb <[email protected]>
|
|
jit_prepare_lazy_frame_call is a complicated trick and comes with memory
overhead. Every use of the function should come with justification.
|
|
* YJIT: Spill/load argument registers to reuse blocks
* Mention the immediate function name
* Explain the context behind spill/load operations
Notes:
Merged-By: k0kubun <[email protected]>
|
|
* YJIT: Generate specialized code for Symbol for objtostring
Co-authored-by: John Hawthorn <[email protected]>
* Update yjit/src/codegen.rs
---------
Co-authored-by: John Hawthorn <[email protected]>
Co-authored-by: Maxime Chevalier-Boisvert <[email protected]>
Notes:
Merged-By: maximecb <[email protected]>
|
|
Fewer allocations on boot, too.
Suggested-by: https://github.com/ruby/ruby/pull/12217
Notes:
Merged: https://github.com/ruby/ruby/pull/12220
|
|
Notes:
Merged: https://github.com/ruby/ruby/pull/12202
|
|
Notes:
Merged-By: maximecb <[email protected]>
|
|
* Add opt_duparray_send insn to skip the allocation on `#include?`
If the method isn't going to modify the array we don't need to copy it.
This avoids the allocation / array copy for things like `[:a, :b].include?(x)`.
This adds a BOP for include? and tracks redefinition for it on Array.
Co-authored-by: Andrew Novoselac <[email protected]>
* YJIT: Implement opt_duparray_send include_p
Co-authored-by: Andrew Novoselac <[email protected]>
* Update opt_newarray_send to support simple forms of include?(arg)
Similar to opt_duparray_send but for non-static arrays.
* YJIT: Implement opt_newarray_send include_p
---------
Co-authored-by: Andrew Novoselac <[email protected]>
Notes:
Merged-By: maximecb <[email protected]>
|
|
It's more concise this way and since `return Some(EndBlock)` is the only
correct answer, no point repeating it everywhere.
Notes:
Merged: https://github.com/ruby/ruby/pull/12124
|
|
When CodeBlock::set_page fails (part of next_page(), see their docs for
exact conditions), it can cause gen_outlined_exit() to fail while there
is still plenty of memory available. Previously, this can have YJIT
running incomplete code due to taking the early return in
end_block_with_jump() that manifested as crashes with SIGILL.
Add and use a wrapper with error handling.
Notes:
Merged: https://github.com/ruby/ruby/pull/12124
|
|
Notes:
Merged-By: maximecb <[email protected]>
|
|
Notes:
Merged-By: maximecb <[email protected]>
|
|
* YJIT: Specialize `String#[]` (`String#slice`) with fixnum arguments
String#[] is in the top few C calls of several YJIT benchmarks:
liquid-compile rubocop mail sudoku
This speeds up these benchmarks by 1-2%.
* YJIT: Try harder to get type info for `String#[]`
In the large generated code of the mail gem the context doesn't have
the type info. In that case if we peek at the stack and add a guard
we can still apply the specialization
and it speeds up the mail benchmark by 5%.
Co-authored-by: Maxime Chevalier-Boisvert <[email protected]>
Co-authored-by: Takashi Kokubun (k0kubun) <[email protected]>
---------
Co-authored-by: Maxime Chevalier-Boisvert <[email protected]>
Co-authored-by: Takashi Kokubun (k0kubun) <[email protected]>
Notes:
Merged-By: maximecb <[email protected]>
|
|
When we run with RUBY_FREE_AT_EXIT, there's a false-positive memory leak
reported in YJIT because the METHOD_CODEGEN_TABLE is never freed. This
commit adds rb_yjit_free_at_exit that is called at shutdown when
RUBY_FREE_AT_EXIT is set.
Reported memory leak:
==699816== 1,104 bytes in 1 blocks are possibly lost in loss record 1 of 1
==699816== at 0x484680F: malloc (vg_replace_malloc.c:446)
==699816== by 0x155B3E: UnknownInlinedFun (unix.rs:14)
==699816== by 0x155B3E: UnknownInlinedFun (stats.rs:36)
==699816== by 0x155B3E: UnknownInlinedFun (stats.rs:27)
==699816== by 0x155B3E: alloc (alloc.rs:98)
==699816== by 0x155B3E: alloc_impl (alloc.rs:181)
==699816== by 0x155B3E: allocate (alloc.rs:241)
==699816== by 0x155B3E: do_alloc<alloc::alloc::Global> (alloc.rs:15)
==699816== by 0x155B3E: new_uninitialized<alloc::alloc::Global> (mod.rs:1750)
==699816== by 0x155B3E: fallible_with_capacity<alloc::alloc::Global> (mod.rs:1788)
==699816== by 0x155B3E: prepare_resize<alloc::alloc::Global> (mod.rs:2864)
==699816== by 0x155B3E: resize_inner<alloc::alloc::Global> (mod.rs:3060)
==699816== by 0x155B3E: reserve_rehash_inner<alloc::alloc::Global> (mod.rs:2950)
==699816== by 0x155B3E: hashbrown::raw::RawTable<T,A>::reserve_rehash (mod.rs:1231)
==699816== by 0x5BC39F: UnknownInlinedFun (mod.rs:1179)
==699816== by 0x5BC39F: find_or_find_insert_slot<(usize, fn(&mut yjit::codegen::JITState, &mut yjit::backend::ir::Assembler, *const yjit::cruby::autogened::rb_callinfo, *const yjit::cruby::autogened::rb_callable_method_entry_struct, core::option::Option<yjit::codegen::BlockHandler>, i32, core::option::Option<yjit::cruby::VALUE>) -> bool), alloc::alloc::Global, hashbrown::map::equivalent_key::{closure_env#0}<usize, usize, fn(&mut yjit::codegen::JITState, &mut yjit::backend::ir::Assembler, *const yjit::cruby::autogened::rb_callinfo, *const yjit::cruby::autogened::rb_callable_method_entry_struct, core::option::Option<yjit::codegen::BlockHandler>, i32, core::option::Option<yjit::cruby::VALUE>) -> bool>, hashbrown::map::make_hasher::{closure_env#0}<usize, fn(&mut yjit::codegen::JITState, &mut yjit::backend::ir::Assembler, *const yjit::cruby::autogened::rb_callinfo, *const yjit::cruby::autogened::rb_callable_method_entry_struct, core::option::Option<yjit::codegen::BlockHandler>, i32, core::option::Option<yjit::cruby::VALUE>) -> bool, std::hash::random::RandomState>> (mod.rs:1413)
==699816== by 0x5BC39F: hashbrown::map::HashMap<K,V,S,A>::insert (map.rs:1754)
==699816== by 0x57C5C6: insert<usize, fn(&mut yjit::codegen::JITState, &mut yjit::backend::ir::Assembler, *const yjit::cruby::autogened::rb_callinfo, *const yjit::cruby::autogened::rb_callable_method_entry_struct, core::option::Option<yjit::codegen::BlockHandler>, i32, core::option::Option<yjit::cruby::VALUE>) -> bool, std::hash::random::RandomState> (map.rs:1104)
==699816== by 0x57C5C6: yjit::codegen::reg_method_codegen (codegen.rs:10521)
==699816== by 0x57C295: yjit::codegen::yjit_reg_method_codegen_fns (codegen.rs:10464)
==699816== by 0x5C6B07: rb_yjit_init (yjit.rs:40)
==699816== by 0x393723: ruby_opt_init (ruby.c:1820)
==699816== by 0x393723: ruby_opt_init (ruby.c:1767)
==699816== by 0x3957D4: prism_script (ruby.c:2215)
==699816== by 0x3957D4: process_options (ruby.c:2538)
==699816== by 0x396065: ruby_process_options (ruby.c:3166)
==699816== by 0x236E56: ruby_options (eval.c:117)
==699816== by 0x15BAED: rb_main (main.c:43)
==699816== by 0x15BAED: main (main.c:62)
After this patch, there are no more memory leaks reported when running
RUBY_FREE_AT_EXIT with Valgrind on an empty Ruby script:
$ RUBY_FREE_AT_EXIT=1 valgrind --leak-check=full ruby -e ""
...
==700357== HEAP SUMMARY:
==700357== in use at exit: 0 bytes in 0 blocks
==700357== total heap usage: 36,559 allocs, 36,559 frees, 6,064,783 bytes allocated
==700357==
==700357== All heap blocks were freed -- no leaks are possible
Notes:
Merged-By: maximecb <[email protected]>
|
|
In [1], we started checking for gen_branch failures, but I made two
crucial mistakes. One, defer_compilation() had the same issue as
gen_branch() but wasn't checked. Two, returning None from a codegen
function does not throw away the block. Checking how gen_single_block()
handles codegen functions, you can see that None terminates the block
with an exit, but does not overall return an Err. This handling is fine
for unimplemented instructions, for example, but incorrect in case
gen_branch() fails. The missing branch essentially corrupts the
block; adding more code after a missing branch doesn't correct the code.
Always abandon the block when defer_compilation() or gen_branch() fails.
[1]: cb661d7d82984cdb54485ea3f4af01ac21960882
Fixup: [1]
Notes:
Merged: https://github.com/ruby/ruby/pull/12035
Merged-By: XrXr
|
|
We got some core dumps in the wild where a PendingBranch had everything
as None, leading to a panic unwrapping in PendingBranch::into_branch().
This happened while compiling a `branchif`.
It seems that the only way this can happen is when core::gen_branch()
fails, but not due to OOM. We wouldn't have reach into_branch() when
OOM, and the only way to not leave markers that would've set the
branch's start_addr to some value in gen_branch() is for set_target() to
fail, causing an early return.
Unfortunately, it's hard to tell the exact sequence of events that led
to this situation, but regardless, the dumps show us that we should
check for errors in gen_branch().
Because gen_branch() is used deep in the stack during compilation (e.g.
guard_known_class() -> jit_chain_guard() -> gen_branch()), it'd be bad
for compile speed to propagate the error everywhere, not to mention the
massive patch required. Opt for a flag checked near the end of
compilation.
Notes:
Merged: https://github.com/ruby/ruby/pull/11938
Merged-By: XrXr
|
|
Notes:
Merged-By: k0kubun <[email protected]>
|
|
Type information in the context for no additional work!
This is the `if (special_object_p(obj)) return obj;` path in
rb_obj_dup() and for Numeric#dup, it's always the identity function.
Notes:
Merged: https://github.com/ruby/ruby/pull/11926
|
|
* YJIT: Fill in commented-out assertion
* YJIT: Rename yjit_reg_method() and add links in docs
Notes:
Merged-By: maximecb <[email protected]>
|
|
* Update yjit-bindgen deps
* YJIT: Allow shareable consts in multi-ractor mode
* Update yjit/src/codegen.rs
Co-authored-by: Alan Wu <[email protected]>
---------
Co-authored-by: Alan Wu <[email protected]>
Notes:
Merged-By: maximecb <[email protected]>
|
|
Previously, in the "Top-N most frequent C calls"
section of --yjit-stats output, we printed the class
name of the receiver, not the method owner. This meant
that calls on subclass instances that land on the same
method showed up as different entires.
Similarly, method called using an alias showed up as
different entries from other aliases.
Group by the resolved method instead.
Test program:
1.itself; [].itself; true.inspect; true.to_s
Before:
Top-4 most frequent C calls (80.0% of C calls):
1 (20.0%): Integer#itself
1 (20.0%): TrueClass#to_s
1 (20.0%): TrueClass#inspect
1 (20.0%): Array#itself
After:
Top-2 most frequent C calls (80.0% of C calls):
2 (40.0%): Kernel#itself
2 (40.0%): TrueClass#to_s
Notes:
Merged: https://github.com/ruby/ruby/pull/11913
|
|
* YJIT: Add `--yjit-compilation-log` flag to print out the compilation log at exit.
* YJIT: Add an option to enable the compilation log at runtime.
* YJIT: Fix a typo in the `IseqPayload` docs.
* YJIT: Add stubs for getting the YJIT compilation log in memory.
* YJIT: Add a compilation log based on a circular buffer to cap the log size.
* YJIT: Allow specifying either a file or directory name for the YJIT compilation log.
The compilation log will be populated as compilation events occur. If a directory is supplied, then a filename based on the PID will be used as the write target. If a file name is supplied instead, the log will be written to that file.
* YJIT: Add JIT compilation of C function substitutions to the compilation log.
* YJIT: Add compilation events to the circular buffer even if output is sent to a file.
Previously, the two modes were treated as being exclusive of one another. However, it could be beneficial to log all events to a file while also allowing for direct access of the last N events via `RubyVM::YJIT.compilation_log`.
* YJIT: Make timestamps the first element in the YJIT compilation log tuple.
* YJIT: Stream log to stderr if `--yjit-compilation-log` is supplied without an argument.
* YJIT: Eagerly compute compilation log messages to avoid hanging on to references that may GC.
* YJIT: Log all compiled blocks, not just the method entry points.
* YJIT: Remove all compilation events other than block compilation to slim down the log.
* YJIT: Replace circular buffer iterator with a consuming loop.
* YJIT: Support `--yjit-compilation-log=quiet` as a way to activate the in-memory log without printing it.
Co-authored-by: Randy Stauner <[email protected]>
* YJIT: Promote the compilation log to being the one YJIT log.
Co-authored-by: Randy Stauner <[email protected]>
* Update doc/yjit/yjit.md
* Update doc/yjit/yjit.md
---------
Co-authored-by: Randy Stauner <[email protected]>
Co-authored-by: Maxime Chevalier-Boisvert <[email protected]>
Notes:
Merged-By: maximecb <[email protected]>
|
|
Module#name shows up as a top C method callee in lobsters so probably
common enough. It's also easy to substitute thanks to rb_mod_name()
already having no GC yield points.
klass = BasicObject
50_000_000.times { klass.name }
Benchmark 1: /.rubies/post/bin/ruby --yjit mod_name.rb
Time (mean ± σ): 1.433 s ± 0.010 s [User: 1.410 s, System: 0.010 s]
Range (min … max): 1.421 s … 1.449 s 10 runs
Benchmark 2: /.rubies/mstr/bin/ruby --yjit mod_name.rb
Time (mean ± σ): 1.491 s ± 0.012 s [User: 1.468 s, System: 0.010 s]
Range (min … max): 1.470 s … 1.511 s 10 runs
Summary
/.rubies/post/bin/ruby --yjit mod_name.rb ran
1.04 ± 0.01 times faster than /.rubies/mstr/bin/ruby --yjit mod_name.rb
|
|
* YJIT: Add --yjit-mem-size option
* Improve --help
* s/the region/this virtual memory region/
Co-authored-by: Maxime Chevalier-Boisvert <[email protected]>
---------
Co-authored-by: Maxime Chevalier-Boisvert <[email protected]>
Notes:
Merged-By: maximecb <[email protected]>
|
|
* Returning an iterator instead of a vec
* Avoid changing the meaning of end_page
---------
Co-authored-by: Takashi Kokubun <[email protected]>
Notes:
Merged-By: k0kubun <[email protected]>
|
|
If a Hash which is empty or only using literals is frozen, we detect
this as a peephole optimization and change the instructions to be
`opt_hash_freeze`.
[Feature #20684]
Co-authored-by: Jean Boussier <[email protected]>
Notes:
Merged: https://github.com/ruby/ruby/pull/11406
|
|
If an Array which is empty or only using literals is frozen, we detect
this as a peephole optimization and change the instructions to be
`opt_ary_freeze`.
[Feature #20684]
Co-authored-by: Jean Boussier <[email protected]>
Notes:
Merged: https://github.com/ruby/ruby/pull/11406
|