Skip to content

Commit

Permalink
YJIT: Consolidate jit_rb_str_concat and jit_rb_str_concat_codepoint.
Browse files Browse the repository at this point in the history
Additionally, move more code out of the inline code block and into C to keep the code size small and reduce the number of unique branch targets.
  • Loading branch information
nirvdrum committed Jul 26, 2024
1 parent 0faa0ec commit dff85fe
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 99 deletions.
27 changes: 23 additions & 4 deletions string.c
Original file line number Diff line number Diff line change
Expand Up @@ -12297,16 12297,35 @@ rb_enc_interned_str_cstr(const char *ptr, rb_encoding *enc)
}

#if USE_YJIT
VALUE
rb_yjit_str_simple_append(VALUE str1, VALUE str2)
{
return rb_str_cat(str1, RSTRING_PTR(str2), RSTRING_LEN(str2));
}

void
rb_yjit_str_concat_codepoint(VALUE str, VALUE codepoint)
rb_yjit_str_concat(VALUE str, VALUE codepoint)
{
if (RB_LIKELY(FIXNUM_P(codepoint)) && ENCODING_GET_INLINED(str) == rb_ascii8bit_encindex()) {
if (STRING_P(codepoint)) {
if (RB_LIKELY(ENCODING_GET_INLINED(str) == ENCODING_GET_INLINED(codepoint))) {
rb_yjit_str_simple_append(str, codepoint);
} else {
rb_str_buf_append(str, codepoint);
}

return;
}

if (FIXNUM_P(codepoint) && ENCODING_GET_INLINED(str) == rb_ascii8bit_encindex()) {
ssize_t code = RB_NUM2SSIZE(codepoint);

if (code >= 0 && code < 0xff) {
if (RB_LIKELY(code >= 0 && code < 0xff)) {
rb_str_buf_cat_byte(str, (char) code);
return;
} else {
rb_str_concat(str, codepoint);
}

return;
}

rb_str_concat(str, codepoint);
Expand Down
6 changes: 0 additions & 6 deletions yjit.c
Original file line number Diff line number Diff line change
Expand Up @@ -776,12 776,6 @@ rb_yjit_builtin_function(const rb_iseq_t *iseq)
}
}

VALUE
rb_yjit_str_simple_append(VALUE str1, VALUE str2)
{
return rb_str_cat(str1, RSTRING_PTR(str2), RSTRING_LEN(str2));
}

struct rb_control_frame_struct *
rb_get_ec_cfp(const rb_execution_context_t *ec)
{
Expand Down
92 changes: 5 additions & 87 deletions yjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5762,9 5762,10 @@ fn jit_rb_str_empty_p(
return true;
}

// Codegen for rb_str_concat() with an integer argument -- *not* String#concat
// Using strings as a byte buffer often includes appending byte values to the end of the string.
fn jit_rb_str_concat_codepoint(
// Codegen for rb_str_concat() -- *not* String#concat
// Frequently strings are concatenated using "out_str << next_str".
// This is common in Erb and similar templating languages.
fn jit_rb_str_concat(
jit: &mut JITState,
asm: &mut Assembler,
_ci: *const rb_callinfo,
Expand All @@ -5783,7 5784,7 @@ fn jit_rb_str_concat_codepoint(
let codepoint = asm.stack_opnd(0);
let recv = asm.stack_opnd(1);

asm.ccall(rb_yjit_str_concat_codepoint as *const u8, vec![recv, codepoint]);
asm.ccall(rb_yjit_str_concat as *const u8, vec![recv, codepoint]);

// The receiver is the return value, so we only need to pop the codepoint argument off the stack.
// We can reuse the receiver slot in the stack as the return value.
Expand All @@ -5792,89 5793,6 @@ fn jit_rb_str_concat_codepoint(
true
}

// Codegen for rb_str_concat() -- *not* String#concat
// Frequently strings are concatenated using "out_str << next_str".
// This is common in Erb and similar templating languages.
fn jit_rb_str_concat(
jit: &mut JITState,
asm: &mut Assembler,
ci: *const rb_callinfo,
cme: *const rb_callable_method_entry_t,
block: Option<BlockHandler>,
argc: i32,
known_recv_class: Option<VALUE>,
) -> bool {
// The << operator can accept integer codepoints for characters
// as the argument. We only specially optimise string arguments.
// If the peeked-at compile time argument is something other than
// a string, assume it won't be a string later either.
let comptime_arg = jit.peek_at_stack(&asm.ctx, 0);
if unsafe { RB_TYPE_P(comptime_arg, RUBY_T_FIXNUM) } {
return jit_rb_str_concat_codepoint(jit, asm, ci, cme, block, argc, known_recv_class);
}

if ! unsafe { RB_TYPE_P(comptime_arg, RUBY_T_STRING) } {
return false;
}

// Guard that the concat argument is a string
guard_object_is_string(asm, asm.stack_opnd(0), StackOpnd(0), Counter::guard_send_not_string);

// Guard buffers from GC since rb_str_buf_append may allocate.
// rb_str_buf_append may raise Encoding::CompatibilityError, but we accept compromised
// backtraces on this method since the interpreter does the same thing on opt_ltlt.
jit_prepare_non_leaf_call(jit, asm);

// Explicitly spill temps before making any C calls. `ccall` will spill temps, but it does a
// check to only spill if it thinks it's necessary. That logic can't see through the runtime
// branching occurring in the code generated for this function. Consequently, the branch for
// the first `ccall` will spill registers but the second one will not. At run time, we may
// jump over that spill code when executing the second branch, leading situations that are
// quite hard to debug. If we spill up front we avoid diverging behavior.
asm.spill_regs();

let concat_arg = asm.stack_pop(1);
let recv = asm.stack_pop(1);

// Test if string encodings differ. If different, use rb_str_append. If the same,
// use rb_yjit_str_simple_append, which calls rb_str_cat.
asm_comment!(asm, "<< on strings");

// Take receiver's object flags XOR arg's flags. If any
// string-encoding flags are different between the two,
// the encodings don't match.
let recv_reg = asm.load(recv);
let concat_arg_reg = asm.load(concat_arg);
let flags_xor = asm.xor(
Opnd::mem(64, recv_reg, RUBY_OFFSET_RBASIC_FLAGS),
Opnd::mem(64, concat_arg_reg, RUBY_OFFSET_RBASIC_FLAGS)
);
asm.test(flags_xor, Opnd::UImm(RUBY_ENCODING_MASK as u64));

let enc_mismatch = asm.new_label("enc_mismatch");
asm.jnz(enc_mismatch);

// If encodings match, call the simple append function and jump to return
let ret_opnd = asm.ccall(rb_yjit_str_simple_append as *const u8, vec![recv, concat_arg]);
let ret_label = asm.new_label("func_return");
let stack_ret = asm.stack_push(Type::TString);
asm.mov(stack_ret, ret_opnd);
asm.stack_pop(1); // forget stack_ret to re-push after ccall
asm.jmp(ret_label);

// If encodings are different, use a slower encoding-aware concatenate
asm.write_label(enc_mismatch);
asm.spill_regs(); // Ignore the register for the other local branch
let ret_opnd = asm.ccall(rb_str_buf_append as *const u8, vec![recv, concat_arg]);
let stack_ret = asm.stack_push(Type::TString);
asm.mov(stack_ret, ret_opnd);
// Drop through to return

asm.write_label(ret_label);

true
}

// Codegen for rb_ary_empty_p()
fn jit_rb_ary_empty_p(
_jit: &mut JITState,
Expand Down
2 changes: 1 addition & 1 deletion yjit/src/cruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 117,7 @@ extern "C" {
ci: *const rb_callinfo,
) -> *const rb_callable_method_entry_t;
pub fn rb_hash_empty_p(hash: VALUE) -> VALUE;
pub fn rb_yjit_str_concat_codepoint(str: VALUE, codepoint: VALUE);
pub fn rb_yjit_str_concat(str: VALUE, codepoint: VALUE);
pub fn rb_str_setbyte(str: VALUE, index: VALUE, value: VALUE) -> VALUE;
pub fn rb_vm_splat_array(flag: VALUE, ary: VALUE) -> VALUE;
pub fn rb_vm_concat_array(ary1: VALUE, ary2st: VALUE) -> VALUE;
Expand Down
1 change: 0 additions & 1 deletion yjit/src/cruby_bindings.inc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1207,7 1207,6 @@ extern "C" {
) -> VALUE;
pub fn rb_yjit_iseq_builtin_attrs(iseq: *const rb_iseq_t) -> ::std::os::raw::c_uint;
pub fn rb_yjit_builtin_function(iseq: *const rb_iseq_t) -> *const rb_builtin_function;
pub fn rb_yjit_str_simple_append(str1: VALUE, str2: VALUE) -> VALUE;
pub fn rb_get_ec_cfp(ec: *const rb_execution_context_t) -> *mut rb_control_frame_struct;
pub fn rb_get_cfp_iseq(cfp: *mut rb_control_frame_struct) -> *const rb_iseq_t;
pub fn rb_get_cfp_pc(cfp: *mut rb_control_frame_struct) -> *mut VALUE;
Expand Down

0 comments on commit dff85fe

Please sign in to comment.