Skip to content
Merged
3 changes: 2 additions & 1 deletion doc/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@
# If the argument +capture+ is provided and not <tt>0</tt>,
# it should be either a capture group index (integer)
# or a capture group name (String or Symbol);
# the slice is the specified capture (see Regexp@Groups and Captures):
# the slice is the specified capture
# (see {Groups and Captures}[rdoc-ref:Regexp@Groups+and+Captures]):
#
# s = 'hello there'
# s[/[aeiou](.)\1/, 1] # => "l"
Expand Down
39 changes: 39 additions & 0 deletions doc/string/inspect.rdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Returns a printable version of +self+, enclosed in double-quotes.

Most printable characters are rendered simply as themselves:

'abc'.inspect # => "\"abc\""
'012'.inspect # => "\"012\""
''.inspect # => "\"\""
"\u000012".inspect # => "\"\\u000012\""
'тест'.inspect # => "\"тест\""
'こんにちは'.inspect # => "\"こんにちは\""

But printable characters double-quote (<tt>'"'</tt>) and backslash and (<tt>'\\'</tt>) are escaped:

'"'.inspect # => "\"\\\"\""
'\\'.inspect # => "\"\\\\\""

Unprintable characters are the {ASCII characters}[https://en.wikipedia.org/wiki/ASCII]
whose values are in range <tt>0..31</tt>,
along with the character whose value is +127+.

Most of these characters are rendered thus:

0.chr.inspect # => "\"\\x00\""
1.chr.inspect # => "\"\\x01\""
2.chr.inspect # => "\"\\x02\""
# ...

A few, however, have special renderings:

7.chr.inspect # => "\"\\a\"" # BEL
8.chr.inspect # => "\"\\b\"" # BS
9.chr.inspect # => "\"\\t\"" # TAB
10.chr.inspect # => "\"\\n\"" # LF
11.chr.inspect # => "\"\\v\"" # VT
12.chr.inspect # => "\"\\f\"" # FF
13.chr.inspect # => "\"\\r\"" # CR
27.chr.inspect # => "\"\\e\"" # ESC

Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString].
9 changes: 9 additions & 0 deletions doc/string/intern.rdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Returns the Symbol object derived from +self+,
creating it if it did not already exist:

'foo'.intern # => :foo
'тест'.intern # => :тест
'こんにちは'.intern # => :こんにちは

Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString].

14 changes: 6 additions & 8 deletions doc/string/ljust.rdoc
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
Returns a left-justified copy of +self+.

If integer argument +size+ is greater than the size (in characters) of +self+,
returns a new string of length +size+ that is a copy of +self+,
left justified and padded on the right with +pad_string+:
Returns a copy of +self+, left-justified and, if necessary, right-padded with the +pad_string+:

'hello'.ljust(10) # => "hello "
' hello'.ljust(10) # => " hello "
'hello'.ljust(10, 'ab') # => "helloababa"
'тест'.ljust(10) # => "тест "
'こんにちは'.ljust(10) # => "こんにちは "
'こんにちは'.ljust(10) # => "こんにちは "

If +size+ is not greater than the size of +self+, returns a copy of +self+:
If <tt>width <= self.length</tt>, returns a copy of +self+:

'hello'.ljust(5) # => "hello"
'hello'.ljust(1) # => "hello"
'hello'.ljust(1) # => "hello" # Does not truncate to width.

Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String].
7 changes: 4 additions & 3 deletions pathname_builtin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,17 @@ def sub_ext(repl)
end

if File::ALT_SEPARATOR
# Separator list string.
SEPARATOR_LIST = "#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}"
# Regexp that matches a separator.
SEPARATOR_PAT = /[#{SEPARATOR_LIST}]/
else
SEPARATOR_LIST = "#{Regexp.quote File::SEPARATOR}"
SEPARATOR_PAT = /#{Regexp.quote File::SEPARATOR}/
end

if File.dirname('A:') == 'A:.' # DOSish drive letter
# Regexp that matches an absoltute path.
ABSOLUTE_PATH = /\A(?:[A-Za-z]:|#{SEPARATOR_PAT})/
else
ABSOLUTE_PATH = /\A#{SEPARATOR_PAT}/
Expand Down Expand Up @@ -1163,9 +1166,7 @@ class Pathname
end

module Kernel
# create a pathname object.
#
# This method is available since 1.8.5.
# Creates a Pathname object.
def Pathname(path) # :doc:
return path if Pathname === path
Pathname.new(path)
Expand Down
11 changes: 2 additions & 9 deletions string.c
Original file line number Diff line number Diff line change
Expand Up @@ -7303,12 +7303,7 @@ rb_str_escape(VALUE str)
* call-seq:
* inspect -> string
*
* Returns a printable version of +self+, enclosed in double-quotes,
* and with special characters escaped:
*
* s = "foo\tbar\tbaz\n"
* s.inspect
* # => "\"foo\\tbar\\tbaz\\n\""
* :include: doc/string/inspect.rdoc
*
*/

Expand Down Expand Up @@ -11075,12 +11070,10 @@ rb_str_justify(int argc, VALUE *argv, VALUE str, char jflag)

/*
* call-seq:
* ljust(size, pad_string = ' ') -> new_string
* ljust(width, pad_string = ' ') -> new_string
*
* :include: doc/string/ljust.rdoc
*
* Related: String#rjust, String#center.
*
*/

static VALUE
Expand Down
16 changes: 2 additions & 14 deletions symbol.c
Original file line number Diff line number Diff line change
Expand Up @@ -927,22 +927,10 @@ rb_gc_free_dsymbol(VALUE sym)

/*
* call-seq:
* str.intern -> symbol
* str.to_sym -> symbol
* intern -> symbol
*
* Returns the +Symbol+ corresponding to <i>str</i>, creating the
* symbol if it did not previously exist. See Symbol#id2name.
* :include: doc/string/intern.rdoc
*
* "Koala".intern #=> :Koala
* s = 'cat'.to_sym #=> :cat
* s == :cat #=> true
* s = '@cat'.to_sym #=> :@cat
* s == :@cat #=> true
*
* This can also be used to create symbols that cannot be represented using the
* <code>:xxx</code> notation.
*
* 'cat and dog'.to_sym #=> :"cat and dog"
*/

VALUE
Expand Down
4 changes: 2 additions & 2 deletions variable.c
Original file line number Diff line number Diff line change
Expand Up @@ -4490,13 +4490,13 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc
fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_fields_new(klass, 1);

shape_id_t current_shape_id = RBASIC_SHAPE_ID(fields_obj);

shape_id_t next_shape_id = current_shape_id; // for too_complex
if (UNLIKELY(rb_shape_too_complex_p(current_shape_id))) {
goto too_complex;
}

bool new_ivar;
shape_id_t next_shape_id = generic_shape_ivar(fields_obj, id, &new_ivar);
next_shape_id = generic_shape_ivar(fields_obj, id, &new_ivar);

if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) {
fields_obj = imemo_fields_complex_from_obj(klass, fields_obj, next_shape_id);
Expand Down
16 changes: 8 additions & 8 deletions zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ def stats_string
buf
end

# Assert that any future ZJIT compilation will return a function pointer
def assert_compiles # :nodoc:
Primitive.rb_zjit_assert_compiles
end

# :stopdoc:
private

def print_counters(keys, buf:, stats:)
left_pad = keys.map(&:size).max + 1
keys.each do |key|
Expand Down Expand Up @@ -90,14 +98,6 @@ def print_counters_with_prefix(buf:, stats:, prefix:, prompt:)
end
end

# Assert that any future ZJIT compilation will return a function pointer
def assert_compiles # :nodoc:
Primitive.rb_zjit_assert_compiles
end

# :stopdoc:
private

def number_with_delimiter(number)
s = number.to_s
i = s.index('.') || s.size
Expand Down
51 changes: 27 additions & 24 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,11 +281,15 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio
// Compile all instructions
for &insn_id in block.insns() {
let insn = function.find(insn_id);
if gen_insn(cb, &mut jit, &mut asm, function, insn_id, &insn).is_none() {
debug!("Failed to compile insn: {insn_id} {insn}");
if let Err(last_snapshot) = gen_insn(cb, &mut jit, &mut asm, function, insn_id, &insn) {
debug!("ZJIT: gen_function: Failed to compile insn: {insn_id} {insn}. Generating side-exit.");
incr_counter!(failed_gen_insn);
return None;
}
gen_side_exit(&mut jit, &mut asm, &SideExitReason::UnhandledInstruction(insn_id), &function.frame_state(last_snapshot));
// Don't bother generating code after a side-exit. We won't run it.
// TODO(max): Generate ud2 or equivalent.
break;
};
// It's fine; we generated the instruction
}
// Make sure the last patch point has enough space to insert a jump
asm.pad_patch_point();
Expand Down Expand Up @@ -316,7 +320,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio
}

/// Compile an instruction
fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, function: &Function, insn_id: InsnId, insn: &Insn) -> Option<()> {
fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, function: &Function, insn_id: InsnId, insn: &Insn) -> Result<(), InsnId> {
// Convert InsnId to lir::Opnd
macro_rules! opnd {
($insn_id:ident) => {
Expand All @@ -334,7 +338,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio

macro_rules! no_output {
($call:expr) => {
{ let () = $call; return Some(()); }
{ let () = $call; return Ok(()); }
};
}

Expand All @@ -344,19 +348,20 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio

let out_opnd = match insn {
Insn::Const { val: Const::Value(val) } => gen_const(*val),
Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"),
Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)),
Insn::NewHash { elements, state } => gen_new_hash(jit, asm, elements, &function.frame_state(*state)),
Insn::NewRange { low, high, flag, state } => gen_new_range(jit, asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)),
Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)),
Insn::StringCopy { val, chilled, state } => gen_string_copy(asm, opnd!(val), *chilled, &function.frame_state(*state)),
// concatstrings shouldn't have 0 strings
// If it happens we abort the compilation for now
Insn::StringConcat { strings, .. } if strings.is_empty() => return None,
Insn::StringConcat { strings, state, .. } if strings.is_empty() => return Err(*state),
Insn::StringConcat { strings, state } => gen_string_concat(jit, asm, opnds!(strings), &function.frame_state(*state)),
Insn::StringIntern { val, state } => gen_intern(asm, opnd!(val), &function.frame_state(*state)),
Insn::ToRegexp { opt, values, state } => gen_toregexp(jit, asm, *opt, opnds!(values), &function.frame_state(*state)),
Insn::Param { idx } => unreachable!("block.insns should not have Insn::Param({idx})"),
Insn::Snapshot { .. } => return Some(()), // we don't need to do anything for this instruction at the moment
Insn::Snapshot { .. } => return Ok(()), // we don't need to do anything for this instruction at the moment
Insn::Jump(branch) => no_output!(gen_jump(jit, asm, branch)),
Insn::IfTrue { val, target } => no_output!(gen_if_true(jit, asm, opnd!(val), target)),
Insn::IfFalse { val, target } => no_output!(gen_if_false(jit, asm, opnd!(val), target)),
Expand All @@ -367,7 +372,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::SendWithoutBlockDirect { cme, iseq, self_val, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(self_val), opnds!(args), &function.frame_state(*state)),
// Ensure we have enough room fit ec, self, and arguments
// TODO remove this check when we have stack args (we can use Time.new to test it)
Insn::InvokeBuiltin { bf, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return None,
Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state),
Insn::InvokeBuiltin { bf, args, state, .. } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, opnds!(args)),
Insn::Return { val } => no_output!(gen_return(asm, opnd!(val))),
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)),
Expand Down Expand Up @@ -403,22 +408,20 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::IncrCounter(counter) => no_output!(gen_incr_counter(asm, counter)),
Insn::ObjToString { val, cd, state, .. } => gen_objtostring(jit, asm, opnd!(val), *cd, &function.frame_state(*state)),
&Insn::CheckInterrupts { state } => no_output!(gen_check_interrupts(jit, asm, &function.frame_state(state))),
Insn::ArrayExtend { .. }
| Insn::ArrayMax { .. }
| Insn::ArrayPush { .. }
| Insn::DefinedIvar { .. }
| Insn::FixnumDiv { .. }
| Insn::FixnumMod { .. }
| Insn::HashDup { .. }
| Insn::Send { .. }
| Insn::Throw { .. }
| Insn::ToArray { .. }
| Insn::ToNewArray { .. }
| Insn::Const { .. }
&Insn::ArrayExtend { state, .. }
| &Insn::ArrayMax { state, .. }
| &Insn::ArrayPush { state, .. }
| &Insn::DefinedIvar { state, .. }
| &Insn::FixnumDiv { state, .. }
| &Insn::FixnumMod { state, .. }
| &Insn::HashDup { state, .. }
| &Insn::Send { state, .. }
| &Insn::Throw { state, .. }
| &Insn::ToArray { state, .. }
| &Insn::ToNewArray { state, .. }
=> {
debug!("ZJIT: gen_function: unexpected insn {insn}");
incr_counter!(failed_gen_insn_unexpected);
return None;
return Err(state);
}
};

Expand All @@ -427,7 +430,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
// If the instruction has an output, remember it in jit.opnds
jit.opnds[insn_id.0] = Some(out_opnd);

Some(())
Ok(())
}

/// Gets the EP of the ISeq of the containing method, or "local level".
Expand Down
15 changes: 9 additions & 6 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ pub enum SideExitReason {
UnknownNewarraySend(vm_opt_newarray_send_type),
UnknownCallType,
UnknownOpcode(u32),
UnhandledInstruction(InsnId),
FixnumAddOverflow,
FixnumSubOverflow,
FixnumMultOverflow,
Expand Down Expand Up @@ -567,7 +568,7 @@ pub enum Insn {
/// Control flow instructions
Return { val: InsnId },
/// Non-local control flow. See the throw YARV instruction
Throw { throw_state: u32, val: InsnId },
Throw { throw_state: u32, val: InsnId, state: InsnId },

/// Fixnum +, -, *, /, %, ==, !=, <, <=, >, >=, &, |
FixnumAdd { left: InsnId, right: InsnId, state: InsnId },
Expand Down Expand Up @@ -854,7 +855,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::AnyToString { val, str, .. } => { write!(f, "AnyToString {val}, str: {str}") },
Insn::SideExit { reason, .. } => write!(f, "SideExit {reason}"),
Insn::PutSpecialObject { value_type } => write!(f, "PutSpecialObject {value_type}"),
Insn::Throw { throw_state, val } => {
Insn::Throw { throw_state, val, .. } => {
write!(f, "Throw ")?;
match throw_state & VM_THROW_STATE_MASK {
RUBY_TAG_NONE => write!(f, "TAG_NONE"),
Expand Down Expand Up @@ -1218,7 +1219,7 @@ impl Function {
}
},
&Return { val } => Return { val: find!(val) },
&Throw { throw_state, val } => Throw { throw_state, val: find!(val) },
&Throw { throw_state, val, state } => Throw { throw_state, val: find!(val), state },
&StringCopy { val, chilled, state } => StringCopy { val: find!(val), chilled, state },
&StringIntern { val, state } => StringIntern { val: find!(val), state: find!(state) },
&StringConcat { ref strings, state } => StringConcat { strings: find_vec!(strings), state: find!(state) },
Expand Down Expand Up @@ -1992,7 +1993,6 @@ impl Function {
worklist.push_back(state);
}
| &Insn::Return { val }
| &Insn::Throw { val, .. }
| &Insn::Test { val }
| &Insn::SetLocal { val, .. }
| &Insn::IsNil { val } =>
Expand Down Expand Up @@ -2040,7 +2040,9 @@ impl Function {
worklist.push_back(val);
worklist.extend(args);
}
&Insn::ArrayDup { val, state } | &Insn::HashDup { val, state } => {
&Insn::ArrayDup { val, state }
| &Insn::Throw { val, state, .. }
| &Insn::HashDup { val, state } => {
worklist.push_back(val);
worklist.push_back(state);
}
Expand Down Expand Up @@ -3261,7 +3263,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
break; // Don't enqueue the next block as a successor
}
YARVINSN_throw => {
fun.push_insn(block, Insn::Throw { throw_state: get_arg(pc, 0).as_u32(), val: state.stack_pop()? });
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
fun.push_insn(block, Insn::Throw { throw_state: get_arg(pc, 0).as_u32(), val: state.stack_pop()?, state: exit_id });
break; // Don't enqueue the next block as a successor
}

Expand Down