diff options
author | Alan Wu <[email protected]> | 2024-02-15 11:59:37 -0500 |
---|---|---|
committer | GitHub <[email protected]> | 2024-02-15 11:59:37 -0500 |
commit | da7b9478d33d212145f2b79e378cb617451f3a5b (patch) | |
tree | 07f2af41bc52301a3f9d70a294c55060775461d9 | |
parent | e779c194b3520973e42e277ed4759a90b97795d0 (diff) |
YJIT: Pass nil to anonymous kwrest when empty (#9972)
This is the same optimization as e4272fd29 ("Avoid allocation when
passing no keywords to anonymous kwrest methods") but for YJIT. For
anonymous kwrest parameters, nil is just as good as an empty hash.
On the usage side, update `splatkw` to handle `nil` with a leaner path.
-rw-r--r-- | yjit.c | 6 | ||||
-rw-r--r-- | yjit/bindgen/src/main.rs | 1 | ||||
-rw-r--r-- | yjit/src/codegen.rs | 43 | ||||
-rw-r--r-- | yjit/src/cruby_bindings.inc.rs | 1 | ||||
-rw-r--r-- | yjit/src/stats.rs | 1 |
5 files changed, 42 insertions, 10 deletions
@@ -660,6 +660,12 @@ rb_get_iseq_flags_has_kwrest(const rb_iseq_t *iseq) } bool +rb_get_iseq_flags_anon_kwrest(const rb_iseq_t *iseq) +{ + return iseq->body->param.flags.anon_kwrest; +} + +bool rb_get_iseq_flags_has_rest(const rb_iseq_t *iseq) { return iseq->body->param.flags.has_rest; diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index ffe56619cc..a1b8cf3a75 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -421,6 +421,7 @@ fn main() { .allowlist_function("rb_get_iseq_flags_has_rest") .allowlist_function("rb_get_iseq_flags_has_post") .allowlist_function("rb_get_iseq_flags_has_kwrest") + .allowlist_function("rb_get_iseq_flags_anon_kwrest") .allowlist_function("rb_get_iseq_flags_has_block") .allowlist_function("rb_get_iseq_flags_ambiguous_param0") .allowlist_function("rb_get_iseq_flags_accepts_no_kwarg") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 28436a402b..6a518292ab 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -1530,6 +1530,19 @@ fn gen_splatkw( // If a compile-time hash operand is T_HASH, just guard that it's T_HASH. let hash_opnd = asm.stack_opnd(1); guard_object_is_hash(asm, hash_opnd, hash_opnd.into(), Counter::splatkw_not_hash); + } else if comptime_hash.nil_p() { + // Speculate we'll see nil if compile-time hash operand is nil + let hash_opnd = asm.stack_opnd(1); + let hash_opnd_type = asm.ctx.get_opnd_type(hash_opnd.into()); + + if hash_opnd_type != Type::Nil { + asm.cmp(hash_opnd, Qnil.into()); + asm.jne(Target::side_exit(Counter::splatkw_not_nil)); + + if Type::Nil.diff(hash_opnd_type) != TypeDiff::Incompatible { + asm.ctx.upgrade_opnd_type(hash_opnd.into(), Type::Nil); + } + } } else { // Otherwise, call #to_hash on the operand if it's not nil. @@ -7283,6 +7296,7 @@ fn gen_iseq_kw_call( unsafe { get_cikw_keyword_len(ci_kwarg) } }; let caller_keyword_len: usize = caller_keyword_len_i32.try_into().unwrap(); + let anon_kwrest = unsafe { rb_get_iseq_flags_anon_kwrest(iseq) }; // This struct represents the metadata about the callee-specified // keyword parameters. @@ -7363,16 +7377,25 @@ fn gen_iseq_kw_call( } } - // Save PC and SP before allocating - jit_save_pc(jit, asm); - gen_save_sp(asm); + let (kwrest, kwrest_type) = if rest_mask == 0 && anon_kwrest { + // In case the kwrest hash should be empty and is anonymous in the callee, + // we can pass nil instead of allocating. Anonymous kwrest can only be + // delegated, and nil is the same as an empty hash when delegating. + (Qnil.into(), Type::Nil) + } else { + // Save PC and SP before allocating + jit_save_pc(jit, asm); + gen_save_sp(asm); + + // Build the kwrest hash. `struct rb_callinfo_kwarg` is malloc'd, so no GC concerns. + let kwargs_start = asm.lea(asm.ctx.sp_opnd(-caller_keyword_len_i32 * SIZEOF_VALUE_I32)); + let hash = asm.ccall( + build_kw_rest as _, + vec![rest_mask.into(), kwargs_start, Opnd::const_ptr(ci_kwarg.cast())] + ); + (hash, Type::THash) + }; - // Build the kwrest hash. `struct rb_callinfo_kwarg` is malloc'd, so no GC concerns. - let kwargs_start = asm.lea(asm.ctx.sp_opnd(-caller_keyword_len_i32 * SIZEOF_VALUE_I32)); - let kwrest = asm.ccall( - build_kw_rest as _, - vec![rest_mask.into(), kwargs_start, Opnd::const_ptr(ci_kwarg.cast())] - ); // The kwrest parameter sits after `unspecified_bits` if the callee specifies any // keywords. let stack_kwrest_idx = kwargs_stack_base - callee_kw_count_i32 - i32::from(callee_kw_count > 0); @@ -7394,7 +7417,7 @@ fn gen_iseq_kw_call( asm.ctx.dealloc_temp_reg(stack_kwrest.stack_idx()); asm.mov(stack_kwrest, kwrest); if stack_kwrest_idx >= 0 { - asm.ctx.set_opnd_mapping(stack_kwrest.into(), TempMapping::map_to_stack(Type::THash)); + asm.ctx.set_opnd_mapping(stack_kwrest.into(), TempMapping::map_to_stack(kwrest_type)); } } diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index d7bfc2a0c9..ec874df2a0 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1149,6 +1149,7 @@ extern "C" { pub fn rb_get_iseq_flags_has_kw(iseq: *const rb_iseq_t) -> bool; pub fn rb_get_iseq_flags_has_post(iseq: *const rb_iseq_t) -> bool; pub fn rb_get_iseq_flags_has_kwrest(iseq: *const rb_iseq_t) -> bool; + pub fn rb_get_iseq_flags_anon_kwrest(iseq: *const rb_iseq_t) -> bool; pub fn rb_get_iseq_flags_has_rest(iseq: *const rb_iseq_t) -> bool; pub fn rb_get_iseq_flags_ruby2_keywords(iseq: *const rb_iseq_t) -> bool; pub fn rb_get_iseq_flags_has_block(iseq: *const rb_iseq_t) -> bool; diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index 5a76c306d9..4211711180 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -544,6 +544,7 @@ make_counters! { objtostring_not_string, splatkw_not_hash, + splatkw_not_nil, binding_allocations, binding_set, |