diff --git a/string.c b/string.c index 3fa23288f89af2..fd5a55d429ea20 100644 --- a/string.c +++ b/string.c @@ -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); diff --git a/yjit.c b/yjit.c index e63b42ea54f3e4..b5e37a32ae1d12 100644 --- a/yjit.c +++ b/yjit.c @@ -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) { diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 4536ecdbb1e13f..781f733cbca1d9 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -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, @@ -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. @@ -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, - argc: i32, - known_recv_class: Option, -) -> 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, diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 0c02ddba863d6c..a1b4e2f312cd47 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -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; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index cbe635f060e380..ad3131ada10170 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -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;