Skip to content

Commit bb75c28

Browse files
sampokuokkanenXrXr
authored andcommitted
ZJIT: Inline String#ascii_only? and #valid_encoding?
Read the cached coderange directly (flags & MASK) instead of calling the builtin: ascii_only? is == 7BIT, valid_encoding? is != BROKEN, side-exit on UNKNOWN. Drops the call, ~20% faster on ZJIT.
1 parent 93e9779 commit bb75c28

5 files changed

Lines changed: 52 additions & 7 deletions

File tree

string.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11736,6 +11736,7 @@ rb_str_b(VALUE str)
1173611736
return str2;
1173711737
}
1173811738

11739+
/* Defined as a leaf builtin in string.rb, so this must never raise or call into Ruby. */
1173911740
static VALUE
1174011741
rb_str_valid_encoding_p(VALUE str)
1174111742
{
@@ -11744,6 +11745,7 @@ rb_str_valid_encoding_p(VALUE str)
1174411745
return RBOOL(cr != ENC_CODERANGE_BROKEN);
1174511746
}
1174611747

11748+
/* Defined as a leaf builtin in string.rb, so this must never raise or call into Ruby. */
1174711749
static VALUE
1174811750
rb_str_is_ascii_only_p(VALUE str)
1174911751
{
@@ -13086,4 +13088,3 @@ Init_String(void)
1308613088
}
1308713089

1308813090
#include "string.rbinc"
13089-

zjit/bindgen/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ fn main() {
8080
.allowlist_type("ruby_preserved_encindex")
8181
.allowlist_function("rb_class2name")
8282

83+
// Coderange constants (ENC_CODERANGE_*) for inlining String predicates
84+
.allowlist_type("ruby_coderange_type")
85+
8386
// This struct is public to Ruby C extensions
8487
.allowlist_type("RBasic")
8588

zjit/src/cruby_bindings.inc.rs

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

zjit/src/cruby_methods.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,10 +288,8 @@ pub fn init() -> Annotations {
288288
annotate_builtin!(rb_mKernel, "frozen?", types::BoolExact);
289289
annotate_builtin!(rb_cSymbol, "name", types::StringExact);
290290
annotate_builtin!(rb_cSymbol, "to_s", types::StringExact);
291-
// TOOD(max): Turn this into a load/compare. Will need to side-exit or do the full call if
292-
// ENC_CODERANGE_UNKNOWN.
293-
annotate_builtin!(rb_cString, "ascii_only?", types::BoolExact, no_gc, leaf);
294-
annotate_builtin!(rb_cString, "valid_encoding?", types::BoolExact, no_gc, leaf);
291+
annotate_builtin!(rb_cString, "ascii_only?", inline_string_ascii_only_p, types::BoolExact, no_gc, leaf);
292+
annotate_builtin!(rb_cString, "valid_encoding?", inline_string_valid_encoding_p, types::BoolExact, no_gc, leaf);
295293

296294
// Array iteration builtins (used in with_jit Array#each, map, select, find)
297295
builtin_funcs.insert(rb_jit_fixnum_inc as *mut c_void, FnProperties { inline: inline_fixnum_inc, return_type: types::Fixnum, ..Default::default() });
@@ -552,6 +550,36 @@ fn inline_string_empty_p(fun: &mut hir::Function, block: hir::BlockId, recv: hir
552550
Some(result)
553551
}
554552

553+
// Load self's cached coderange (flags & MASK), guarding it's been computed
554+
// (UNKNOWN is 0, so side-exit there and let the builtin scan the string).
555+
fn guard_string_coderange(fun: &mut hir::Function, block: hir::BlockId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
556+
use crate::hir::SideExitReason;
557+
let &[recv] = args else { return None; };
558+
if !fun.likely_a(recv, types::String, state) { return None; }
559+
let recv = fun.coerce_to(block, recv, types::String, state);
560+
let flags = fun.load_rbasic_flags(block, recv);
561+
let mask = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CUInt64(RUBY_ENC_CODERANGE_MASK.into()) });
562+
let cr = fun.push_insn(block, hir::Insn::IntAnd { left: flags, right: mask });
563+
let min_known = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(RUBY_ENC_CODERANGE_7BIT.into()) });
564+
Some(fun.push_insn(block, hir::Insn::GuardGreaterEq { left: cr, right: min_known, reason: SideExitReason::GuardGreaterEq, state }))
565+
}
566+
567+
// Inlines String#ascii_only? (rb_str_is_ascii_only_p): coderange == 7BIT.
568+
fn inline_string_ascii_only_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
569+
let cr = guard_string_coderange(fun, block, args, state)?;
570+
let seven_bit = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(RUBY_ENC_CODERANGE_7BIT.into()) });
571+
let is_7bit = fun.push_insn(block, hir::Insn::IsBitEqual { left: cr, right: seven_bit });
572+
Some(fun.push_insn(block, hir::Insn::BoxBool { val: is_7bit }))
573+
}
574+
575+
// Inlines String#valid_encoding? (rb_str_valid_encoding_p): coderange != BROKEN.
576+
fn inline_string_valid_encoding_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
577+
let cr = guard_string_coderange(fun, block, args, state)?;
578+
let broken = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(RUBY_ENC_CODERANGE_BROKEN.into()) });
579+
let is_valid = fun.push_insn(block, hir::Insn::IsBitNotEqual { left: cr, right: broken });
580+
Some(fun.push_insn(block, hir::Insn::BoxBool { val: is_valid }))
581+
}
582+
555583
fn inline_string_append(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
556584
let &[other] = args else { return None; };
557585
// Inline only StringExact << String, which matches original type check from

zjit/src/hir/opt_tests.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11911,9 +11911,16 @@ mod hir_opt_tests {
1191111911
PatchPoint NoSingletonClass(String@0x1008)
1191211912
PatchPoint MethodRedefined(String@0x1008, ascii_only?@0x1010, cme:0x1018)
1191311913
v23:StringExact = GuardType v10, StringExact recompile
11914-
v25:BoolExact = InvokeBuiltin leaf <inline_expr>, v23
11914+
v26:CUInt64 = LoadField v23, :RBASIC_FLAGS@0x1040
11915+
v27:CUInt64[3145728] = Const CUInt64(3145728)
11916+
v28:CInt64 = IntAnd v26, v27
11917+
v29:CInt64[1048576] = Const CInt64(1048576)
11918+
v30:CInt64 = GuardGreaterEq v28, v29
11919+
v31:CInt64[1048576] = Const CInt64(1048576)
11920+
v32:CBool = IsBitEqual v30, v31
11921+
v33:BoolExact = BoxBool v32
1191511922
CheckInterrupts
11916-
Return v25
11923+
Return v33
1191711924
");
1191811925
}
1191911926

0 commit comments

Comments
 (0)