summaryrefslogtreecommitdiff
path: root/tool/ruby_vm/views/mjit_compile.inc.erb
blob: 6836d59a4fddd6b76cad06fc1971f5acaf1b8c6f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/* -*- 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_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_cache_for_send(captured_cc_entries(status)[call_data_index((CALL_DATA)operands[<%= cd_index %>], body)], BIN(<%= insn.name %>))) {
<%=       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 'invokebuiltin', 'opt_invokebuiltin_delegate'
    {
<%=   render 'mjit_compile_invokebuiltin', locals: { insn: insn } -%>
    }
%   when 'leave', 'opt_invokebuiltin_delegate_leave'
    {
%     # opt_invokebuiltin_delegate_leave also implements leave insn. We need to handle it here for inlining.
%     if insn.name == 'opt_invokebuiltin_delegate_leave'
<%=   render 'mjit_compile_invokebuiltin', locals: { insn: insn } -%>
%     else
      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;
      }
%     end
%     # 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, "        RB_DEBUG_COUNTER_INC(mjit_cancel_leave);\n");
          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;
}