summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <[email protected]>2024-06-18 15:41:45 -0700
committerJeremy Evans <[email protected]>2024-07-10 16:38:06 -0700
commitbfba96a106ca4fd1323955974574ea402151270e (patch)
tree498216ddf3c71fa8656427a63ee7dde4e3181d4a
parent8c69caa495c7f74dcb29afeb34089a5457b5ba20 (diff)
Avoid a hash allocation when keyword splatting empty hash when calling ruby2_keywords method
Treat this similar to keyword splatting nil, using goto ignore. However, keep previous behavior if the method accepts a keyword splat, to avoid double hash allocation. This also can avoid an array allocation when calling a method that doesn't have any splat parameters but supports literal keyword parameters, because ignore_keyword_hash_p was not ignoring the keyword hash in that case. This change doesn't remove the empty ruby2_keywords hash from the array, which caused an assertion failure if the method being called accepted keywords in some cases. Modify the assertion to handle this case. An alternative approach would add a flag to the args struct so the args_argc calculation could handle this case and report the correct argc, but such an approach would likely be slower.
-rw-r--r--test/ruby/test_allocation.rb12
-rw-r--r--vm_args.c17
2 files changed, 22 insertions, 7 deletions
diff --git a/test/ruby/test_allocation.rb b/test/ruby/test_allocation.rb
index 6534c123fb..fa12bf481b 100644
--- a/test/ruby/test_allocation.rb
+++ b/test/ruby/test_allocation.rb
@@ -273,7 +273,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})")
check_allocations(1, 0, "keyword(*empty_array, *empty_array, **empty_hash#{block})")
- check_allocations(1, 0, "keyword(*r2k_empty_array#{block})")
+ check_allocations(0, 0, "keyword(*r2k_empty_array#{block})")
check_allocations(1, 1, "keyword(*r2k_array#{block})")
check_allocations(0, 1, "keyword(*empty_array, a: 2, **empty_hash#{block})")
@@ -379,7 +379,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(1, 1, "required_and_keyword(*array1, *empty_array, a: 2, **empty_hash#{block})")
check_allocations(1, 1, "required_and_keyword(*array1, *empty_array, **hash1, **empty_hash#{block})")
- check_allocations(1, 0, "required_and_keyword(*r2k_empty_array1#{block})")
+ check_allocations(0, 0, "required_and_keyword(*r2k_empty_array1#{block})")
check_allocations(1, 1, "required_and_keyword(*r2k_array1#{block})")
# Currently allocates 1 array unnecessarily due to splatarray true
@@ -724,7 +724,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(1, 1, "r2k(1, **empty_hash, a: 2#{block})")
check_allocations(1, 0, "r2k(1, **nil#{block})")
- check_allocations(1, 1, "r2k(1, **empty_hash#{block})")
+ check_allocations(1, 0, "r2k(1, **empty_hash#{block})")
check_allocations(1, 1, "r2k(1, **hash1#{block})")
check_allocations(1, 1, "r2k(1, *empty_array, **hash1#{block})")
check_allocations(1, 1, "r2k(1, **hash1, **empty_hash#{block})")
@@ -732,17 +732,17 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(1, 0, "r2k(1, *empty_array#{block})")
check_allocations(1, 1, "r2k(1, **hash1, **empty_hash#{block})")
- check_allocations(1, 1, "r2k(1, *empty_array, *empty_array, **empty_hash#{block})")
+ check_allocations(1, 0, "r2k(1, *empty_array, *empty_array, **empty_hash#{block})")
check_allocations(1, 1, "r2k(*array1, a: 2#{block})")
check_allocations(1, 0, "r2k(*array1, **nill#{block})")
- check_allocations(1, 1, "r2k(*array1, **empty_hash#{block})")
+ check_allocations(1, 0, "r2k(*array1, **empty_hash#{block})")
check_allocations(1, 1, "r2k(*array1, **hash1#{block})")
check_allocations(1, 1, "r2k(*array1, *empty_array, **hash1#{block})")
check_allocations(1, 0, "r2k(*array1, *empty_array#{block})")
- check_allocations(1, 1, "r2k(*array1, *empty_array, **empty_hash#{block})")
+ check_allocations(1, 0, "r2k(*array1, *empty_array, **empty_hash#{block})")
check_allocations(1, 1, "r2k(*array1, *empty_array, a: 2, **empty_hash#{block})")
check_allocations(1, 1, "r2k(*array1, *empty_array, **hash1, **empty_hash#{block})")
diff --git a/vm_args.c b/vm_args.c
index b4a8fcb8fb..90105f6900 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -535,6 +535,10 @@ ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq, unsigned
}
}
+ if (RHASH_EMPTY_P(keyword_hash) && !ISEQ_BODY(iseq)->param.flags.has_kwrest) {
+ goto ignore;
+ }
+
if (!(*kw_flag & VM_CALL_KW_SPLAT_MUT) &&
(ISEQ_BODY(iseq)->param.flags.has_kwrest ||
ISEQ_BODY(iseq)->param.flags.ruby2_keywords)) {
@@ -856,7 +860,18 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
args_setup_kw_parameters_from_kwsplat(ec, iseq, keyword_hash, klocals, remove_hash_value);
}
else {
- VM_ASSERT(args_argc(args) == 0);
+#if VM_CHECK_MODE > 0
+ if (args_argc(args) != 0) {
+ VM_ASSERT(ci_flag & VM_CALL_ARGS_SPLAT);
+ VM_ASSERT(!(ci_flag & (VM_CALL_KWARG | VM_CALL_KW_SPLAT | VM_CALL_KW_SPLAT_MUT)));
+ VM_ASSERT(!kw_flag);
+ VM_ASSERT(!ISEQ_BODY(iseq)->param.flags.has_rest);
+ VM_ASSERT(RARRAY_LENINT(args->rest) > 0);
+ VM_ASSERT(RB_TYPE_P(rest_last, T_HASH));
+ VM_ASSERT(FL_TEST_RAW(rest_last, RHASH_PASS_AS_KEYWORDS));
+ VM_ASSERT(args_argc(args) == 1);
+ }
+#endif
args_setup_kw_parameters(ec, iseq, NULL, 0, NULL, klocals);
}
}