Skip to content

Commit 2f8723d

Browse files
committed
ZJIT: Implement concatstrings
Quite tricky to use the native stack to pass a C `VALUE[]`.
1 parent 09d4555 commit 2f8723d

3 files changed

Lines changed: 81 additions & 7 deletions

File tree

test/ruby/test_zjit.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ def test = "#{""}"
3838
}, insns: [:putstring]
3939
end
4040

41+
def test_concatstrings
42+
assert_compiles '"Object is not Kernel"', %q{
43+
def test = "#{Object} is not #{Kernel}"
44+
test
45+
}, insns: [:concatstrings]
46+
end
47+
4148
def test_putchilldedstring
4249
assert_compiles '""', %q{
4350
def test = ""

zjit/src/codegen.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
299299
Insn::SideExit { state, reason: _ } => return gen_side_exit(jit, asm, &function.frame_state(*state)),
300300
Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type),
301301
Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state))?,
302+
Insn::ConcatStrings { strings, state } => gen_concatstrings(jit, asm, strings, &function.frame_state(*state))?,
302303
Insn::Defined { op_type, obj, pushval, v } => gen_defined(jit, asm, *op_type, *obj, *pushval, opnd!(v))?,
303304
_ => {
304305
debug!("ZJIT: gen_function: unexpected insn {insn}");
@@ -314,6 +315,48 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
314315
Some(())
315316
}
316317

318+
fn gen_concatstrings(jit: &JITState, asm: &mut Assembler, strings: &[InsnId], state: &FrameState) -> Option<Opnd> {
319+
// In practice, concatstrings with 0 strings doesn't seem to happen.
320+
if strings.len() == 0 {
321+
return None;
322+
}
323+
324+
// Find how much to grow the stack, aligning to 16 for calling convention
325+
let mut stack_growth = strings.len() * SIZEOF_VALUE;
326+
if stack_growth % 16 != 0 {
327+
stack_growth += 16 - stack_growth % 16;
328+
}
329+
330+
// Grow the stack and put the strings onto the stack
331+
for (i, string) in strings.iter().enumerate() {
332+
let dest = Opnd::mem(64, NATIVE_STACK_PTR, i32::try_from(strings.len() - i).ok()? * -SIZEOF_VALUE_I32);
333+
asm.mov(dest, jit.get_opnd(*string)?);
334+
}
335+
// Write, then move the SP, since the source might be from the stack
336+
let new_sp = asm.sub(NATIVE_STACK_PTR, stack_growth.into());
337+
asm.mov(NATIVE_STACK_PTR, new_sp);
338+
339+
// Save PC since the call allocates and can raise
340+
gen_save_pc(asm, state);
341+
342+
// We've moved the SP, so the array is at SP or one element above
343+
// if we grew the stack with extra space for alignment.
344+
let str_array = if strings.len() % 2 == 0 {
345+
NATIVE_STACK_PTR
346+
} else {
347+
asm.add(NATIVE_STACK_PTR, SIZEOF_VALUE.into())
348+
};
349+
350+
// Call rb_str_concat_literals()
351+
let new_string = asm.ccall(rb_str_concat_literals as _, vec![strings.len().into(), str_array]);
352+
353+
// Undo C SP change
354+
let prev_sp = asm.add(NATIVE_STACK_PTR, stack_growth.into());
355+
asm.mov(NATIVE_STACK_PTR, prev_sp);
356+
357+
Some(new_string)
358+
}
359+
317360
/// Gets the EP of the ISeq of the containing method, or "local level".
318361
/// Equivalent of GET_LEP() macro.
319362
fn gen_get_lep(jit: &JITState, asm: &mut Assembler) -> Opnd {

zjit/src/hir.rs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ pub enum Insn {
529529
// Distinct from `SendWithoutBlock` with `mid:to_s` because does not have a patch point for String to_s being redefined
530530
ObjToString { val: InsnId, call_info: CallInfo, cd: *const rb_call_data, state: InsnId },
531531
AnyToString { val: InsnId, str: InsnId, state: InsnId },
532+
ConcatStrings { strings: Vec<InsnId>, state: InsnId },
532533

533534
/// Side-exit if val doesn't have the expected type.
534535
GuardType { val: InsnId, guard_type: Type, state: InsnId },
@@ -705,7 +706,14 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
705706
write!(f, ", {arg}")?;
706707
}
707708
Ok(())
708-
},
709+
}
710+
Insn::ConcatStrings { strings, state: _ } => {
711+
write!(f, "ConcatStrings {}", strings[0])?;
712+
for string in strings.iter().skip(1) {
713+
write!(f, ", {string}")?;
714+
}
715+
Ok(())
716+
}
709717
Insn::Snapshot { state } => write!(f, "Snapshot {}", state),
710718
Insn::Defined { op_type, v, .. } => {
711719
// op_type (enum defined_type) printing logic from iseq.c.
@@ -1088,6 +1096,7 @@ impl Function {
10881096
str: find!(*str),
10891097
state: *state,
10901098
},
1099+
ConcatStrings { state, strings } => ConcatStrings { state: *state, strings: find_vec!(strings) },
10911100
SendWithoutBlock { self_val, call_info, cd, args, state } => SendWithoutBlock {
10921101
self_val: find!(*self_val),
10931102
call_info: call_info.clone(),
@@ -1219,6 +1228,7 @@ impl Function {
12191228
Insn::ToArray { .. } => types::ArrayExact,
12201229
Insn::ObjToString { .. } => types::BasicObject,
12211230
Insn::AnyToString { .. } => types::String,
1231+
Insn::ConcatStrings { .. } => types::StringExact,
12221232
Insn::GetLocal { .. } => types::BasicObject,
12231233
}
12241234
}
@@ -1778,7 +1788,8 @@ impl Function {
17781788
worklist.push_back(state);
17791789
}
17801790
Insn::ArrayMax { elements, state }
1781-
| Insn::NewArray { elements, state } => {
1791+
| Insn::NewArray { elements, state }
1792+
| Insn::ConcatStrings { strings: elements, state } => {
17821793
worklist.extend(elements);
17831794
worklist.push_back(state);
17841795
}
@@ -2939,6 +2950,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
29392950
let anytostring = fun.push_insn(block, Insn::AnyToString { val, str, state: exit_id });
29402951
state.stack_push(anytostring);
29412952
}
2953+
YARVINSN_concatstrings => {
2954+
let num = get_arg(pc, 0).as_u32();
2955+
let mut strings = Vec::with_capacity(num.as_usize());
2956+
for _ in 0..num {
2957+
strings.push(state.stack_pop()?);
2958+
}
2959+
strings.reverse();
2960+
let snapshot = fun.push_insn(block, Insn::Snapshot { state: exit_state });
2961+
state.stack_push(fun.push_insn(block, Insn::ConcatStrings { state: snapshot, strings }));
2962+
}
29422963
_ => {
29432964
// Unknown opcode; side-exit into the interpreter
29442965
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
@@ -4664,7 +4685,8 @@ mod tests {
46644685
v3:Fixnum[1] = Const Value(1)
46654686
v5:BasicObject = ObjToString v3
46664687
v7:String = AnyToString v3, str: v5
4667-
SideExit UnknownOpcode(concatstrings)
4688+
v9:StringExact = ConcatStrings v2, v7
4689+
Return v9
46684690
"#]]);
46694691
}
46704692

@@ -6305,7 +6327,8 @@ mod opt_tests {
63056327
v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
63066328
v3:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
63076329
v4:StringExact = StringCopy v3
6308-
SideExit UnknownOpcode(concatstrings)
6330+
v10:StringExact = ConcatStrings v2, v4
6331+
Return v10
63096332
"#]]);
63106333
}
63116334

@@ -6319,9 +6342,10 @@ mod opt_tests {
63196342
bb0(v0:BasicObject):
63206343
v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
63216344
v3:Fixnum[1] = Const Value(1)
6322-
v10:BasicObject = SendWithoutBlock v3, :to_s
6323-
v7:String = AnyToString v3, str: v10
6324-
SideExit UnknownOpcode(concatstrings)
6345+
v11:BasicObject = SendWithoutBlock v3, :to_s
6346+
v7:String = AnyToString v3, str: v11
6347+
v9:StringExact = ConcatStrings v2, v7
6348+
Return v9
63256349
"#]]);
63266350
}
63276351

0 commit comments

Comments
 (0)