summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Wu <[email protected]>2024-02-15 11:59:37 -0500
committerGitHub <[email protected]>2024-02-15 11:59:37 -0500
commitda7b9478d33d212145f2b79e378cb617451f3a5b (patch)
tree07f2af41bc52301a3f9d70a294c55060775461d9
parente779c194b3520973e42e277ed4759a90b97795d0 (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.c6
-rw-r--r--yjit/bindgen/src/main.rs1
-rw-r--r--yjit/src/codegen.rs43
-rw-r--r--yjit/src/cruby_bindings.inc.rs1
-rw-r--r--yjit/src/stats.rs1
5 files changed, 42 insertions, 10 deletions
diff --git a/yjit.c b/yjit.c
index 4e9fed35ae..207974073b 100644
--- a/yjit.c
+++ b/yjit.c
@@ -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,