mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
f6da559d5b
For upstreaming, we want functions we export either prefixed with "rb_" or made static. Historically we haven't been following this rule, so we were "leaking" a lot of symbols as `make leak-globals` would tell us. This change unifies everything YJIT into a single compilation unit, yjit.o, and makes everything unprefixed static to pass `make leak-globals`. This manual "unified build" setup is similar to that of vm.o. Having everything in one compilation unit allows static functions to be visible across YJIT files and removes the need for declarations in headers in some cases. Unnecessary declarations were removed. Other changes of note: - switched to MJIT_SYMBOL_EXPORT_BEGIN which indicates stuff as being off limits for native extensions - the first include of each YJIT file is change to be "internal.h" - undefined MAP_STACK before explicitly redefining it since it collide's with a definition in system headers. Consider renaming?
306 lines
7.9 KiB
C
306 lines
7.9 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 REG1 RCX
|
|
#define REG0_32 EAX
|
|
#define REG1_32 ECX
|
|
|
|
#define REG0_8 AL
|
|
|
|
// 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 idenfity 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
|
|
uint32_t start_pos;
|
|
uint32_t end_pos;
|
|
|
|
// 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
|
|
uint32_t start_pos;
|
|
uint32_t end_pos;
|
|
|
|
// 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 page this block lives on
|
|
VALUE code_page;
|
|
|
|
// 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
|