mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
YJIT: Fancier opt_getinlinecache
Make sure `opt_getinlinecache` is in a block all on its own, and invalidate it from the interpreter when `opt_setinlinecache`. It will recompile with a filled cache the second time around. This lets YJIT runs well when the IC for constant is cold.
This commit is contained in:
parent
e81d1f4ae3
commit
b626dd7211
9 changed files with 146 additions and 73 deletions
34
compile.c
34
compile.c
|
@ -2259,6 +2259,7 @@ iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor)
|
||||||
VALUE *generated_iseq;
|
VALUE *generated_iseq;
|
||||||
rb_event_flag_t events = 0;
|
rb_event_flag_t events = 0;
|
||||||
long data = 0;
|
long data = 0;
|
||||||
|
long getinlinecache_idx = -1;
|
||||||
|
|
||||||
int insn_num, code_index, insns_info_index, sp = 0;
|
int insn_num, code_index, insns_info_index, sp = 0;
|
||||||
int stack_max = fix_sp_depth(iseq, anchor);
|
int stack_max = fix_sp_depth(iseq, anchor);
|
||||||
|
@ -2362,6 +2363,11 @@ iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor)
|
||||||
types = insn_op_types(insn);
|
types = insn_op_types(insn);
|
||||||
len = insn_len(insn);
|
len = insn_len(insn);
|
||||||
|
|
||||||
|
if (insn == BIN(opt_getinlinecache)) {
|
||||||
|
assert(getinlinecache_idx < 0 && "one get per set, no nesting");
|
||||||
|
getinlinecache_idx = code_index;
|
||||||
|
}
|
||||||
|
|
||||||
for (j = 0; types[j]; j++) {
|
for (j = 0; types[j]; j++) {
|
||||||
char type = types[j];
|
char type = types[j];
|
||||||
/* printf("--> [%c - (%d-%d)]\n", type, k, j); */
|
/* printf("--> [%c - (%d-%d)]\n", type, k, j); */
|
||||||
|
@ -2419,6 +2425,13 @@ iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor)
|
||||||
}
|
}
|
||||||
generated_iseq[code_index + 1 + j] = (VALUE)ic;
|
generated_iseq[code_index + 1 + j] = (VALUE)ic;
|
||||||
FL_SET(iseqv, ISEQ_MARKABLE_ISEQ);
|
FL_SET(iseqv, ISEQ_MARKABLE_ISEQ);
|
||||||
|
|
||||||
|
if (insn == BIN(opt_setinlinecache) && type == TS_IC) {
|
||||||
|
assert(getinlinecache_idx >= 0);
|
||||||
|
// Store index to the matching opt_getinlinecache on the IC for YJIT
|
||||||
|
ic->get_insn_idx = (unsigned)getinlinecache_idx;
|
||||||
|
getinlinecache_idx = -1;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TS_CALLDATA:
|
case TS_CALLDATA:
|
||||||
|
@ -11107,6 +11120,7 @@ ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecod
|
||||||
unsigned int code_index;
|
unsigned int code_index;
|
||||||
ibf_offset_t reading_pos = bytecode_offset;
|
ibf_offset_t reading_pos = bytecode_offset;
|
||||||
VALUE *code = ALLOC_N(VALUE, iseq_size);
|
VALUE *code = ALLOC_N(VALUE, iseq_size);
|
||||||
|
long getinlinecache_idx = -1;
|
||||||
|
|
||||||
struct rb_iseq_constant_body *load_body = iseq->body;
|
struct rb_iseq_constant_body *load_body = iseq->body;
|
||||||
struct rb_call_data *cd_entries = load_body->call_data;
|
struct rb_call_data *cd_entries = load_body->call_data;
|
||||||
|
@ -11114,13 +11128,22 @@ ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecod
|
||||||
|
|
||||||
for (code_index=0; code_index<iseq_size;) {
|
for (code_index=0; code_index<iseq_size;) {
|
||||||
/* opcode */
|
/* opcode */
|
||||||
const VALUE insn = code[code_index++] = ibf_load_small_value(load, &reading_pos);
|
const VALUE insn = code[code_index] = ibf_load_small_value(load, &reading_pos);
|
||||||
const char *types = insn_op_types(insn);
|
const char *types = insn_op_types(insn);
|
||||||
int op_index;
|
int op_index;
|
||||||
|
|
||||||
|
if (insn == BIN(opt_getinlinecache)) {
|
||||||
|
assert(getinlinecache_idx < 0 && "one get per set, no nesting");
|
||||||
|
getinlinecache_idx = code_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
code_index++;
|
||||||
|
|
||||||
/* operands */
|
/* operands */
|
||||||
for (op_index=0; types[op_index]; op_index++, code_index++) {
|
for (op_index=0; types[op_index]; op_index++, code_index++) {
|
||||||
switch (types[op_index]) {
|
char type = types[op_index];
|
||||||
|
switch (type) {
|
||||||
|
case TS_CDHASH:
|
||||||
case TS_VALUE:
|
case TS_VALUE:
|
||||||
{
|
{
|
||||||
VALUE op = ibf_load_small_value(load, &reading_pos);
|
VALUE op = ibf_load_small_value(load, &reading_pos);
|
||||||
|
@ -11168,6 +11191,13 @@ ibf_load_code(const struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t bytecod
|
||||||
{
|
{
|
||||||
VALUE op = ibf_load_small_value(load, &reading_pos);
|
VALUE op = ibf_load_small_value(load, &reading_pos);
|
||||||
code[code_index] = (VALUE)&is_entries[op];
|
code[code_index] = (VALUE)&is_entries[op];
|
||||||
|
|
||||||
|
if (insn == BIN(opt_setinlinecache) && type == TS_IC) {
|
||||||
|
assert(getinlinecache_idx >= 0);
|
||||||
|
// Store index to the matching opt_getinlinecache on the IC for YJIT
|
||||||
|
is_entries[op].ic_cache.get_insn_idx = (unsigned)getinlinecache_idx;
|
||||||
|
getinlinecache_idx = -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
FL_SET(iseqv, ISEQ_MARKABLE_ISEQ);
|
FL_SET(iseqv, ISEQ_MARKABLE_ISEQ);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -236,6 +236,9 @@ STATIC_ASSERT(sizeof_iseq_inline_constant_cache_entry,
|
||||||
|
|
||||||
struct iseq_inline_constant_cache {
|
struct iseq_inline_constant_cache {
|
||||||
struct iseq_inline_constant_cache_entry *entry;
|
struct iseq_inline_constant_cache_entry *entry;
|
||||||
|
// For YJIT: the index to the opt_getinlinecache instruction in the same iseq.
|
||||||
|
// It's set during compile time and constant once set.
|
||||||
|
unsigned get_insn_idx;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct iseq_inline_iv_cache_entry {
|
struct iseq_inline_iv_cache_entry {
|
||||||
|
|
|
@ -4743,6 +4743,9 @@ vm_ic_update(const rb_iseq_t *iseq, IC ic, VALUE val, const VALUE *reg_ep)
|
||||||
if (rb_ractor_shareable_p(val)) ice->flags |= IMEMO_CONST_CACHE_SHAREABLE;
|
if (rb_ractor_shareable_p(val)) ice->flags |= IMEMO_CONST_CACHE_SHAREABLE;
|
||||||
ruby_vm_const_missing_count = 0;
|
ruby_vm_const_missing_count = 0;
|
||||||
RB_OBJ_WRITE(iseq, &ic->entry, ice);
|
RB_OBJ_WRITE(iseq, &ic->entry, ice);
|
||||||
|
#ifndef MJIT_HEADER
|
||||||
|
yjit_constant_ic_update(iseq, ic);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
|
|
5
yjit.h
5
yjit.h
|
@ -5,9 +5,7 @@
|
||||||
#ifndef YJIT_H
|
#ifndef YJIT_H
|
||||||
#define YJIT_H 1
|
#define YJIT_H 1
|
||||||
|
|
||||||
#include "stddef.h"
|
#include "vm_core.h"
|
||||||
#include "stdint.h"
|
|
||||||
#include "stdbool.h"
|
|
||||||
#include "method.h"
|
#include "method.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -61,5 +59,6 @@ void rb_yjit_iseq_mark(const struct rb_iseq_constant_body *body);
|
||||||
void rb_yjit_iseq_update_references(const struct rb_iseq_constant_body *body);
|
void rb_yjit_iseq_update_references(const struct rb_iseq_constant_body *body);
|
||||||
void rb_yjit_iseq_free(const struct rb_iseq_constant_body *body);
|
void rb_yjit_iseq_free(const struct rb_iseq_constant_body *body);
|
||||||
void rb_yjit_before_ractor_spawn(void);
|
void rb_yjit_before_ractor_spawn(void);
|
||||||
|
void yjit_constant_ic_update(const rb_iseq_t *iseq, IC ic);
|
||||||
|
|
||||||
#endif // #ifndef YJIT_H
|
#endif // #ifndef YJIT_H
|
||||||
|
|
102
yjit_codegen.c
102
yjit_codegen.c
|
@ -43,7 +43,7 @@ jit_print_loc(jitstate_t* jit, const char* msg)
|
||||||
static int
|
static int
|
||||||
jit_get_opcode(jitstate_t* jit)
|
jit_get_opcode(jitstate_t* jit)
|
||||||
{
|
{
|
||||||
return opcode_at_pc(jit->iseq, jit->pc);
|
return yjit_opcode_at_pc(jit->iseq, jit->pc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the index of the next instruction
|
// Get the index of the next instruction
|
||||||
|
@ -147,7 +147,7 @@ yjit_gen_exit(jitstate_t *jit, ctx_t *ctx, codeblock_t *cb)
|
||||||
// Write back the old instruction at the exit PC
|
// Write back the old instruction at the exit PC
|
||||||
// Otherwise the interpreter may jump right back to the
|
// Otherwise the interpreter may jump right back to the
|
||||||
// JITted code we're trying to exit
|
// JITted code we're trying to exit
|
||||||
int exit_opcode = opcode_at_pc(jit->iseq, exit_pc);
|
int exit_opcode = yjit_opcode_at_pc(jit->iseq, exit_pc);
|
||||||
void* handler_addr = (void*)handler_table[exit_opcode];
|
void* handler_addr = (void*)handler_table[exit_opcode];
|
||||||
mov(cb, REG0, const_ptr_opnd(exit_pc));
|
mov(cb, REG0, const_ptr_opnd(exit_pc));
|
||||||
mov(cb, REG1, const_ptr_opnd(handler_addr));
|
mov(cb, REG1, const_ptr_opnd(handler_addr));
|
||||||
|
@ -255,9 +255,8 @@ yjit_entry_prologue(void)
|
||||||
return code_ptr;
|
return code_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Generate code to check for interrupts and take a side-exit
|
// Generate code to check for interrupts and take a side-exit
|
||||||
*/
|
|
||||||
static void
|
static void
|
||||||
yjit_check_ints(codeblock_t* cb, uint8_t* side_exit)
|
yjit_check_ints(codeblock_t* cb, uint8_t* side_exit)
|
||||||
{
|
{
|
||||||
|
@ -269,17 +268,36 @@ yjit_check_ints(codeblock_t* cb, uint8_t* side_exit)
|
||||||
jnz_ptr(cb, side_exit);
|
jnz_ptr(cb, side_exit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Generate a stubbed unconditional jump to the next bytecode instruction.
|
||||||
Compile a sequence of bytecode instructions for a given basic block version
|
// Blocks that are part of a guard chain can use this to share the same successor.
|
||||||
*/
|
static void
|
||||||
|
jit_jump_to_next_insn(jitstate_t *jit, const ctx_t *current_context)
|
||||||
|
{
|
||||||
|
// Reset the depth since in current usages we only ever jump to to
|
||||||
|
// chain_depth > 0 from the same instruction.
|
||||||
|
ctx_t reset_depth = *current_context;
|
||||||
|
reset_depth.chain_depth = 0;
|
||||||
|
|
||||||
|
blockid_t jump_block = { jit->iseq, jit_next_insn_idx(jit) };
|
||||||
|
|
||||||
|
// Generate the jump instruction
|
||||||
|
gen_direct_jump(
|
||||||
|
&reset_depth,
|
||||||
|
jump_block
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Compile a sequence of bytecode instructions for a given basic block version
|
||||||
void
|
void
|
||||||
yjit_gen_block(ctx_t* ctx, block_t* block, rb_execution_context_t* ec)
|
yjit_gen_block(ctx_t *ctx, block_t *block, rb_execution_context_t *ec)
|
||||||
{
|
{
|
||||||
RUBY_ASSERT(cb != NULL);
|
RUBY_ASSERT(cb != NULL);
|
||||||
RUBY_ASSERT(block != NULL);
|
RUBY_ASSERT(block != NULL);
|
||||||
|
|
||||||
const rb_iseq_t *iseq = block->blockid.iseq;
|
const rb_iseq_t *iseq = block->blockid.iseq;
|
||||||
uint32_t insn_idx = block->blockid.idx;
|
uint32_t insn_idx = block->blockid.idx;
|
||||||
|
const uint32_t starting_insn_idx = insn_idx;
|
||||||
|
|
||||||
// NOTE: if we are ever deployed in production, we
|
// NOTE: if we are ever deployed in production, we
|
||||||
// should probably just log an error and return NULL here,
|
// should probably just log an error and return NULL here,
|
||||||
|
@ -305,13 +323,21 @@ yjit_gen_block(ctx_t* ctx, block_t* block, rb_execution_context_t* ec)
|
||||||
|
|
||||||
// For each instruction to compile
|
// For each instruction to compile
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
// Get the current pc and opcode
|
||||||
|
VALUE *pc = yjit_iseq_pc_at_idx(iseq, insn_idx);
|
||||||
|
int opcode = yjit_opcode_at_pc(iseq, pc);
|
||||||
|
RUBY_ASSERT(opcode >= 0 && opcode < VM_INSTRUCTION_SIZE);
|
||||||
|
|
||||||
|
// opt_getinlinecache wants to be in a block all on its own. Cut the block short
|
||||||
|
// if we run into it. See gen_opt_getinlinecache for details.
|
||||||
|
if (opcode == BIN(opt_getinlinecache) && insn_idx > starting_insn_idx) {
|
||||||
|
jit_jump_to_next_insn(&jit, ctx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the current instruction
|
// Set the current instruction
|
||||||
jit.insn_idx = insn_idx;
|
jit.insn_idx = insn_idx;
|
||||||
jit.pc = iseq_pc_at_idx(iseq, insn_idx);
|
jit.pc = pc;
|
||||||
|
|
||||||
// Get the current opcode
|
|
||||||
int opcode = jit_get_opcode(&jit);
|
|
||||||
RUBY_ASSERT(opcode >= 0 && opcode < VM_INSTRUCTION_SIZE);
|
|
||||||
|
|
||||||
// Lookup the codegen function for this instruction
|
// Lookup the codegen function for this instruction
|
||||||
codegen_fn gen_fn = gen_fns[opcode];
|
codegen_fn gen_fn = gen_fns[opcode];
|
||||||
|
@ -322,8 +348,10 @@ yjit_gen_block(ctx_t* ctx, block_t* block, rb_execution_context_t* ec)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
//fprintf(stderr, "compiling %d: %s\n", insn_idx, insn_name(opcode));
|
if (0) {
|
||||||
//print_str(cb, insn_name(opcode));
|
fprintf(stderr, "compiling %d: %s\n", insn_idx, insn_name(opcode));
|
||||||
|
print_str(cb, insn_name(opcode));
|
||||||
|
}
|
||||||
|
|
||||||
// :count-placement:
|
// :count-placement:
|
||||||
// Count bytecode instructions that execute in generated code.
|
// Count bytecode instructions that execute in generated code.
|
||||||
|
@ -366,9 +394,8 @@ yjit_gen_block(ctx_t* ctx, block_t* block, rb_execution_context_t* ec)
|
||||||
if (YJIT_DUMP_MODE >= 2) {
|
if (YJIT_DUMP_MODE >= 2) {
|
||||||
// Dump list of compiled instrutions
|
// Dump list of compiled instrutions
|
||||||
fprintf(stderr, "Compiled the following for iseq=%p:\n", (void *)iseq);
|
fprintf(stderr, "Compiled the following for iseq=%p:\n", (void *)iseq);
|
||||||
for (uint32_t idx = block->blockid.idx; idx < insn_idx;)
|
for (uint32_t idx = block->blockid.idx; idx < insn_idx; ) {
|
||||||
{
|
int opcode = yjit_opcode_at_pc(iseq, yjit_iseq_pc_at_idx(iseq, idx));
|
||||||
int opcode = opcode_at_pc(iseq, iseq_pc_at_idx(iseq, idx));
|
|
||||||
fprintf(stderr, " %04d %s\n", idx, insn_name(opcode));
|
fprintf(stderr, " %04d %s\n", idx, insn_name(opcode));
|
||||||
idx += insn_len(opcode);
|
idx += insn_len(opcode);
|
||||||
}
|
}
|
||||||
|
@ -605,25 +632,6 @@ guard_self_is_heap(codeblock_t *cb, x86opnd_t self_opnd, uint8_t *side_exit, ctx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a stubbed unconditional jump to the next bytecode instruction.
|
|
||||||
// Blocks that are part of a guard chain can use this to share the same successor.
|
|
||||||
static void
|
|
||||||
jit_jump_to_next_insn(jitstate_t *jit, const ctx_t *current_context)
|
|
||||||
{
|
|
||||||
// Reset the depth since in current usages we only ever jump to to
|
|
||||||
// chain_depth > 0 from the same instruction.
|
|
||||||
ctx_t reset_depth = *current_context;
|
|
||||||
reset_depth.chain_depth = 0;
|
|
||||||
|
|
||||||
blockid_t jump_block = { jit->iseq, jit_next_insn_idx(jit) };
|
|
||||||
|
|
||||||
// Generate the jump instruction
|
|
||||||
gen_direct_jump(
|
|
||||||
&reset_depth,
|
|
||||||
jump_block
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gen_jnz_to_target0(codeblock_t *cb, uint8_t *target0, uint8_t *target1, uint8_t shape)
|
gen_jnz_to_target0(codeblock_t *cb, uint8_t *target0, uint8_t *target1, uint8_t shape)
|
||||||
{
|
{
|
||||||
|
@ -1918,6 +1926,7 @@ gen_leave(jitstate_t* jit, ctx_t* ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
RUBY_EXTERN rb_serial_t ruby_vm_global_constant_state;
|
RUBY_EXTERN rb_serial_t ruby_vm_global_constant_state;
|
||||||
|
|
||||||
static codegen_status_t
|
static codegen_status_t
|
||||||
gen_opt_getinlinecache(jitstate_t *jit, ctx_t *ctx)
|
gen_opt_getinlinecache(jitstate_t *jit, ctx_t *ctx)
|
||||||
{
|
{
|
||||||
|
@ -1927,16 +1936,11 @@ gen_opt_getinlinecache(jitstate_t *jit, ctx_t *ctx)
|
||||||
|
|
||||||
// See vm_ic_hit_p().
|
// See vm_ic_hit_p().
|
||||||
struct iseq_inline_constant_cache_entry *ice = ic->entry;
|
struct iseq_inline_constant_cache_entry *ice = ic->entry;
|
||||||
if (!ice) {
|
if (!ice || // cache not filled
|
||||||
// Cache not filled
|
ice->ic_serial != ruby_vm_global_constant_state || // cache out of date
|
||||||
return YJIT_CANT_COMPILE;
|
ice->ic_cref /* cache only valid for certain lexical scopes */) {
|
||||||
}
|
// In these cases, leave a block that unconditionally side exits
|
||||||
if (ice->ic_serial != ruby_vm_global_constant_state) {
|
// for the interpreter to invalidate.
|
||||||
// Cache miss at compile time.
|
|
||||||
return YJIT_CANT_COMPILE;
|
|
||||||
}
|
|
||||||
if (ice->ic_cref) {
|
|
||||||
// Only compile for caches that don't care about lexical scope.
|
|
||||||
return YJIT_CANT_COMPILE;
|
return YJIT_CANT_COMPILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1946,7 +1950,7 @@ gen_opt_getinlinecache(jitstate_t *jit, ctx_t *ctx)
|
||||||
|
|
||||||
// Invalidate output code on any and all constant writes
|
// Invalidate output code on any and all constant writes
|
||||||
// FIXME: This leaks when st_insert raises NoMemoryError
|
// FIXME: This leaks when st_insert raises NoMemoryError
|
||||||
if (!assume_stable_global_constant_state(jit->block)) return YJIT_CANT_COMPILE;
|
assume_stable_global_constant_state(jit->block);
|
||||||
|
|
||||||
x86opnd_t stack_top = ctx_stack_push(ctx, TYPE_UNKNOWN);
|
x86opnd_t stack_top = ctx_stack_push(ctx, TYPE_UNKNOWN);
|
||||||
jit_mov_gc_ptr(jit, cb, REG0, ice->value);
|
jit_mov_gc_ptr(jit, cb, REG0, ice->value);
|
||||||
|
|
19
yjit_core.c
19
yjit_core.c
|
@ -289,8 +289,8 @@ int ctx_diff(const ctx_t* src, const ctx_t* dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all blocks for a particular place in an iseq.
|
// Get all blocks for a particular place in an iseq.
|
||||||
static rb_yjit_block_array_t
|
rb_yjit_block_array_t
|
||||||
get_version_array(const rb_iseq_t *iseq, unsigned idx)
|
yjit_get_version_array(const rb_iseq_t *iseq, unsigned idx)
|
||||||
{
|
{
|
||||||
struct rb_iseq_constant_body *body = iseq->body;
|
struct rb_iseq_constant_body *body = iseq->body;
|
||||||
|
|
||||||
|
@ -305,7 +305,7 @@ get_version_array(const rb_iseq_t *iseq, unsigned idx)
|
||||||
// Count the number of block versions matching a given blockid
|
// Count the number of block versions matching a given blockid
|
||||||
static size_t get_num_versions(blockid_t blockid)
|
static size_t get_num_versions(blockid_t blockid)
|
||||||
{
|
{
|
||||||
return rb_darray_size(get_version_array(blockid.iseq, blockid.idx));
|
return rb_darray_size(yjit_get_version_array(blockid.iseq, blockid.idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep track of a block version. Block should be fully constructed.
|
// Keep track of a block version. Block should be fully constructed.
|
||||||
|
@ -364,7 +364,7 @@ add_block_version(blockid_t blockid, block_t* block)
|
||||||
// Retrieve a basic block version for an (iseq, idx) tuple
|
// Retrieve a basic block version for an (iseq, idx) tuple
|
||||||
block_t* find_block_version(blockid_t blockid, const ctx_t* ctx)
|
block_t* find_block_version(blockid_t blockid, const ctx_t* ctx)
|
||||||
{
|
{
|
||||||
rb_yjit_block_array_t versions = get_version_array(blockid.iseq, blockid.idx);
|
rb_yjit_block_array_t versions = yjit_get_version_array(blockid.iseq, blockid.idx);
|
||||||
|
|
||||||
// Best match found
|
// Best match found
|
||||||
block_t* best_version = NULL;
|
block_t* best_version = NULL;
|
||||||
|
@ -522,7 +522,7 @@ branch_stub_hit(const uint32_t branch_idx, const uint32_t target_idx, rb_executi
|
||||||
|
|
||||||
// Update the PC in the current CFP, because it
|
// Update the PC in the current CFP, because it
|
||||||
// may be out of sync in JITted code
|
// may be out of sync in JITted code
|
||||||
ec->cfp->pc = iseq_pc_at_idx(target.iseq, target.idx);
|
ec->cfp->pc = yjit_iseq_pc_at_idx(target.iseq, target.idx);
|
||||||
|
|
||||||
// Try to find an existing compiled version of this block
|
// Try to find an existing compiled version of this block
|
||||||
block_t* p_block = find_block_version(target, target_ctx);
|
block_t* p_block = find_block_version(target, target_ctx);
|
||||||
|
@ -846,7 +846,8 @@ void
|
||||||
invalidate_block_version(block_t* block)
|
invalidate_block_version(block_t* block)
|
||||||
{
|
{
|
||||||
ASSERT_vm_locking();
|
ASSERT_vm_locking();
|
||||||
rb_vm_barrier(); // Stop other ractors since we are going to patch machine code.
|
// TODO: want to assert that all other ractors are stopped here. Can't patch
|
||||||
|
// machine code that some other thread is running.
|
||||||
|
|
||||||
const rb_iseq_t *iseq = block->blockid.iseq;
|
const rb_iseq_t *iseq = block->blockid.iseq;
|
||||||
|
|
||||||
|
@ -854,7 +855,7 @@ invalidate_block_version(block_t* block)
|
||||||
// fprintf(stderr, "block=%p\n", block);
|
// fprintf(stderr, "block=%p\n", block);
|
||||||
|
|
||||||
// Remove this block from the version array
|
// Remove this block from the version array
|
||||||
rb_yjit_block_array_t versions = get_version_array(iseq, block->blockid.idx);
|
rb_yjit_block_array_t versions = yjit_get_version_array(iseq, block->blockid.idx);
|
||||||
RB_UNUSED_VAR(bool removed);
|
RB_UNUSED_VAR(bool removed);
|
||||||
removed = block_array_remove(versions, block);
|
removed = block_array_remove(versions, block);
|
||||||
RUBY_ASSERT(removed);
|
RUBY_ASSERT(removed);
|
||||||
|
@ -909,8 +910,8 @@ invalidate_block_version(block_t* block)
|
||||||
uint32_t idx = block->blockid.idx;
|
uint32_t idx = block->blockid.idx;
|
||||||
// FIXME: the following says "if", but it's unconditional.
|
// FIXME: the following says "if", but it's unconditional.
|
||||||
// If the block is an entry point, it needs to be unmapped from its iseq
|
// If the block is an entry point, it needs to be unmapped from its iseq
|
||||||
VALUE* entry_pc = iseq_pc_at_idx(iseq, idx);
|
VALUE* entry_pc = yjit_iseq_pc_at_idx(iseq, idx);
|
||||||
int entry_opcode = opcode_at_pc(iseq, entry_pc);
|
int entry_opcode = yjit_opcode_at_pc(iseq, entry_pc);
|
||||||
|
|
||||||
// TODO: unmap_addr2insn in yjit_iface.c? Maybe we can write a function to encompass this logic?
|
// TODO: unmap_addr2insn in yjit_iface.c? Maybe we can write a function to encompass this logic?
|
||||||
// Should check how it's used in exit and side-exit
|
// Should check how it's used in exit and side-exit
|
||||||
|
|
|
@ -235,6 +235,7 @@ block_t* gen_block_version(blockid_t blockid, const ctx_t* ctx, rb_execution_con
|
||||||
uint8_t* gen_entry_point(const rb_iseq_t *iseq, uint32_t insn_idx, rb_execution_context_t *ec);
|
uint8_t* gen_entry_point(const rb_iseq_t *iseq, uint32_t insn_idx, rb_execution_context_t *ec);
|
||||||
void yjit_free_block(block_t *block);
|
void yjit_free_block(block_t *block);
|
||||||
void yjit_branches_update_references(void);
|
void yjit_branches_update_references(void);
|
||||||
|
rb_yjit_block_array_t yjit_get_version_array(const rb_iseq_t *iseq, unsigned idx);
|
||||||
|
|
||||||
void gen_branch(
|
void gen_branch(
|
||||||
const ctx_t* src_ctx,
|
const ctx_t* src_ctx,
|
||||||
|
|
44
yjit_iface.c
44
yjit_iface.c
|
@ -65,7 +65,8 @@ cb_write_post_call_bytes(codeblock_t* cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the PC for a given index in an iseq
|
// Get the PC for a given index in an iseq
|
||||||
VALUE *iseq_pc_at_idx(const rb_iseq_t *iseq, uint32_t insn_idx)
|
VALUE *
|
||||||
|
yjit_iseq_pc_at_idx(const rb_iseq_t *iseq, uint32_t insn_idx)
|
||||||
{
|
{
|
||||||
RUBY_ASSERT(iseq != NULL);
|
RUBY_ASSERT(iseq != NULL);
|
||||||
RUBY_ASSERT(insn_idx < iseq->body->iseq_size);
|
RUBY_ASSERT(insn_idx < iseq->body->iseq_size);
|
||||||
|
@ -91,7 +92,7 @@ map_addr2insn(void *code_ptr, int insn)
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
opcode_at_pc(const rb_iseq_t *iseq, const VALUE *pc)
|
yjit_opcode_at_pc(const rb_iseq_t *iseq, const VALUE *pc)
|
||||||
{
|
{
|
||||||
const VALUE at_pc = *pc;
|
const VALUE at_pc = *pc;
|
||||||
if (FL_TEST_RAW((VALUE)iseq, ISEQ_TRANSLATED)) {
|
if (FL_TEST_RAW((VALUE)iseq, ISEQ_TRANSLATED)) {
|
||||||
|
@ -269,11 +270,9 @@ static st_table *blocks_assuming_stable_global_constant_state;
|
||||||
|
|
||||||
// Assume that the global constant state has not changed since call to this function.
|
// Assume that the global constant state has not changed since call to this function.
|
||||||
// Can raise NoMemoryError.
|
// Can raise NoMemoryError.
|
||||||
RBIMPL_ATTR_NODISCARD()
|
void
|
||||||
bool
|
|
||||||
assume_stable_global_constant_state(block_t *block) {
|
assume_stable_global_constant_state(block_t *block) {
|
||||||
st_insert(blocks_assuming_stable_global_constant_state, (st_data_t)block, 1);
|
st_insert(blocks_assuming_stable_global_constant_state, (st_data_t)block, 1);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -491,7 +490,7 @@ rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec)
|
||||||
if (code_ptr)
|
if (code_ptr)
|
||||||
{
|
{
|
||||||
// Map the code address to the corresponding opcode
|
// Map the code address to the corresponding opcode
|
||||||
int first_opcode = opcode_at_pc(iseq, &encoded[0]);
|
int first_opcode = yjit_opcode_at_pc(iseq, &encoded[0]);
|
||||||
map_addr2insn(code_ptr, first_opcode);
|
map_addr2insn(code_ptr, first_opcode);
|
||||||
encoded[0] = (VALUE)code_ptr;
|
encoded[0] = (VALUE)code_ptr;
|
||||||
}
|
}
|
||||||
|
@ -601,6 +600,39 @@ rb_yjit_constant_state_changed(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Callback from the opt_setinlinecache instruction in the interpreter
|
||||||
|
void
|
||||||
|
yjit_constant_ic_update(const rb_iseq_t *iseq, IC ic)
|
||||||
|
{
|
||||||
|
RB_VM_LOCK_ENTER();
|
||||||
|
rb_vm_barrier(); // Stop other ractors since we are going to patch machine code.
|
||||||
|
{
|
||||||
|
|
||||||
|
const struct rb_iseq_constant_body *const body = iseq->body;
|
||||||
|
VALUE *code = body->iseq_encoded;
|
||||||
|
|
||||||
|
// This should come from a running iseq, so direct threading translation
|
||||||
|
// should have been done
|
||||||
|
RUBY_ASSERT(FL_TEST((VALUE)iseq, ISEQ_TRANSLATED));
|
||||||
|
RUBY_ASSERT(ic->get_insn_idx < body->iseq_size);
|
||||||
|
RUBY_ASSERT(rb_vm_insn_addr2insn((const void *)code[ic->get_insn_idx]) == BIN(opt_getinlinecache));
|
||||||
|
|
||||||
|
// Find the matching opt_getinlinecache and invalidate all the blocks there
|
||||||
|
RUBY_ASSERT(insn_op_type(BIN(opt_getinlinecache), 1) == TS_IC);
|
||||||
|
if (ic == (IC)code[ic->get_insn_idx + 1 + 1]) {
|
||||||
|
rb_yjit_block_array_t getinlinecache_blocks = yjit_get_version_array(iseq, ic->get_insn_idx);
|
||||||
|
rb_darray_for(getinlinecache_blocks, i) {
|
||||||
|
block_t *block = rb_darray_get(getinlinecache_blocks, i);
|
||||||
|
invalidate_block_version(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RUBY_ASSERT(false && "ic->get_insn_diex not set properly");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RB_VM_LOCK_LEAVE();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
rb_yjit_before_ractor_spawn(void)
|
rb_yjit_before_ractor_spawn(void)
|
||||||
{
|
{
|
||||||
|
|
|
@ -85,9 +85,9 @@ RUBY_EXTERN struct rb_yjit_runtime_counters yjit_runtime_counters;
|
||||||
void cb_write_pre_call_bytes(codeblock_t* cb);
|
void cb_write_pre_call_bytes(codeblock_t* cb);
|
||||||
void cb_write_post_call_bytes(codeblock_t* cb);
|
void cb_write_post_call_bytes(codeblock_t* cb);
|
||||||
|
|
||||||
VALUE *iseq_pc_at_idx(const rb_iseq_t *iseq, uint32_t insn_idx);
|
void yjit_map_addr2insn(void *code_ptr, int insn);
|
||||||
void map_addr2insn(void *code_ptr, int insn);
|
VALUE *yjit_iseq_pc_at_idx(const rb_iseq_t *iseq, uint32_t insn_idx);
|
||||||
int opcode_at_pc(const rb_iseq_t *iseq, const VALUE *pc);
|
int yjit_opcode_at_pc(const rb_iseq_t *iseq, const VALUE *pc);
|
||||||
|
|
||||||
void check_cfunc_dispatch(VALUE receiver, struct rb_callinfo *ci, void *callee, rb_callable_method_entry_t *compile_time_cme);
|
void check_cfunc_dispatch(VALUE receiver, struct rb_callinfo *ci, void *callee, rb_callable_method_entry_t *compile_time_cme);
|
||||||
bool cfunc_needs_frame(const rb_method_cfunc_t *cfunc);
|
bool cfunc_needs_frame(const rb_method_cfunc_t *cfunc);
|
||||||
|
@ -95,7 +95,7 @@ bool cfunc_needs_frame(const rb_method_cfunc_t *cfunc);
|
||||||
RBIMPL_ATTR_NODISCARD() bool assume_bop_not_redefined(block_t *block, int redefined_flag, enum ruby_basic_operators bop);
|
RBIMPL_ATTR_NODISCARD() bool assume_bop_not_redefined(block_t *block, int redefined_flag, enum ruby_basic_operators bop);
|
||||||
void assume_method_lookup_stable(VALUE receiver_klass, const rb_callable_method_entry_t *cme, block_t *block);
|
void assume_method_lookup_stable(VALUE receiver_klass, const rb_callable_method_entry_t *cme, block_t *block);
|
||||||
RBIMPL_ATTR_NODISCARD() bool assume_single_ractor_mode(block_t *block);
|
RBIMPL_ATTR_NODISCARD() bool assume_single_ractor_mode(block_t *block);
|
||||||
RBIMPL_ATTR_NODISCARD() bool assume_stable_global_constant_state(block_t *block);
|
void assume_stable_global_constant_state(block_t *block);
|
||||||
|
|
||||||
// this function *must* return passed exit_pc
|
// this function *must* return passed exit_pc
|
||||||
const VALUE *rb_yjit_count_side_exit_op(const VALUE *exit_pc);
|
const VALUE *rb_yjit_count_side_exit_op(const VALUE *exit_pc);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue