summaryrefslogtreecommitdiff
path: root/yjit_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'yjit_core.c')
-rw-r--r--yjit_core.c1186
1 files changed, 0 insertions, 1186 deletions
diff --git a/yjit_core.c b/yjit_core.c
deleted file mode 100644
index 2b6fb47cd2..0000000000
--- a/yjit_core.c
+++ /dev/null
@@ -1,1186 +0,0 @@
-// This file is a fragment of the yjit.o compilation unit. See yjit.c.
-#include "internal.h"
-#include "vm_sync.h"
-#include "builtin.h"
-
-#include "yjit.h"
-#include "yjit_asm.h"
-#include "yjit_iface.h"
-#include "yjit_core.h"
-#include "yjit_codegen.h"
-
-/*
-Get an operand for the adjusted stack pointer address
-*/
-static x86opnd_t
-ctx_sp_opnd(ctx_t *ctx, int32_t offset_bytes)
-{
- int32_t offset = (ctx->sp_offset * sizeof(VALUE)) + offset_bytes;
- return mem_opnd(64, REG_SP, offset);
-}
-
-/*
-Push one new value on the temp stack with an explicit mapping
-Return a pointer to the new stack top
-*/
-static x86opnd_t
-ctx_stack_push_mapping(ctx_t *ctx, temp_type_mapping_t mapping)
-{
- // Keep track of the type and mapping of the value
- if (ctx->stack_size < MAX_TEMP_TYPES) {
- ctx->temp_mapping[ctx->stack_size] = mapping.mapping;
- ctx->temp_types[ctx->stack_size] = mapping.type;
-
- RUBY_ASSERT(mapping.mapping.kind != TEMP_LOCAL || mapping.mapping.idx < MAX_LOCAL_TYPES);
- RUBY_ASSERT(mapping.mapping.kind != TEMP_STACK || mapping.mapping.idx == 0);
- RUBY_ASSERT(mapping.mapping.kind != TEMP_SELF || mapping.mapping.idx == 0);
- }
-
- ctx->stack_size += 1;
- ctx->sp_offset += 1;
-
- // SP points just above the topmost value
- int32_t offset = (ctx->sp_offset - 1) * sizeof(VALUE);
- return mem_opnd(64, REG_SP, offset);
-}
-
-
-/*
-Push one new value on the temp stack
-Return a pointer to the new stack top
-*/
-static x86opnd_t
-ctx_stack_push(ctx_t *ctx, val_type_t type)
-{
- temp_type_mapping_t mapping = { MAP_STACK, type };
- return ctx_stack_push_mapping(ctx, mapping);
-}
-
-/*
-Push the self value on the stack
-*/
-static x86opnd_t
-ctx_stack_push_self(ctx_t *ctx)
-{
- temp_type_mapping_t mapping = { MAP_SELF, TYPE_UNKNOWN };
- return ctx_stack_push_mapping(ctx, mapping);
-}
-
-/*
-Push a local variable on the stack
-*/
-static x86opnd_t
-ctx_stack_push_local(ctx_t *ctx, size_t local_idx)
-{
- if (local_idx >= MAX_LOCAL_TYPES) {
- return ctx_stack_push(ctx, TYPE_UNKNOWN);
- }
-
- temp_type_mapping_t mapping = {
- (temp_mapping_t){ .kind = TEMP_LOCAL, .idx = local_idx },
- TYPE_UNKNOWN
- };
- return ctx_stack_push_mapping(ctx, mapping);
-}
-
-/*
-Pop N values off the stack
-Return a pointer to the stack top before the pop operation
-*/
-static x86opnd_t
-ctx_stack_pop(ctx_t *ctx, size_t n)
-{
- RUBY_ASSERT(n <= ctx->stack_size);
-
- // SP points just above the topmost value
- int32_t offset = (ctx->sp_offset - 1) * sizeof(VALUE);
- x86opnd_t top = mem_opnd(64, REG_SP, offset);
-
- // Clear the types of the popped values
- for (size_t i = 0; i < n; ++i)
- {
- size_t idx = ctx->stack_size - i - 1;
- if (idx < MAX_TEMP_TYPES) {
- ctx->temp_types[idx] = TYPE_UNKNOWN;
- ctx->temp_mapping[idx] = MAP_STACK;
- }
- }
-
- ctx->stack_size -= n;
- ctx->sp_offset -= n;
-
- return top;
-}
-
-/**
-Get an operand pointing to a slot on the temp stack
-*/
-static x86opnd_t
-ctx_stack_opnd(ctx_t *ctx, int32_t idx)
-{
- // SP points just above the topmost value
- int32_t offset = (ctx->sp_offset - 1 - idx) * sizeof(VALUE);
- x86opnd_t opnd = mem_opnd(64, REG_SP, offset);
-
- return opnd;
-}
-
-/**
-Get the type of an instruction operand
-*/
-static val_type_t
-ctx_get_opnd_type(const ctx_t *ctx, insn_opnd_t opnd)
-{
- if (opnd.is_self)
- return ctx->self_type;
-
- RUBY_ASSERT(opnd.idx < ctx->stack_size);
- int stack_idx = ctx->stack_size - 1 - opnd.idx;
-
- // If outside of tracked range, do nothing
- if (stack_idx >= MAX_TEMP_TYPES)
- return TYPE_UNKNOWN;
-
- temp_mapping_t mapping = ctx->temp_mapping[stack_idx];
-
- switch (mapping.kind) {
- case TEMP_SELF:
- return ctx->self_type;
-
- case TEMP_STACK:
- return ctx->temp_types[ctx->stack_size - 1 - opnd.idx];
-
- case TEMP_LOCAL:
- RUBY_ASSERT(mapping.idx < MAX_LOCAL_TYPES);
- return ctx->local_types[mapping.idx];
- }
-
- rb_bug("unreachable");
-}
-
-static int type_diff(val_type_t src, val_type_t dst);
-
-#define UPGRADE_TYPE(dest, src) do { \
- RUBY_ASSERT(type_diff((src), (dest)) != INT_MAX); \
- (dest) = (src); \
-} while (false)
-
-
-/**
-Upgrade (or "learn") the type of an instruction operand
-This value must be compatible and at least as specific as the previously known type.
-If this value originated from self, or an lvar, the learned type will be
-propagated back to its source.
-*/
-static void
-ctx_upgrade_opnd_type(ctx_t *ctx, insn_opnd_t opnd, val_type_t type)
-{
- if (opnd.is_self) {
- UPGRADE_TYPE(ctx->self_type, type);
- return;
- }
-
- RUBY_ASSERT(opnd.idx < ctx->stack_size);
- int stack_idx = ctx->stack_size - 1 - opnd.idx;
-
- // If outside of tracked range, do nothing
- if (stack_idx >= MAX_TEMP_TYPES)
- return;
-
- temp_mapping_t mapping = ctx->temp_mapping[stack_idx];
-
- switch (mapping.kind) {
- case TEMP_SELF:
- UPGRADE_TYPE(ctx->self_type, type);
- break;
-
- case TEMP_STACK:
- UPGRADE_TYPE(ctx->temp_types[stack_idx], type);
- break;
-
- case TEMP_LOCAL:
- RUBY_ASSERT(mapping.idx < MAX_LOCAL_TYPES);
- UPGRADE_TYPE(ctx->local_types[mapping.idx], type);
- break;
- }
-}
-
-/*
-Get both the type and mapping (where the value originates) of an operand.
-This is can be used with ctx_stack_push_mapping or ctx_set_opnd_mapping to copy
-a stack value's type while maintaining the mapping.
-*/
-static temp_type_mapping_t
-ctx_get_opnd_mapping(const ctx_t *ctx, insn_opnd_t opnd)
-{
- temp_type_mapping_t type_mapping;
- type_mapping.type = ctx_get_opnd_type(ctx, opnd);
-
- if (opnd.is_self) {
- type_mapping.mapping = MAP_SELF;
- return type_mapping;
- }
-
- RUBY_ASSERT(opnd.idx < ctx->stack_size);
- int stack_idx = ctx->stack_size - 1 - opnd.idx;
-
- if (stack_idx < MAX_TEMP_TYPES) {
- type_mapping.mapping = ctx->temp_mapping[stack_idx];
- }
- else {
- // We can't know the source of this stack operand, so we assume it is
- // a stack-only temporary. type will be UNKNOWN
- RUBY_ASSERT(type_mapping.type.type == ETYPE_UNKNOWN);
- type_mapping.mapping = MAP_STACK;
- }
-
- return type_mapping;
-}
-
-/*
-Overwrite both the type and mapping of a stack operand.
-*/
-static void
-ctx_set_opnd_mapping(ctx_t *ctx, insn_opnd_t opnd, temp_type_mapping_t type_mapping)
-{
- // self is always MAP_SELF
- RUBY_ASSERT(!opnd.is_self);
-
- RUBY_ASSERT(opnd.idx < ctx->stack_size);
- int stack_idx = ctx->stack_size - 1 - opnd.idx;
-
- // If outside of tracked range, do nothing
- if (stack_idx >= MAX_TEMP_TYPES)
- return;
-
- ctx->temp_mapping[stack_idx] = type_mapping.mapping;
-
- // Only used when mapping == MAP_STACK
- ctx->temp_types[stack_idx] = type_mapping.type;
-}
-
-/**
-Set the type of a local variable
-*/
-static void
-ctx_set_local_type(ctx_t *ctx, size_t idx, val_type_t type)
-{
- if (idx >= MAX_LOCAL_TYPES)
- return;
-
- // If any values on the stack map to this local we must detach them
- for (int i = 0; i < MAX_TEMP_TYPES; i++) {
- temp_mapping_t *mapping = &ctx->temp_mapping[i];
- if (mapping->kind == TEMP_LOCAL && mapping->idx == idx) {
- ctx->temp_types[i] = ctx->local_types[mapping->idx];
- *mapping = MAP_STACK;
- }
- }
-
- ctx->local_types[idx] = type;
-}
-
-// Erase local variable type information
-// eg: because of a call we can't track
-static void
-ctx_clear_local_types(ctx_t *ctx)
-{
- // When clearing local types we must detach any stack mappings to those
- // locals. Even if local values may have changed, stack values will not.
- for (int i = 0; i < MAX_TEMP_TYPES; i++) {
- temp_mapping_t *mapping = &ctx->temp_mapping[i];
- if (mapping->kind == TEMP_LOCAL) {
- RUBY_ASSERT(mapping->idx < MAX_LOCAL_TYPES);
- ctx->temp_types[i] = ctx->local_types[mapping->idx];
- *mapping = MAP_STACK;
- }
- RUBY_ASSERT(mapping->kind == TEMP_STACK || mapping->kind == TEMP_SELF);
- }
- memset(&ctx->local_types, 0, sizeof(ctx->local_types));
-}
-
-
-/* This returns an appropriate val_type_t based on a known value */
-static val_type_t
-yjit_type_of_value(VALUE val)
-{
- if (SPECIAL_CONST_P(val)) {
- if (FIXNUM_P(val)) {
- return TYPE_FIXNUM;
- }
- else if (NIL_P(val)) {
- return TYPE_NIL;
- }
- else if (val == Qtrue) {
- return TYPE_TRUE;
- }
- else if (val == Qfalse) {
- return TYPE_FALSE;
- }
- else if (STATIC_SYM_P(val)) {
- return TYPE_STATIC_SYMBOL;
- }
- else if (FLONUM_P(val)) {
- return TYPE_FLONUM;
- }
- else {
- RUBY_ASSERT(false);
- UNREACHABLE_RETURN(TYPE_IMM);
- }
- }
- else {
- switch (BUILTIN_TYPE(val)) {
- case T_ARRAY:
- return TYPE_ARRAY;
- case T_HASH:
- return TYPE_HASH;
- case T_STRING:
- return TYPE_STRING;
- default:
- // generic heap object
- return TYPE_HEAP;
- }
- }
-}
-
-/* The name of a type, for debugging */
-RBIMPL_ATTR_MAYBE_UNUSED()
-static const char *
-yjit_type_name(val_type_t type)
-{
- RUBY_ASSERT(!(type.is_imm && type.is_heap));
-
- switch (type.type) {
- case ETYPE_UNKNOWN:
- if (type.is_imm) {
- return "unknown immediate";
- }
- else if (type.is_heap) {
- return "unknown heap";
- }
- else {
- return "unknown";
- }
- case ETYPE_NIL:
- return "nil";
- case ETYPE_TRUE:
- return "true";
- case ETYPE_FALSE:
- return "false";
- case ETYPE_FIXNUM:
- return "fixnum";
- case ETYPE_FLONUM:
- return "flonum";
- case ETYPE_ARRAY:
- return "array";
- case ETYPE_HASH:
- return "hash";
- case ETYPE_SYMBOL:
- return "symbol";
- case ETYPE_STRING:
- return "string";
- }
-
- UNREACHABLE_RETURN("");
-}
-
-/*
-Compute a difference between two value types
-Returns 0 if the two are the same
-Returns > 0 if different but compatible
-Returns INT_MAX if incompatible
-*/
-static int
-type_diff(val_type_t src, val_type_t dst)
-{
- RUBY_ASSERT(!src.is_heap || !src.is_imm);
- RUBY_ASSERT(!dst.is_heap || !dst.is_imm);
-
- // If dst assumes heap but src doesn't
- if (dst.is_heap && !src.is_heap)
- return INT_MAX;
-
- // If dst assumes imm but src doesn't
- if (dst.is_imm && !src.is_imm)
- return INT_MAX;
-
- // If dst assumes known type different from src
- if (dst.type != ETYPE_UNKNOWN && dst.type != src.type)
- return INT_MAX;
-
- if (dst.is_heap != src.is_heap)
- return 1;
-
- if (dst.is_imm != src.is_imm)
- return 1;
-
- if (dst.type != src.type)
- return 1;
-
- return 0;
-}
-
-/**
-Compute a difference score for two context objects
-Returns 0 if the two contexts are the same
-Returns > 0 if different but compatible
-Returns INT_MAX if incompatible
-*/
-static int
-ctx_diff(const ctx_t *src, const ctx_t *dst)
-{
- // Can only lookup the first version in the chain
- if (dst->chain_depth != 0)
- return INT_MAX;
-
- // Blocks with depth > 0 always produce new versions
- // Sidechains cannot overlap
- if (src->chain_depth != 0)
- return INT_MAX;
-
- if (dst->stack_size != src->stack_size)
- return INT_MAX;
-
- if (dst->sp_offset != src->sp_offset)
- return INT_MAX;
-
- // Difference sum
- int diff = 0;
-
- // Check the type of self
- int self_diff = type_diff(src->self_type, dst->self_type);
-
- if (self_diff == INT_MAX)
- return INT_MAX;
-
- diff += self_diff;
-
- // For each local type we track
- for (size_t i = 0; i < MAX_LOCAL_TYPES; ++i)
- {
- val_type_t t_src = src->local_types[i];
- val_type_t t_dst = dst->local_types[i];
- int temp_diff = type_diff(t_src, t_dst);
-
- if (temp_diff == INT_MAX)
- return INT_MAX;
-
- diff += temp_diff;
- }
-
- // For each value on the temp stack
- for (size_t i = 0; i < src->stack_size; ++i)
- {
- temp_type_mapping_t m_src = ctx_get_opnd_mapping(src, OPND_STACK(i));
- temp_type_mapping_t m_dst = ctx_get_opnd_mapping(dst, OPND_STACK(i));
-
- if (m_dst.mapping.kind != m_src.mapping.kind) {
- if (m_dst.mapping.kind == TEMP_STACK) {
- // We can safely drop information about the source of the temp
- // stack operand.
- diff += 1;
- }
- else {
- return INT_MAX;
- }
- }
- else if (m_dst.mapping.idx != m_src.mapping.idx) {
- return INT_MAX;
- }
-
- int temp_diff = type_diff(m_src.type, m_dst.type);
-
- if (temp_diff == INT_MAX)
- return INT_MAX;
-
- diff += temp_diff;
- }
-
- return diff;
-}
-
-// Get all blocks for a particular place in an iseq.
-static rb_yjit_block_array_t
-yjit_get_version_array(const rb_iseq_t *iseq, unsigned idx)
-{
- struct rb_iseq_constant_body *body = iseq->body;
-
- if (rb_darray_size(body->yjit_blocks) == 0) {
- return NULL;
- }
-
- RUBY_ASSERT((unsigned)rb_darray_size(body->yjit_blocks) == body->iseq_size);
- return rb_darray_get(body->yjit_blocks, idx);
-}
-
-// Count the number of block versions matching a given blockid
-static size_t get_num_versions(blockid_t blockid)
-{
- return rb_darray_size(yjit_get_version_array(blockid.iseq, blockid.idx));
-}
-
-// Keep track of a block version. Block should be fully constructed.
-static void
-add_block_version(blockid_t blockid, block_t *block)
-{
- const rb_iseq_t *iseq = block->blockid.iseq;
- struct rb_iseq_constant_body *body = iseq->body;
-
- // Function entry blocks must have stack size 0
- RUBY_ASSERT(!(block->blockid.idx == 0 && block->ctx.stack_size > 0));
-
- // Ensure yjit_blocks is initialized for this iseq
- if (rb_darray_size(body->yjit_blocks) == 0) {
- // Initialize yjit_blocks to be as wide as body->iseq_encoded
- int32_t casted = (int32_t)body->iseq_size;
- if ((unsigned)casted != body->iseq_size) {
- rb_bug("iseq too large");
- }
- if (!rb_darray_make(&body->yjit_blocks, casted)) {
- rb_bug("allocation failed");
- }
-
-#if YJIT_STATS
- // First block compiled for this iseq
- yjit_runtime_counters.compiled_iseq_count++;
-#endif
- }
-
- RUBY_ASSERT((int32_t)blockid.idx < rb_darray_size(body->yjit_blocks));
- rb_yjit_block_array_t *block_array_ref = rb_darray_ref(body->yjit_blocks, blockid.idx);
-
- // Add the new block
- if (!rb_darray_append(block_array_ref, block)) {
- rb_bug("allocation failed");
- }
-
- {
- // By writing the new block to the iseq, the iseq now
- // contains new references to Ruby objects. Run write barriers.
- cme_dependency_t *cme_dep;
- rb_darray_foreach(block->cme_dependencies, cme_dependency_idx, cme_dep) {
- RB_OBJ_WRITTEN(iseq, Qundef, cme_dep->receiver_klass);
- RB_OBJ_WRITTEN(iseq, Qundef, cme_dep->callee_cme);
- }
-
- // Run write barriers for all objects in generated code.
- uint32_t *offset_element;
- rb_darray_foreach(block->gc_object_offsets, offset_idx, offset_element) {
- uint32_t offset_to_value = *offset_element;
- uint8_t *value_address = cb_get_ptr(cb, offset_to_value);
-
- VALUE object;
- memcpy(&object, value_address, SIZEOF_VALUE);
- RB_OBJ_WRITTEN(iseq, Qundef, object);
- }
- }
-
-#if YJIT_STATS
- yjit_runtime_counters.compiled_block_count++;
-#endif
-}
-
-// Create a new outgoing branch entry for a block
-static branch_t*
-make_branch_entry(block_t *block, const ctx_t *src_ctx, branchgen_fn gen_fn)
-{
- RUBY_ASSERT(block != NULL);
-
- // Allocate and zero-initialize
- branch_t *branch = calloc(1, sizeof(branch_t));
-
- branch->block = block;
- branch->src_ctx = *src_ctx;
- branch->gen_fn = gen_fn;
- branch->shape = SHAPE_DEFAULT;
-
- // Add to the list of outgoing branches for the block
- rb_darray_append(&block->outgoing, branch);
-
- return branch;
-}
-
-// Retrieve a basic block version for an (iseq, idx) tuple
-static block_t *
-find_block_version(blockid_t blockid, const ctx_t *ctx)
-{
- rb_yjit_block_array_t versions = yjit_get_version_array(blockid.iseq, blockid.idx);
-
- // Best match found
- block_t *best_version = NULL;
- int best_diff = INT_MAX;
-
- // For each version matching the blockid
- rb_darray_for(versions, idx) {
- block_t *version = rb_darray_get(versions, idx);
- int diff = ctx_diff(ctx, &version->ctx);
-
- // Note that we always prefer the first matching
- // version because of inline-cache chains
- if (diff < best_diff) {
- best_version = version;
- best_diff = diff;
- }
- }
-
- // If greedy versioning is enabled
- if (rb_yjit_opts.greedy_versioning)
- {
- // If we're below the version limit, don't settle for an imperfect match
- if ((uint32_t)rb_darray_size(versions) + 1 < rb_yjit_opts.max_versions && best_diff > 0) {
- return NULL;
- }
- }
-
- return best_version;
-}
-
-// Produce a generic context when the block version limit is hit for a blockid
-// Note that this will mutate the ctx argument
-static void
-limit_block_versions(blockid_t blockid, ctx_t *ctx)
-{
- // Guard chains implement limits separately, do nothing
- if (ctx->chain_depth > 0)
- return;
-
- // If this block version we're about to add will hit the version limit
- if (get_num_versions(blockid) + 1 >= rb_yjit_opts.max_versions)
- {
- // Produce a generic context that stores no type information,
- // but still respects the stack_size and sp_offset constraints
- // This new context will then match all future requests.
- ctx_t generic_ctx = DEFAULT_CTX;
- generic_ctx.stack_size = ctx->stack_size;
- generic_ctx.sp_offset = ctx->sp_offset;
-
- // Mutate the incoming context
- *ctx = generic_ctx;
- }
-}
-
-// Compile a new block version immediately
-static block_t *
-gen_block_version(blockid_t blockid, const ctx_t *start_ctx, rb_execution_context_t *ec)
-{
- // Allocate a new block version object
- block_t *block = calloc(1, sizeof(block_t));
- block->blockid = blockid;
- memcpy(&block->ctx, start_ctx, sizeof(ctx_t));
-
- // Store a pointer to the first block (returned by this function)
- block_t *first_block = block;
-
- // Limit the number of specialized versions for this block
- limit_block_versions(block->blockid, &block->ctx);
-
- // Generate code for the first block
- yjit_gen_block(block, ec);
-
- // Keep track of the new block version
- add_block_version(block->blockid, block);
-
- // For each successor block to compile
- for (;;) {
- // If the previous block compiled doesn't have outgoing branches, stop
- if (rb_darray_size(block->outgoing) == 0) {
- break;
- }
-
- // Get the last outgoing branch from the previous block
- branch_t *last_branch = rb_darray_back(block->outgoing);
-
- // If there is no next block to compile, stop
- if (last_branch->dst_addrs[0] || last_branch->dst_addrs[1]) {
- break;
- }
-
- if (last_branch->targets[0].iseq == NULL) {
- rb_bug("invalid target for last branch");
- }
-
- // Allocate a new block version object
- // Use the context from the branch
- block = calloc(1, sizeof(block_t));
- block->blockid = last_branch->targets[0];
- block->ctx = last_branch->target_ctxs[0];
- //memcpy(&block->ctx, ctx, sizeof(ctx_t));
-
- // Limit the number of specialized versions for this block
- limit_block_versions(block->blockid, &block->ctx);
-
- // Generate code for the current block
- yjit_gen_block(block, ec);
-
- // Keep track of the new block version
- add_block_version(block->blockid, block);
-
- // Patch the last branch address
- last_branch->dst_addrs[0] = cb_get_ptr(cb, block->start_pos);
- rb_darray_append(&block->incoming, last_branch);
- last_branch->blocks[0] = block;
-
- RUBY_ASSERT(block->start_pos == last_branch->end_pos);
- }
-
- return first_block;
-}
-
-// Generate a block version that is an entry point inserted into an iseq
-static uint8_t *
-gen_entry_point(const rb_iseq_t *iseq, uint32_t insn_idx, rb_execution_context_t *ec)
-{
- // If we aren't at PC 0, don't generate code
- // See yjit_pc_guard
- if (iseq->body->iseq_encoded != ec->cfp->pc) {
- return NULL;
- }
-
- // The entry context makes no assumptions about types
- blockid_t blockid = { iseq, insn_idx };
-
- // Write the interpreter entry prologue
- uint8_t *code_ptr = yjit_entry_prologue(cb, iseq);
-
- // Try to generate code for the entry block
- block_t *block = gen_block_version(blockid, &DEFAULT_CTX, ec);
-
- // If we couldn't generate any code
- if (block->end_idx == insn_idx)
- {
- return NULL;
- }
-
- return code_ptr;
-}
-
-// Called by the generated code when a branch stub is executed
-// Triggers compilation of branches and code patching
-static uint8_t *
-branch_stub_hit(branch_t *branch, const uint32_t target_idx, rb_execution_context_t *ec)
-{
- uint8_t *dst_addr;
-
- // Stop other ractors since we are going to patch machine code.
- // This is how the GC does it.
- RB_VM_LOCK_ENTER();
- rb_vm_barrier();
-
- RUBY_ASSERT(branch != NULL);
- RUBY_ASSERT(target_idx < 2);
- blockid_t target = branch->targets[target_idx];
- const ctx_t *target_ctx = &branch->target_ctxs[target_idx];
-
- // If this branch has already been patched, return the dst address
- // Note: ractors can cause the same stub to be hit multiple times
- if (branch->blocks[target_idx]) {
- dst_addr = branch->dst_addrs[target_idx];
- }
- else
- {
- //fprintf(stderr, "\nstub hit, branch: %p, target idx: %d\n", branch, target_idx);
- //fprintf(stderr, "blockid.iseq=%p, blockid.idx=%d\n", target.iseq, target.idx);
- //fprintf(stderr, "chain_depth=%d\n", target_ctx->chain_depth);
-
- // :stub-sp-flush:
- // Generated code do stack operations without modifying cfp->sp, while the
- // cfp->sp tells the GC what values on the stack to root. Generated code
- // generally takes care of updating cfp->sp when it calls runtime routines that
- // could trigger GC, but for the case of branch stubs, it's inconvenient. So
- // we do it here.
- VALUE *const original_interp_sp = ec->cfp->sp;
- ec->cfp->sp += target_ctx->sp_offset;
-
- // Update the PC in the current CFP, because it
- // may be out of sync in JITted code
- ec->cfp->pc = yjit_iseq_pc_at_idx(target.iseq, target.idx);
-
- // Try to find an existing compiled version of this block
- block_t *p_block = find_block_version(target, target_ctx);
-
- // If this block hasn't yet been compiled
- if (!p_block) {
- // If the new block can be generated right after the branch (at cb->write_pos)
- if (cb->write_pos == branch->end_pos && branch->start_pos >= yjit_codepage_frozen_bytes) {
- // This branch should be terminating its block
- RUBY_ASSERT(branch->end_pos == branch->block->end_pos);
-
- // Change the branch shape to indicate the target block will be placed next
- branch->shape = (uint8_t)target_idx;
-
- // Rewrite the branch with the new, potentially more compact shape
- cb_set_pos(cb, branch->start_pos);
- branch->gen_fn(cb, branch->dst_addrs[0], branch->dst_addrs[1], branch->shape);
- RUBY_ASSERT(cb->write_pos <= branch->end_pos && "can't enlarge branches");
- branch->end_pos = cb->write_pos;
- branch->block->end_pos = cb->write_pos;
- }
-
- // Compile the new block version
- p_block = gen_block_version(target, target_ctx, ec);
- RUBY_ASSERT(p_block);
- RUBY_ASSERT(!(branch->shape == (uint8_t)target_idx && p_block->start_pos != branch->end_pos));
- }
-
- // Add this branch to the list of incoming branches for the target
- rb_darray_append(&p_block->incoming, branch);
-
- // Update the branch target address
- dst_addr = cb_get_ptr(cb, p_block->start_pos);
- branch->dst_addrs[target_idx] = dst_addr;
-
- // Rewrite the branch with the new jump target address
- if (branch->start_pos >= yjit_codepage_frozen_bytes) {
- RUBY_ASSERT(branch->dst_addrs[0] != NULL);
- uint32_t cur_pos = cb->write_pos;
- cb_set_pos(cb, branch->start_pos);
- branch->gen_fn(cb, branch->dst_addrs[0], branch->dst_addrs[1], branch->shape);
- RUBY_ASSERT(cb->write_pos == branch->end_pos && "branch can't change size");
- cb_set_pos(cb, cur_pos);
- }
-
- // Mark this branch target as patched (no longer a stub)
- branch->blocks[target_idx] = p_block;
-
- // Restore interpreter sp, since the code hitting the stub expects the original.
- ec->cfp->sp = original_interp_sp;
- }
-
- RB_VM_LOCK_LEAVE();
-
- // Return a pointer to the compiled block version
- return dst_addr;
-}
-
-// Get a version or stub corresponding to a branch target
-static uint8_t *
-get_branch_target(
- blockid_t target,
- const ctx_t *ctx,
- branch_t *branch,
- uint32_t target_idx
-)
-{
- //fprintf(stderr, "get_branch_target, block (%p, %d)\n", target.iseq, target.idx);
-
- block_t *p_block = find_block_version(target, ctx);
-
- // If the block already exists
- if (p_block)
- {
- // Add an incoming branch for this version
- rb_darray_append(&p_block->incoming, branch);
- branch->blocks[target_idx] = p_block;
-
- // Return a pointer to the compiled code
- return cb_get_ptr(cb, p_block->start_pos);
- }
-
- // Generate an outlined stub that will call branch_stub_hit()
- uint8_t *stub_addr = cb_get_ptr(ocb, ocb->write_pos);
-
- // Call branch_stub_hit(branch_idx, target_idx, ec)
- mov(ocb, C_ARG_REGS[2], REG_EC);
- mov(ocb, C_ARG_REGS[1], imm_opnd(target_idx));
- mov(ocb, C_ARG_REGS[0], const_ptr_opnd(branch));
- call_ptr(ocb, REG0, (void *)&branch_stub_hit);
-
- // Jump to the address returned by the
- // branch_stub_hit call
- jmp_rm(ocb, RAX);
-
- return stub_addr;
-}
-
-static void
-gen_branch(
- jitstate_t *jit,
- const ctx_t *src_ctx,
- blockid_t target0,
- const ctx_t *ctx0,
- blockid_t target1,
- const ctx_t *ctx1,
- branchgen_fn gen_fn
-)
-{
- RUBY_ASSERT(target0.iseq != NULL);
-
- branch_t *branch = make_branch_entry(jit->block, src_ctx, gen_fn);
- branch->targets[0] = target0;
- branch->targets[1] = target1;
- branch->target_ctxs[0] = *ctx0;
- branch->target_ctxs[1] = ctx1? *ctx1:DEFAULT_CTX;
-
- // Get the branch targets or stubs
- branch->dst_addrs[0] = get_branch_target(target0, ctx0, branch, 0);
- branch->dst_addrs[1] = ctx1? get_branch_target(target1, ctx1, branch, 1):NULL;
-
- // Call the branch generation function
- branch->start_pos = cb->write_pos;
- gen_fn(cb, branch->dst_addrs[0], branch->dst_addrs[1], SHAPE_DEFAULT);
- branch->end_pos = cb->write_pos;
-}
-
-static void
-gen_jump_branch(codeblock_t *cb, uint8_t *target0, uint8_t *target1, uint8_t shape)
-{
- switch (shape) {
- case SHAPE_NEXT0:
- break;
-
- case SHAPE_NEXT1:
- RUBY_ASSERT(false);
- break;
-
- case SHAPE_DEFAULT:
- jmp_ptr(cb, target0);
- break;
- }
-}
-
-static void
-gen_direct_jump(
- jitstate_t *jit,
- const ctx_t *ctx,
- blockid_t target0
-)
-{
- RUBY_ASSERT(target0.iseq != NULL);
-
- branch_t *branch = make_branch_entry(jit->block, ctx, gen_jump_branch);
- branch->targets[0] = target0;
- branch->target_ctxs[0] = *ctx;
-
- block_t *p_block = find_block_version(target0, ctx);
-
- // If the version already exists
- if (p_block) {
- rb_darray_append(&p_block->incoming, branch);
-
- branch->dst_addrs[0] = cb_get_ptr(cb, p_block->start_pos);
- branch->blocks[0] = p_block;
- branch->shape = SHAPE_DEFAULT;
-
- // Call the branch generation function
- branch->start_pos = cb->write_pos;
- gen_jump_branch(cb, branch->dst_addrs[0], NULL, SHAPE_DEFAULT);
- branch->end_pos = cb->write_pos;
- }
- else {
- // This NULL target address signals gen_block_version() to compile the
- // target block right after this one (fallthrough).
- branch->dst_addrs[0] = NULL;
- branch->shape = SHAPE_NEXT0;
- branch->start_pos = cb->write_pos;
- branch->end_pos = cb->write_pos;
- }
-}
-
-// Create a stub to force the code up to this point to be executed
-static void
-defer_compilation(
- jitstate_t *jit,
- ctx_t *cur_ctx
-)
-{
- //fprintf(stderr, "defer compilation at (%p, %d) depth=%d\n", block->blockid.iseq, insn_idx, cur_ctx->chain_depth);
-
- if (cur_ctx->chain_depth != 0) {
- rb_bug("double defer");
- }
-
- ctx_t next_ctx = *cur_ctx;
-
- if (next_ctx.chain_depth >= UINT8_MAX) {
- rb_bug("max block version chain depth reached");
- }
-
- next_ctx.chain_depth += 1;
-
- branch_t *branch = make_branch_entry(jit->block, cur_ctx, gen_jump_branch);
-
- // Get the branch targets or stubs
- branch->target_ctxs[0] = next_ctx;
- branch->targets[0] = (blockid_t){ jit->block->blockid.iseq, jit->insn_idx };
- branch->dst_addrs[0] = get_branch_target(branch->targets[0], &next_ctx, branch, 0);
-
- // Call the branch generation function
- codeblock_t *cb = jit->cb;
- branch->start_pos = cb->write_pos;
- gen_jump_branch(cb, branch->dst_addrs[0], NULL, SHAPE_DEFAULT);
- branch->end_pos = cb->write_pos;
-}
-
-// Remove all references to a block then free it.
-static void
-yjit_free_block(block_t *block)
-{
- yjit_unlink_method_lookup_dependency(block);
- yjit_block_assumptions_free(block);
-
- // Remove this block from the predecessor's targets
- rb_darray_for(block->incoming, incoming_idx) {
- // Branch from the predecessor to us
- branch_t *pred_branch = rb_darray_get(block->incoming, incoming_idx);
-
- // If this is us, nullify the target block
- for (size_t succ_idx = 0; succ_idx < 2; succ_idx++) {
- if (pred_branch->blocks[succ_idx] == block) {
- pred_branch->blocks[succ_idx] = NULL;
- }
- }
- }
-
- // For each outgoing branch
- rb_darray_for(block->outgoing, branch_idx) {
- branch_t *out_branch = rb_darray_get(block->outgoing, branch_idx);
-
- // For each successor block
- for (size_t succ_idx = 0; succ_idx < 2; succ_idx++) {
- block_t *succ = out_branch->blocks[succ_idx];
-
- if (succ == NULL)
- continue;
-
- // Remove this block from the successor's incoming list
- rb_darray_for(succ->incoming, incoming_idx) {
- branch_t *pred_branch = rb_darray_get(succ->incoming, incoming_idx);
- if (pred_branch == out_branch) {
- rb_darray_remove_unordered(succ->incoming, incoming_idx);
- break;
- }
- }
- }
-
- // Free the outgoing branch entry
- free(out_branch);
- }
-
- rb_darray_free(block->incoming);
- rb_darray_free(block->outgoing);
- rb_darray_free(block->gc_object_offsets);
-
- free(block);
-}
-
-// Remove a block version
-static void
-block_array_remove(rb_yjit_block_array_t block_array, block_t *block)
-{
- block_t **element;
- rb_darray_foreach(block_array, idx, element) {
- if (*element == block) {
- rb_darray_remove_unordered(block_array, idx);
- return;
- }
- }
-
- RUBY_ASSERT(false);
-}
-
-// Invalidate one specific block version
-static void
-invalidate_block_version(block_t *block)
-{
- ASSERT_vm_locking();
- // TODO: want to assert that all other ractors are stopped here. Can't patch
- // machine code that some other thread is running.
-
- const rb_iseq_t *iseq = block->blockid.iseq;
-
- //fprintf(stderr, "invalidating block (%p, %d)\n", block->blockid.iseq, block->blockid.idx);
- //fprintf(stderr, "block=%p\n", block);
-
- // Remove this block from the version array
- rb_yjit_block_array_t versions = yjit_get_version_array(iseq, block->blockid.idx);
- block_array_remove(versions, block);
-
- // Get a pointer to the generated code for this block
- uint8_t *code_ptr = cb_get_ptr(cb, block->start_pos);
-
- // For each incoming branch
- rb_darray_for(block->incoming, incoming_idx) {
- branch_t *branch = rb_darray_get(block->incoming, incoming_idx);
- uint32_t target_idx = (branch->dst_addrs[0] == code_ptr)? 0:1;
- RUBY_ASSERT(branch->dst_addrs[target_idx] == code_ptr);
- RUBY_ASSERT(branch->blocks[target_idx] == block);
-
- // Mark this target as being a stub
- branch->blocks[target_idx] = NULL;
-
- // Don't patch frozen code region
- if (branch->start_pos < yjit_codepage_frozen_bytes) {
- continue;
- }
-
- // Create a stub for this branch target
- branch->dst_addrs[target_idx] = get_branch_target(
- block->blockid,
- &block->ctx,
- branch,
- target_idx
- );
-
- // Check if the invalidated block immediately follows
- bool target_next = block->start_pos == branch->end_pos;
-
- if (target_next) {
- // The new block will no longer be adjacent
- branch->shape = SHAPE_DEFAULT;
- }
-
- // Rewrite the branch with the new jump target address
- RUBY_ASSERT(branch->dst_addrs[0] != NULL);
- uint32_t cur_pos = cb->write_pos;
- cb_set_pos(cb, branch->start_pos);
- branch->gen_fn(cb, branch->dst_addrs[0], branch->dst_addrs[1], branch->shape);
- branch->end_pos = cb->write_pos;
- branch->block->end_pos = cb->write_pos;
- cb_set_pos(cb, cur_pos);
-
- if (target_next && branch->end_pos > block->end_pos) {
- fprintf(stderr, "branch_block_idx=%u block_idx=%u over=%d block_size=%d\n",
- branch->block->blockid.idx,
- block->blockid.idx,
- branch->end_pos - block->end_pos,
- block->end_pos - block->start_pos);
- yjit_print_iseq(branch->block->blockid.iseq);
- rb_bug("yjit invalidate rewrote branch past end of invalidated block");
- }
- }
-
- // Clear out the JIT func so that we can recompile later and so the
- // interpreter will run the iseq
-
-#if JIT_ENABLED
- // Only clear the jit_func when we're invalidating the JIT entry block.
- // We only support compiling iseqs from index 0 right now. So entry
- // points will always have an instruction index of 0. We'll need to
- // change this in the future when we support optional parameters because
- // they enter the function with a non-zero PC
- if (block->blockid.idx == 0) {
- iseq->body->jit_func = 0;
- }
-#endif
-
- // TODO:
- // May want to recompile a new entry point (for interpreter entry blocks)
- // This isn't necessary for correctness
-
- // FIXME:
- // Call continuation addresses on the stack can also be atomically replaced by jumps going to the stub.
-
- yjit_free_block(block);
-
-#if YJIT_STATS
- yjit_runtime_counters.invalidation_count++;
-#endif
-
- // fprintf(stderr, "invalidation done\n");
-}
-
-static void
-yjit_init_core(void)
-{
- // Nothing yet
-}