mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	 b5b6ab4194
			
		
	
	
		b5b6ab4194
		
			
		
	
	
	
	
		
			
			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 8fa0ee4d40.
 * yjit_iface.c (rb_yjit_init): Run yjit_init_core() after
       yjit_init_codegen() so `cb` and `ocb` are available.
		
	
			
		
			
				
	
	
		
			306 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			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
 |