Any iseq created inside an eval, class_eval or module_eval had its
`location.pathobj` set to an Array of 2 elements where the 2nd element was
`nil`. This is wasteful in terms of memory and also causes longer major GC
times. This was true for the eval forms even when `__FILE__` was given
directly.
Example script:
```
class Foo; end
Foo.class_eval("def hi = 'hi'")
ObjectSpace.each_object(Array) { |obj|
p obj if obj.frozen? && obj.size == 2 && obj[0].to_s.start_with?("(eval at")
}
```
output before:
```
["(eval at ../ruby/test.rb:47)", nil]
["(eval at ../ruby/test.rb:47)", nil]
```
output after: empty
The changes to the 2 structs do not alter the struct sizes (they make use of
padding that was already there).
This also saves ~1000 Arrays at startup, some from the builtin_prelude: arrays
like `["<internal:numeric>", nil]` per iseq created in those files. Around half
of those survive GC and become long-lived Arrays.
Since the fix to make send and symbol proc work correctly with refined
module method, the correctly setup iclass is already created by the time
mnew_from_me is called, so it doesn't need to call
find_refinement_iclass to create it.
Extract the code for finding/creating the refinement iclass from
mnew_from_me into a find_refinement_iclass function, and call that in
convert_umethod_to_method_components to fix handling of
UnboundMethod#bind.
When getting the appropriate refinement module/iclass from a callable
method entry, to ensure we only use create an module refinement iclass
when we are dealing with iclass and not with the module directly,
add a refinement_iclass_for_cme function to handle that check. This
logic is also needed in method_super_method, so also add a non-static
version with an rb_vm_ prefix.
Make find_refined_target_ancestor only return iclasses.
Co-authored-by: Shugo Maeda <shugo@ruby-lang.org>
Method#super_method needs to skip other refinements. The test added
when using the refinement iclass as the hash key showed incorrect
Method#super_method, with #super_method returning a refinement
method even though that refinement was not in scope at the definition
of the other refinement method.
This fixes cases where multiple refinements are in use in different
scopes.
The added test case also found a bug in Method#super_method for
refinement module methods that requires a couple of fixes. First,
add a non-static wrapper function for the module_refinement_iclass
function so the Method#super_method handling can use it to get the
iclass with the correct super pointer. Second, extract a
find_refined_target_ancestor function from method_super_method,
and use it mnew_from_me to find the correct ancestor to pass to
module_refinement_iclass.
Co-authored-by: Shugo Maeda <shugo@ruby-lang.org>
Similar to the defined?(super) change, #super_method has a separate
lookup path than regular super. It's further removed from the super
and defined?(super) paths, since you cannot use the same approach of
using the cfp to find the method entry, and finding the appropriate
iclass from there.
struct METHOD already has a member named iclass that is used to
implement #super_method. Update iclass member handling that it can
correctly handle #super_method for module Method created by calling
Add tests for {,Unbound}Method#super_method for all refinement
specs that test this type of super call. For Method, also check
at every step that method.unbind.super_method is the same as
method.super_method.unbind.
These still held String and Array lengths and indices in long, or cast them to long. rb_len_t is still long everywhere, so the generated code does not change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
These range checks guard values that are now rb_len_t but still compared SIZEOF_LONG, so they would be compiled out once rb_len_t gets wider than long on LLP64. They compile the same while rb_len_t is long. ibf_dump_write takes the size as size_t so that its check sees the full value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Argument counts stay `int` or `long`. The int range checks in proc.c and compile.c now depend on SIZEOF_VALUE so that they are kept once rb_len_t widens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
During method aliasing, we check if the methods `a` and `b` are equal
before aliasing. If they are equal, we don't need to alias. That check
was running Ruby code with the VM lock held when the first method was a
bmethod, which is not good and can cause incorrect behavior. Now, we call
`rb_proc_eq` directly. This means an override of `Proc#==` will not be
called to check if 2 methods are equal and will instead use the definition
of Proc equality at VM startup. `Method#==` and `UnboundMethod==` will
also not call into `Proc#==` now for bmethods. This is probably better behavior
even without the VM lock fix because `Method#hash` never called `Proc#hash` when
the method was a bmethod, it used `rb_hash_proc` directly.
Fixes [Bug #22398]
FIX2INT does not call to_int where int and long have the same size, so `method(:puts).curry(1r)` waited for a garbage number of arguments on Windows. Convert the arity once and keep the result as a Fixnum, which also limits the to_int call to one.
[Bug #22321]
In the following script, the klass of the Method object is an iclass, which
will raise an error "NoMethodError: undefined method 'inspect' for an instance of M":
module M
def foo = 1
end
class C
include M
end
obj = C.new
meth = obj.method(:foo).unbind.bind(obj)
puts meth.inspect
We know the type of the proc object at allocation time, so we can allocate
exactly the amount of memory needed for that type.
We can also shrink the type from a 4 byte enum since there are only 4 types
of procs. This allows us to put some flags after it, which saves us 8 bytes.
This commit reduces symbol and proc procs by 24 bytes (80 bytes to 56 bytes).
It also reduces iseq and ifunc procs by 8 bytes (80 bytes to 72 bytes).
If we embed the iseq_body, then it won't be a stable address, so we
shouldn't depend on the pointer address as a stable hash key. We can instead
use the location where it's defined as the hash key.
Since `prc.refined(*ms).refined(*ns)` is behaviorally equivalent to
`prc.refined(*ms, *ns)`, the copy of the block is deferred until
the first call and the memo is shared by these behaviorally
equivalent Procs. A Proc that is never called is no longer copied
at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add documentation-only deprecation notices to Module#ruby2_keywords,
main.ruby2_keywords, Proc#ruby2_keywords, Hash.ruby2_keywords_hash?,
and Hash.ruby2_keywords_hash, as the first phase of the schedule
proposed at https://bugs.ruby-lang.org/issues/22205.
Proc#refined memoizes {copied_iseq, cref} per source iseq in a
process-global map so the copy can be reused from any Ractor. The
cref's refinements table is already frozen and marked shareable for
that reason, but the copied iseq and the memo array holding it are
left unshareable, so the shared payload is not actually a shareable
object graph.
Complete that intent: mark the copied iseq subtree shareable (it is
self-contained -- nested block iseqs are copied too) and mark the
frozen memo array shareable. A copy's local_iseq/parent_iseq point at
already-shareable iseqs (other copies in the subtree, or the shareable
enclosing iseq), so the shareable check needs no special casing.
Add Proc#refined(mod, ...), which returns a new Proc that behaves like
the receiver but with the refinements activated by the given modules in
effect inside its body, without affecting the original Proc. This is
an alternative to the previously proposed Proc#using
(https://bugs.ruby-lang.org/issues/16461) that does not modify the
existing block and needs no `using Proc::Refinements` declaration.
Refinements are resolved at run time through the cref, so a refined
proc gets two pieces of its own:
* A cref carrying the additional refinements, built by activating the
modules (rb_using_module_recursive, under the VM lock) on a duplicate
of the block's captured cref. It is stored in a hidden ivar on the
Proc and injected into the block frame on every proc-invocation path
(Proc#call, yield from C methods, invokeblock/opt_call, and
instance_eval/instance_exec/module_eval/class_eval via yield_under).
The captured environment stays shared, so closure variables are still
shared with the original Proc. `using` inside the body is rejected
(CREF_REFINED_PROC), so the cref's refinements never change after
publication.
* A recursive deep copy of the block's instruction sequences
(rb_iseq_dup_with_independent_caches) so the new Proc has its own
inline method caches. This is required for correctness: the VM
caches refinement method resolution per call site assuming a single
lexical cref per iseq, so sharing the iseq would leak the refined
methods into the original Proc. Coverage keeps measuring the copied
iseq.
The copy and the cref are memoized in a hidden identity Hash keyed by
the source iseq (one slot per iseq, matched by the captured cref and
the module arguments, accessed under the VM lock), so repeated calls at
the same site reuse one copy and procs refined with the same modules
share the iseq and the cref. A differing module set or a ruby2_keywords
flag mismatch rebuilds the entry (with a performance warning). The
refinements table of the memoized cref is frozen and marked shareable so
the memo also serves other Ractors.
Procs without an iseq block (Symbol#to_proc, C-function procs), procs
created from methods, and procs that already have refinements are
rejected with ArgumentError.
The YJIT/ZJIT Rust bindings are regenerated for the new rb_proc_t field.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Set RUBY_TYPED_THREAD_SAFE_FREE on TypedData types whose dfree function
is trivially safe, and only frees its own memory
Types not yet marked as THREAD_SAFE_FREE:
- id2ref_tbl_type: sets the process-global id2ref_tbl to NULL.
- mutex_data_type: unlinks itself from another thread's keeping_mutexes list.
- autoload_data_type: deletes nodes from a shared intrusive list.
- autoload_const_type: deletes itself from a shared list.
- rb_cont_data_type: mutates the shared fiber pool and the global first_jit_cont list.
- rb_fiber_data_type: delegates to cont_free.
- FiberPoolDataType: mutates shared fiber pool free-lists.
- ractor_data_type: reconfigures global VM event-hook flags and call caches.
- exported_object_registry: frees a global table under the VM lock.
- rb_box_data_type (Box::Entry): unlinks classext from other live class and module objects.
- box_ext_cleanup_type: dereferences another String object and calls unlink.
- monitor_data_type: uses the default free, so the flag has no effect.
Co-authored-by: Luke Gruber <luke.gruber@shopify.com>
In the call branch of `curry`, `passed` is the only reference keeping the
freshly built argument array alive, yet its backing buffer is handed to
`rb_proc_call_with_block` as `argv` via `RARRAY_CONST_PTR(passed)`. After
taking that pointer `passed` is otherwise unused, so the compiler may drop
it before the call returns. Conservative stack marking then fails to find
the array, and a GC triggered during the call (e.g. under GC.stress) can
reclaim it while `argv` still points into its (embedded) buffer, crashing
with:
[BUG] try to mark T_NONE object (... parent: T_ARRAY ...)
It also shows up as silent argument corruption, e.g. a curried proc raising
"false can't be coerced into Integer", which is how it surfaced as a flaky
Proc#curry spec failure in CI.
Add RB_GC_GUARD(passed) to keep the array live until the call returns. Note
that `curry` calls with kw_splat == 0, so the argv-rewriting path in
invoke_block_from_c_proc (the rb_proc_call_kw / bd16973b85 case) is not
reached here; this is purely a liveness issue, not a write-barrier one.
The added test reproduces the crash deterministically under GC.stress.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A `Method#super_method` chain should return the methods that `super`
would call if the method was called normally. Previously, there were
multiple problems:
* There was an infinite `super_method` loop for refined methods.
* Lookup considered the refinements activated at the call site of
`super_method`, and not all the call site of `super` inside the
method.
This tries to recreate the logic that `super` uses inside
`super_method`. It avoids the loop. It also considers the refinements
activated for the method itself, not for the caller of `super_method`,
correctly handling refinements in outer scopes of the method. This
requires avoiding the use of rb_callable_method_entry_with_refinements,
which implicitly will consider refinements activated in the caller of
`super_method`.
The added tests attempt to ensure that the `super_method` lookup chain
matches the methods that `super` calls if you call the method.
This adds an `RICLASS_FOR_REFINEMENT_P` helper method, for logic that
is used a couple times in the new code and once in
`vm_search_normal_superclass`.
fafb55877a fixed issues with
refinement zsuper methods by converting them to a cfunc that calls
super. This changes Method creation to recognize the cfunc used
and skip those methods just as regular zsuper methods are skipped.
This makes refinement zsuper function the same way as subclass
zsuper in regards to Method/UnboundMethod.
We should never initialize a class with an existing singleton class
(singleton classes definitionally should not be shared).
The only cases this happened in Ruby itself is methods, which exposes a
bug that dup did not behave correctly.
Similar to `:nokey` for `**nil` declaring methods/procs, a method/proc with a `&nil` declaration will return a `:noblock` entry in the parameters array.
Method#unbind clones the method entry, preserving its defined_class.
For methods mixed in via include/extend, defined_class is an ICLASS,
causing UnboundMethod#== to return false when comparing against the
same method obtained via Module#instance_method.
Resolve ICLASS defined_class in method_eq.
Fixes [Bug #21873]
The previous example code was too complex and includes extra logics
that's not relevant to its main usage: `bind`.
The new example code focuses on `bind_call` so that readers can
understand how it works more easily.
* This reverts commit 065c48cdf1.
* This functionality is very valuable and has already taken 14 years
to agree on the API.
* Let's just document it's byte columns (in the next commit).
* See https://bugs.ruby-lang.org/issues/21783#note-9