Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion class.c
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace
RCLASSEXT_SUPER(ext) = RCLASSEXT_SUPER(orig);

RCLASSEXT_M_TBL(ext) = duplicate_classext_m_tbl(RCLASSEXT_M_TBL(orig), klass, dup_iclass);
RCLASSEXT_ICLASS_IS_ORIGIN(ext) = true;
RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(ext) = false;

if (orig->fields_obj) {
RB_OBJ_WRITE(klass, &ext->fields_obj, rb_imemo_fields_clone(orig->fields_obj));
Expand Down Expand Up @@ -1191,7 +1193,7 @@ rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach)
if (RCLASS_CONST_TBL(klass)) {
struct clone_const_arg arg;
struct rb_id_table *table;
arg.tbl = table = rb_id_table_create(0);
arg.tbl = table = rb_id_table_create(rb_id_table_size(RCLASS_CONST_TBL(klass)));
arg.klass = clone;
rb_id_table_foreach(RCLASS_CONST_TBL(klass), clone_const_i, &arg);
RCLASS_SET_CONST_TBL(clone, table, false);
Expand Down
2 changes: 1 addition & 1 deletion doc/string/index.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ returns the index of the first matching substring in +self+:
'тест'.index('с') # => 2 # Characters, not bytes.
'こんにちは'.index('ち') # => 3

When +pattern is a Regexp, returns the index of the first match in +self+:
When +pattern+ is a Regexp, returns the index of the first match in +self+:

'foo'.index(/o./) # => 1
'foo'.index(/.o/) # => 0
Expand Down
52 changes: 52 additions & 0 deletions doc/string/rindex.rdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
Returns the integer position of the _last_ substring that matches the given argument +pattern+,
or +nil+ if none found.

When +pattern+ is a string, returns the index of the last matching substring in self:

'foo'.rindex('f') # => 0
'foo'.rindex('o') # => 2
'foo'.rindex('oo' # => 1
'foo'.rindex('ooo') # => nil
'тест'.rindex('т') # => 3
'こんにちは'.rindex('ち') # => 3

When +pattern+ is a Regexp, returns the index of the last match in self:

'foo'.rindex(/f/) # => 0
'foo'.rindex(/o/) # => 2
'foo'.rindex(/oo/) # => 1
'foo'.rindex(/ooo/) # => nil

When +offset+ is non-negative, it specifies the maximum starting position in the
string to end the search:

'foo'.rindex('o', 0) # => nil
'foo'.rindex('o', 1) # => 1
'foo'.rindex('o', 2) # => 2
'foo'.rindex('o', 3) # => 2

With negative integer argument +offset+,
selects the search position by counting backward from the end of +self+:

'foo'.rindex('o', -1) # => 2
'foo'.rindex('o', -2) # => 1
'foo'.rindex('o', -3) # => nil
'foo'.rindex('o', -4) # => nil

The last match means starting at the possible last position, not
the last of longest matches:

'foo'.rindex(/o+/) # => 2
$~ # => #<MatchData "o">

To get the last longest match, combine with negative lookbehind:

'foo'.rindex(/(?<!o)o+/) # => 1
$~ # => #<MatchData "oo">

Or String#index with negative lookforward.

'foo'.index(/o+(?!.*o)/) # => 1
$~ # => #<MatchData "oo">

Related: see {Querying}[rdoc-ref:String@Querying].
53 changes: 2 additions & 51 deletions string.c
Original file line number Diff line number Diff line change
Expand Up @@ -4770,59 +4770,10 @@ rb_str_rindex(VALUE str, VALUE sub, long pos)

/*
* call-seq:
* rindex(substring, offset = self.length) -> integer or nil
* rindex(regexp, offset = self.length) -> integer or nil
* rindex(pattern, offset = self.length) -> integer or nil
*
* Returns the Integer index of the _last_ occurrence of the given +substring+,
* or +nil+ if none found:
* :include:doc/string/rindex.rdoc
*
* 'foo'.rindex('f') # => 0
* 'foo'.rindex('o') # => 2
* 'foo'.rindex('oo') # => 1
* 'foo'.rindex('ooo') # => nil
*
* Returns the Integer index of the _last_ match for the given Regexp +regexp+,
* or +nil+ if none found:
*
* 'foo'.rindex(/f/) # => 0
* 'foo'.rindex(/o/) # => 2
* 'foo'.rindex(/oo/) # => 1
* 'foo'.rindex(/ooo/) # => nil
*
* The _last_ match means starting at the possible last position, not
* the last of longest matches.
*
* 'foo'.rindex(/o+/) # => 2
* $~ #=> #<MatchData "o">
*
* To get the last longest match, needs to combine with negative
* lookbehind.
*
* 'foo'.rindex(/(?<!o)o+/) # => 1
* $~ #=> #<MatchData "oo">
*
* Or String#index with negative lookforward.
*
* 'foo'.index(/o+(?!.*o)/) # => 1
* $~ #=> #<MatchData "oo">
*
* Integer argument +offset+, if given and non-negative, specifies the maximum starting position in the
* string to _end_ the search:
*
* 'foo'.rindex('o', 0) # => nil
* 'foo'.rindex('o', 1) # => 1
* 'foo'.rindex('o', 2) # => 2
* 'foo'.rindex('o', 3) # => 2
*
* If +offset+ is a negative Integer, the maximum starting position in the
* string to _end_ the search is the sum of the string's length and +offset+:
*
* 'foo'.rindex('o', -1) # => 2
* 'foo'.rindex('o', -2) # => 1
* 'foo'.rindex('o', -3) # => nil
* 'foo'.rindex('o', -4) # => nil
*
* Related: String#index.
*/

static VALUE
Expand Down
113 changes: 113 additions & 0 deletions zjit/src/cruby_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,11 @@ pub fn init() -> Annotations {
annotate!(rb_cArray, "join", types::StringExact);
annotate!(rb_cArray, "[]", inline_array_aref);
annotate!(rb_cHash, "[]", inline_hash_aref);
annotate!(rb_cHash, "size", types::Fixnum, no_gc, leaf, elidable);
annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable);
annotate!(rb_cNilClass, "nil?", types::TrueClass, no_gc, leaf, elidable);
annotate!(rb_mKernel, "nil?", types::FalseClass, no_gc, leaf, elidable);
annotate!(rb_mKernel, "respond_to?", inline_kernel_respond_to_p);
annotate!(rb_cBasicObject, "==", types::BoolExact, no_gc, leaf, elidable);
annotate!(rb_cBasicObject, "!", types::BoolExact, no_gc, leaf, elidable);
annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize);
Expand Down Expand Up @@ -289,3 +291,114 @@ fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId,
let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qnil) });
Some(result)
}

fn inline_kernel_respond_to_p(
fun: &mut hir::Function,
block: hir::BlockId,
recv: hir::InsnId,
args: &[hir::InsnId],
state: hir::InsnId,
) -> Option<hir::InsnId> {
// Parse arguments: respond_to?(method_name, allow_priv = false)
let (method_name, allow_priv) = match *args {
[method_name] => (method_name, false),
[method_name, arg] => match fun.type_of(arg) {
t if t.is_known_truthy() => (method_name, true),
t if t.is_known_falsy() => (method_name, false),
// Unknown type; bail out
_ => return None,
},
// Unknown args; bail out
_ => return None,
};

// Method name must be a static symbol
let method_name = fun.type_of(method_name).ruby_object()?;
if !method_name.static_sym_p() {
return None;
}

// The receiver must have a known class to call `respond_to?` on
// TODO: This is technically overly strict. This would also work if all of the
// observed objects at this point agree on `respond_to?` and we can add many patchpoints.
let recv_class = fun.type_of(recv).runtime_exact_ruby_class()?;

// Get the method ID and its corresponding callable method entry
let mid = unsafe { rb_sym2id(method_name) };
let target_cme = unsafe { rb_callable_method_entry_or_negative(recv_class, mid) };
assert!(
!target_cme.is_null(),
"Should never be null, as in that case we will be returned a \"negative CME\""
);

let cme_def_type = unsafe { get_cme_def_type(target_cme) };

// Cannot inline a refined method, since their refinement depends on lexical scope
if cme_def_type == VM_METHOD_TYPE_REFINED {
return None;
}

let visibility = match cme_def_type {
VM_METHOD_TYPE_UNDEF => METHOD_VISI_UNDEF,
_ => unsafe { METHOD_ENTRY_VISI(target_cme) },
};

let result = match (visibility, allow_priv) {
// Method undefined; check `respond_to_missing?`
(METHOD_VISI_UNDEF, _) => {
let respond_to_missing = ID!(respond_to_missing);
if unsafe { rb_method_basic_definition_p(recv_class, respond_to_missing) } == 0 {
return None; // Custom definition of respond_to_missing?, so cannot inline
}
let respond_to_missing_cme =
unsafe { rb_callable_method_entry(recv_class, respond_to_missing) };
// Protect against redefinition of `respond_to_missing?`
fun.push_insn(
block,
hir::Insn::PatchPoint {
invariant: hir::Invariant::NoTracePoint,
state,
},
);
fun.push_insn(
block,
hir::Insn::PatchPoint {
invariant: hir::Invariant::MethodRedefined {
klass: recv_class,
method: respond_to_missing,
cme: respond_to_missing_cme,
},
state,
},
);
Qfalse
}
// Private method with allow priv=false, so `respond_to?` returns false
(METHOD_VISI_PRIVATE, false) => Qfalse,
// Public method or allow_priv=true: check if implemented
(METHOD_VISI_PUBLIC, _) | (_, true) => {
if cme_def_type == VM_METHOD_TYPE_NOTIMPLEMENTED {
// C method with rb_f_notimplement(). `respond_to?` returns false
// without consulting `respond_to_missing?`. See also: rb_add_method_cfunc()
Qfalse
} else {
Qtrue
}
}
(_, _) => return None, // not public and include_all not known, can't compile
};
fun.push_insn(block, hir::Insn::PatchPoint { invariant: hir::Invariant::NoTracePoint, state });
fun.push_insn(block, hir::Insn::PatchPoint {
invariant: hir::Invariant::MethodRedefined {
klass: recv_class,
method: mid,
cme: target_cme
}, state
});
if recv_class.instance_can_have_singleton_class() {
fun.push_insn(block, hir::Insn::PatchPoint {
invariant: hir::Invariant::NoSingletonClass { klass: recv_class }, state
});
}
Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(result) }))
}
Loading