summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bootstraptest/test_yjit.rb22
-rw-r--r--yjit.c1
-rw-r--r--yjit_codegen.c104
3 files changed, 99 insertions, 28 deletions
diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb
index 0a3aa81860..13d63b521c 100644
--- a/bootstraptest/test_yjit.rb
+++ b/bootstraptest/test_yjit.rb
@@ -2165,6 +2165,28 @@ assert_equal '[2]', %q{
5.times.map { default_expression(value: 2) }.uniq
}
+# constant default values on keywords
+assert_equal '[3]', %q{
+ def default_expression(value: 3)
+ value
+ end
+
+ 5.times.map { default_expression }.uniq
+}
+
+# non-constant default values on keywords
+assert_equal '[3]', %q{
+ def default_value
+ 3
+ end
+
+ def default_expression(value: default_value)
+ value
+ end
+
+ 5.times.map { default_expression }.uniq
+}
+
# attr_reader on frozen object
assert_equal 'false', %q{
class Foo
diff --git a/yjit.c b/yjit.c
index a0ac959d4f..cef7492e34 100644
--- a/yjit.c
+++ b/yjit.c
@@ -82,7 +82,6 @@ YJIT_DECLARE_COUNTERS(
send_iseq_arity_error,
send_iseq_only_keywords,
send_iseq_kwargs_req_and_opt_missing,
- send_iseq_kwargs_none_passed,
send_iseq_kwargs_mismatch,
send_iseq_complex_callee,
send_not_implemented_method,
diff --git a/yjit_codegen.c b/yjit_codegen.c
index 5a40edfe1c..327e74b811 100644
--- a/yjit_codegen.c
+++ b/yjit_codegen.c
@@ -3398,7 +3398,7 @@ rb_leaf_builtin_function(const rb_iseq_t *iseq)
}
static codegen_status_t
-gen_send_iseq(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const rb_callable_method_entry_t *cme, rb_iseq_t *block, const int32_t argc)
+gen_send_iseq(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const rb_callable_method_entry_t *cme, rb_iseq_t *block, int32_t argc)
{
const rb_iseq_t *iseq = def_iseq_ptr(cme->def);
@@ -3466,18 +3466,28 @@ gen_send_iseq(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const r
else if (rb_iseq_only_kwparam_p(iseq)) {
const int lead_num = iseq->body->param.lead_num;
- if (vm_ci_flag(ci) & VM_CALL_KWARG) {
- // Here we're calling a method with keyword arguments and specifying
- // keyword arguments at this call site.
+ doing_kw_call = true;
- // This struct represents the metadata about the caller-specified
- // keyword arguments.
- const struct rb_callinfo_kwarg *kw_arg = vm_ci_kwarg(ci);
+ // Here we're calling a method with keyword arguments and specifying
+ // keyword arguments at this call site.
- // This struct represents the metadata about the callee-specified
- // keyword parameters.
- const struct rb_iseq_param_keyword *keyword = iseq->body->param.keyword;
+ // This struct represents the metadata about the caller-specified
+ // keyword arguments.
+ const struct rb_callinfo_kwarg *kw_arg = vm_ci_kwarg(ci);
+ // This struct represents the metadata about the callee-specified
+ // keyword parameters.
+ const struct rb_iseq_param_keyword *keyword = iseq->body->param.keyword;
+
+ if (keyword->num > 30) {
+ // We have so many keywords that (1 << num) encoded as a FIXNUM
+ // (which shifts it left one more) no longer fits inside a 32-bit
+ // immediate.
+ GEN_COUNTER_INC(cb, send_iseq_complex_callee);
+ return YJIT_CANT_COMPILE;
+ }
+
+ if (vm_ci_flag(ci) & VM_CALL_KWARG) {
if ((kw_arg->keyword_len != keyword->num) || (lead_num != argc - kw_arg->keyword_len)) {
// Here the method being called specifies optional and required
// keyword arguments and the callee is not specifying every one
@@ -3519,16 +3529,19 @@ gen_send_iseq(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const r
return YJIT_CANT_COMPILE;
}
}
-
- doing_kw_call = true;
}
else if (argc == lead_num) {
// Here we are calling a method that accepts keyword arguments
// (optional or required) but we're not passing any keyword
// arguments at this call site
- GEN_COUNTER_INC(cb, send_iseq_kwargs_none_passed);
- return YJIT_CANT_COMPILE;
+ if (keyword->required_num != 0) {
+ // If any of the keywords are required this is a mismatch
+ GEN_COUNTER_INC(cb, send_iseq_kwargs_mismatch);
+ return YJIT_CANT_COMPILE;
+ }
+
+ doing_kw_call = true;
}
else {
GEN_COUNTER_INC(cb, send_iseq_complex_callee);
@@ -3593,16 +3606,26 @@ gen_send_iseq(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const r
// This struct represents the metadata about the caller-specified
// keyword arguments.
- const struct rb_callinfo_kwarg *kw_arg = vm_ci_kwarg(ci);
+ int caller_keyword_len = 0;
+ const VALUE *caller_keywords = NULL;
+ if (vm_ci_kwarg(ci)) {
+ caller_keyword_len = vm_ci_kwarg(ci)->keyword_len;
+ caller_keywords = &vm_ci_kwarg(ci)->keywords[0];
+ }
// This struct represents the metadata about the callee-specified
// keyword parameters.
const struct rb_iseq_param_keyword *keyword = iseq->body->param.keyword;
// Note: we are about to do argument shuffling for a keyword argument
- // call. The various checks for whether we can do it happened earlier
- // in this function.
- RUBY_ASSERT((kw_arg->keyword_len == keyword->num) && (lead_num == argc - kw_arg->keyword_len));
+ // call. Assert that we are in one of currently supported cases: either
+ // all keywords are provided or they are all the default values.
+ // The various checks for whether we can do it happened earlier in this
+ // function.
+ RUBY_ASSERT(
+ ((caller_keyword_len == keyword->num) &&
+ (lead_num == argc - caller_keyword_len)) ||
+ (caller_keyword_len == 0 && lead_num == argc));
// This is the list of keyword arguments that the callee specified
// in its initial declaration.
@@ -3613,14 +3636,14 @@ gen_send_iseq(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const r
// same order as the order specified in the callee declaration, then
// we're going to need to generate some code to swap values around
// on the stack.
- ID *caller_kwargs = ALLOCA_N(VALUE, kw_arg->keyword_len);
- for (int kwarg_idx = 0; kwarg_idx < kw_arg->keyword_len; kwarg_idx++)
- caller_kwargs[kwarg_idx] = SYM2ID(kw_arg->keywords[kwarg_idx]);
+ ID *caller_kwargs = ALLOCA_N(VALUE, caller_keyword_len);
+ for (int kwarg_idx = 0; kwarg_idx < caller_keyword_len; kwarg_idx++)
+ caller_kwargs[kwarg_idx] = SYM2ID(caller_keywords[kwarg_idx]);
// Next, we're going to loop through every keyword that was
// specified by the caller and make sure that it's in the correct
// place. If it's not we're going to swap it around with another one.
- for (int kwarg_idx = 0; kwarg_idx < kw_arg->keyword_len; kwarg_idx++) {
+ for (int kwarg_idx = 0; kwarg_idx < caller_keyword_len; kwarg_idx++) {
ID callee_kwarg = callee_kwargs[kwarg_idx];
// If the argument is already in the right order, then we don't
@@ -3631,7 +3654,7 @@ gen_send_iseq(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const r
// In this case the argument is not in the right place, so we
// need to find its position where it _should_ be and swap with
// that location.
- for (int swap_idx = kwarg_idx + 1; swap_idx < kw_arg->keyword_len; swap_idx++) {
+ for (int swap_idx = kwarg_idx + 1; swap_idx < caller_keyword_len; swap_idx++) {
if (callee_kwarg == caller_kwargs[swap_idx]) {
// First we're going to generate the code that is going
// to perform the actual swapping at runtime.
@@ -3649,12 +3672,39 @@ gen_send_iseq(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const r
}
}
+ int unspecified_bits = 0;
+
+ if (caller_keyword_len == 0) {
+ ADD_COMMENT(cb, "default kwarg values");
+
+ for (int callee_idx = 0; callee_idx < keyword->num; callee_idx++) {
+ // Reserve space on the stack for each default value we'll be
+ // filling in (which is done in the next loop). Also increments
+ // argc so that the callee's SP is recorded correctly.
+ argc++;
+ ctx_stack_push(ctx, TYPE_UNKNOWN);
+ }
+
+ for (int callee_idx = 0; callee_idx < keyword->num; callee_idx++) {
+ VALUE default_value = keyword->default_values[callee_idx];
+
+ if (default_value == Qundef) {
+ // Qundef means that this value is not constant and must be
+ // recalculated at runtime, so we record it in unspecified_bits
+ // (Qnil is then used as a placeholder instead of Qundef).
+ unspecified_bits |= 0x01 << callee_idx;
+ default_value = Qnil;
+ }
+
+ x86opnd_t stack_arg = ctx_stack_opnd(ctx, keyword->num - callee_idx - 1);
+ mov(cb, stack_arg, imm_opnd(default_value));
+ }
+ }
+
// Keyword arguments cause a special extra local variable to be
// pushed onto the stack that represents the parameters that weren't
- // explicitly given a value. Its value is a bitmap that corresponds
- // to the indices of the missing parameters. In this case since we
- // know every value was specified, we can just write the value 0.
- mov(cb, ctx_stack_opnd(ctx, -1), imm_opnd(INT2FIX(0)));
+ // explicitly given a value and have a non-constant default.
+ mov(cb, ctx_stack_opnd(ctx, -1), imm_opnd(INT2FIX(unspecified_bits)));
}
// Points to the receiver operand on the stack
x86opnd_t recv = ctx_stack_opnd(ctx, argc);