diff --git a/doc/string.rb b/doc/string.rb
index 9ed97d49f6fb95..c75d171876a1ea 100644
--- a/doc/string.rb
+++ b/doc/string.rb
@@ -280,7 +280,8 @@
# If the argument +capture+ is provided and not 0,
# 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"
diff --git a/doc/string/inspect.rdoc b/doc/string/inspect.rdoc
new file mode 100644
index 00000000000000..828ecf966dd26f
--- /dev/null
+++ b/doc/string/inspect.rdoc
@@ -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 ('"') and backslash and ('\\') are escaped:
+
+ '"'.inspect # => "\"\\\"\""
+ '\\'.inspect # => "\"\\\\\""
+
+Unprintable characters are the {ASCII characters}[https://en.wikipedia.org/wiki/ASCII]
+whose values are in range 0..31,
+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].
diff --git a/doc/string/intern.rdoc b/doc/string/intern.rdoc
new file mode 100644
index 00000000000000..1336e4688f7a2e
--- /dev/null
+++ b/doc/string/intern.rdoc
@@ -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].
+
diff --git a/doc/string/ljust.rdoc b/doc/string/ljust.rdoc
index 8e23c1fc8fef16..f37c0b3151176d 100644
--- a/doc/string/ljust.rdoc
+++ b/doc/string/ljust.rdoc
@@ -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 width <= self.length, 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].
diff --git a/pathname_builtin.rb b/pathname_builtin.rb
index 081b82ba9a6a51..486e49d0920212 100644
--- a/pathname_builtin.rb
+++ b/pathname_builtin.rb
@@ -311,7 +311,9 @@ 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}"
@@ -319,6 +321,7 @@ def sub_ext(repl)
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}/
@@ -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)
diff --git a/string.c b/string.c
index e022831ba5c8d5..01cfc9c17563cc 100644
--- a/string.c
+++ b/string.c
@@ -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
*
*/
@@ -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
diff --git a/symbol.c b/symbol.c
index ddb0f1556ba019..e8eacd34c2b4b8 100644
--- a/symbol.c
+++ b/symbol.c
@@ -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 str, 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
- * :xxx notation.
- *
- * 'cat and dog'.to_sym #=> :"cat and dog"
*/
VALUE
diff --git a/variable.c b/variable.c
index e38af116ec679f..b5620af27b8657 100644
--- a/variable.c
+++ b/variable.c
@@ -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);
diff --git a/zjit.rb b/zjit.rb
index e07bccb132f1d0..b20c110046fd59 100644
--- a/zjit.rb
+++ b/zjit.rb
@@ -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|
@@ -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
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index ed0c52a91169a0..51c54846ba8cf5 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -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();
@@ -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) => {
@@ -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(()); }
};
}
@@ -344,6 +348,7 @@ 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)),
@@ -351,12 +356,12 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
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)),
@@ -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)),
@@ -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);
}
};
@@ -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".
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 15836b8c447444..5cbedece682f70 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -431,6 +431,7 @@ pub enum SideExitReason {
UnknownNewarraySend(vm_opt_newarray_send_type),
UnknownCallType,
UnknownOpcode(u32),
+ UnhandledInstruction(InsnId),
FixnumAddOverflow,
FixnumSubOverflow,
FixnumMultOverflow,
@@ -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 },
@@ -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"),
@@ -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) },
@@ -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 } =>
@@ -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);
}
@@ -3261,7 +3263,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
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
}