summaryrefslogtreecommitdiff
path: root/mjit_compiler.c
blob: fccd6cfd2a0e8336a7c310d86de5ee7769f20e73 (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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/**********************************************************************

  mjit_compiler.c - MRI method JIT compiler

  Copyright (C) 2017 Takashi Kokubun <takashikkbn@gmail.com>.

**********************************************************************/

#include "ruby/internal/config.h" // defines USE_MJIT

#if USE_MJIT

#include "mjit_compiler.h"
#include "internal.h"
#include "internal/compile.h"
#include "internal/hash.h"
#include "internal/object.h"
#include "internal/variable.h"
#include "mjit.h"
#include "mjit_unit.h"
#include "yjit.h"
#include "vm_callinfo.h"
#include "vm_exec.h"
#include "vm_insnhelper.h"

#include "builtin.h"
#include "insns.inc"
#include "insns_info.inc"

struct case_dispatch_var {
    FILE *f;
    unsigned int base_pos;
    VALUE last_value;
};

static VALUE
rb_ptr(const char *type, const void *ptr)
{
    // TODO: cache constant
    VALUE rb_mMJIT = rb_const_get(rb_cRubyVM, rb_intern("MJIT"));
    VALUE rb_mC = rb_const_get(rb_mMJIT, rb_intern("C"));
    VALUE rb_type = rb_funcall(rb_mC, rb_intern(type), 0);
    return rb_funcall(rb_type, rb_intern("new"), 1, ULONG2NUM((size_t)ptr));
}

// Returns true if call cache is still not obsoleted and vm_cc_cme(cc)->def->type is available.
static bool
has_valid_method_type(CALL_CACHE cc)
{
    return vm_cc_cme(cc) != NULL;
}

// Returns true if iseq can use fastpath for setup, otherwise NULL. This becomes true in the same condition
// as CC_SET_FASTPATH (in vm_callee_setup_arg) is called from vm_call_iseq_setup.
static bool
fastpath_applied_iseq_p(const CALL_INFO ci, const CALL_CACHE cc, const rb_iseq_t *iseq)
{
    extern bool rb_simple_iseq_p(const rb_iseq_t *iseq);
    return iseq != NULL
        && !(vm_ci_flag(ci) & VM_CALL_KW_SPLAT) && rb_simple_iseq_p(iseq) // Top of vm_callee_setup_arg. In this case, opt_pc is 0.
        && vm_ci_argc(ci) == (unsigned int)ISEQ_BODY(iseq)->param.lead_num // exclude argument_arity_error (assumption: `calling->argc == ci->orig_argc` in send insns)
        && vm_call_iseq_optimizable_p(ci, cc); // CC_SET_FASTPATH condition
}

#define hidden_obj_p(obj) (!SPECIAL_CONST_P(obj) && !RBASIC(obj)->klass)

// TODO: Share this with iseq.c
static inline VALUE
obj_resurrect(VALUE obj)
{
    if (hidden_obj_p(obj)) {
        switch (BUILTIN_TYPE(obj)) {
          case T_STRING:
            obj = rb_str_resurrect(obj);
            break;
          case T_ARRAY:
            obj = rb_ary_resurrect(obj);
            break;
          case T_HASH:
            obj = rb_hash_resurrect(obj);
            break;
          default:
            break;
        }
    }
    return obj;
}

static int
cdhash_each(VALUE key, VALUE value, VALUE hash)
{
    rb_hash_aset(hash, obj_resurrect(key), value);
    return ST_CONTINUE;
}

#include "mjit_compile_attr.inc"

#if SIZEOF_LONG == SIZEOF_VOIDP
#define NUM2PTR(x) NUM2ULONG(x)
#define PTR2NUM(x) ULONG2NUM(x)
#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
#define NUM2PTR(x) NUM2ULL(x)
#define PTR2NUM(x) ULL2NUM(x)
#endif

extern int
mjit_capture_cc_entries(const struct rb_iseq_constant_body *compiled_iseq, const struct rb_iseq_constant_body *captured_iseq);

// Copy current is_entries and use it throughout the current compilation consistently.
// While ic->entry has been immutable since https://github.com/ruby/ruby/pull/3662,
// we still need this to avoid a race condition between entries and ivar_serial/max_ivar_index.
static void
mjit_capture_is_entries(const struct rb_iseq_constant_body *body, union iseq_inline_storage_entry *is_entries)
{
    if (is_entries == NULL)
        return;
    memcpy(is_entries, body->is_entries, sizeof(union iseq_inline_storage_entry) * ISEQ_IS_SIZE(body));
}

// Compile ISeq to C code in `f`. It returns true if it succeeds to compile.
bool
mjit_compile(FILE *f, const rb_iseq_t *iseq, const char *funcname, int id)
{
    bool original_call_p = mjit_call_p;
    mjit_call_p = false; // Avoid impacting JIT metrics by itself

    // TODO: initialize the constant in mjit_init and use it
    VALUE rb_mMJIT = rb_const_get(rb_cRubyVM, rb_intern("MJIT"));
    VALUE rb_mCompiler = rb_const_get(rb_mMJIT, rb_intern("Compiler"));
    bool success = RTEST(rb_funcall(rb_mCompiler, rb_intern("compile"), 4,
                PTR2NUM((VALUE)f), rb_ptr("rb_iseq_t", iseq), rb_str_new_cstr(funcname), INT2NUM(id)));

    mjit_call_p = original_call_p;
    return success;
}

//
// Primitive.methods
//

static VALUE
cdhash_to_hash(rb_execution_context_t *ec, VALUE self, VALUE cdhash_addr)
{
    VALUE hash = rb_hash_new();
    rb_hash_foreach((VALUE)NUM2PTR(cdhash_addr), cdhash_each, hash);
    return hash;
}

static VALUE
builtin_compile(rb_execution_context_t *ec, VALUE self, VALUE f_addr, VALUE bf_addr, VALUE index, VALUE stack_size, VALUE builtin_inline_p)
{
    FILE *f = (FILE *)NUM2PTR(f_addr);
    RB_BUILTIN bf = (RB_BUILTIN)NUM2PTR(bf_addr);
    bf->compiler(f, NIL_P(index) ? -1 : NUM2LONG(index), NUM2UINT(stack_size), RTEST(builtin_inline_p));
    return Qnil;
}

// Returns true if MJIT thinks this cc's opt_* insn may fallback to opt_send_without_block.
static VALUE
has_cache_for_send(rb_execution_context_t *ec, VALUE self, VALUE cc_addr, VALUE insn)
{
    extern bool rb_vm_opt_cfunc_p(CALL_CACHE cc, int insn);
    CALL_CACHE cc = (CALL_CACHE)NUM2PTR(cc_addr);
    bool has_cache = has_valid_method_type(cc) &&
        !(vm_cc_cme(cc)->def->type == VM_METHOD_TYPE_CFUNC && rb_vm_opt_cfunc_p(cc, NUM2INT(insn)));
    return RBOOL(has_cache);
}

extern bool rb_splat_or_kwargs_p(const struct rb_callinfo *restrict ci);

#include "mjit_compiler.rbinc"
#include "mjit_instruction.rbinc"

#endif // USE_MJIT