1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/tool/ruby_vm/views/mjit_compile.inc.erb
Takashi Kokubun a5073c053f
Always correct sp on leave cancel
Even if local stack optimization is not used and values are written to
VM stack, the stack pointer itself may not be moved properly. So this
should be always moved on JIT cancellation.

By the way it's hard to write a test for this because if we try to
generate an interrupt, it will be a method call and it consumes the
interrupt by itself on popping a frame.
2020-05-06 20:26:03 -07:00

101 lines
4.5 KiB
Text

/* -*- C -*- */
% # Copyright (c) 2018 Takashi Kokubun. All rights reserved.
% #
% # This file is a part of the programming language Ruby. Permission is hereby
% # granted, to either redistribute and/or modify this file, provided that the
% # conditions mentioned in the file COPYING are met. Consult the file for
% # details.
<%= render 'copyright' %>
%
% # This is an ERB template that generates Ruby code that generates C code that
% # generates JIT-ed C code.
<%= render 'notice', locals: {
this_file: 'is the main part of compile_insn() in mjit_compile.c',
edit: __FILE__,
} -%>
%
% unsupported_insns = [
% 'defineclass', # low priority
% 'opt_call_c_function', # low priority
% ]
%
% opt_send_without_block = RubyVM::Instructions.find { |i| i.name == 'opt_send_without_block' }
% if opt_send_without_block.nil?
% raise 'opt_send_without_block not found'
% end
%
% send_compatible_opt_insns = RubyVM::BareInstructions.to_a.select do |insn|
% insn.name.start_with?('opt_') && opt_send_without_block.opes == insn.opes &&
% insn.expr.expr.lines.any? { |l| l.match(/\A\s+CALL_SIMPLE_METHOD\(\);\s+\z/) }
% end.map(&:name)
%
% # Available variables and macros in JIT-ed function:
% # ec: the first argument of _mjitXXX
% # reg_cfp: the second argument of _mjitXXX
% # GET_CFP(): refers to `reg_cfp`
% # GET_EP(): refers to `reg_cfp->ep`
% # GET_SP(): refers to `reg_cfp->sp`, or `(stack + stack_size)` if local_stack_p
% # GET_SELF(): refers to `reg_cfp->self`
% # GET_LEP(): refers to `VM_EP_LEP(reg_cfp->ep)`
% # EXEC_EC_CFP(): refers to `val = vm_exec(ec, TRUE)` with frame setup
% # CALL_METHOD(): using `GET_CFP()` and `EXEC_EC_CFP()`
% # TOPN(): refers to `reg_cfp->sp`, or `*(stack + (stack_size - num - 1))` if local_stack_p
% # STACK_ADDR_FROM_TOP(): refers to `reg_cfp->sp`, or `stack + (stack_size - num)` if local_stack_p
% # DISPATCH_ORIGINAL_INSN(): expanded in _mjit_compile_insn.erb
% # THROW_EXCEPTION(): specially defined for JIT
% # RESTORE_REGS(): specially defined for `leave`
switch (insn) {
% (RubyVM::BareInstructions.to_a + RubyVM::OperandsUnifications.to_a).each do |insn|
% next if unsupported_insns.include?(insn.name)
case BIN(<%= insn.name %>):
% # Instruction-specific behavior in JIT
% case insn.name
% when 'opt_send_without_block', 'send'
<%= render 'mjit_compile_send', locals: { insn: insn } -%>
% when *send_compatible_opt_insns
% # To avoid cancel, just emit `opt_send_without_block` instead of `opt_*` insn if call cache is populated.
% cd_index = insn.opes.index { |o| o.fetch(:type) == 'CALL_DATA' }
if (has_valid_method_type(captured_cc_entries(status)[call_data_index((CALL_DATA)operands[<%= cd_index %>], body)])) {
<%= render 'mjit_compile_send', locals: { insn: opt_send_without_block } -%>
<%= render 'mjit_compile_insn', locals: { insn: opt_send_without_block } -%>
break;
}
% when 'getinstancevariable', 'setinstancevariable'
<%= render 'mjit_compile_ivar', locals: { insn: insn } -%>
% when 'leave'
if (b->stack_size != 1) {
if (mjit_opts.warnings || mjit_opts.verbose)
fprintf(stderr, "MJIT warning: Unexpected JIT stack_size on leave: %d\n", b->stack_size);
status->success = false;
}
% # Skip vm_pop_frame for inlined call
if (status->inlined_iseqs != NULL) { // the current ISeq is NOT being inlined
% # Cancel on interrupts to make leave insn leaf
fprintf(f, " if (UNLIKELY(RUBY_VM_INTERRUPTED_ANY(ec))) {\n");
fprintf(f, " reg_cfp->sp = vm_base_ptr(reg_cfp) + %d;\n", b->stack_size);
fprintf(f, " reg_cfp->pc = original_body_iseq + %d;\n", pos);
fprintf(f, " goto cancel;\n");
fprintf(f, " }\n");
fprintf(f, " ec->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(reg_cfp);\n"); // vm_pop_frame
}
fprintf(f, " return stack[0];\n");
b->stack_size += <%= insn.call_attribute('sp_inc') %>;
b->finish_p = TRUE;
break;
% end
%
% # Main insn implementation generated by insns.def
<%= render 'mjit_compile_insn', locals: { insn: insn } -%>
break;
% end
%
% # We don't support InstructionsUnifications yet because it's not used for now.
% # We don't support TraceInstructions yet. There is no blocker for it but it's just not implemented.
default:
if (mjit_opts.warnings || mjit_opts.verbose)
fprintf(stderr, "MJIT warning: Skipped to compile unsupported instruction: %s\n", insn_name(insn));
status->success = false;
break;
}