diff options
author | Jean Boussier <[email protected]> | 2025-06-13 11:23:32 +0200 |
---|---|---|
committer | Jean Boussier <[email protected]> | 2025-06-13 19:46:29 +0200 |
commit | a99d941cacbb9d5d277400abf76f5648f91009ea (patch) | |
tree | 062e514cbaf393b275e5c111c7ace438bfed08be | |
parent | fb0dbbc0e660d0c77ebba292578945aca8baafac (diff) |
Add SHAPE_ID_HAS_IVAR_MASK for quick ivar check
This allow checking if an object has ivars with just a shape_id
mask.
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/13606
-rw-r--r-- | internal/string.h | 1 | ||||
-rw-r--r-- | shape.c | 20 | ||||
-rw-r--r-- | shape.h | 16 | ||||
-rw-r--r-- | string.c | 9 | ||||
-rw-r--r-- | yjit/bindgen/src/main.rs | 1 | ||||
-rw-r--r-- | yjit/src/codegen.rs | 6 | ||||
-rw-r--r-- | yjit/src/cruby_bindings.inc.rs | 1 |
7 files changed, 41 insertions, 13 deletions
diff --git a/internal/string.h b/internal/string.h index 50561924f2..d6fea62061 100644 --- a/internal/string.h +++ b/internal/string.h @@ -30,6 +30,7 @@ enum ruby_rstring_private_flags { #endif /* string.c */ +VALUE rb_str_dup_m(VALUE str); VALUE rb_fstring(VALUE); VALUE rb_fstring_cstr(const char *str); VALUE rb_fstring_enc_new(const char *ptr, long len, rb_encoding *enc); @@ -1234,6 +1234,23 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) } } + // Make sure SHAPE_ID_HAS_IVAR_MASK is valid. + if (rb_shape_too_complex_p(shape_id)) { + RUBY_ASSERT(shape_id & SHAPE_ID_HAS_IVAR_MASK); + } + else { + attr_index_t ivar_count = RSHAPE_LEN(shape_id); + if (has_object_id) { + ivar_count--; + } + if (ivar_count) { + RUBY_ASSERT(shape_id & SHAPE_ID_HAS_IVAR_MASK); + } + else { + RUBY_ASSERT(!(shape_id & SHAPE_ID_HAS_IVAR_MASK)); + } + } + uint8_t flags_heap_index = rb_shape_heap_index(shape_id); if (RB_TYPE_P(obj, T_OBJECT)) { size_t shape_id_slot_size = rb_shape_tree.capacities[flags_heap_index - 1] * sizeof(VALUE) + sizeof(struct RBasic); @@ -1524,6 +1541,7 @@ Init_default_shapes(void) root->type = SHAPE_ROOT; rb_shape_tree.root_shape = root; RUBY_ASSERT(raw_shape_id(rb_shape_tree.root_shape) == ROOT_SHAPE_ID); + RUBY_ASSERT(!(raw_shape_id(rb_shape_tree.root_shape) & SHAPE_ID_HAS_IVAR_MASK)); bool dontcare; rb_shape_t *root_with_obj_id = get_next_shape_internal(root, id_object_id, SHAPE_OBJ_ID, &dontcare, true); @@ -1531,7 +1549,7 @@ Init_default_shapes(void) RUBY_ASSERT(root_with_obj_id->type == SHAPE_OBJ_ID); RUBY_ASSERT(root_with_obj_id->edge_name == id_object_id); RUBY_ASSERT(root_with_obj_id->next_field_index == 1); - (void)root_with_obj_id; + RUBY_ASSERT(!(raw_shape_id(root_with_obj_id) & SHAPE_ID_HAS_IVAR_MASK)); } void @@ -23,6 +23,10 @@ STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_ #define SHAPE_ID_HEAP_INDEX_MAX ((1 << SHAPE_ID_HEAP_INDEX_BITS) - 1) #define SHAPE_ID_HEAP_INDEX_MASK (SHAPE_ID_HEAP_INDEX_MAX << SHAPE_ID_HEAP_INDEX_OFFSET) +// This masks allows to check if a shape_id contains any ivar. +// It rely on ROOT_SHAPE_WITH_OBJ_ID==1. +#define SHAPE_ID_HAS_IVAR_MASK (SHAPE_ID_FL_TOO_COMPLEX | (SHAPE_ID_OFFSET_MASK - 1)) + // The interpreter doesn't care about frozen status or slot size when reading ivars. // So we normalize shape_id by clearing these bits to improve cache hits. // JITs however might care about it. @@ -327,6 +331,18 @@ rb_shape_obj_has_id(VALUE obj) return rb_shape_has_object_id(RBASIC_SHAPE_ID(obj)); } +static inline bool +rb_shape_has_ivars(shape_id_t shape_id) +{ + return shape_id & SHAPE_ID_HAS_IVAR_MASK; +} + +static inline bool +rb_shape_obj_has_ivars(VALUE obj) +{ + return rb_shape_has_ivars(RBASIC_SHAPE_ID(obj)); +} + // For ext/objspace RUBY_SYMBOL_EXPORT_BEGIN typedef void each_shape_callback(shape_id_t shape_id, void *data); @@ -388,12 +388,7 @@ fstring_hash(VALUE str) static inline bool BARE_STRING_P(VALUE str) { - if (RBASIC_CLASS(str) != rb_cString) return false; - - if (FL_TEST_RAW(str, FL_EXIVAR)) { - return rb_ivar_count(str) == 0; - } - return true; + return RBASIC_CLASS(str) == rb_cString && !rb_shape_obj_has_ivars(str); } static inline st_index_t @@ -2316,7 +2311,7 @@ VALUE rb_str_dup_m(VALUE str) { if (LIKELY(BARE_STRING_P(str))) { - return str_duplicate(rb_obj_class(str), str); + return str_duplicate(rb_cString, str); } else { return rb_obj_dup(str); diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index e65f001145..41d383f8bd 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -228,6 +228,7 @@ fn main() { .allowlist_function("rb_obj_as_string_result") .allowlist_function("rb_str_byte_substr") .allowlist_function("rb_str_substr_two_fixnums") + .allowlist_function("rb_str_dup_m") // From include/ruby/internal/intern/parse.h .allowlist_function("rb_backref_get") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 2e2ca51b17..3e08857295 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -6275,16 +6275,12 @@ fn jit_rb_str_dup( jit_prepare_call_with_gc(jit, asm); - // Check !FL_ANY_RAW(str, FL_EXIVAR), which is part of BARE_STRING_P. let recv_opnd = asm.stack_pop(1); let recv_opnd = asm.load(recv_opnd); - let flags_opnd = Opnd::mem(64, recv_opnd, RUBY_OFFSET_RBASIC_FLAGS); - asm.test(flags_opnd, Opnd::Imm(RUBY_FL_EXIVAR as i64)); - asm.jnz(Target::side_exit(Counter::send_str_dup_exivar)); // Call rb_str_dup let stack_ret = asm.stack_push(Type::CString); - let ret_opnd = asm.ccall(rb_str_dup as *const u8, vec![recv_opnd]); + let ret_opnd = asm.ccall(rb_str_dup_m as *const u8, vec![recv_opnd]); asm.mov(stack_ret, ret_opnd); true diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index d42df7b267..a1c7464805 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1119,6 +1119,7 @@ extern "C" { pub fn rb_gvar_set(arg1: ID, arg2: VALUE) -> VALUE; pub fn rb_ensure_iv_list_size(obj: VALUE, current_len: u32, newsize: u32); pub fn rb_vm_barrier(); + pub fn rb_str_dup_m(str_: VALUE) -> VALUE; pub fn rb_str_byte_substr(str_: VALUE, beg: VALUE, len: VALUE) -> VALUE; pub fn rb_str_substr_two_fixnums( str_: VALUE, |