diff --git a/benchmark/vm_call_kw_and_kw_splat.yml b/benchmark/vm_call_kw_and_kw_splat.yml new file mode 100644 index 00000000000000..aa6e549e0c4224 --- /dev/null +++ b/benchmark/vm_call_kw_and_kw_splat.yml @@ -0,0 +1,25 @@ +prelude: | + h1, h10, h100, h1000 = [1, 10, 100, 1000].map do |n| + h = {kw: 1} + n.times{|i| h[i.to_s.to_sym] = i} + h + end + eh = {} + def kw(kw: nil, **kws) end +benchmark: + 1: | + kw(**h1) + 1_mutable: | + kw(**eh, **h1) + 10: | + kw(**h10) + 10_mutable: | + kw(**eh, **h10) + 100: | + kw(**h100) + 100_mutable: | + kw(**eh, **h100) + 1000: | + kw(**h1000) + 1000_mutable: | + kw(**eh, **h1000) diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 956cc9c56da8ec..06bba60ac5c98b 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -4002,6 +4002,20 @@ def m(a: []) }, bug8964 end + def test_large_kwsplat_to_method_taking_kw_and_kwsplat + assert_separately(['-'], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + n = 100000 + x = Fiber.new do + h = {kw: 2} + n.times{|i| h[i.to_s.to_sym] = i} + def self.f(kw: 1, **kws) kws.size end + f(**h) + end.resume + assert_equal(n, x) + end; + end + def test_dynamic_symbol_keyword bug10266 = '[ruby-dev:48564] [Bug #10266]' assert_separately(['-', bug10266], "#{<<~"begin;"}\n#{<<~'end;'}") diff --git a/vm_args.c b/vm_args.c index aa8c2ec2993b72..28c0e339a36a7e 100644 --- a/vm_args.c +++ b/vm_args.c @@ -396,6 +396,83 @@ args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *cons locals[key_num] = unspecified_bits_value; } +static void +args_setup_kw_parameters_from_kwsplat(rb_execution_context_t *const ec, const rb_iseq_t *const iseq, + VALUE keyword_hash, VALUE *const locals) +{ + const ID *acceptable_keywords = ISEQ_BODY(iseq)->param.keyword->table; + const int req_key_num = ISEQ_BODY(iseq)->param.keyword->required_num; + const int key_num = ISEQ_BODY(iseq)->param.keyword->num; + const VALUE * const default_values = ISEQ_BODY(iseq)->param.keyword->default_values; + VALUE missing = 0; + int i, di; + int unspecified_bits = 0; + VALUE unspecified_bits_value = Qnil; + + for (i=0; i hash */ + int j; + unspecified_bits_value = rb_hash_new(); + + for (j=0; jparam.flags.has_kwrest) { + const int rest_hash_index = key_num + 1; + locals[rest_hash_index] = keyword_hash; + } + else { + if (!RHASH_EMPTY_P(keyword_hash)) { + argument_kw_error(ec, iseq, "unknown", rb_hash_keys(keyword_hash)); + } + } + + if (NIL_P(unspecified_bits_value)) { + unspecified_bits_value = INT2FIX(unspecified_bits); + } + locals[key_num] = unspecified_bits_value; +} + static inline void args_setup_kw_rest_parameter(VALUE keyword_hash, VALUE *locals, int kw_flag) { @@ -415,22 +492,6 @@ args_setup_block_parameter(const rb_execution_context_t *ec, struct rb_calling_i *locals = rb_vm_bh_to_procval(ec, block_handler); } -struct fill_values_arg { - VALUE *keys; - VALUE *vals; - int argc; -}; - -static int -fill_keys_values(st_data_t key, st_data_t val, st_data_t ptr) -{ - struct fill_values_arg *arg = (struct fill_values_arg *)ptr; - int i = arg->argc++; - arg->keys[i] = (VALUE)key; - arg->vals[i] = (VALUE)val; - return ST_CONTINUE; -} - static inline int ignore_keyword_hash_p(VALUE keyword_hash, const rb_iseq_t * const iseq, unsigned int * kw_flag, VALUE * converted_keyword_hash) { @@ -746,15 +807,8 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co args_setup_kw_parameters(ec, iseq, args->kw_argv, kw_arg->keyword_len, kw_arg->keywords, klocals); } else if (!NIL_P(keyword_hash)) { - int kw_len = rb_long2int(RHASH_SIZE(keyword_hash)); - struct fill_values_arg arg; - /* copy kw_argv */ - arg.keys = args->kw_argv = ALLOCA_N(VALUE, kw_len * 2); - arg.vals = arg.keys + kw_len; - arg.argc = 0; - rb_hash_foreach(keyword_hash, fill_keys_values, (VALUE)&arg); - VM_ASSERT(arg.argc == kw_len); - args_setup_kw_parameters(ec, iseq, arg.vals, kw_len, arg.keys, klocals); + keyword_hash = check_kwrestarg(keyword_hash, &kw_flag); + args_setup_kw_parameters_from_kwsplat(ec, iseq, keyword_hash, klocals); } else { VM_ASSERT(args_argc(args) == 0);