@@ -1150,14 +1150,7 @@ def dupn(jit, ctx, asm)
11501150 # @param ctx [RubyVM::RJIT::Context]
11511151 # @param asm [RubyVM::RJIT::Assembler]
11521152 def swap ( jit , ctx , asm )
1153- stack0_mem = ctx . stack_opnd ( 0 )
1154- stack1_mem = ctx . stack_opnd ( 1 )
1155-
1156- asm . mov ( :rax , stack0_mem )
1157- asm . mov ( :rcx , stack1_mem )
1158- asm . mov ( stack0_mem , :rcx )
1159- asm . mov ( stack1_mem , :rax )
1160-
1153+ stack_swap ( jit , ctx , asm , 0 , 1 )
11611154 KeepCompiling
11621155 end
11631156
@@ -3243,6 +3236,16 @@ def lookup_cfunc_codegen(cme_def)
32433236 @cfunc_codegen_table [ cme_def . method_serial ]
32443237 end
32453238
3239+ def stack_swap ( _jit , ctx , asm , offset0 , offset1 )
3240+ stack0_mem = ctx . stack_opnd ( offset0 )
3241+ stack1_mem = ctx . stack_opnd ( offset1 )
3242+
3243+ asm . mov ( :rax , stack0_mem )
3244+ asm . mov ( :rcx , stack1_mem )
3245+ asm . mov ( stack0_mem , :rcx )
3246+ asm . mov ( stack1_mem , :rax )
3247+ end
3248+
32463249 def jit_getlocal_generic ( jit , ctx , asm , idx :, level :)
32473250 # Load environment pointer EP at level
32483251 ep_reg = :rax
@@ -4232,17 +4235,87 @@ def jit_call_iseq(jit, ctx, asm, cme, calling, iseq, frame_type: nil, prev_ep: n
42324235 end
42334236
42344237 if doing_kw_call
4235- asm . incr_counter ( :send_iseq_kw_call )
4236- return CantCompile
4238+ # Here we're calling a method with keyword arguments and specifying
4239+ # keyword arguments at this call site.
4240+
4241+ # This struct represents the metadata about the callee-specified
4242+ # keyword parameters.
4243+ keyword = iseq . body . param . keyword
4244+ keyword_num = keyword . num
4245+ keyword_required_num = keyword . required_num
4246+
4247+ required_kwargs_filled = 0
4248+
4249+ if keyword_num > 30
4250+ # We have so many keywords that (1 << num) encoded as a FIXNUM
4251+ # (which shifts it left one more) no longer fits inside a 32-bit
4252+ # immediate.
4253+ asm . incr_counter ( :send_iseq_too_many_kwargs )
4254+ return CantCompile
4255+ end
4256+
4257+ # Check that the kwargs being passed are valid
4258+ if supplying_kws
4259+ # This is the list of keyword arguments that the callee specified
4260+ # in its initial declaration.
4261+ # SAFETY: see compile.c for sizing of this slice.
4262+ callee_kwargs = keyword_num . times . map { |i | keyword . table [ i ] }
4263+
4264+ # Here we're going to build up a list of the IDs that correspond to
4265+ # the caller-specified keyword arguments. If they're not in the
4266+ # same order as the order specified in the callee declaration, then
4267+ # we're going to need to generate some code to swap values around
4268+ # on the stack.
4269+ caller_kwargs = [ ]
4270+ kw_arg . keyword_len . times do |kwarg_idx |
4271+ sym = C . to_ruby ( kw_arg [ :keywords ] [ kwarg_idx ] )
4272+ caller_kwargs << C . rb_sym2id ( sym )
4273+ end
4274+
4275+ # First, we're going to be sure that the names of every
4276+ # caller-specified keyword argument correspond to a name in the
4277+ # list of callee-specified keyword parameters.
4278+ caller_kwargs . each do |caller_kwarg |
4279+ search_result = callee_kwargs . map . with_index . find { |kwarg , _ | kwarg == caller_kwarg }
4280+
4281+ case search_result
4282+ in nil
4283+ # If the keyword was never found, then we know we have a
4284+ # mismatch in the names of the keyword arguments, so we need to
4285+ # bail.
4286+ asm . incr_counter ( :send_iseq_kwargs_mismatch )
4287+ return CantCompile
4288+ in _ , callee_idx if callee_idx < keyword_required_num
4289+ # Keep a count to ensure all required kwargs are specified
4290+ required_kwargs_filled += 1
4291+ else
4292+ end
4293+ end
4294+ end
4295+ assert_equal ( true , required_kwargs_filled <= keyword_required_num )
4296+ if required_kwargs_filled != keyword_required_num
4297+ asm . incr_counter ( :send_iseq_kwargs_mismatch )
4298+ return CantCompile
4299+ end
42374300 end
42384301
42394302 # Check if we need the arg0 splat handling of vm_callee_setup_block_arg
42404303 arg_setup_block = ( block_handler == :captured ) # arg_setup_type: arg_setup_block (invokeblock)
42414304 block_arg0_splat = arg_setup_block && argc == 1 &&
42424305 iseq . body . param . flags . has_lead && !iseq . body . param . flags . ambiguous_param0
42434306 if block_arg0_splat
4244- asm . incr_counter ( :send_iseq_block_arg0_splat )
4245- return CantCompile
4307+ # If block_arg0_splat, we still need side exits after splat, but
4308+ # doing push_splat_args here disallows it. So bail out.
4309+ if flags & VM_CALL_ARGS_SPLAT != 0 && !iseq_has_rest
4310+ asm . incr_counter ( :invokeblock_iseq_arg0_args_splat )
4311+ return CantCompile
4312+ end
4313+ # The block_arg0_splat implementation is for the rb_simple_iseq_p case,
4314+ # but doing_kw_call means it's not a simple ISEQ.
4315+ if doing_kw_call
4316+ asm . incr_counter ( :invokeblock_iseq_arg0_has_kw )
4317+ return CantCompile
4318+ end
42464319 end
42474320 if flags & C ::VM_CALL_ARGS_SPLAT != 0 && !iseq_has_rest
42484321 splat_array_length = false
@@ -4373,8 +4446,131 @@ def jit_call_iseq(jit, ctx, asm, cme, calling, iseq, frame_type: nil, prev_ep: n
43734446 end
43744447
43754448 if doing_kw_call
4376- asm . incr_counter ( :send_iseq_kw_call )
4377- return CantCompile
4449+ # Here we're calling a method with keyword arguments and specifying
4450+ # keyword arguments at this call site.
4451+
4452+ # Number of positional arguments the callee expects before the first
4453+ # keyword argument
4454+ args_before_kw = required_num + opt_num
4455+
4456+ # This struct represents the metadata about the caller-specified
4457+ # keyword arguments.
4458+ ci_kwarg = calling . kwarg
4459+ caller_keyword_len = if ci_kwarg . nil?
4460+ 0
4461+ else
4462+ ci_kwarg . keyword_len
4463+ end
4464+
4465+ # This struct represents the metadata about the callee-specified
4466+ # keyword parameters.
4467+ keyword = iseq . body . param . keyword
4468+
4469+ asm . comment ( 'keyword args' )
4470+
4471+ # This is the list of keyword arguments that the callee specified
4472+ # in its initial declaration.
4473+ callee_kwargs = keyword . table
4474+ total_kwargs = keyword . num
4475+
4476+ # Here we're going to build up a list of the IDs that correspond to
4477+ # the caller-specified keyword arguments. If they're not in the
4478+ # same order as the order specified in the callee declaration, then
4479+ # we're going to need to generate some code to swap values around
4480+ # on the stack.
4481+ caller_kwargs = [ ]
4482+
4483+ caller_keyword_len . times do |kwarg_idx |
4484+ sym = C . to_ruby ( ci_kwarg [ :keywords ] [ kwarg_idx ] )
4485+ caller_kwargs << C . rb_sym2id ( sym )
4486+ end
4487+ kwarg_idx = caller_keyword_len
4488+
4489+ unspecified_bits = 0
4490+
4491+ keyword_required_num = keyword . required_num
4492+ ( keyword_required_num ...total_kwargs ) . each do |callee_idx |
4493+ already_passed = false
4494+ callee_kwarg = callee_kwargs [ callee_idx ]
4495+
4496+ caller_keyword_len . times do |caller_idx |
4497+ if caller_kwargs [ caller_idx ] == callee_kwarg
4498+ already_passed = true
4499+ break
4500+ end
4501+ end
4502+
4503+ unless already_passed
4504+ # Reserve space on the stack for each default value we'll be
4505+ # filling in (which is done in the next loop). Also increments
4506+ # argc so that the callee's SP is recorded correctly.
4507+ argc += 1
4508+ default_arg = ctx . stack_push
4509+
4510+ # callee_idx - keyword->required_num is used in a couple of places below.
4511+ req_num = keyword . required_num
4512+ extra_args = callee_idx - req_num
4513+
4514+ # VALUE default_value = keyword->default_values[callee_idx - keyword->required_num];
4515+ default_value = keyword . default_values [ extra_args ]
4516+
4517+ if default_value == Qundef
4518+ # Qundef means that this value is not constant and must be
4519+ # recalculated at runtime, so we record it in unspecified_bits
4520+ # (Qnil is then used as a placeholder instead of Qundef).
4521+ unspecified_bits |= 0x01 << extra_args
4522+ default_value = Qnil
4523+ end
4524+
4525+ asm . mov ( :rax , default_value )
4526+ asm . mov ( default_arg , :rax )
4527+
4528+ caller_kwargs [ kwarg_idx ] = callee_kwarg
4529+ kwarg_idx += 1
4530+ end
4531+ end
4532+
4533+ assert_equal ( kwarg_idx , total_kwargs )
4534+
4535+ # Next, we're going to loop through every keyword that was
4536+ # specified by the caller and make sure that it's in the correct
4537+ # place. If it's not we're going to swap it around with another one.
4538+ total_kwargs . times do |kwarg_idx |
4539+ callee_kwarg = callee_kwargs [ kwarg_idx ]
4540+
4541+ # If the argument is already in the right order, then we don't
4542+ # need to generate any code since the expected value is already
4543+ # in the right place on the stack.
4544+ if callee_kwarg == caller_kwargs [ kwarg_idx ]
4545+ next
4546+ end
4547+
4548+ # In this case the argument is not in the right place, so we
4549+ # need to find its position where it _should_ be and swap with
4550+ # that location.
4551+ ( ( kwarg_idx + 1 ) ...total_kwargs ) . each do |swap_idx |
4552+ if callee_kwarg == caller_kwargs [ swap_idx ]
4553+ # First we're going to generate the code that is going
4554+ # to perform the actual swapping at runtime.
4555+ offset0 = argc - 1 - swap_idx - args_before_kw
4556+ offset1 = argc - 1 - kwarg_idx - args_before_kw
4557+ stack_swap ( jit , ctx , asm , offset0 , offset1 )
4558+
4559+ # Next we're going to do some bookkeeping on our end so
4560+ # that we know the order that the arguments are
4561+ # actually in now.
4562+ caller_kwargs [ kwarg_idx ] , caller_kwargs [ swap_idx ] =
4563+ caller_kwargs [ swap_idx ] , caller_kwargs [ kwarg_idx ]
4564+
4565+ break
4566+ end
4567+ end
4568+ end
4569+
4570+ # Keyword arguments cause a special extra local variable to be
4571+ # pushed onto the stack that represents the parameters that weren't
4572+ # explicitly given a value and have a non-constant default.
4573+ asm . mov ( ctx . stack_opnd ( -1 ) , C . to_value ( unspecified_bits ) )
43784574 end
43794575
43804576 # Same as vm_callee_setup_block_arg_arg0_check and vm_callee_setup_block_arg_arg0_splat
@@ -4395,6 +4591,7 @@ def jit_call_iseq(jit, ctx, asm, cme, calling, iseq, frame_type: nil, prev_ep: n
43954591 local_size : num_locals ,
43964592 stack_max : iseq . body . stack_max ,
43974593 prev_ep :,
4594+ doing_kw_call :,
43984595 )
43994596
44004597 # Create a context for the callee
@@ -4953,12 +5150,11 @@ def jit_call_symbol(jit, ctx, asm, cme, calling, known_recv_class, flags)
49535150 # @param jit [RubyVM::RJIT::JITState]
49545151 # @param ctx [RubyVM::RJIT::Context]
49555152 # @param asm [RubyVM::RJIT::Assembler]
4956- def jit_push_frame ( jit , ctx , asm , cme , flags , argc , frame_type , block_handler , iseq : nil , local_size : 0 , stack_max : 0 , prev_ep : nil )
5153+ def jit_push_frame ( jit , ctx , asm , cme , flags , argc , frame_type , block_handler , iseq : nil , local_size : 0 , stack_max : 0 , prev_ep : nil , doing_kw_call : nil )
49575154 # Save caller SP and PC before pushing a callee frame for backtrace and side exits
49585155 asm . comment ( 'save SP to caller CFP' )
49595156 recv_idx = argc # blockarg is already popped
49605157 recv_idx += ( block_handler == :captured ) ? 0 : 1 # receiver is not on stack when captured->self is used
4961- # TODO: consider doing_kw_call
49625158 if iseq
49635159 # Skip setting this to SP register. This cfp->sp will be copied to SP on leave insn.
49645160 asm . lea ( :rax , ctx . sp_opnd ( C . VALUE . size * -recv_idx ) ) # Pop receiver and arguments to prepare for side exits
@@ -4977,7 +5173,8 @@ def jit_push_frame(jit, ctx, asm, cme, flags, argc, frame_type, block_handler, i
49775173 end
49785174
49795175 asm . comment ( 'set up EP with managing data' )
4980- ep_offset = ctx . sp_offset + local_size + 2
5176+ sp_offset = ctx . sp_offset + 3 + local_size + ( doing_kw_call ? 1 : 0 )
5177+ ep_offset = sp_offset - 1
49815178 # ep[-2]: cref_or_me
49825179 asm . mov ( :rax , cme . to_i )
49835180 asm . mov ( [ SP , C . VALUE . size * ( ep_offset - 2 ) ] , :rax )
@@ -5032,7 +5229,7 @@ def jit_push_frame(jit, ctx, asm, cme, flags, argc, frame_type, block_handler, i
50325229 asm . mov ( [ CFP , cfp_offset + C . rb_control_frame_t . offsetof ( :block_code ) ] , 0 )
50335230 # Update SP register only for ISEQ calls. SP-relative operations should be done above this.
50345231 sp_reg = iseq ? SP : :rax
5035- asm . lea ( sp_reg , [ SP , C . VALUE . size * ( ctx . sp_offset + local_size + 3 ) ] )
5232+ asm . lea ( sp_reg , [ SP , C . VALUE . size * sp_offset ] )
50365233 asm . mov ( [ CFP , cfp_offset + C . rb_control_frame_t . offsetof ( :sp ) ] , sp_reg )
50375234 asm . mov ( [ CFP , cfp_offset + C . rb_control_frame_t . offsetof ( :__bp__ ) ] , sp_reg ) # TODO: get rid of this!!
50385235
0 commit comments