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 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
|
#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
// Unused for now.
// 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
|