Skip to content

Commit 9b7432c

Browse files
committed
ZJIT: Implement concatstrings
Quite tricky to use the native stack to pass a C `VALUE[]`.
1 parent f19a670 commit 9b7432c

File tree

3 files changed

+76
-7
lines changed

3 files changed

+76
-7
lines changed

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: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
315315
Insn::SideExit { state, reason } => return gen_side_exit(jit, asm, reason, &function.frame_state(*state)),
316316
Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type),
317317
Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state))?,
318+
Insn::ConcatStrings { strings, state } => gen_concatstrings(jit, asm, strings, &function.frame_state(*state))?,
318319
Insn::Defined { op_type, obj, pushval, v } => gen_defined(jit, asm, *op_type, *obj, *pushval, opnd!(v))?,
319320
_ => {
320321
debug!("ZJIT: gen_function: unexpected insn {insn}");
@@ -330,6 +331,43 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
330331
Some(())
331332
}
332333

334+
fn gen_concatstrings(jit: &JITState, asm: &mut Assembler, strings: &[InsnId], state: &FrameState) -> Option<Opnd> {
335+
// In practice, concatstrings with 0 strings doesn't seem to happen.
336+
if strings.len() == 0 {
337+
return None;
338+
}
339+
340+
// Put the strings onto the stack
341+
for (i, string) in strings.iter().enumerate() {
342+
let dest = Opnd::mem(64, NATIVE_STACK_PTR, i32::try_from(strings.len() - i).ok()? * -SIZEOF_VALUE_I32);
343+
asm.mov(dest, jit.get_opnd(*string)?);
344+
}
345+
// Write, then move the SP, since the source might be from the stack
346+
let stack_growth = aligned_stack_bytes(strings.len());
347+
let new_sp = asm.sub(NATIVE_STACK_PTR, stack_growth.into());
348+
asm.mov(NATIVE_STACK_PTR, new_sp);
349+
350+
// Save PC since the call allocates and can raise
351+
gen_save_pc(asm, state);
352+
353+
// We've moved the SP, so the array is at SP or one element above
354+
// if we grew the stack with extra space for alignment.
355+
let str_array = if strings.len() % 2 == 0 {
356+
NATIVE_STACK_PTR
357+
} else {
358+
asm.add(NATIVE_STACK_PTR, SIZEOF_VALUE.into())
359+
};
360+
361+
// Call rb_str_concat_literals()
362+
let new_string = asm.ccall(rb_str_concat_literals as _, vec![strings.len().into(), str_array]);
363+
364+
// Undo C SP change
365+
let prev_sp = asm.add(NATIVE_STACK_PTR, stack_growth.into());
366+
asm.mov(NATIVE_STACK_PTR, prev_sp);
367+
368+
Some(new_string)
369+
}
370+
333371
/// Gets the EP of the ISeq of the containing method, or "local level".
334372
/// Equivalent of GET_LEP() macro.
335373
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
@@ -550,6 +550,7 @@ pub enum Insn {
550550
// Distinct from `SendWithoutBlock` with `mid:to_s` because does not have a patch point for String to_s being redefined
551551
ObjToString { val: InsnId, call_info: CallInfo, cd: *const rb_call_data, state: InsnId },
552552
AnyToString { val: InsnId, str: InsnId, state: InsnId },
553+
ConcatStrings { strings: Vec<InsnId>, state: InsnId },
553554

554555
/// Side-exit if val doesn't have the expected type.
555556
GuardType { val: InsnId, guard_type: Type, state: InsnId },
@@ -730,7 +731,14 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
730731
write!(f, ", {arg}")?;
731732
}
732733
Ok(())
733-
},
734+
}
735+
Insn::ConcatStrings { strings, state: _ } => {
736+
write!(f, "ConcatStrings {}", strings[0])?;
737+
for string in strings.iter().skip(1) {
738+
write!(f, ", {string}")?;
739+
}
740+
Ok(())
741+
}
734742
Insn::Snapshot { state } => write!(f, "Snapshot {}", state),
735743
Insn::Defined { op_type, v, .. } => {
736744
// op_type (enum defined_type) printing logic from iseq.c.
@@ -1138,6 +1146,7 @@ impl Function {
11381146
str: find!(*str),
11391147
state: *state,
11401148
},
1149+
ConcatStrings { state, strings } => ConcatStrings { state: *state, strings: find_vec!(strings) },
11411150
SendWithoutBlock { self_val, call_info, cd, args, state } => SendWithoutBlock {
11421151
self_val: find!(*self_val),
11431152
call_info: call_info.clone(),
@@ -1271,6 +1280,7 @@ impl Function {
12711280
Insn::ToArray { .. } => types::ArrayExact,
12721281
Insn::ObjToString { .. } => types::BasicObject,
12731282
Insn::AnyToString { .. } => types::String,
1283+
Insn::ConcatStrings { .. } => types::StringExact,
12741284
Insn::GetLocal { .. } => types::BasicObject,
12751285
// The type of Snapshot doesn't really matter; it's never materialized. It's used only
12761286
// as a reference for FrameState, which we use to generate side-exit code.
@@ -1902,7 +1912,8 @@ impl Function {
19021912
worklist.extend(args);
19031913
worklist.push_back(state);
19041914
}
1905-
&Insn::InvokeBuiltin { ref args, state, .. } => {
1915+
&Insn::InvokeBuiltin { ref args, state, .. }
1916+
| &Insn::ConcatStrings { strings: ref args, state } => {
19061917
worklist.extend(args);
19071918
worklist.push_back(state)
19081919
}
@@ -3162,6 +3173,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
31623173
let anytostring = fun.push_insn(block, Insn::AnyToString { val, str, state: exit_id });
31633174
state.stack_push(anytostring);
31643175
}
3176+
YARVINSN_concatstrings => {
3177+
let num = get_arg(pc, 0).as_u32();
3178+
let mut strings = Vec::with_capacity(num.as_usize());
3179+
for _ in 0..num {
3180+
strings.push(state.stack_pop()?);
3181+
}
3182+
strings.reverse();
3183+
let snapshot = fun.push_insn(block, Insn::Snapshot { state: exit_state });
3184+
state.stack_push(fun.push_insn(block, Insn::ConcatStrings { state: snapshot, strings }));
3185+
}
31653186
_ => {
31663187
// Unknown opcode; side-exit into the interpreter
31673188
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
@@ -5046,7 +5067,8 @@ mod tests {
50465067
v3:Fixnum[1] = Const Value(1)
50475068
v5:BasicObject = ObjToString v3
50485069
v7:String = AnyToString v3, str: v5
5049-
SideExit UnknownOpcode(concatstrings)
5070+
v9:StringExact = ConcatStrings v2, v7
5071+
Return v9
50505072
"#]]);
50515073
}
50525074

@@ -6827,7 +6849,8 @@ mod opt_tests {
68276849
v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
68286850
v3:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
68296851
v4:StringExact = StringCopy v3
6830-
SideExit UnknownOpcode(concatstrings)
6852+
v10:StringExact = ConcatStrings v2, v4
6853+
Return v10
68316854
"#]]);
68326855
}
68336856

@@ -6841,9 +6864,10 @@ mod opt_tests {
68416864
bb0(v0:BasicObject):
68426865
v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
68436866
v3:Fixnum[1] = Const Value(1)
6844-
v10:BasicObject = SendWithoutBlock v3, :to_s
6845-
v7:String = AnyToString v3, str: v10
6846-
SideExit UnknownOpcode(concatstrings)
6867+
v11:BasicObject = SendWithoutBlock v3, :to_s
6868+
v7:String = AnyToString v3, str: v11
6869+
v9:StringExact = ConcatStrings v2, v7
6870+
Return v9
68476871
"#]]);
68486872
}
68496873

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy