YJIT: Add support for **kwrest
parameters
Now that `...` uses `**kwrest` instead of regular splat and ruby2keywords, we need to support these type of methods to support `...` well.
This commit is contained in:
parent
7af97dc71f
commit
2131d04f43
@ -1532,6 +1532,34 @@ class TestYJIT < Test::Unit::TestCase
|
||||
RUBY
|
||||
end
|
||||
|
||||
def test_kwrest
|
||||
assert_compiles(<<~RUBY, result: true, no_send_fallbacks: true)
|
||||
def req_rest(r1:, **kwrest) = [r1, kwrest]
|
||||
def opt_rest(r1: 1.succ, **kwrest) = [r1, kwrest]
|
||||
def kwrest(**kwrest) = kwrest
|
||||
|
||||
def calls
|
||||
[
|
||||
[1, {}] == req_rest(r1: 1),
|
||||
[1, {:r2=>2, :r3=>3}] == req_rest(r1: 1, r2: 2, r3: 3),
|
||||
[1, {:r2=>2, :r3=>3}] == req_rest(r2: 2, r1:1, r3: 3),
|
||||
[1, {:r2=>2, :r3=>3}] == req_rest(r2: 2, r3: 3, r1: 1),
|
||||
|
||||
[2, {}] == opt_rest,
|
||||
[2, { r2: 2, r3: 3 }] == opt_rest(r2: 2, r3: 3),
|
||||
[0, { r2: 2, r3: 3 }] == opt_rest(r1: 0, r3: 3, r2: 2),
|
||||
[0, { r2: 2, r3: 3 }] == opt_rest(r2: 2, r1: 0, r3: 3),
|
||||
[1, { r2: 2, r3: 3 }] == opt_rest(r2: 2, r3: 3, r1: 1),
|
||||
|
||||
{} == kwrest,
|
||||
{ r0: 88, r1: 99 } == kwrest(r0: 88, r1: 99),
|
||||
]
|
||||
end
|
||||
|
||||
calls.all?
|
||||
RUBY
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def code_gc_helpers
|
||||
|
@ -196,8 +196,6 @@ fn main() {
|
||||
.allowlist_type("rb_call_data")
|
||||
.blocklist_type("rb_callcache.*") // Not used yet - opaque to make it easy to import rb_call_data
|
||||
.opaque_type("rb_callcache.*")
|
||||
.blocklist_type("rb_callinfo_kwarg") // Contains a VALUE[] array of undefined size, so we don't import
|
||||
.opaque_type("rb_callinfo_kwarg")
|
||||
.allowlist_type("rb_callinfo")
|
||||
|
||||
// From vm_insnhelper.h
|
||||
@ -267,6 +265,7 @@ fn main() {
|
||||
.allowlist_var("VM_BLOCK_HANDLER_NONE")
|
||||
.allowlist_type("vm_frame_env_flags")
|
||||
.allowlist_type("rb_seq_param_keyword_struct")
|
||||
.allowlist_type("rb_callinfo_kwarg")
|
||||
.allowlist_type("ruby_basic_operators")
|
||||
.allowlist_var(".*_REDEFINED_OP_FLAG")
|
||||
.allowlist_type("rb_num_t")
|
||||
|
@ -1174,11 +1174,11 @@ fn gen_swap(
|
||||
|
||||
fn stack_swap(
|
||||
asm: &mut Assembler,
|
||||
offset0: u16,
|
||||
offset1: u16,
|
||||
offset0: i32,
|
||||
offset1: i32,
|
||||
) {
|
||||
let stack0_mem = asm.stack_opnd(offset0 as i32);
|
||||
let stack1_mem = asm.stack_opnd(offset1 as i32);
|
||||
let stack0_mem = asm.stack_opnd(offset0);
|
||||
let stack1_mem = asm.stack_opnd(offset1);
|
||||
|
||||
let mapping0 = asm.ctx.get_opnd_mapping(stack0_mem.into());
|
||||
let mapping1 = asm.ctx.get_opnd_mapping(stack1_mem.into());
|
||||
@ -6412,7 +6412,8 @@ fn gen_send_iseq(
|
||||
// that the callee could use to know which keywords are unspecified
|
||||
// (see the `checkkeyword` instruction and check `ruby --dump=insn -e 'def foo(k:itself)=k'`).
|
||||
// We always need to set up this local if the call goes through.
|
||||
let doing_kw_call = unsafe { get_iseq_flags_has_kw(iseq) };
|
||||
let has_kwrest = unsafe { get_iseq_flags_has_kwrest(iseq) };
|
||||
let doing_kw_call = unsafe { get_iseq_flags_has_kw(iseq) } || has_kwrest;
|
||||
let supplying_kws = unsafe { vm_ci_flag(ci) & VM_CALL_KWARG } != 0;
|
||||
let iseq_has_rest = unsafe { get_iseq_flags_has_rest(iseq) };
|
||||
let iseq_has_block_param = unsafe { get_iseq_flags_has_block(iseq) };
|
||||
@ -6450,11 +6451,11 @@ fn gen_send_iseq(
|
||||
exit_if_stack_too_large(iseq)?;
|
||||
exit_if_tail_call(asm, ci)?;
|
||||
exit_if_has_post(asm, iseq)?;
|
||||
exit_if_has_kwrest(asm, iseq)?;
|
||||
exit_if_kw_splat(asm, flags)?;
|
||||
exit_if_has_rest_and_captured(asm, iseq_has_rest, captured_opnd)?;
|
||||
exit_if_has_rest_and_supplying_kws(asm, iseq_has_rest, iseq, supplying_kws)?;
|
||||
exit_if_supplying_kw_and_has_no_kw(asm, supplying_kws, iseq)?;
|
||||
exit_if_has_kwrest_and_captured(asm, has_kwrest, captured_opnd)?;
|
||||
exit_if_has_rest_and_supplying_kws(asm, iseq_has_rest, supplying_kws)?;
|
||||
exit_if_supplying_kw_and_has_no_kw(asm, supplying_kws, doing_kw_call)?;
|
||||
exit_if_supplying_kws_and_accept_no_kwargs(asm, supplying_kws, iseq)?;
|
||||
exit_if_doing_kw_and_splat(asm, doing_kw_call, flags)?;
|
||||
exit_if_wrong_number_arguments(asm, arg_setup_block, opts_filled, flags, opt_num, iseq_has_rest)?;
|
||||
@ -6481,10 +6482,9 @@ fn gen_send_iseq(
|
||||
}
|
||||
}
|
||||
|
||||
// Check that required keyword arguments are supplied and find any extras
|
||||
// that should go into the keyword rest parameter (**kw_rest).
|
||||
if doing_kw_call {
|
||||
// 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.
|
||||
let keyword = unsafe { get_iseq_body_param_keyword(iseq) };
|
||||
@ -6493,10 +6493,10 @@ fn gen_send_iseq(
|
||||
|
||||
let mut required_kwargs_filled = 0;
|
||||
|
||||
if keyword_num > 30 {
|
||||
if keyword_num > 30 || kw_arg_num > 64 {
|
||||
// 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.
|
||||
// immediate. Similarly, we use a u64 in case of keyword rest parameter.
|
||||
gen_counter_incr(asm, Counter::send_iseq_too_many_kwargs);
|
||||
return None;
|
||||
}
|
||||
@ -6506,7 +6506,11 @@ fn gen_send_iseq(
|
||||
// This is the list of keyword arguments that the callee specified
|
||||
// in its initial declaration.
|
||||
// SAFETY: see compile.c for sizing of this slice.
|
||||
let callee_kwargs = unsafe { slice::from_raw_parts((*keyword).table, keyword_num) };
|
||||
let callee_kwargs = if keyword_num == 0 {
|
||||
&[]
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts((*keyword).table, keyword_num) }
|
||||
};
|
||||
|
||||
// Here we're going to build up a list of the IDs that correspond to
|
||||
// the caller-specified keyword arguments. If they're not in the
|
||||
@ -6531,7 +6535,7 @@ fn gen_send_iseq(
|
||||
.find(|(_, &kwarg)| kwarg == caller_kwarg);
|
||||
|
||||
match search_result {
|
||||
None => {
|
||||
None if !has_kwrest => {
|
||||
// If the keyword was never found, then we know we have a
|
||||
// mismatch in the names of the keyword arguments, so we need to
|
||||
// bail.
|
||||
@ -6954,24 +6958,17 @@ fn gen_send_iseq(
|
||||
}
|
||||
}
|
||||
|
||||
// Keyword argument passing
|
||||
if doing_kw_call {
|
||||
// Here we're calling a method with keyword arguments and specifying
|
||||
// keyword arguments at this call site.
|
||||
|
||||
// Number of positional arguments the callee expects before the first
|
||||
// keyword argument
|
||||
let args_before_kw = required_num + opt_num;
|
||||
|
||||
// This struct represents the metadata about the caller-specified
|
||||
// keyword arguments.
|
||||
let ci_kwarg = unsafe { vm_ci_kwarg(ci) };
|
||||
let caller_keyword_len: usize = if ci_kwarg.is_null() {
|
||||
let caller_keyword_len_i32: i32 = if ci_kwarg.is_null() {
|
||||
0
|
||||
} else {
|
||||
unsafe { get_cikw_keyword_len(ci_kwarg) }
|
||||
.try_into()
|
||||
.unwrap()
|
||||
};
|
||||
let caller_keyword_len: usize = caller_keyword_len_i32.try_into().unwrap();
|
||||
|
||||
// This struct represents the metadata about the callee-specified
|
||||
// keyword parameters.
|
||||
@ -6982,114 +6979,189 @@ fn gen_send_iseq(
|
||||
// This is the list of keyword arguments that the callee specified
|
||||
// in its initial declaration.
|
||||
let callee_kwargs = unsafe { (*keyword).table };
|
||||
let total_kwargs: usize = unsafe { (*keyword).num }.try_into().unwrap();
|
||||
let callee_kw_count_i32: i32 = unsafe { (*keyword).num };
|
||||
let callee_kw_count: usize = callee_kw_count_i32.try_into().unwrap();
|
||||
let keyword_required_num: usize = unsafe { (*keyword).required_num }.try_into().unwrap();
|
||||
|
||||
// Here we're going to build up a list of the IDs that correspond to
|
||||
// the caller-specified keyword arguments. If they're not in the
|
||||
// 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.
|
||||
let mut caller_kwargs: Vec<ID> = vec![0; total_kwargs];
|
||||
|
||||
let mut kwargs_order: Vec<ID> = vec![0; cmp::max(caller_keyword_len, callee_kw_count)];
|
||||
for kwarg_idx in 0..caller_keyword_len {
|
||||
let sym = unsafe { get_cikw_keywords_idx(ci_kwarg, kwarg_idx.try_into().unwrap()) };
|
||||
caller_kwargs[kwarg_idx] = unsafe { rb_sym2id(sym) };
|
||||
kwargs_order[kwarg_idx] = unsafe { rb_sym2id(sym) };
|
||||
}
|
||||
let mut kwarg_idx = caller_keyword_len;
|
||||
|
||||
let mut unspecified_bits = 0;
|
||||
|
||||
let keyword_required_num: usize = unsafe { (*keyword).required_num }.try_into().unwrap();
|
||||
for callee_idx in keyword_required_num..total_kwargs {
|
||||
let mut already_passed = false;
|
||||
let callee_kwarg = unsafe { *(callee_kwargs.offset(callee_idx.try_into().unwrap())) };
|
||||
// Start by ensuring the stack is large enough for the callee
|
||||
for _ in caller_keyword_len..callee_kw_count {
|
||||
argc += 1;
|
||||
asm.stack_push(Type::Unknown);
|
||||
}
|
||||
// Now this is the stack_opnd() index to the 0th keyword argument.
|
||||
let kwargs_stack_base = kwargs_order.len() as i32 - 1;
|
||||
|
||||
for caller_idx in 0..caller_keyword_len {
|
||||
if caller_kwargs[caller_idx] == callee_kwarg {
|
||||
already_passed = true;
|
||||
break;
|
||||
// Build the keyword rest parameter hash before we make any changes to the order of
|
||||
// the supplied keyword arguments
|
||||
if has_kwrest {
|
||||
c_callable! {
|
||||
fn build_kw_rest(rest_mask: u64, stack_kwargs: *const VALUE, keywords: *const rb_callinfo_kwarg) -> VALUE {
|
||||
if keywords.is_null() {
|
||||
return unsafe { rb_hash_new() };
|
||||
}
|
||||
|
||||
// Use the total number of supplied keywords as a size upper bound
|
||||
let keyword_len = unsafe { (*keywords).keyword_len } as usize;
|
||||
let hash = unsafe { rb_hash_new_with_size(keyword_len as u64) };
|
||||
|
||||
// Put pairs into the kwrest hash as the mask describes
|
||||
for kwarg_idx in 0..keyword_len {
|
||||
if (rest_mask & (1 << kwarg_idx)) != 0 {
|
||||
unsafe {
|
||||
let keyword_symbol = (*keywords).keywords.as_ptr().add(kwarg_idx).read();
|
||||
let keyword_value = stack_kwargs.add(kwarg_idx).read();
|
||||
rb_hash_aset(hash, keyword_symbol, keyword_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
if !already_passed {
|
||||
// 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 += 1;
|
||||
let default_arg = asm.stack_push(Type::Unknown);
|
||||
asm_comment!(asm, "build kwrest hash");
|
||||
|
||||
// callee_idx - keyword->required_num is used in a couple of places below.
|
||||
let req_num: isize = unsafe { (*keyword).required_num }.try_into().unwrap();
|
||||
let callee_idx_isize: isize = callee_idx.try_into().unwrap();
|
||||
let extra_args = callee_idx_isize - req_num;
|
||||
|
||||
//VALUE default_value = keyword->default_values[callee_idx - keyword->required_num];
|
||||
let mut default_value = unsafe { *((*keyword).default_values.offset(extra_args)) };
|
||||
|
||||
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 << extra_args;
|
||||
default_value = Qnil;
|
||||
// Make a bit mask describing which keywords should go into kwrest.
|
||||
let mut rest_mask: u64 = 0;
|
||||
// Index for one argument that will go into kwrest.
|
||||
let mut rest_collected_idx = None;
|
||||
for (supplied_kw_idx, &supplied_kw) in kwargs_order.iter().take(caller_keyword_len).enumerate() {
|
||||
let mut found = false;
|
||||
for callee_idx in 0..callee_kw_count {
|
||||
let callee_kw = unsafe { callee_kwargs.add(callee_idx).read() };
|
||||
if callee_kw == supplied_kw {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
rest_mask |= 1 << supplied_kw_idx;
|
||||
if rest_collected_idx.is_none() {
|
||||
rest_collected_idx = Some(supplied_kw_idx as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
asm.mov(default_arg, default_value.into());
|
||||
// Save PC and SP before allocating
|
||||
jit_save_pc(jit, asm);
|
||||
gen_save_sp(asm);
|
||||
|
||||
caller_kwargs[kwarg_idx] = callee_kwarg;
|
||||
kwarg_idx += 1;
|
||||
// Build the kwrest hash. `struct rb_callinfo_kwarg` is malloc'd, so no GC concerns.
|
||||
let kwargs_start = asm.lea(asm.ctx.sp_opnd(-(kwargs_order.len() as i32 * SIZEOF_VALUE_I32) as isize));
|
||||
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);
|
||||
let stack_kwrest = asm.stack_opnd(stack_kwrest_idx);
|
||||
// If `stack_kwrest` already has another argument there, we need to stow it elsewhere
|
||||
// first before putting kwrest there. Use `rest_collected_idx` because that value went
|
||||
// into kwrest so the slot is now free.
|
||||
let kwrest_idx = callee_kw_count + usize::from(callee_kw_count > 0);
|
||||
if let (Some(rest_collected_idx), true) = (rest_collected_idx, kwrest_idx < kwargs_order.len()) {
|
||||
let rest_collected = asm.stack_opnd(kwargs_stack_base - rest_collected_idx);
|
||||
let mapping = asm.ctx.get_opnd_mapping(stack_kwrest.into());
|
||||
asm.mov(rest_collected, stack_kwrest);
|
||||
asm.ctx.set_opnd_mapping(rest_collected.into(), mapping);
|
||||
// Update our bookkeeping to inform the reordering step later.
|
||||
kwargs_order[rest_collected_idx as usize] = kwargs_order[kwrest_idx];
|
||||
kwargs_order[kwrest_idx] = 0;
|
||||
}
|
||||
// Put kwrest straight into memory, since we might pop it later
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
assert!(kwarg_idx == total_kwargs);
|
||||
|
||||
// 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 kwarg_idx in 0..total_kwargs {
|
||||
let kwarg_idx_isize: isize = kwarg_idx.try_into().unwrap();
|
||||
let callee_kwarg = unsafe { *(callee_kwargs.offset(kwarg_idx_isize)) };
|
||||
for kwarg_idx in 0..callee_kw_count {
|
||||
let callee_kwarg = unsafe { callee_kwargs.add(kwarg_idx).read() };
|
||||
|
||||
// If the argument is already in the right order, then we don't
|
||||
// need to generate any code since the expected value is already
|
||||
// in the right place on the stack.
|
||||
if callee_kwarg == caller_kwargs[kwarg_idx] {
|
||||
if callee_kwarg == kwargs_order[kwarg_idx] {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 swap_idx in (kwarg_idx + 1)..total_kwargs {
|
||||
if callee_kwarg == caller_kwargs[swap_idx] {
|
||||
for swap_idx in 0..kwargs_order.len() {
|
||||
if callee_kwarg == kwargs_order[swap_idx] {
|
||||
// First we're going to generate the code that is going
|
||||
// to perform the actual swapping at runtime.
|
||||
let swap_idx_i32: i32 = swap_idx.try_into().unwrap();
|
||||
let kwarg_idx_i32: i32 = kwarg_idx.try_into().unwrap();
|
||||
let offset0: u16 = (argc - 1 - swap_idx_i32 - args_before_kw)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let offset1: u16 = (argc - 1 - kwarg_idx_i32 - args_before_kw)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let offset0 = kwargs_stack_base - swap_idx_i32;
|
||||
let offset1 = kwargs_stack_base - kwarg_idx_i32;
|
||||
stack_swap(asm, offset0, offset1);
|
||||
|
||||
// Next we're going to do some bookkeeping on our end so
|
||||
// that we know the order that the arguments are
|
||||
// actually in now.
|
||||
caller_kwargs.swap(kwarg_idx, swap_idx);
|
||||
kwargs_order.swap(kwarg_idx, swap_idx);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that every caller specified kwarg is in the right place, filling
|
||||
// in unspecified default paramters won't overwrite anything.
|
||||
for kwarg_idx in keyword_required_num..callee_kw_count {
|
||||
if kwargs_order[kwarg_idx] != unsafe { callee_kwargs.add(kwarg_idx).read() } {
|
||||
let default_param_idx = kwarg_idx - keyword_required_num;
|
||||
let mut default_value = unsafe { (*keyword).default_values.add(default_param_idx).read() };
|
||||
|
||||
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 << default_param_idx;
|
||||
default_value = Qnil;
|
||||
}
|
||||
|
||||
let default_param = asm.stack_opnd(kwargs_stack_base - kwarg_idx as i32);
|
||||
let param_type = Type::from(default_value);
|
||||
asm.mov(default_param, default_value.into());
|
||||
asm.ctx.set_opnd_mapping(default_param.into(), TempMapping::map_to_stack(param_type));
|
||||
}
|
||||
}
|
||||
|
||||
// Pop extra arguments that went into kwrest now that they're at stack top
|
||||
if has_kwrest && caller_keyword_len > callee_kw_count {
|
||||
let extra_kwarg_count = caller_keyword_len - callee_kw_count;
|
||||
asm.stack_pop(extra_kwarg_count);
|
||||
argc = argc - extra_kwarg_count as i32;
|
||||
}
|
||||
|
||||
// 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 and have a non-constant default.
|
||||
let unspec_opnd = VALUE::fixnum_from_usize(unspecified_bits).as_u64();
|
||||
asm.ctx.dealloc_temp_reg(asm.stack_opnd(-1).stack_idx()); // avoid using a register for unspecified_bits
|
||||
asm.mov(asm.stack_opnd(-1), unspec_opnd.into());
|
||||
if callee_kw_count > 0 {
|
||||
let unspec_opnd = VALUE::fixnum_from_usize(unspecified_bits).as_u64();
|
||||
asm.ctx.dealloc_temp_reg(asm.stack_opnd(-1).stack_idx()); // avoid using a register for unspecified_bits
|
||||
asm.mov(asm.stack_opnd(-1), unspec_opnd.into());
|
||||
}
|
||||
}
|
||||
|
||||
// Same as vm_callee_setup_block_arg_arg0_check and vm_callee_setup_block_arg_arg0_splat
|
||||
@ -7326,11 +7398,6 @@ fn exit_if_has_post(asm: &mut Assembler, iseq: *const rb_iseq_t) -> Option<()> {
|
||||
exit_if(asm, unsafe { get_iseq_flags_has_post(iseq) }, Counter::send_iseq_has_post)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn exit_if_has_kwrest(asm: &mut Assembler, iseq: *const rb_iseq_t) -> Option<()> {
|
||||
exit_if(asm, unsafe { get_iseq_flags_has_kwrest(iseq) }, Counter::send_iseq_has_kwrest)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn exit_if_kw_splat(asm: &mut Assembler, flags: u32) -> Option<()> {
|
||||
exit_if(asm, flags & VM_CALL_KW_SPLAT != 0, Counter::send_iseq_kw_splat)
|
||||
@ -7342,22 +7409,31 @@ fn exit_if_has_rest_and_captured(asm: &mut Assembler, iseq_has_rest: bool, captu
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn exit_if_has_rest_and_supplying_kws(asm: &mut Assembler, iseq_has_rest: bool, iseq: *const rb_iseq_t, supplying_kws: bool) -> Option<()> {
|
||||
fn exit_if_has_kwrest_and_captured(asm: &mut Assembler, iseq_has_kwrest: bool, captured_opnd: Option<Opnd>) -> Option<()> {
|
||||
// We need to call a C function to allocate the kwrest hash, but also need to hold the captred
|
||||
// block across the call, which we can't do.
|
||||
exit_if(asm, iseq_has_kwrest && captured_opnd.is_some(), Counter::send_iseq_has_kwrest_and_captured)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn exit_if_has_rest_and_supplying_kws(asm: &mut Assembler, iseq_has_rest: bool, supplying_kws: bool) -> Option<()> {
|
||||
// There can be a gap between the rest parameter array and the supplied keywords, or
|
||||
// no space to put the rest array (e.g. `def foo(*arr, k:) = arr; foo(k: 1)` 1 is
|
||||
// sitting where the rest array should be).
|
||||
exit_if(
|
||||
asm,
|
||||
iseq_has_rest && unsafe { get_iseq_flags_has_kw(iseq) } && supplying_kws,
|
||||
iseq_has_rest && supplying_kws,
|
||||
Counter::send_iseq_has_rest_and_kw_supplied,
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn exit_if_supplying_kw_and_has_no_kw(asm: &mut Assembler, supplying_kws: bool, iseq: *const rb_iseq_t) -> Option<()> {
|
||||
// If we have keyword arguments being passed to a callee that only takes
|
||||
// positionals, then we need to allocate a hash. For now we're going to
|
||||
// call that too complex and bail.
|
||||
fn exit_if_supplying_kw_and_has_no_kw(asm: &mut Assembler, supplying_kws: bool, callee_kws: bool) -> Option<()> {
|
||||
// Passing keyword arguments to a callee means allocating a hash and treating
|
||||
// that as a positional argument. Bail for now.
|
||||
exit_if(
|
||||
asm,
|
||||
supplying_kws && !unsafe { get_iseq_flags_has_kw(iseq) },
|
||||
supplying_kws && !callee_kws,
|
||||
Counter::send_iseq_has_no_kw,
|
||||
)
|
||||
}
|
||||
|
@ -294,13 +294,6 @@ pub struct rb_callcache {
|
||||
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
|
||||
}
|
||||
|
||||
/// Opaque call-info type from vm_callinfo.h
|
||||
#[repr(C)]
|
||||
pub struct rb_callinfo_kwarg {
|
||||
_data: [u8; 0],
|
||||
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
|
||||
}
|
||||
|
||||
/// Opaque control_frame (CFP) struct from vm_core.h
|
||||
#[repr(C)]
|
||||
pub struct rb_control_frame_struct {
|
||||
|
@ -81,6 +81,36 @@ where
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
|
||||
impl<T> __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
pub const fn new() -> Self {
|
||||
__IncompleteArrayField(::std::marker::PhantomData, [])
|
||||
}
|
||||
#[inline]
|
||||
pub fn as_ptr(&self) -> *const T {
|
||||
self as *const _ as *const T
|
||||
}
|
||||
#[inline]
|
||||
pub fn as_mut_ptr(&mut self) -> *mut T {
|
||||
self as *mut _ as *mut T
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
|
||||
::std::slice::from_raw_parts(self.as_ptr(), len)
|
||||
}
|
||||
#[inline]
|
||||
pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
|
||||
::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
|
||||
}
|
||||
}
|
||||
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
|
||||
fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
fmt.write_str("__IncompleteArrayField")
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct __BindgenUnionField<T>(::std::marker::PhantomData<T>);
|
||||
impl<T> __BindgenUnionField<T> {
|
||||
#[inline]
|
||||
@ -636,6 +666,12 @@ pub const VM_CALL_ARGS_SPLAT_MUT_bit: vm_call_flag_bits = 12;
|
||||
pub const VM_CALL__END: vm_call_flag_bits = 13;
|
||||
pub type vm_call_flag_bits = u32;
|
||||
#[repr(C)]
|
||||
pub struct rb_callinfo_kwarg {
|
||||
pub keyword_len: ::std::os::raw::c_int,
|
||||
pub references: ::std::os::raw::c_int,
|
||||
pub keywords: __IncompleteArrayField<VALUE>,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct rb_callinfo {
|
||||
pub flags: VALUE,
|
||||
pub kwarg: *const rb_callinfo_kwarg,
|
||||
|
@ -380,7 +380,6 @@ make_counters! {
|
||||
send_iseq_kwargs_req_and_opt_missing,
|
||||
send_iseq_kwargs_mismatch,
|
||||
send_iseq_has_post,
|
||||
send_iseq_has_kwrest,
|
||||
send_iseq_has_no_kw,
|
||||
send_iseq_accepts_no_kwarg,
|
||||
send_iseq_materialized_block,
|
||||
@ -412,6 +411,7 @@ make_counters! {
|
||||
send_send_getter,
|
||||
send_send_builtin,
|
||||
send_iseq_has_rest_and_captured,
|
||||
send_iseq_has_kwrest_and_captured,
|
||||
send_iseq_has_rest_and_splat,
|
||||
send_iseq_has_rest_and_kw_supplied,
|
||||
send_iseq_has_rest_opt_and_block,
|
||||
|
Loading…
x
Reference in New Issue
Block a user