Define String#ascii_only? and #valid_encoding? in Ruby

Get rid of the CFUNC overhead in String#ascii_only? and #valid_encoding?. Define them as leaf Primitive.cexpr! builtins in a new string.rb, following the pattern of numeric.rb.

This makes them 1.2x-1.4x faster across the interpreter, YJIT, and
ZJIT.

Move ZJIT's cfunc annotation for String#ascii_only? to annotate_builtin!, and add one for String#valid_encoding?.

Fix CI for builtin String#ascii_only?
This commit is contained in:
Sampo Kuokkanen authored and Alan Wu committed 2026-06-24 17:17:33 -04:00
1 parent 5465dca954
commit 93e977936f
9 files changed
+51 -27
Notes: git 2026-06-24 21:21:21 +00:00

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
prelude: |
ascii = "hello world"
utf8 = "héllo wörld 晦"
benchmark:
ascii_only_true: ascii.ascii_only?
ascii_only_false: utf8.ascii_only?
loop_count: 20000000
+7
View File
@@ -0,0 +1,7 @@
prelude: |
utf8 = "héllo wörld 晦"
broken = "\xff\xfe".dup.force_encoding("UTF-8")
benchmark:
valid_enc_true: utf8.valid_encoding?
valid_enc_false: broken.valid_encoding?
loop_count: 20000000
+1
View File
@@ -1199,6 +1199,7 @@ BUILTIN_RB_SRCS = \
$(srcdir)/kernel.rb \
$(srcdir)/pathname_builtin.rb \
$(srcdir)/ractor.rb \
$(srcdir)/string.rb \
$(srcdir)/symbol.rb \
$(srcdir)/timev.rb \
$(srcdir)/thread_sync.rb \
+2
View File
@@ -17718,6 +17718,7 @@ string.$(OBJEXT): {$(VPATH)}backward/2/limits.h
string.$(OBJEXT): {$(VPATH)}backward/2/long_long.h
string.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h
string.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h
string.$(OBJEXT): {$(VPATH)}builtin.h
string.$(OBJEXT): {$(VPATH)}config.h
string.$(OBJEXT): {$(VPATH)}constant.h
string.$(OBJEXT): {$(VPATH)}debug_counter.h
@@ -17896,6 +17897,7 @@ string.$(OBJEXT): {$(VPATH)}rubyparser.h
string.$(OBJEXT): {$(VPATH)}shape.h
string.$(OBJEXT): {$(VPATH)}st.h
string.$(OBJEXT): {$(VPATH)}string.c
string.$(OBJEXT): {$(VPATH)}string.rbinc
string.$(OBJEXT): {$(VPATH)}subst.h
string.$(OBJEXT): {$(VPATH)}thread.h
string.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h
+1
View File
@@ -105,6 +105,7 @@ rb_call_builtin_inits(void)
BUILTIN(warning);
BUILTIN(array);
BUILTIN(hash);
BUILTIN(string);
BUILTIN(symbol);
BUILTIN(timev);
BUILTIN(thread_sync);
+2 -22
View File
@@ -11736,14 +11736,6 @@ rb_str_b(VALUE str)
return str2;
}
/*
* call-seq:
* valid_encoding? -> true or false
*
* :include: doc/string/valid_encoding_p.rdoc
*
*/
static VALUE
rb_str_valid_encoding_p(VALUE str)
{
@@ -11752,18 +11744,6 @@ rb_str_valid_encoding_p(VALUE str)
return RBOOL(cr != ENC_CODERANGE_BROKEN);
}
/*
* call-seq:
* ascii_only? -> true or false
*
* Returns whether +self+ contains only ASCII characters:
*
* 'abc'.ascii_only? # => true
* "abc\u{6666}".ascii_only? # => false
*
* Related: see {Querying}[rdoc-ref:String@Querying].
*/
static VALUE
rb_str_is_ascii_only_p(VALUE str)
{
@@ -13053,8 +13033,6 @@ Init_String(void)
rb_define_method(rb_cString, "encoding", rb_obj_encoding, 0); /* in encoding.c */
rb_define_method(rb_cString, "force_encoding", rb_str_force_encoding, 1);
rb_define_method(rb_cString, "b", rb_str_b, 0);
rb_define_method(rb_cString, "valid_encoding?", rb_str_valid_encoding_p, 0);
rb_define_method(rb_cString, "ascii_only?", rb_str_is_ascii_only_p, 0);
/* define UnicodeNormalize module here so that we don't have to look it up */
mUnicodeNormalize = rb_define_module("UnicodeNormalize");
@@ -13107,3 +13085,5 @@ Init_String(void)
rb_define_method(rb_cSymbol, "encoding", sym_encoding, 0);
}
#include "string.rbinc"
+25
View File
@@ -0,0 +1,25 @@
class String
# call-seq:
# valid_encoding? -> true or false
#
# :include: doc/string/valid_encoding_p.rdoc
#
def valid_encoding?
Primitive.attr! :leaf
Primitive.cexpr! 'rb_str_valid_encoding_p(self)'
end
# call-seq:
# ascii_only? -> true or false
#
# Returns whether +self+ contains only ASCII characters:
#
# 'abc'.ascii_only? # => true
# "abc\u{6666}".ascii_only? # => false
#
# Related: see {Querying}[rdoc-ref:String@Querying].
def ascii_only?
Primitive.attr! :leaf
Primitive.cexpr! 'rb_str_is_ascii_only_p(self)'
end
end
+4 -3
View File
@@ -222,9 +222,6 @@ pub fn init() -> Annotations {
annotate!(rb_cString, "<<", inline_string_append);
annotate!(rb_cString, "==", inline_string_eq);
// Not elidable; has a side effect of setting the encoding if ENC_CODERANGE_UNKNOWN.
// TOOD(max): Turn this into a load/compare. Will need to side-exit or do the full call if
// ENC_CODERANGE_UNKNOWN.
annotate!(rb_cString, "ascii_only?", types::BoolExact, no_gc, leaf);
annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable);
annotate!(rb_cModule, "===", inline_module_eqq, types::BoolExact, no_gc, leaf);
annotate!(rb_cArray, "length", inline_array_length, types::Fixnum, no_gc, leaf, elidable);
@@ -291,6 +288,10 @@ pub fn init() -> Annotations {
annotate_builtin!(rb_mKernel, "frozen?", types::BoolExact);
annotate_builtin!(rb_cSymbol, "name", types::StringExact);
annotate_builtin!(rb_cSymbol, "to_s", types::StringExact);
// TOOD(max): Turn this into a load/compare. Will need to side-exit or do the full call if
// ENC_CODERANGE_UNKNOWN.
annotate_builtin!(rb_cString, "ascii_only?", types::BoolExact, no_gc, leaf);
annotate_builtin!(rb_cString, "valid_encoding?", types::BoolExact, no_gc, leaf);
// Array iteration builtins (used in with_jit Array#each, map, select, find)
builtin_funcs.insert(rb_jit_fixnum_inc as *mut c_void, FnProperties { inline: inline_fixnum_inc, return_type: types::Fixnum, ..Default::default() });
+2 -2
View File
@@ -11910,8 +11910,8 @@ mod hir_opt_tests {
bb3(v9:BasicObject, v10:BasicObject):
PatchPoint NoSingletonClass(String@0x1008)
PatchPoint MethodRedefined(String@0x1008, ascii_only?@0x1010, cme:0x1018)
v24:StringExact = GuardType v10, StringExact recompile
v25:BoolExact = CCall v24, :String#ascii_only?@0x1040
v23:StringExact = GuardType v10, StringExact recompile
v25:BoolExact = InvokeBuiltin leaf <inline_expr>, v23
CheckInterrupts
Return v25
");