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

308 lines
8 KiB
C
Raw Normal View History

#ifndef YJIT_CORE_H
#define YJIT_CORE_H 1
2021-10-01 07:17:09 -04:00
#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
2021-03-30 15:37:20 -04:00
// 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 } )
2021-04-09 10:14:06 -04:00
enum yjit_type_enum
{
ETYPE_UNKNOWN = 0,
ETYPE_NIL,
ETYPE_TRUE,
ETYPE_FALSE,
2021-04-09 10:14:06 -04:00
ETYPE_FIXNUM,
2021-06-24 11:15:53 -04:00
ETYPE_FLONUM,
2021-04-09 10:14:06 -04:00
ETYPE_ARRAY,
ETYPE_HASH,
ETYPE_SYMBOL,
ETYPE_STRING
};
// Represent the type of a value (local/stack/self) in YJIT
2021-03-31 15:54:46 -04:00
typedef struct yjit_type_struct
2021-03-30 10:59:53 -04:00
{
// Value is definitely a heap object
uint8_t is_heap : 1;
// Value is definitely an immediate
uint8_t is_imm : 1;
2021-03-31 15:54:46 -04:00
// Specific value type, if known
uint8_t type : 4;
2021-03-30 10:59:53 -04:00
} 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 } )
2021-03-31 15:54:46 -04:00
#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 } )
2021-03-31 15:54:46 -04:00
#define TYPE_FIXNUM ( (val_type_t){ .is_imm = 1, .type = ETYPE_FIXNUM } )
2021-06-24 11:15:53 -04:00
#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 } )
2021-04-01 16:56:20 -04:00
#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 } )
2021-03-30 10:59:53 -04:00
2021-04-09 10:14:06 -04:00
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
2021-03-30 10:59:53 -04:00
typedef struct yjit_temp_mapping
{
// Where/how is the value stored?
2021-03-30 10:59:53 -04:00
uint8_t kind: 2;
// Index of the local variale,
2021-03-30 15:37:20 -04:00
// or small non-negative constant in [0, 63]
2021-03-30 10:59:53 -04:00
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
2021-03-30 10:59:53 -04:00
#define MAP_STACK ( (temp_mapping_t) { 0 } )
// Temp value is actually self
#define MAP_SELF ( (temp_mapping_t) { .kind = TEMP_SELF } )
2021-07-14 14:36:33 -04:00
// 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
*/
2021-03-17 11:00:36 -04:00
typedef struct yjit_context
{
2021-02-09 16:24:06 -05:00
// Number of values currently on the temporary stack
2021-01-26 16:51:44 -05:00
uint16_t stack_size;
2020-12-10 00:06:10 -05:00
2021-02-09 16:24:06 -05:00
// Offset of the JIT SP relative to the interpreter SP
// This represents how far the JIT's SP is from the "real" SP
2021-02-09 16:24:06 -05:00
int16_t sp_offset;
2021-03-31 15:54:46 -04:00
// Depth of this block in the sidechain (eg: inline-cache chain)
uint8_t chain_depth;
2021-03-30 15:37:20 -04:00
2021-03-31 15:54:46 -04:00
// Local variable types we keepp track of
val_type_t local_types[MAX_LOCAL_TYPES];
2021-03-30 15:37:20 -04:00
2021-03-31 15:54:46 -04:00
// Temporary variable types we keep track of
val_type_t temp_types[MAX_TEMP_TYPES];
2021-03-30 15:37:20 -04:00
2021-03-31 15:54:46 -04:00
// Type we track for self
val_type_t self_type;
2021-03-30 15:37:20 -04:00
2021-03-31 15:54:46 -04:00
// Mapping of temp stack entries to types we track
temp_mapping_t temp_mapping[MAX_TEMP_TYPES];
2021-03-30 15:37:20 -04:00
} ctx_t;
2021-03-31 15:54:46 -04:00
STATIC_ASSERT(yjit_ctx_size, sizeof(ctx_t) <= 32);
2021-11-02 05:29:53 -04:00
// Tuple of (iseq, idx) used to identify basic blocks
2020-12-16 17:07:18 -05:00
typedef struct BlockId
{
// Instruction sequence
const rb_iseq_t *iseq;
// Index in the iseq where the block starts
uint32_t idx;
2020-12-16 17:07:18 -05:00
} blockid_t;
// Null block id constant
static const blockid_t BLOCKID_NULL = { 0, 0 };
2020-12-16 17:07:18 -05:00
/// Branch code shape enumeration
typedef enum branch_shape
2020-12-16 17:07:18 -05:00
{
2020-12-16 21:45:51 -05:00
SHAPE_NEXT0, // Target 0 is next
SHAPE_NEXT1, // Target 1 is next
SHAPE_DEFAULT // Neither target is next
} branch_shape_t;
2020-12-16 17:07:18 -05:00
// 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
2020-12-16 17:07:18 -05:00
{
// Block this is attached to
struct yjit_block_version *block;
2020-12-16 17:07:18 -05:00
// Positions where the generated code starts and ends
YJIT: Add ability to exit to interpreter from stubs Previously, YJIT assumed that it's always possible to generate a new basic block when servicing a stub in branch_stub_hit(). When YJIT is out of executable memory, for example, this assumption doesn't hold up. Add handling to branch_stub_hit() for servicing stubs without consuming more executable memory by adding a code path that exits to the interpreter at the location the branch stub represents. The new code path reconstructs interpreter state in branch_stub_hit() and then exits with a new snippet called `code_for_exit_from_stub` that returns `Qundef` from the YJIT native stack frame. As this change adds another place where we regenerate code from `branch_t`, extract the logic for it into a new function and call it regenerate_branch(). While we are at it, make the branch shrinking code path in branch_stub_hit() more explicit. This new functionality is hard to test without full support for out of memory conditions. To verify this change, I ran `RUBY_YJIT_ENABLE=1 make check -j12` with the following patch to stress test the new code path: ```diff diff --git a/yjit_core.c b/yjit_core.c index 4ab63d9806..5788b8c5ed 100644 --- a/yjit_core.c +++ b/yjit_core.c @@ -878,8 +878,12 @@ branch_stub_hit(branch_t *branch, const uint32_t target_idx, rb_execution_contex cb_set_write_ptr(cb, branch->end_addr); } +if (rand() < RAND_MAX/2) { // Compile the new block version p_block = gen_block_version(target, target_ctx, ec); +}else{ + p_block = NULL; +} if (!p_block && branch_modified) { // We couldn't generate a new block for the branch, but we modified the branch. ``` We can enable the new test along with other OOM tests once full support lands. Other small changes: * yjit_utils.c (print_str): Update to work with new native frame shape. Follow up for 8fa0ee4d404. * yjit_iface.c (rb_yjit_init): Run yjit_init_core() after yjit_init_codegen() so `cb` and `ocb` are available.
2021-11-26 18:00:42 -05:00
uint8_t *start_addr;
uint8_t *end_addr;
2020-12-16 17:07:18 -05:00
// Context right after the branch instruction
// Unused for now.
// ctx_t src_ctx;
// Branch target blocks and their contexts
2020-12-16 17:07:18 -05:00
blockid_t targets[2];
ctx_t target_ctxs[2];
struct yjit_block_version *blocks[2];
2020-12-16 17:07:18 -05:00
2020-12-16 21:45:51 -05:00
// Jump target addresses
YJIT: Add ability to exit to interpreter from stubs Previously, YJIT assumed that it's always possible to generate a new basic block when servicing a stub in branch_stub_hit(). When YJIT is out of executable memory, for example, this assumption doesn't hold up. Add handling to branch_stub_hit() for servicing stubs without consuming more executable memory by adding a code path that exits to the interpreter at the location the branch stub represents. The new code path reconstructs interpreter state in branch_stub_hit() and then exits with a new snippet called `code_for_exit_from_stub` that returns `Qundef` from the YJIT native stack frame. As this change adds another place where we regenerate code from `branch_t`, extract the logic for it into a new function and call it regenerate_branch(). While we are at it, make the branch shrinking code path in branch_stub_hit() more explicit. This new functionality is hard to test without full support for out of memory conditions. To verify this change, I ran `RUBY_YJIT_ENABLE=1 make check -j12` with the following patch to stress test the new code path: ```diff diff --git a/yjit_core.c b/yjit_core.c index 4ab63d9806..5788b8c5ed 100644 --- a/yjit_core.c +++ b/yjit_core.c @@ -878,8 +878,12 @@ branch_stub_hit(branch_t *branch, const uint32_t target_idx, rb_execution_contex cb_set_write_ptr(cb, branch->end_addr); } +if (rand() < RAND_MAX/2) { // Compile the new block version p_block = gen_block_version(target, target_ctx, ec); +}else{ + p_block = NULL; +} if (!p_block && branch_modified) { // We couldn't generate a new block for the branch, but we modified the branch. ``` We can enable the new test along with other OOM tests once full support lands. Other small changes: * yjit_utils.c (print_str): Update to work with new native frame shape. Follow up for 8fa0ee4d404. * yjit_iface.c (rb_yjit_init): Run yjit_init_core() after yjit_init_codegen() so `cb` and `ocb` are available.
2021-11-26 18:00:42 -05:00
uint8_t *dst_addrs[2];
2020-12-16 21:45:51 -05:00
2020-12-16 17:07:18 -05:00
// Branch code generation function
branchgen_fn gen_fn;
2020-12-16 21:45:51 -05:00
// Shape of the branch
branch_shape_t shape : 2;
2020-12-16 17:07:18 -05:00
} branch_t;
2020-12-14 15:57:55 -05:00
// 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;
2021-02-19 16:04:23 -05:00
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;
2021-07-13 14:56:02 -04:00
} 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