Граф коммитов

66 Коммитов

Автор SHA1 Сообщение Дата
Aaron Patterson cdf33ed5f3 Optimized forwarding callers and callees
This patch optimizes forwarding callers and callees. It only optimizes methods that only take `...` as their parameter, and then pass `...` to other calls.

Calls it optimizes look like this:

```ruby
def bar(a) = a
def foo(...) = bar(...) # optimized
foo(123)
```

```ruby
def bar(a) = a
def foo(...) = bar(1, 2, ...) # optimized
foo(123)
```

```ruby
def bar(*a) = a

def foo(...)
  list = [1, 2]
  bar(*list, ...) # optimized
end
foo(123)
```

All variants of the above but using `super` are also optimized, including a bare super like this:

```ruby
def foo(...)
  super
end
```

This patch eliminates intermediate allocations made when calling methods that accept `...`.
We can observe allocation elimination like this:

```ruby
def m
  x = GC.stat(:total_allocated_objects)
  yield
  GC.stat(:total_allocated_objects) - x
end

def bar(a) = a
def foo(...) = bar(...)

def test
  m { foo(123) }
end

test
p test # allocates 1 object on master, but 0 objects with this patch
```

```ruby
def bar(a, b:) = a + b
def foo(...) = bar(...)

def test
  m { foo(1, b: 2) }
end

test
p test # allocates 2 objects on master, but 0 objects with this patch
```

How does it work?
-----------------

This patch works by using a dynamic stack size when passing forwarded parameters to callees.
The caller's info object (known as the "CI") contains the stack size of the
parameters, so we pass the CI object itself as a parameter to the callee.
When forwarding parameters, the forwarding ISeq uses the caller's CI to determine how much stack to copy, then copies the caller's stack before calling the callee.
The CI at the forwarded call site is adjusted using information from the caller's CI.

I think this description is kind of confusing, so let's walk through an example with code.

```ruby
def delegatee(a, b) = a + b

def delegator(...)
  delegatee(...)  # CI2 (FORWARDING)
end

def caller
  delegator(1, 2) # CI1 (argc: 2)
end
```

Before we call the delegator method, the stack looks like this:

```
Executing Line | Code                                  | Stack
---------------+---------------------------------------+--------
              1| def delegatee(a, b) = a + b           | self
              2|                                       | 1
              3| def delegator(...)                    | 2
              4|   #                                   |
              5|   delegatee(...)  # CI2 (FORWARDING)  |
              6| end                                   |
              7|                                       |
              8| def caller                            |
          ->  9|   delegator(1, 2) # CI1 (argc: 2)     |
             10| end                                   |
```

The ISeq for `delegator` is tagged as "forwardable", so when `caller` calls in
to `delegator`, it writes `CI1` on to the stack as a local variable for the
`delegator` method.  The `delegator` method has a special local called `...`
that holds the caller's CI object.

Here is the ISeq disasm fo `delegator`:

```
== disasm: #<ISeq:delegator@-e:1 (1,0)-(1,39)>
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] "..."@0
0000 putself                                                          (   1)[LiCa]
0001 getlocal_WC_0                          "..."@0
0003 send                                   <calldata!mid:delegatee, argc:0, FCALL|FORWARDING>, nil
0006 leave                                  [Re]
```

The local called `...` will contain the caller's CI: CI1.

Here is the stack when we enter `delegator`:

```
Executing Line | Code                                  | Stack
---------------+---------------------------------------+--------
              1| def delegatee(a, b) = a + b           | self
              2|                                       | 1
              3| def delegator(...)                    | 2
           -> 4|   #                                   | CI1 (argc: 2)
              5|   delegatee(...)  # CI2 (FORWARDING)  | cref_or_me
              6| end                                   | specval
              7|                                       | type
              8| def caller                            |
              9|   delegator(1, 2) # CI1 (argc: 2)     |
             10| end                                   |
```

The CI at `delegatee` on line 5 is tagged as "FORWARDING", so it knows to
memcopy the caller's stack before calling `delegatee`.  In this case, it will
memcopy self, 1, and 2 to the stack before calling `delegatee`.  It knows how much
memory to copy from the caller because `CI1` contains stack size information
(argc: 2).

Before executing the `send` instruction, we push `...` on the stack.  The
`send` instruction pops `...`, and because it is tagged with `FORWARDING`, it
knows to memcopy (using the information in the CI it just popped):

```
== disasm: #<ISeq:delegator@-e:1 (1,0)-(1,39)>
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] "..."@0
0000 putself                                                          (   1)[LiCa]
0001 getlocal_WC_0                          "..."@0
0003 send                                   <calldata!mid:delegatee, argc:0, FCALL|FORWARDING>, nil
0006 leave                                  [Re]
```

Instruction 001 puts the caller's CI on the stack.  `send` is tagged with
FORWARDING, so it reads the CI and _copies_ the callers stack to this stack:

```
Executing Line | Code                                  | Stack
---------------+---------------------------------------+--------
              1| def delegatee(a, b) = a + b           | self
              2|                                       | 1
              3| def delegator(...)                    | 2
              4|   #                                   | CI1 (argc: 2)
           -> 5|   delegatee(...)  # CI2 (FORWARDING)  | cref_or_me
              6| end                                   | specval
              7|                                       | type
              8| def caller                            | self
              9|   delegator(1, 2) # CI1 (argc: 2)     | 1
             10| end                                   | 2
```

The "FORWARDING" call site combines information from CI1 with CI2 in order
to support passing other values in addition to the `...` value, as well as
perfectly forward splat args, kwargs, etc.

Since we're able to copy the stack from `caller` in to `delegator`'s stack, we
can avoid allocating objects.

I want to do this to eliminate object allocations for delegate methods.
My long term goal is to implement `Class#new` in Ruby and it uses `...`.

I was able to implement `Class#new` in Ruby
[here](https://github.com/ruby/ruby/pull/9289).
If we adopt the technique in this patch, then we can optimize allocating
objects that take keyword parameters for `initialize`.

For example, this code will allocate 2 objects: one for `SomeObject`, and one
for the kwargs:

```ruby
SomeObject.new(foo: 1)
```

If we combine this technique, plus implement `Class#new` in Ruby, then we can
reduce allocations for this common operation.

Co-Authored-By: John Hawthorn <john@hawthorn.email>
Co-Authored-By: Alan Wu <XrXr@users.noreply.github.com>
2024-06-18 09:28:25 -07:00
Nobuyoshi Nakada 49fcd33e13 Introduce a specialize instruction for Array#pack
Instructions for this code:

```ruby
  # frozen_string_literal: true

[a].pack("C")
```

Before this commit:

```
== disasm: #<ISeq:<main>@test.rb:1 (1,0)-(3,13)>
0000 putself                                                          (   3)[Li]
0001 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 newarray                               1
0005 putobject                              "C"
0007 opt_send_without_block                 <calldata!mid:pack, argc:1, ARGS_SIMPLE>
0009 leave
```

After this commit:

```
== disasm: #<ISeq:<main>@test.rb:1 (1,0)-(3,13)>
0000 putself                                                          (   3)[Li]
0001 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 putobject                              "C"
0005 opt_newarray_send                      2, :pack
0008 leave
```

Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com>
Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
2024-05-23 12:11:50 -07:00
Jean Boussier b4a69351ec Move FL_SINGLETON to FL_USER1
This frees FL_USER0 on both T_MODULE and T_CLASS.

Note: prior to this, FL_SINGLETON was never set on T_MODULE,
so checking for `FL_SINGLETON` without first checking that
`FL_TYPE` was `T_CLASS` was valid. That's no longer the case.
2024-03-06 13:11:41 -05:00
Takashi Kokubun 607b86ed1f Update a stubbed type for RJIT
cfunc.func is actually used by RJIT
2024-03-01 15:10:26 -08:00
Takashi Kokubun bc7266c5ce
Bump the required BASERUBY version to 3.0 (#9976) 2024-02-15 23:13:45 -08:00
Takashi Kokubun 996776e936 Leave a comment about the limitation of Primitive
and adjust some code styling from that PR.
2024-01-23 14:39:37 -08:00
Nobuyoshi Nakada d940e3b2c3 `cexpr!` must be up to one per line now 2024-01-22 19:39:34 +09:00
Takashi Kokubun e37a37e696 Drop obsoleted BUILTIN_ATTR_NO_GC attribute
The thing that has used this in the past was very buggy, and we've never
revisied it. Let's remove it until we need it again.
2024-01-16 17:27:53 -08:00
Takashi Kokubun 19d082dcfa RJIT: Distinguish Pointer with Array
This is more convenient for accessing those fields.
2023-12-22 11:24:04 -08:00
Takashi Kokubun eb872d1752 RJIT: Share rb_vm_insns_count for vm_insns_count 2023-12-18 23:55:40 -08:00
Peter Zhu 68869e9bd9 Revert "Revert "Remove SHAPE_CAPACITY_CHANGE shapes""
This reverts commit 5f3fb4f4e3.
2023-11-13 18:26:36 -05:00
Peter Zhu 5f3fb4f4e3 Revert "Remove SHAPE_CAPACITY_CHANGE shapes"
This reverts commit f6910a6112.

We're seeing crashes in the test suite of Shopify's core monolith after
this change.
2023-11-10 11:27:49 -05:00
Peter Zhu f6910a6112 Remove SHAPE_CAPACITY_CHANGE shapes
We don't need to create a shape to transition capacity as we can
transition the capacity when the capacity of the SHAPE_IVAR changes.
2023-11-09 09:25:02 -05:00
Peter Zhu 38ba040d8b Make every initial size pool shape a root shape
This commit makes every initial size pool shape a root shape and assigns
it a capacity of 0.
2023-11-02 13:42:11 -04:00
Maxime Chevalier-Boisvert b2e1ddffa5
YJIT: port call threshold logic from Rust to C for performance (#8628)
* Port call threshold logic from Rust to C for performance

* Prefix global/field names with yjit_

* Fix linker error

* Fix preprocessor condition for rb_yjit_threshold_hit

* Fix third linker issue

* Exclude yjit_calls_at_interv from RJIT bindgen

---------

Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com>
2023-10-12 10:05:34 -04:00
Takashi Kokubun 8705e734ed Remove a binding.irb in rjit-bindgen 2023-10-04 11:43:29 -07:00
Takashi Kokubun cd8d20cd1f
YJIT: Compile exception handlers (#8171)
Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com>
2023-08-08 16:06:22 -07:00
Aaron Patterson 7ce6bcaf8b Expose rb_hash_resurrect
This is for implementing the `duphash` instruction
2023-06-23 11:46:21 -07:00
Aaron Patterson bdffcd6df3 Update RJIT to support newarray_send
This also adds max / hash support
2023-04-18 17:16:22 -07:00
Aaron Patterson a9bfb64153 Expose rb_sym_to_proc via RJIT
This is needed for getblockparamproxy
2023-04-07 09:49:15 -07:00
Peter Zhu 1da2e7fca3
[Feature #19579] Remove !USE_RVARGC code (#7655)
Remove !USE_RVARGC code

[Feature #19579]

The Variable Width Allocation feature was turned on by default in Ruby
3.2. Since then, we haven't received bug reports or backports to the
non-Variable Width Allocation code paths, so we assume that nobody is
using it. We also don't plan on maintaining the non-Variable Width
Allocation code, so we are going to remove it.
2023-04-04 17:30:06 -04:00
Takashi Kokubun d546f8c518 RJIT: Store type information in Context 2023-04-02 22:32:16 -07:00
Takashi Kokubun 6002b12611 RJIT: Support entry with different PCs 2023-04-02 15:27:40 -07:00
Takashi Kokubun 66f8efc342 RJIT: Simplify cfunc implementation 2023-04-02 13:58:39 -07:00
Takashi Kokubun 1b475fcd10 Remove an unneeded function copy 2023-04-01 23:09:05 -07:00
Takashi Kokubun a077b7e36b RJIT: Support rest args 2023-04-01 23:00:36 -07:00
Takashi Kokubun 0973b93e49 RJIT: Start moving away from VM-like ISEQ handling 2023-04-01 16:56:05 -07:00
Koichi Sasada ad7362db07 rename `rb_thread_t::locking_native_thread`
to `rb_thread_t::has_dedicated_nt`
2023-03-31 18:10:00 +09:00
Takashi Kokubun 3352e76441 RJIT: Implement leaf builtin call 2023-03-26 19:25:17 -07:00
Takashi Kokubun dc270fc632 RJIT: Implement attr_writer 2023-03-26 18:02:25 -07:00
Takashi Kokubun 1dd65f7c55 RJIT: Initial support of splat 2023-03-25 00:31:11 -07:00
Takashi Kokubun 70ea58bd5b RJIT: Break up RJIT send_iseq_complex exit reasons 2023-03-19 23:58:14 -07:00
Takashi Kokubun 59b86da82c RJIT: Implement ifunc invokeblock 2023-03-19 23:32:07 -07:00
Takashi Kokubun 83ad1cac81 RJIT: Optimize Kernel#respond_to? 2023-03-19 14:04:58 -07:00
Takashi Kokubun cd5a8d0160 RJIT: Optimize String#+@ 2023-03-19 13:36:26 -07:00
Takashi Kokubun 2121282753 RJIT: Optimize String#<< 2023-03-19 13:25:41 -07:00
Takashi Kokubun c4e2718763 RJIT: Workaround USE_RVARGC=0 CI 2023-03-18 23:42:58 -07:00
Takashi Kokubun 32e0c97dfa RJIT: Optimize String#bytesize 2023-03-18 23:35:42 -07:00
Takashi Kokubun 106cca5111 RJIT: Optimize String#empty? 2023-03-18 23:24:57 -07:00
Takashi Kokubun 824cf88969 RJIT: Optimize Kernel#is_a? 2023-03-18 22:59:46 -07:00
Takashi Kokubun cc9330f8c0 RJIT: Reorder opt_case_dispatch branches 2023-03-18 22:32:27 -07:00
Takashi Kokubun 71bcab4519 RJIT: Implement setclassvariable 2023-03-18 21:49:46 -07:00
Takashi Kokubun 8510f33cc1 RJIT: Implement intern 2023-03-18 21:42:20 -07:00
Takashi Kokubun 9c2792c3d3 RJIT: Implement toregexp 2023-03-18 21:37:49 -07:00
Takashi Kokubun d189f8d870 RJIT: Prefix rjit_options with rb_ 2023-03-18 21:28:55 -07:00
Takashi Kokubun 2eefd71e0f RJIT: Implement newrange 2023-03-18 21:24:31 -07:00
Takashi Kokubun 81e19b7d99 RJIT: Implement getglobal 2023-03-18 21:20:58 -07:00
Takashi Kokubun 9f8e914943 RJIT: Implement checkkeyword 2023-03-18 21:15:22 -07:00
Takashi Kokubun dc28ccbb6d RJIT: Implement getspecial insn 2023-03-18 00:00:18 -07:00
Takashi Kokubun 93e05aaa74 RJIT: Implement putspecialobject insn 2023-03-17 23:46:56 -07:00