1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/yjit_core.h
Alan Wu 13d1ded253 YJIT: Make block invalidation more robust
This commit adds an entry_exit field to block_t for use in
invalidate_block_version(). By patching the start of the block while
invalidating it, invalidate_block_version() can function correctly
while there is no executable memory left for new branch stubs.

This change additionally fixes correctness for situations where we
cannot patch incoming jumps to the invalidated block. In situations
such as Shopify/yjit#226, the address to the start of the block
is saved and used later, possibly after the block is invalidated.

The assume_* family of function now generate block->entry_exit before
remembering blocks for invalidation.

RubyVM::YJIT.simulate_oom! is introduced for testing out of memory
conditions. The test for it is disabled for now because OOM triggers
other failure conditions not addressed by this commit.

Fixes Shopify/yjit#226
2021-11-22 18:23:28 -05:00

306 lines
8 KiB
C

#ifndef YJIT_CORE_H
#define YJIT_CORE_H 1
#include <stddef.h>
#include <stdint.h>
#include "yjit_asm.h"
// Callee-saved regs
#define REG_CFP R13
#define REG_EC R12
#define REG_SP RBX
// Scratch registers used by YJIT
#define REG0 RAX
#define REG0_32 EAX
#define REG0_8 AL
#define REG1 RCX
#define REG1_32 ECX
// Maximum number of temp value types we keep track of
#define MAX_TEMP_TYPES 8
// Maximum number of local variable types we keep track of
#define MAX_LOCAL_TYPES 8
// Default versioning context (no type information)
#define DEFAULT_CTX ( (ctx_t){ 0 } )
enum yjit_type_enum
{
ETYPE_UNKNOWN = 0,
ETYPE_NIL,
ETYPE_TRUE,
ETYPE_FALSE,
ETYPE_FIXNUM,
ETYPE_FLONUM,
ETYPE_ARRAY,
ETYPE_HASH,
ETYPE_SYMBOL,
ETYPE_STRING
};
// Represent the type of a value (local/stack/self) in YJIT
typedef struct yjit_type_struct
{
// Value is definitely a heap object
uint8_t is_heap : 1;
// Value is definitely an immediate
uint8_t is_imm : 1;
// Specific value type, if known
uint8_t type : 4;
} val_type_t;
STATIC_ASSERT(val_type_size, sizeof(val_type_t) == 1);
// Unknown type, could be anything, all zeroes
#define TYPE_UNKNOWN ( (val_type_t){ 0 } )
// Could be any heap object
#define TYPE_HEAP ( (val_type_t){ .is_heap = 1 } )
// Could be any immediate
#define TYPE_IMM ( (val_type_t){ .is_imm = 1 } )
#define TYPE_NIL ( (val_type_t){ .is_imm = 1, .type = ETYPE_NIL } )
#define TYPE_TRUE ( (val_type_t){ .is_imm = 1, .type = ETYPE_TRUE } )
#define TYPE_FALSE ( (val_type_t){ .is_imm = 1, .type = ETYPE_FALSE } )
#define TYPE_FIXNUM ( (val_type_t){ .is_imm = 1, .type = ETYPE_FIXNUM } )
#define TYPE_FLONUM ( (val_type_t){ .is_imm = 1, .type = ETYPE_FLONUM } )
#define TYPE_STATIC_SYMBOL ( (val_type_t){ .is_imm = 1, .type = ETYPE_SYMBOL } )
#define TYPE_ARRAY ( (val_type_t){ .is_heap = 1, .type = ETYPE_ARRAY } )
#define TYPE_HASH ( (val_type_t){ .is_heap = 1, .type = ETYPE_HASH } )
#define TYPE_STRING ( (val_type_t){ .is_heap = 1, .type = ETYPE_STRING } )
enum yjit_temp_loc
{
TEMP_STACK = 0,
TEMP_SELF,
TEMP_LOCAL, // Local with index
//TEMP_CONST, // Small constant (0, 1, 2, Qnil, Qfalse, Qtrue)
};
// Potential mapping of a value on the temporary stack to
// self, a local variable or constant so that we can track its type
typedef struct yjit_temp_mapping
{
// Where/how is the value stored?
uint8_t kind: 2;
// Index of the local variale,
// or small non-negative constant in [0, 63]
uint8_t idx : 6;
} temp_mapping_t;
STATIC_ASSERT(temp_mapping_size, sizeof(temp_mapping_t) == 1);
// By default, temps are just temps on the stack.
// Name conflict with an mmap flag. This is a struct instance,
// so the compiler will check for wrong usage.
#undef MAP_STACK
#define MAP_STACK ( (temp_mapping_t) { 0 } )
// Temp value is actually self
#define MAP_SELF ( (temp_mapping_t) { .kind = TEMP_SELF } )
// Represents both the type and mapping
typedef struct {
temp_mapping_t mapping;
val_type_t type;
} temp_type_mapping_t;
STATIC_ASSERT(temp_type_mapping_size, sizeof(temp_type_mapping_t) == 2);
// Operand to a bytecode instruction
typedef struct yjit_insn_opnd
{
// Indicates if the value is self
bool is_self;
// Index on the temporary stack (for stack operands only)
uint16_t idx;
} insn_opnd_t;
#define OPND_SELF ( (insn_opnd_t){ .is_self = true } )
#define OPND_STACK(stack_idx) ( (insn_opnd_t){ .is_self = false, .idx = stack_idx } )
/**
Code generation context
Contains information we can use to optimize code
*/
typedef struct yjit_context
{
// Number of values currently on the temporary stack
uint16_t stack_size;
// Offset of the JIT SP relative to the interpreter SP
// This represents how far the JIT's SP is from the "real" SP
int16_t sp_offset;
// Depth of this block in the sidechain (eg: inline-cache chain)
uint8_t chain_depth;
// Local variable types we keepp track of
val_type_t local_types[MAX_LOCAL_TYPES];
// Temporary variable types we keep track of
val_type_t temp_types[MAX_TEMP_TYPES];
// Type we track for self
val_type_t self_type;
// Mapping of temp stack entries to types we track
temp_mapping_t temp_mapping[MAX_TEMP_TYPES];
} ctx_t;
STATIC_ASSERT(yjit_ctx_size, sizeof(ctx_t) <= 32);
// Tuple of (iseq, idx) used to identify basic blocks
typedef struct BlockId
{
// Instruction sequence
const rb_iseq_t *iseq;
// Index in the iseq where the block starts
uint32_t idx;
} blockid_t;
// Null block id constant
static const blockid_t BLOCKID_NULL = { 0, 0 };
/// Branch code shape enumeration
typedef enum branch_shape
{
SHAPE_NEXT0, // Target 0 is next
SHAPE_NEXT1, // Target 1 is next
SHAPE_DEFAULT // Neither target is next
} branch_shape_t;
// Branch code generation function signature
typedef void (*branchgen_fn)(codeblock_t* cb, uint8_t* target0, uint8_t* target1, uint8_t shape);
/**
Store info about an outgoing branch in a code segment
Note: care must be taken to minimize the size of branch_t objects
*/
typedef struct yjit_branch_entry
{
// Block this is attached to
struct yjit_block_version *block;
// Positions where the generated code starts and ends
uint8_t* start_addr;
uint8_t* end_addr;
// Context right after the branch instruction
ctx_t src_ctx;
// Branch target blocks and their contexts
blockid_t targets[2];
ctx_t target_ctxs[2];
struct yjit_block_version *blocks[2];
// Jump target addresses
uint8_t* dst_addrs[2];
// Branch code generation function
branchgen_fn gen_fn;
// Shape of the branch
branch_shape_t shape : 2;
} branch_t;
// In case this block is invalidated, these two pieces of info
// help to remove all pointers to this block in the system.
typedef struct {
VALUE receiver_klass;
VALUE callee_cme;
} cme_dependency_t;
typedef rb_darray(cme_dependency_t) cme_dependency_array_t;
typedef rb_darray(branch_t*) branch_array_t;
typedef rb_darray(uint32_t) int32_array_t;
/**
Basic block version
Represents a portion of an iseq compiled with a given context
Note: care must be taken to minimize the size of block_t objects
*/
typedef struct yjit_block_version
{
// Bytecode sequence (iseq, idx) this is a version of
blockid_t blockid;
// Context at the start of the block
ctx_t ctx;
// Positions where the generated code starts and ends
uint8_t *start_addr;
uint8_t *end_addr;
// List of incoming branches (from predecessors)
branch_array_t incoming;
// List of outgoing branches (to successors)
// Note: these are owned by this block version
branch_array_t outgoing;
// Offsets for GC managed objects in the mainline code block
int32_array_t gc_object_offsets;
// CME dependencies of this block, to help to remove all pointers to this
// block in the system.
cme_dependency_array_t cme_dependencies;
// Code address of an exit for `ctx` and `blockid`. Used for block
// invalidation.
uint8_t *entry_exit;
// Index one past the last instruction in the iseq
uint32_t end_idx;
} block_t;
// Code generation state
typedef struct JITState
{
// Inline and outlined code blocks we are
// currently generating code into
codeblock_t* cb;
codeblock_t* ocb;
// Block version being compiled
block_t *block;
// Instruction sequence this is associated with
const rb_iseq_t *iseq;
// Index of the current instruction being compiled
uint32_t insn_idx;
// Opcode for the instruction being compiled
int opcode;
// PC of the instruction being compiled
VALUE *pc;
// Side exit to the instruction being compiled. See :side-exit:.
uint8_t *side_exit_for_pc;
// Execution context when compilation started
// This allows us to peek at run-time values
rb_execution_context_t *ec;
// Whether we need to record the code address at
// the end of this bytecode instruction for global invalidation
bool record_boundary_patch_point;
} jitstate_t;
#endif // #ifndef YJIT_CORE_H