Skip to content

Commit b1ce569

Browse files
ZJIT: anytostring to HIR (rubyGH-13658)
Pop two values from the stack, return the first if it is a string, otherwise return string coercion of the second Also piggybacks a fix for string subclasses skipping `to_s` for `objtostring`. Co-authored-by: composerinteralia <[email protected]>
1 parent af6b98f commit b1ce569

File tree

2 files changed

+47
-5
lines changed

2 files changed

+47
-5
lines changed

zjit/src/codegen.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
283283
Insn::SetIvar { self_val, id, val, state: _ } => return gen_setivar(asm, opnd!(self_val), *id, opnd!(val)),
284284
Insn::SideExit { state } => return gen_side_exit(jit, asm, &function.frame_state(*state)),
285285
Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type),
286+
Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state))?,
286287
_ => {
287288
debug!("ZJIT: gen_function: unexpected insn {:?}", insn);
288289
return None;
@@ -814,6 +815,17 @@ fn gen_fixnum_ge(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> Opti
814815
Some(asm.csel_ge(Qtrue.into(), Qfalse.into()))
815816
}
816817

818+
fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, str: lir::Opnd, state: &FrameState) -> Option<lir::Opnd> {
819+
// Save PC
820+
gen_save_pc(asm, state);
821+
822+
asm_comment!(asm, "call rb_obj_as_string_result");
823+
Some(asm.ccall(
824+
rb_obj_as_string_result as *const u8,
825+
vec![str, val],
826+
))
827+
}
828+
817829
/// Evaluate if a value is truthy
818830
/// Produces a CBool type (0 or 1)
819831
/// In Ruby, only nil and false are falsy

zjit/src/hir.rs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ pub enum Insn {
498498

499499
// Distinct from `SendWithoutBlock` with `mid:to_s` because does not have a patch point for String to_s being redefined
500500
ObjToString { val: InsnId, call_info: CallInfo, cd: *const rb_call_data, state: InsnId },
501+
AnyToString { val: InsnId, str: InsnId, state: InsnId },
501502

502503
/// Side-exit if val doesn't have the expected type.
503504
GuardType { val: InsnId, guard_type: Type, state: InsnId },
@@ -699,6 +700,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
699700
Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"),
700701
Insn::ArrayPush { array, val, .. } => write!(f, "ArrayPush {array}, {val}"),
701702
Insn::ObjToString { val, .. } => { write!(f, "ObjToString {val}") },
703+
Insn::AnyToString { val, str, .. } => { write!(f, "AnyToString {val}, str: {str}") },
702704
Insn::SideExit { .. } => write!(f, "SideExit"),
703705
Insn::PutSpecialObject { value_type } => {
704706
write!(f, "PutSpecialObject {}", value_type)
@@ -1023,6 +1025,11 @@ impl Function {
10231025
cd: *cd,
10241026
state: *state,
10251027
},
1028+
AnyToString { val, str, state } => AnyToString {
1029+
val: find!(*val),
1030+
str: find!(*str),
1031+
state: *state,
1032+
},
10261033
SendWithoutBlock { self_val, call_info, cd, args, state } => SendWithoutBlock {
10271034
self_val: find!(*self_val),
10281035
call_info: call_info.clone(),
@@ -1154,6 +1161,7 @@ impl Function {
11541161
Insn::ToNewArray { .. } => types::ArrayExact,
11551162
Insn::ToArray { .. } => types::ArrayExact,
11561163
Insn::ObjToString { .. } => types::BasicObject,
1164+
Insn::AnyToString { .. } => types::String,
11571165
}
11581166
}
11591167

@@ -1398,14 +1406,21 @@ impl Function {
13981406
self.make_equal_to(insn_id, replacement);
13991407
}
14001408
Insn::ObjToString { val, call_info, cd, state, .. } => {
1401-
if self.is_a(val, types::StringExact) {
1409+
if self.is_a(val, types::String) {
14021410
// behaves differently from `SendWithoutBlock` with `mid:to_s` because ObjToString should not have a patch point for String to_s being redefined
14031411
self.make_equal_to(insn_id, val);
14041412
} else {
14051413
let replacement = self.push_insn(block, Insn::SendWithoutBlock { self_val: val, call_info, cd, args: vec![], state });
14061414
self.make_equal_to(insn_id, replacement)
14071415
}
14081416
}
1417+
Insn::AnyToString { str, .. } => {
1418+
if self.is_a(str, types::String) {
1419+
self.make_equal_to(insn_id, str);
1420+
} else {
1421+
self.push_insn_id(block, insn_id);
1422+
}
1423+
}
14091424
_ => { self.push_insn_id(block, insn_id); }
14101425
}
14111426
}
@@ -1782,6 +1797,11 @@ impl Function {
17821797
worklist.push_back(val);
17831798
worklist.push_back(state);
17841799
}
1800+
Insn::AnyToString { val, str, state, .. } => {
1801+
worklist.push_back(val);
1802+
worklist.push_back(str);
1803+
worklist.push_back(state);
1804+
}
17851805
Insn::GetGlobal { state, .. } |
17861806
Insn::SideExit { state } => worklist.push_back(state),
17871807
}
@@ -2783,6 +2803,14 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
27832803
let objtostring = fun.push_insn(block, Insn::ObjToString { val: recv, call_info: CallInfo { method_name }, cd, state: exit_id });
27842804
state.stack_push(objtostring)
27852805
}
2806+
YARVINSN_anytostring => {
2807+
let str = state.stack_pop()?;
2808+
let val = state.stack_pop()?;
2809+
2810+
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
2811+
let anytostring = fun.push_insn(block, Insn::AnyToString { val, str, state: exit_id });
2812+
state.stack_push(anytostring);
2813+
}
27862814
_ => {
27872815
// Unknown opcode; side-exit into the interpreter
27882816
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
@@ -4452,7 +4480,7 @@ mod tests {
44524480
}
44534481

44544482
#[test]
4455-
fn test_objtostring() {
4483+
fn test_objtostring_anytostring() {
44564484
eval("
44574485
def test = \"#{1}\"
44584486
");
@@ -4462,6 +4490,7 @@ mod tests {
44624490
v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
44634491
v3:Fixnum[1] = Const Value(1)
44644492
v5:BasicObject = ObjToString v3
4493+
v7:String = AnyToString v3, str: v5
44654494
SideExit
44664495
"#]]);
44674496
}
@@ -6010,7 +6039,7 @@ mod opt_tests {
60106039
}
60116040

60126041
#[test]
6013-
fn test_objtostring_string() {
6042+
fn test_objtostring_anytostring_string() {
60146043
eval(r##"
60156044
def test = "#{('foo')}"
60166045
"##);
@@ -6025,7 +6054,7 @@ mod opt_tests {
60256054
}
60266055

60276056
#[test]
6028-
fn test_objtostring_with_non_string() {
6057+
fn test_objtostring_anytostring_with_non_string() {
60296058
eval(r##"
60306059
def test = "#{1}"
60316060
"##);
@@ -6034,7 +6063,8 @@ mod opt_tests {
60346063
bb0(v0:BasicObject):
60356064
v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
60366065
v3:Fixnum[1] = Const Value(1)
6037-
v8:BasicObject = SendWithoutBlock v3, :to_s
6066+
v10:BasicObject = SendWithoutBlock v3, :to_s
6067+
v7:String = AnyToString v3, str: v10
60386068
SideExit
60396069
"#]]);
60406070
}

0 commit comments

Comments
 (0)