diff --git a/doc/string/start_with_p.rdoc b/doc/string/start_with_p.rdoc
index 5d1f9f954370e0..298a5572769ea8 100644
--- a/doc/string/start_with_p.rdoc
+++ b/doc/string/start_with_p.rdoc
@@ -1,10 +1,9 @@
-Returns whether +self+ starts with any of the given +string_or_regexp+.
+Returns whether +self+ starts with any of the given +patterns+.
-Matches patterns against the beginning of +self+.
-For each given +string_or_regexp+, the pattern is:
+For each argument, the pattern used is:
-- +string_or_regexp+ itself, if it is a Regexp.
-- Regexp.quote(string_or_regexp), if +string_or_regexp+ is a string.
+- The pattern itself, if it is a Regexp.
+- Regexp.quote(pattern), if it is a string.
Returns +true+ if any pattern matches the beginning, +false+ otherwise:
@@ -15,4 +14,4 @@ Returns +true+ if any pattern matches the beginning, +false+ otherwise:
'тест'.start_with?('т') # => true
'こんにちは'.start_with?('こ') # => true
-Related: String#end_with?.
+Related: see {Querying}[rdoc-ref:String@Querying].
diff --git a/string.c b/string.c
index 1de87071272938..55a7eebc5a579f 100644
--- a/string.c
+++ b/string.c
@@ -8903,8 +8903,12 @@ rb_str_delete(int argc, VALUE *argv, VALUE str)
* call-seq:
* squeeze!(*selectors) -> self or nil
*
- * Like String#squeeze, but modifies +self+ in place.
- * Returns +self+ if any changes were made, +nil+ otherwise.
+ * Like String#squeeze, except that:
+ *
+ * - Characters are squeezed in +self+ (not in a copy of +self+).
+ * - Returns +self+ if any changes are made, +nil+ otherwise.
+ *
+ * Related: See {Modifying}[rdoc-ref:String@Modifying].
*/
static VALUE
@@ -10482,10 +10486,12 @@ rb_str_rstrip(VALUE str)
* call-seq:
* strip! -> self or nil
*
- * Like String#strip, except that any modifications are made in +self+;
- * returns +self+ if any modification are made, +nil+ otherwise.
+ * Like String#strip, except that:
*
- * Related: String#lstrip!, String#strip!.
+ * - Any modifications are made to +self+.
+ * - Returns +self+ if any modification are made, +nil+ otherwise.
+ *
+ * Related: see {Modifying}[rdoc-ref:String@Modifying].
*/
static VALUE
@@ -10519,15 +10525,15 @@ rb_str_strip_bang(VALUE str)
* call-seq:
* strip -> new_string
*
- * Returns a copy of the receiver with leading and trailing whitespace removed;
+ * Returns a copy of +self+ with leading and trailing whitespace removed;
* see {Whitespace in Strings}[rdoc-ref:String@Whitespace+in+Strings]:
*
* whitespace = "\x00\t\n\v\f\r "
* s = whitespace + 'abc' + whitespace
- * s # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r "
+ * # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r "
* s.strip # => "abc"
*
- * Related: String#lstrip, String#rstrip.
+ * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String].
*/
static VALUE
@@ -11214,7 +11220,7 @@ rb_str_rpartition(VALUE str, VALUE sep)
/*
* call-seq:
- * start_with?(*string_or_regexp) -> true or false
+ * start_with?(*patterns) -> true or false
*
* :include: doc/string/start_with_p.rdoc
*
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
index 13c78170177a20..e76e140ba13f2b 100644
--- a/test/ruby/test_zjit.rb
+++ b/test/ruby/test_zjit.rb
@@ -1675,6 +1675,20 @@ def self.test = @@x
}
end
+ def test_getclassvariable_raises
+ assert_compiles '"uninitialized class variable @@x in Foo"', %q{
+ class Foo
+ def self.test = @@x
+ end
+
+ begin
+ Foo.test
+ rescue NameError => e
+ e.message
+ end
+ }
+ end
+
def test_setclassvariable
assert_compiles '42', %q{
class Foo
@@ -1686,6 +1700,21 @@ def self.test = @@x = 42
}
end
+ def test_setclassvariable_raises
+ assert_compiles '"can\'t modify frozen #: Foo"', %q{
+ class Foo
+ def self.test = @@x = 42
+ freeze
+ end
+
+ begin
+ Foo.test
+ rescue FrozenError => e
+ e.message
+ end
+ }
+ end
+
def test_attr_reader
assert_compiles '[4, 4]', %q{
class C
diff --git a/zjit/src/cast.rs b/zjit/src/cast.rs
index c6d11ef4af18dc..52e2078cde3cc3 100644
--- a/zjit/src/cast.rs
+++ b/zjit/src/cast.rs
@@ -16,19 +16,19 @@
/// the method `into()` also causes a name conflict.
pub(crate) trait IntoUsize {
/// Convert to usize. Implementation conditional on width of [usize].
- fn as_usize(self) -> usize;
+ fn to_usize(self) -> usize;
}
#[cfg(target_pointer_width = "64")]
impl IntoUsize for u64 {
- fn as_usize(self) -> usize {
+ fn to_usize(self) -> usize {
self as usize
}
}
#[cfg(target_pointer_width = "64")]
impl IntoUsize for u32 {
- fn as_usize(self) -> usize {
+ fn to_usize(self) -> usize {
self as usize
}
}
@@ -36,7 +36,7 @@ impl IntoUsize for u32 {
impl IntoUsize for u16 {
/// Alias for `.into()`. For convenience so you could use the trait for
/// all unsgined types.
- fn as_usize(self) -> usize {
+ fn to_usize(self) -> usize {
self.into()
}
}
@@ -44,7 +44,7 @@ impl IntoUsize for u16 {
impl IntoUsize for u8 {
/// Alias for `.into()`. For convenience so you could use the trait for
/// all unsgined types.
- fn as_usize(self) -> usize {
+ fn to_usize(self) -> usize {
self.into()
}
}
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index 0a3be9277bb0e8..16b5e94d342263 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -188,8 +188,8 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function_ptr: CodePtr) -> Result
let (code_ptr, gc_offsets) = asm.compile(cb)?;
assert!(gc_offsets.is_empty());
if get_option!(perf) {
- let start_ptr = code_ptr.raw_ptr(cb) as usize;
- let end_ptr = cb.get_write_ptr().raw_ptr(cb) as usize;
+ let start_ptr = code_ptr.raw_addr(cb);
+ let end_ptr = cb.get_write_ptr().raw_addr(cb);
let code_size = end_ptr - start_ptr;
let iseq_name = iseq_get_location(iseq, 0);
register_with_perf(format!("entry for {iseq_name}"), start_ptr, code_size);
@@ -298,8 +298,8 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Resul
let result = asm.compile(cb);
if let Ok((start_ptr, _)) = result {
if get_option!(perf) {
- let start_usize = start_ptr.raw_ptr(cb) as usize;
- let end_usize = cb.get_write_ptr().raw_ptr(cb) as usize;
+ let start_usize = start_ptr.raw_addr(cb);
+ let end_usize = cb.get_write_ptr().raw_addr(cb);
let code_size = end_usize - start_usize;
let iseq_name = iseq_get_location(iseq, 0);
register_with_perf(iseq_name, start_usize, code_size);
@@ -508,7 +508,7 @@ fn gen_objtostring(jit: &mut JITState, asm: &mut Assembler, val: Opnd, cd: *cons
gen_prepare_non_leaf_call(jit, asm, state);
// TODO: Specialize for immediate types
// Call rb_vm_objtostring(iseq, recv, cd)
- let ret = asm_ccall!(asm, rb_vm_objtostring, VALUE(jit.iseq as usize).into(), val, (cd as usize).into());
+ let ret = asm_ccall!(asm, rb_vm_objtostring, VALUE::from(jit.iseq).into(), val, Opnd::const_ptr(cd));
// TODO: Call `to_s` on the receiver if rb_vm_objtostring returns Qundef
// Need to replicate what CALL_SIMPLE_METHOD does
@@ -736,7 +736,7 @@ fn gen_ccall_with_frame(
});
asm_comment!(asm, "switch to new SP register");
- let sp_offset = (caller_stack_size + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE;
+ let sp_offset = (caller_stack_size + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE;
let new_sp = asm.add(SP, sp_offset.into());
asm.mov(SP, new_sp);
@@ -792,7 +792,7 @@ fn gen_ccall_variadic(
});
asm_comment!(asm, "switch to new SP register");
- let sp_offset = (state.stack().len() - args.len() + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE;
+ let sp_offset = (state.stack().len() - args.len() + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE;
let new_sp = asm.add(SP, sp_offset.into());
asm.mov(SP, new_sp);
@@ -833,12 +833,12 @@ fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, val:
fn gen_getclassvar(jit: &mut JITState, asm: &mut Assembler, id: ID, ic: *const iseq_inline_cvar_cache_entry, state: &FrameState) -> Opnd {
gen_prepare_non_leaf_call(jit, asm, state);
- asm_ccall!(asm, rb_vm_getclassvariable, VALUE(jit.iseq as usize).into(), CFP, id.0.into(), Opnd::const_ptr(ic))
+ asm_ccall!(asm, rb_vm_getclassvariable, VALUE::from(jit.iseq).into(), CFP, id.0.into(), Opnd::const_ptr(ic))
}
fn gen_setclassvar(jit: &mut JITState, asm: &mut Assembler, id: ID, val: Opnd, ic: *const iseq_inline_cvar_cache_entry, state: &FrameState) {
gen_prepare_non_leaf_call(jit, asm, state);
- asm_ccall!(asm, rb_vm_setclassvariable, VALUE(jit.iseq as usize).into(), CFP, id.0.into(), val, Opnd::const_ptr(ic));
+ asm_ccall!(asm, rb_vm_setclassvariable, VALUE::from(jit.iseq).into(), CFP, id.0.into(), val, Opnd::const_ptr(ic));
}
/// Look up global variables
@@ -975,7 +975,7 @@ fn gen_load_ivar_embedded(asm: &mut Assembler, self_val: Opnd, id: ID, index: u1
// See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h
asm_comment!(asm, "Load embedded ivar id={} index={}", id.contents_lossy(), index);
- let offs = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * index as usize) as i32;
+ let offs = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * index.to_usize()) as i32;
let self_val = asm.load(self_val);
let ivar_opnd = Opnd::mem(64, self_val, offs);
asm.load(ivar_opnd)
@@ -990,7 +990,7 @@ fn gen_load_ivar_extended(asm: &mut Assembler, self_val: Opnd, id: ID, index: u1
let tbl_opnd = asm.load(Opnd::mem(64, self_val, ROBJECT_OFFSET_AS_HEAP_FIELDS as i32));
// Read the ivar from the extended table
- let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * index as usize) as i32);
+ let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * index.to_usize()) as i32);
asm.load(ivar_opnd)
}
@@ -1113,7 +1113,7 @@ fn gen_send(
}
asm.ccall(
rb_vm_send as *const u8,
- vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()],
+ vec![EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into()],
)
}
@@ -1136,7 +1136,7 @@ fn gen_send_forward(
}
asm.ccall(
rb_vm_sendforward as *const u8,
- vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()],
+ vec![EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into()],
)
}
@@ -1157,7 +1157,7 @@ fn gen_send_without_block(
}
asm.ccall(
rb_vm_opt_send_without_block as *const u8,
- vec![EC, CFP, (cd as usize).into()],
+ vec![EC, CFP, Opnd::const_ptr(cd)],
)
}
@@ -1174,8 +1174,8 @@ fn gen_send_without_block_direct(
) -> lir::Opnd {
gen_incr_counter(asm, Counter::iseq_optimized_send_count);
- let local_size = unsafe { get_iseq_body_local_table_size(iseq) }.as_usize();
- let stack_growth = state.stack_size() + local_size + unsafe { get_iseq_body_stack_max(iseq) }.as_usize();
+ let local_size = unsafe { get_iseq_body_local_table_size(iseq) }.to_usize();
+ let stack_growth = state.stack_size() + local_size + unsafe { get_iseq_body_stack_max(iseq) }.to_usize();
gen_stack_overflow_check(jit, asm, state, stack_growth);
// Save cfp->pc and cfp->sp for the caller frame
@@ -1211,7 +1211,7 @@ fn gen_send_without_block_direct(
});
asm_comment!(asm, "switch to new SP register");
- let sp_offset = (state.stack().len() + local_size - args.len() + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE;
+ let sp_offset = (state.stack().len() + local_size - args.len() + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE;
let new_sp = asm.add(SP, sp_offset.into());
asm.mov(SP, new_sp);
@@ -1263,7 +1263,7 @@ fn gen_invokeblock(
}
asm.ccall(
rb_vm_invokeblock as *const u8,
- vec![EC, CFP, (cd as usize).into()],
+ vec![EC, CFP, Opnd::const_ptr(cd)],
)
}
@@ -1285,7 +1285,7 @@ fn gen_invokesuper(
}
asm.ccall(
rb_vm_invokesuper as *const u8,
- vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()],
+ vec![EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into()],
)
}
@@ -1544,7 +1544,7 @@ fn gen_is_method_cfunc(jit: &JITState, asm: &mut Assembler, val: lir::Opnd, cd:
unsafe extern "C" {
fn rb_vm_method_cfunc_is(iseq: IseqPtr, cd: *const rb_call_data, recv: VALUE, cfunc: *const u8) -> VALUE;
}
- asm_ccall!(asm, rb_vm_method_cfunc_is, VALUE(jit.iseq as usize).into(), (cd as usize).into(), val, (cfunc as usize).into())
+ asm_ccall!(asm, rb_vm_method_cfunc_is, VALUE::from(jit.iseq).into(), Opnd::const_ptr(cd), val, Opnd::const_ptr(cfunc))
}
fn gen_is_bit_equal(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir::Opnd {
@@ -1889,7 +1889,7 @@ fn param_opnd(idx: usize) -> Opnd {
/// Inverse of ep_offset_to_local_idx(). See ep_offset_to_local_idx() for details.
pub fn local_idx_to_ep_offset(iseq: IseqPtr, local_idx: usize) -> i32 {
let local_size = unsafe { get_iseq_body_local_table_size(iseq) };
- local_size_and_idx_to_ep_offset(local_size as usize, local_idx)
+ local_size_and_idx_to_ep_offset(local_size.to_usize(), local_idx)
}
/// Convert the number of locals and a local index to an offset from the EP
@@ -2005,8 +2005,8 @@ c_callable! {
rb_set_cfp_sp(cfp, sp);
// Fill nils to uninitialized (non-argument) locals
- let local_size = get_iseq_body_local_table_size(iseq) as usize;
- let num_params = get_iseq_body_param_size(iseq) as usize;
+ let local_size = get_iseq_body_local_table_size(iseq).to_usize();
+ let num_params = get_iseq_body_param_size(iseq).to_usize();
let base = sp.offset(-local_size_and_idx_to_bp_offset(local_size, num_params) as isize);
slice::from_raw_parts_mut(base, local_size - num_params).fill(Qnil);
}
diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs
index ee10eaa681c7e4..0d77b1d6cc7e11 100644
--- a/zjit/src/cruby_methods.rs
+++ b/zjit/src/cruby_methods.rs
@@ -203,6 +203,8 @@ pub fn init() -> Annotations {
annotate!(rb_cArray, "reverse", types::ArrayExact, leaf, elidable);
annotate!(rb_cArray, "join", types::StringExact);
annotate!(rb_cArray, "[]", inline_array_aref);
+ annotate!(rb_cArray, "<<", inline_array_push);
+ annotate!(rb_cArray, "push", inline_array_push);
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);
@@ -266,6 +268,15 @@ fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In
None
}
+fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option {
+ // Inline only the case of `<<` or `push` when called with a single argument.
+ if let &[val] = args {
+ let _ = fun.push_insn(block, hir::Insn::ArrayPush { array: recv, val, state });
+ return Some(recv);
+ }
+ None
+}
+
fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option {
if let &[key] = args {
let result = fun.push_insn(block, hir::Insn::HashAref { hash: recv, key, state });
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 9f422c0146cce9..dbb9177ee3e6e8 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -1873,7 +1873,7 @@ impl Function {
/// Set self.param_types. They are copied to the param types of jit_entry_blocks.
fn set_param_types(&mut self) {
let iseq = self.iseq;
- let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize();
+ let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize();
let rest_param_idx = iseq_rest_param_idx(iseq);
self.param_types.push(types::BasicObject); // self
@@ -3885,7 +3885,7 @@ pub enum ParseError {
/// Return the number of locals in the current ISEQ (includes parameters)
fn num_locals(iseq: *const rb_iseq_t) -> usize {
- (unsafe { get_iseq_body_local_table_size(iseq) }).as_usize()
+ (unsafe { get_iseq_body_local_table_size(iseq) }).to_usize()
}
/// If we can't handle the type of send (yet), bail out.
@@ -4896,7 +4896,7 @@ fn compile_entry_block(fun: &mut Function, jit_entry_insns: &[u32]) {
/// Compile initial locals for an entry_block for the interpreter
fn compile_entry_state(fun: &mut Function, entry_block: BlockId) -> (InsnId, FrameState) {
let iseq = fun.iseq;
- let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize();
+ let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize();
let rest_param_idx = iseq_rest_param_idx(iseq);
let self_param = fun.push_insn(entry_block, Insn::LoadSelf);
@@ -4929,7 +4929,7 @@ fn compile_jit_entry_block(fun: &mut Function, jit_entry_idx: usize, target_bloc
/// Compile params and initial locals for a jit_entry_block
fn compile_jit_entry_state(fun: &mut Function, jit_entry_block: BlockId) -> (InsnId, FrameState) {
let iseq = fun.iseq;
- let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize();
+ let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize();
let self_param = fun.push_insn(jit_entry_block, Insn::Param);
let mut entry_state = FrameState::new(iseq);
@@ -14008,7 +14008,69 @@ mod opt_tests {
PatchPoint MethodRedefined(Array@0x1000, <<@0x1008, cme:0x1010)
PatchPoint NoSingletonClass(Array@0x1000)
v26:ArrayExact = GuardType v9, ArrayExact
- v27:BasicObject = CCallWithFrame <<@0x1038, v26, v13
+ ArrayPush v26, v13
+ IncrCounter inline_cfunc_optimized_send_count
+ CheckInterrupts
+ Return v26
+ ");
+ }
+
+ #[test]
+ fn test_optimize_array_push_single_arg() {
+ eval("
+ def test(arr)
+ arr.push(1)
+ end
+ test([])
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@:3:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal l0, SP@4
+ Jump bb2(v1, v2)
+ bb1(v5:BasicObject, v6:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v5, v6)
+ bb2(v8:BasicObject, v9:BasicObject):
+ v13:Fixnum[1] = Const Value(1)
+ PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010)
+ PatchPoint NoSingletonClass(Array@0x1000)
+ v24:ArrayExact = GuardType v9, ArrayExact
+ ArrayPush v24, v13
+ IncrCounter inline_cfunc_optimized_send_count
+ CheckInterrupts
+ Return v24
+ ");
+ }
+
+ #[test]
+ fn test_do_not_optimize_array_push_multi_arg() {
+ eval("
+ def test(arr)
+ arr.push(1,2,3)
+ end
+ test([])
+ ");
+ assert_snapshot!(hir_string("test"), @r"
+ fn test@:3:
+ bb0():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:BasicObject = GetLocal l0, SP@4
+ Jump bb2(v1, v2)
+ bb1(v5:BasicObject, v6:BasicObject):
+ EntryPoint JIT(0)
+ Jump bb2(v5, v6)
+ bb2(v8:BasicObject, v9:BasicObject):
+ v13:Fixnum[1] = Const Value(1)
+ v14:Fixnum[2] = Const Value(2)
+ v15:Fixnum[3] = Const Value(3)
+ PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010)
+ PatchPoint NoSingletonClass(Array@0x1000)
+ v26:ArrayExact = GuardType v9, ArrayExact
+ v27:BasicObject = CCallVariadic push@0x1038, v26, v13, v14, v15
CheckInterrupts
Return v27
");
diff --git a/zjit/src/virtualmem.rs b/zjit/src/virtualmem.rs
index 5af5c0e8b999a4..2717dfcf225f23 100644
--- a/zjit/src/virtualmem.rs
+++ b/zjit/src/virtualmem.rs
@@ -86,7 +86,7 @@ impl CodePtr {
/// Get the address of the code pointer.
pub fn raw_addr(self, base: &impl CodePtrBase) -> usize {
- self.raw_ptr(base) as usize
+ self.raw_ptr(base).addr()
}
/// Get the offset component for the code pointer. Useful finding the distance between two