static void insert_log(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins, MVMint32 next_bb) { /* Add the entry. */ MVMSpeshIns *log_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); log_ins->info = MVM_op_get_op(MVM_OP_sp_log); log_ins->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand)); log_ins->operands[0].reg = ins->operands[0].reg; log_ins->operands[1].lit_i16 = g->num_log_slots; if (next_bb) MVM_spesh_manipulate_insert_ins(tc, bb->succ[0], NULL, log_ins); else MVM_spesh_manipulate_insert_ins(tc, bb, ins, log_ins); g->num_log_slots++; /* Steal the de-opt annotation into the log instruction, if it exists. */ if (ins->annotations) { MVMSpeshAnn *prev_ann = NULL; MVMSpeshAnn *cur_ann = ins->annotations; while (cur_ann) { if (cur_ann->type == MVM_SPESH_ANN_DEOPT_ONE_INS) { if (prev_ann) prev_ann->next = cur_ann->next; else ins->annotations = cur_ann->next; cur_ann->next = NULL; log_ins->annotations = cur_ann; break; } prev_ann = cur_ann; cur_ann = cur_ann->next; } } }
/* Handles a pos arg that needs boxing. */ static void pos_box(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins, const MVMOpInfo *hlltype_op, const MVMOpInfo *box_op, const MVMOpInfo *arg_op, MVMuint8 kind) { MVMSpeshOperand temp_bt, temp_arg; MVMSpeshIns *hlltype, *box; /* Add HLL type op. */ temp_bt = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_obj); hlltype = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); hlltype->info = hlltype_op; hlltype->operands = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshOperand)); hlltype->operands[0] = temp_bt; MVM_spesh_manipulate_insert_ins(tc, bb, ins, hlltype); /* Add box op. */ temp_arg = MVM_spesh_manipulate_get_temp_reg(tc, g, kind); box = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); box->info = box_op; box->operands = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand)); box->operands[0] = ins->operands[0]; box->operands[1] = temp_arg; box->operands[2] = temp_bt; MVM_spesh_manipulate_insert_ins(tc, bb, hlltype, box); /* Update instruction to receive unboxed arg. */ ins->info = arg_op; ins->operands[0] = temp_arg; /* Release temporary registers. */ MVM_spesh_manipulate_release_temp_reg(tc, g, temp_bt); MVM_spesh_manipulate_release_temp_reg(tc, g, temp_arg); }
static void add_nativecall_logging(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) { MVMSpeshIns *enter_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); MVMSpeshIns *exit_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); enter_ins->info = MVM_op_get_op(MVM_OP_prof_enternative); enter_ins->operands = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshOperand)); enter_ins->operands[0] = ins->operands[2]; MVM_spesh_manipulate_insert_ins(tc, bb, ins->prev, enter_ins); exit_ins->info = MVM_op_get_op(MVM_OP_prof_exit); MVM_spesh_manipulate_insert_ins(tc, bb, ins, exit_ins); }
/* Adds an instruction to log an allocation. */ static void add_allocation_logging(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) { MVMSpeshIns *alloc_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); alloc_ins->info = MVM_op_get_op(MVM_OP_prof_allocated); alloc_ins->operands = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshOperand)); alloc_ins->operands[0] = ins->operands[0]; MVM_spesh_manipulate_insert_ins(tc, bb, ins, alloc_ins); }
/* Adds an instruction marking a name arg as being used (if we turned its * fetching into a positional). */ static MVMSpeshIns * add_named_used_ins(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins, MVMint32 idx) { MVMSpeshIns *inserted_ins = MVM_spesh_alloc(tc, g, sizeof( MVMSpeshIns )); MVMSpeshOperand *operands = MVM_spesh_alloc(tc, g, sizeof( MVMSpeshOperand )); inserted_ins->info = MVM_op_get_op(MVM_OP_sp_namedarg_used); inserted_ins->operands = operands; operands[0].lit_i16 = (MVMint16)idx; MVM_spesh_manipulate_insert_ins(tc, bb, ins, inserted_ins); return inserted_ins; }
/* Walk graph and insert write check instructions. */ static void prepend_ctw_check(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *before_ins, MVMSpeshOperand check_reg, MVMint16 guilty) { MVMSpeshIns *ctw_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); ctw_ins->info = MVM_op_get_op(MVM_OP_ctw_check); ctw_ins->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand)); ctw_ins->operands[0] = check_reg; ctw_ins->operands[1].lit_i16 = guilty; MVM_spesh_manipulate_insert_ins(tc, bb, before_ins->prev, ctw_ins); }
/* Handles a pos arg that needs unboxing. */ static void pos_unbox(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins, const MVMOpInfo *unbox_op) { MVMSpeshOperand temp = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_obj); MVMSpeshIns *unbox = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); unbox->info = unbox_op; unbox->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand)); unbox->operands[0] = ins->operands[0]; unbox->operands[1] = temp; ins->info = MVM_op_get_op(MVM_OP_sp_getarg_o); ins->operands[0] = temp; MVM_spesh_manipulate_insert_ins(tc, bb, ins, unbox); MVM_spesh_manipulate_release_temp_reg(tc, g, temp); }
/* boolification has a major indirection, which we can spesh away. * Afterwards, we may be able to spesh even further, so we defer * to other optimization methods. */ static void optimize_istrue_isfalse(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) { MVMuint8 negated_op; MVMSpeshFacts *facts = MVM_spesh_get_facts(tc, g, ins->operands[1]); if (ins->info->opcode == MVM_OP_istrue) { negated_op = 0; } else if (ins->info->opcode == MVM_OP_isfalse) { negated_op = 1; } else { return; } /* Let's try to figure out the boolification spec. */ if (facts->flags & MVM_SPESH_FACT_KNOWN_TYPE) { MVMBoolificationSpec *bs = STABLE(facts->type)->boolification_spec; switch (bs == NULL ? MVM_BOOL_MODE_NOT_TYPE_OBJECT : bs->mode) { case MVM_BOOL_MODE_UNBOX_INT: /* We can just unbox the int and pretend it's a bool. */ ins->info = MVM_op_get_op(MVM_OP_unbox_i); /* And then we might be able to optimize this even further. */ optimize_repr_op(tc, g, bb, ins, 1); break; case MVM_BOOL_MODE_NOT_TYPE_OBJECT: /* This is the same as isconcrete. */ ins->info = MVM_op_get_op(MVM_OP_isconcrete); /* And now defer another bit of optimization */ optimize_isconcrete(tc, g, ins); break; default: return; } /* Now we can take care of the negation. */ if (negated_op) { MVMSpeshIns *new_ins = MVM_spesh_alloc(tc, g, sizeof( MVMSpeshIns )); MVMSpeshOperand *operands = MVM_spesh_alloc(tc, g, sizeof( MVMSpeshOperand ) * 2); /* This is a bit naughty with regards to the SSA form, but * we'll hopefully get away with it until we have a proper * way to get new registers crammed in the middle of things */ new_ins->info = MVM_op_get_op(MVM_OP_not_i); new_ins->operands = operands; operands[0] = ins->operands[0]; operands[1] = ins->operands[0]; MVM_spesh_manipulate_insert_ins(tc, bb, ins, new_ins); } } }
static void return_to_box(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *return_bb, MVMSpeshIns *return_ins, MVMSpeshOperand target, MVMuint16 box_type_op, MVMuint16 box_op) { /* Create and insert boxing instruction after current return instruction. */ MVMSpeshIns *box_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); MVMSpeshOperand *box_operands = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand)); box_ins->info = MVM_op_get_op(box_op); box_ins->operands = box_operands; box_operands[0] = target; box_operands[1] = return_ins->operands[0]; box_operands[2] = target; MVM_spesh_manipulate_insert_ins(tc, return_bb, return_ins, box_ins); /* Now turn return instruction node into lookup of appropraite box * type. */ return_ins->info = MVM_op_get_op(box_type_op); return_ins->operands[0] = target; }
static void instrument_graph(MVMThreadContext *tc, MVMSpeshGraph *g) { /* Insert entry instruction. */ MVMSpeshBB *bb = g->entry->linear_next; MVMSpeshIns *enter_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); enter_ins->info = MVM_op_get_op(MVM_OP_prof_enter); MVM_spesh_manipulate_insert_ins(tc, bb, NULL, enter_ins); /* Walk the code and insert profile logging instructions as needed. */ while (bb) { MVMSpeshIns *ins = bb->first_ins; while (ins) { switch (ins->info->opcode) { case MVM_OP_return_i: case MVM_OP_return_n: case MVM_OP_return_s: case MVM_OP_return_o: case MVM_OP_return: { /* Log a normal exit prior to returning. */ MVMSpeshIns *exit_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); exit_ins->info = MVM_op_get_op(MVM_OP_prof_exit); MVM_spesh_manipulate_insert_ins(tc, bb, ins->prev, exit_ins); /* If the return instruction is a goto target, move to the * instrumentation instruction. */ if (ins->annotations) { MVMSpeshAnn *ann = ins->annotations; MVMSpeshAnn *prev_ann = NULL; while (ann) { if (ann->type == MVM_SPESH_ANN_FH_GOTO) { if (prev_ann) prev_ann->next = ann->next; else ins->annotations = ann->next; exit_ins->annotations = ann; ann->next = NULL; break; } prev_ann = ann; ann = ann->next; } } break; } case MVM_OP_invoke_o: case MVM_OP_param_rp_o: case MVM_OP_param_op_o: case MVM_OP_param_rn_o: case MVM_OP_param_on_o: case MVM_OP_param_sp: case MVM_OP_param_sn: case MVM_OP_newexception: case MVM_OP_usecapture: case MVM_OP_savecapture: case MVM_OP_takeclosure: case MVM_OP_getattr_o: case MVM_OP_getattrs_o: case MVM_OP_sp_p6ogetvc_o: case MVM_OP_create: case MVM_OP_sp_fastcreate: case MVM_OP_clone: case MVM_OP_box_i: case MVM_OP_box_n: case MVM_OP_box_s: case MVM_OP_iter: case MVM_OP_add_I: case MVM_OP_sub_I: case MVM_OP_mul_I: case MVM_OP_div_I: case MVM_OP_mod_I: case MVM_OP_neg_I: case MVM_OP_abs_I: case MVM_OP_bor_I: case MVM_OP_bxor_I: case MVM_OP_band_I: case MVM_OP_bnot_I: case MVM_OP_blshift_I: case MVM_OP_brshift_I: case MVM_OP_pow_I: case MVM_OP_gcd_I: case MVM_OP_lcm_I: case MVM_OP_expmod_I: case MVM_OP_rand_I: case MVM_OP_coerce_nI: case MVM_OP_coerce_sI: case MVM_OP_radix_I: { add_allocation_logging(tc, g, bb, ins); break; } case MVM_OP_getlex: case MVM_OP_getlex_no: case MVM_OP_getlexstatic_o: case MVM_OP_getlexperinvtype_o: case MVM_OP_getlexouter: case MVM_OP_getlexrel: case MVM_OP_getlexreldyn: case MVM_OP_getlexrelcaller: case MVM_OP_getlexcaller: { /* We have to check if the target register is actually * an object register. */ if ((g->local_types && g->local_types[ins->operands[0].reg.orig] == MVM_reg_obj) || (!g->local_types && g->sf->body.local_types[ins->operands[0].reg.orig] == MVM_reg_obj)) add_allocation_logging(tc, g, bb, ins); break; } case MVM_OP_getregref_i: case MVM_OP_getregref_n: case MVM_OP_getregref_s: case MVM_OP_getlexref_i: case MVM_OP_getlexref_n: case MVM_OP_getlexref_s: case MVM_OP_getlexref_ni: case MVM_OP_getlexref_nn: case MVM_OP_getlexref_ns: case MVM_OP_atposref_i: case MVM_OP_atposref_n: case MVM_OP_atposref_s: case MVM_OP_getattrref_i: case MVM_OP_getattrref_n: case MVM_OP_getattrref_s: case MVM_OP_getattrsref_i: case MVM_OP_getattrsref_n: case MVM_OP_getattrsref_s: add_allocation_logging(tc, g, bb, ins); break; case MVM_OP_nativecallinvoke: add_nativecall_logging(tc, g, bb, ins); break; default: /* See if it's an allocating extop. */ if (ins->info->opcode == (MVMuint16)-1) { MVMExtOpRecord *extops = g->sf->body.cu->body.extops; MVMuint16 num_extops = g->sf->body.cu->body.num_extops; MVMuint16 i; for (i = 0; i < num_extops; i++) { if (extops[i].info == ins->info) { if (extops[i].allocating && extops[i].info->num_operands >= 1) add_allocation_logging(tc, g, bb, ins); break; } } } break; } ins = ins->next; } bb = bb->linear_next; } }
/* Drives optimization of a call. */ static void optimize_call(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins, MVMint32 callee_idx, MVMSpeshCallInfo *arg_info) { /* Ensure we know what we're going to be invoking. */ MVMSpeshFacts *callee_facts = MVM_spesh_get_facts(tc, g, ins->operands[callee_idx]); if (callee_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) { MVMObject *code = callee_facts->value.o; MVMObject *target = NULL; if (REPR(code)->ID == MVM_REPR_ID_MVMCode) { /* Already have a code object we know we'll call. */ target = code; } else if (STABLE(code)->invocation_spec) { /* What kind of invocation will it be? */ MVMInvocationSpec *is = STABLE(code)->invocation_spec; if (!MVM_is_null(tc, is->md_class_handle)) { /* Multi-dispatch. Check if this is a dispatch where we can * use the cache directly. */ MVMRegister dest; REPR(code)->attr_funcs.get_attribute(tc, STABLE(code), code, OBJECT_BODY(code), is->md_class_handle, is->md_valid_attr_name, is->md_valid_hint, &dest, MVM_reg_int64); if (dest.i64) { /* Yes. Try to obtain the cache. */ REPR(code)->attr_funcs.get_attribute(tc, STABLE(code), code, OBJECT_BODY(code), is->md_class_handle, is->md_cache_attr_name, is->md_cache_hint, &dest, MVM_reg_obj); if (!MVM_is_null(tc, dest.o)) { MVMObject *found = MVM_multi_cache_find_spesh(tc, dest.o, arg_info); if (found) { /* Found it. Is it a code object already, or do we * have futher unpacking to do? */ if (REPR(found)->ID == MVM_REPR_ID_MVMCode) { target = found; } else if (STABLE(found)->invocation_spec) { MVMInvocationSpec *m_is = STABLE(found)->invocation_spec; if (!MVM_is_null(tc, m_is->class_handle)) { REPR(found)->attr_funcs.get_attribute(tc, STABLE(found), found, OBJECT_BODY(found), is->class_handle, is->attr_name, is->hint, &dest, MVM_reg_obj); if (REPR(dest.o)->ID == MVM_REPR_ID_MVMCode) target = dest.o; } } } } } } else if (!MVM_is_null(tc, is->class_handle)) { /* Single dispatch; retrieve the code object. */ MVMRegister dest; REPR(code)->attr_funcs.get_attribute(tc, STABLE(code), code, OBJECT_BODY(code), is->class_handle, is->attr_name, is->hint, &dest, MVM_reg_obj); if (REPR(dest.o)->ID == MVM_REPR_ID_MVMCode) target = dest.o; } } /* If we resolved to something better than the code object, then add * the resolved item in a spesh slot and insert a lookup. */ if (target && target != code && !((MVMCode *)target)->body.is_compiler_stub) { MVMSpeshIns *ss_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); ss_ins->info = MVM_op_get_op(MVM_OP_sp_getspeshslot); ss_ins->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand)); ss_ins->operands[0] = ins->operands[callee_idx]; ss_ins->operands[1].lit_i16 = MVM_spesh_add_spesh_slot(tc, g, (MVMCollectable *)target); MVM_spesh_manipulate_insert_ins(tc, bb, ins->prev, ss_ins); /* XXX TODO: Do this differently so we can eliminate the original * lookup of the enclosing code object also. */ } /* See if we can point the call at a particular specialization. */ if (target) { MVMCode *target_code = (MVMCode *)target; MVMint32 spesh_cand = try_find_spesh_candidate(tc, target_code, arg_info); if (spesh_cand >= 0) { /* Yes. Will we be able to inline? */ MVMSpeshGraph *inline_graph = MVM_spesh_inline_try_get_graph(tc, g, target_code, &target_code->body.sf->body.spesh_candidates[spesh_cand]); if (inline_graph) { /* Yes, have inline graph, so go ahead and do it. */ /*char *c_name_i = MVM_string_utf8_encode_C_string(tc, target_code->body.sf->body.name); char *c_cuid_i = MVM_string_utf8_encode_C_string(tc, target_code->body.sf->body.cuuid); char *c_name_t = MVM_string_utf8_encode_C_string(tc, g->sf->body.name); char *c_cuid_t = MVM_string_utf8_encode_C_string(tc, g->sf->body.cuuid); printf("Can inline %s (%s) into %s (%s)\n", c_name_i, c_cuid_i, c_name_t, c_cuid_t); free(c_name_i); free(c_cuid_i); free(c_name_t); free(c_cuid_t);*/ MVM_spesh_inline(tc, g, arg_info, bb, ins, inline_graph, target_code); } else { /* Can't inline, so just identify candidate. */ MVMSpeshOperand *new_operands = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand)); if (ins->info->opcode == MVM_OP_invoke_v) { new_operands[0] = ins->operands[0]; new_operands[1].lit_i16 = spesh_cand; ins->operands = new_operands; ins->info = MVM_op_get_op(MVM_OP_sp_fastinvoke_v); } else { new_operands[0] = ins->operands[0]; new_operands[1] = ins->operands[1]; new_operands[2].lit_i16 = spesh_cand; ins->operands = new_operands; switch (ins->info->opcode) { case MVM_OP_invoke_i: ins->info = MVM_op_get_op(MVM_OP_sp_fastinvoke_i); break; case MVM_OP_invoke_n: ins->info = MVM_op_get_op(MVM_OP_sp_fastinvoke_n); break; case MVM_OP_invoke_s: ins->info = MVM_op_get_op(MVM_OP_sp_fastinvoke_s); break; case MVM_OP_invoke_o: ins->info = MVM_op_get_op(MVM_OP_sp_fastinvoke_o); break; default: MVM_exception_throw_adhoc(tc, "Spesh: unhandled invoke instruction"); } } } } } } }
static void instrument_graph_with_breakpoints(MVMThreadContext *tc, MVMSpeshGraph *g) { MVMSpeshBB *bb = g->entry->linear_next; MVMuint16 array_slot = 0; MVMint32 last_line_number = -2; MVMint32 last_filename = -1; char *filename_buf = NULL; while (bb) { MVMSpeshIns *ins = bb->first_ins; MVMSpeshIns *breakpoint_ins; MVMBytecodeAnnotation *bbba = MVM_bytecode_resolve_annotation(tc, &g->sf->body, bb->initial_pc); MVMint64 line_number = -1; MVMint64 filename_string_index = -1; MVMuint32 file_bp_idx; if (bbba) { line_number = bbba->line_number; filename_string_index = bbba->filename_string_heap_index; MVM_free(bbba); } else { line_number = -1; bb = bb->linear_next; continue; } /* skip PHI instructions, to make sure PHI only occur uninterrupted after start-of-bb */ while (ins && ins->info->opcode == MVM_SSA_PHI) { ins = ins->next; } if (!ins) ins = bb->last_ins; /* Jumplists require the target BB to start in the goto op. * We must not break this, or we cause the interpreter to derail */ if (bb->last_ins->info->opcode == MVM_OP_jumplist) { MVMint16 to_skip = bb->num_succ; for (; to_skip > 0; to_skip--) { bb = bb->linear_next; } continue; } if (line_number >= 0) { breakpoint_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); breakpoint_ins->info = MVM_op_get_op(MVM_OP_breakpoint); breakpoint_ins->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand)); if (last_filename != filename_string_index) { if (filename_buf) MVM_free(filename_buf); filename_buf = MVM_string_utf8_encode_C_string(tc, MVM_cu_string(tc, g->sf->body.cu, filename_string_index)); } MVM_debugserver_register_line(tc, filename_buf, strlen(filename_buf), line_number, &file_bp_idx); breakpoint_ins->operands[0].lit_i32 = file_bp_idx; breakpoint_ins->operands[1].lit_i32 = line_number; last_filename = filename_string_index; MVM_spesh_manipulate_insert_ins(tc, bb, ins->prev, breakpoint_ins); } /* Now go through instructions to see if any are annotated with a * precise filename/lineno as well. */ while (ins) { MVMSpeshAnn *ann = ins->annotations; while (ann) { if (ann->type == MVM_SPESH_ANN_LINENO) { /* We are very likely to have one instruction here that has * the same annotation as the bb itself. We skip that one.*/ if (ann->data.lineno.line_number == line_number && ann->data.lineno.filename_string_index == filename_string_index) { break; } line_number = ann->data.lineno.line_number; filename_string_index = ann->data.lineno.filename_string_index; /*breakpoint_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));*/ /*breakpoint_ins->info = MVM_op_get_op(MVM_OP_breakpoint);*/ /*breakpoint_ins->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand));*/ if (last_filename != filename_string_index) { if (filename_buf) MVM_free(filename_buf); filename_buf = MVM_string_utf8_encode_C_string(tc, MVM_cu_string(tc, g->sf->body.cu, filename_string_index)); } MVM_debugserver_register_line(tc, filename_buf, strlen(filename_buf), line_number, &file_bp_idx); /*breakpoint_ins->operands[0].lit_i32 = file_bp_idx;*/ /*breakpoint_ins->operands[1].lit_i32 = ann->data.lineno.line_number;*/ /* XXX insert breakpoint op here, too, maybe? */ break; } ann = ann->next; } ins = ins->next; } bb = bb->linear_next; } if (filename_buf) MVM_free(filename_buf); }
static void instrument_graph(MVMThreadContext *tc, MVMSpeshGraph *g) { MVMSpeshBB *bb = g->entry->linear_next; MVMuint16 array_slot = 0; MVMint32 last_line_number = -2; MVMint32 last_filename = -1; MVMuint16 allocd_slots = g->num_bbs * 2; char *line_report_store = MVM_calloc(allocd_slots, sizeof(char)); /* Since we don't know the right size for the line report store * up front, we will have to realloc it along the way. After that * we havee to fix up the arguments to the coverage log instructions */ MVMuint32 fixup_alloc = g->num_bbs * 2; MVMuint32 fixup_elems = 0; MVMuint32 fixup_idx; /* for iterating over the fixup array */ MVMSpeshIns **to_fixup = MVM_malloc(fixup_alloc * sizeof(MVMSpeshIns*)); while (bb) { MVMSpeshIns *ins = bb->first_ins; MVMSpeshIns *log_ins; MVMBytecodeAnnotation *bbba = MVM_bytecode_resolve_annotation(tc, &g->sf->body, bb->initial_pc); MVMint64 line_number; MVMint64 filename_string_index; if (bbba) { line_number = bbba->line_number; filename_string_index = bbba->filename_string_heap_index; MVM_free(bbba); } else { line_number = -1; bb = bb->linear_next; continue; } /* skip PHI instructions, to make sure PHI only occur uninterrupted after start-of-bb */ while (ins && ins->info->opcode == MVM_SSA_PHI) { ins = ins->next; } if (!ins) ins = bb->last_ins; /* Jumplists require the target BB to start in the goto op. * We must not break this, or we cause the interpreter to derail */ if (bb->last_ins->info->opcode == MVM_OP_jumplist) { MVMint16 to_skip = bb->num_succ; for (; to_skip > 0; to_skip--) { bb = bb->linear_next; } continue; } log_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); log_ins->info = MVM_op_get_op(MVM_OP_coverage_log); log_ins->operands = MVM_spesh_alloc(tc, g, 4 * sizeof(MVMSpeshOperand)); log_ins->operands[0].lit_str_idx = filename_string_index; log_ins->operands[1].lit_i32 = line_number; if (last_line_number == line_number && last_filename == filename_string_index) { /* Consecutive BBs with the same line number and filename should * share one "already reported" slot. */ log_ins->operands[2].lit_i32 = array_slot; } else { log_ins->operands[2].lit_i32 = array_slot++; last_line_number = line_number; last_filename = filename_string_index; if (array_slot == allocd_slots) { allocd_slots *= 2; line_report_store = MVM_realloc(line_report_store, sizeof(char) * allocd_slots); } } to_fixup[fixup_elems++] = log_ins; if (fixup_elems == fixup_alloc) { fixup_alloc *= 2; to_fixup = MVM_realloc(to_fixup, sizeof(MVMSpeshIns*) * fixup_alloc); } MVM_spesh_manipulate_insert_ins(tc, bb, ins, log_ins); /* Now go through instructions to see if any are annotated with a * precise filename/lineno as well. */ while (ins) { MVMSpeshAnn *ann = ins->annotations; while (ann) { if (ann->type == MVM_SPESH_ANN_LINENO) { /* We are very likely to have one instruction here that has * the same annotation as the bb itself. We skip that one.*/ if (ann->data.lineno.line_number == line_number && ann->data.lineno.filename_string_index == filename_string_index) { break; } log_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); log_ins->info = MVM_op_get_op(MVM_OP_coverage_log); log_ins->operands = MVM_spesh_alloc(tc, g, 4 * sizeof(MVMSpeshOperand)); log_ins->operands[0].lit_str_idx = ann->data.lineno.filename_string_index; log_ins->operands[1].lit_i32 = ann->data.lineno.line_number; log_ins->operands[2].lit_i32 = array_slot++; if (array_slot == allocd_slots) { allocd_slots *= 2; line_report_store = MVM_realloc(line_report_store, sizeof(char) * allocd_slots); } to_fixup[fixup_elems++] = log_ins; if (fixup_elems == fixup_alloc) { fixup_alloc *= 2; to_fixup = MVM_realloc(to_fixup, sizeof(MVMSpeshIns*) * fixup_alloc); } break; } ann = ann->next; } ins = ins->next; } bb = bb->linear_next; } line_report_store = MVM_realloc(line_report_store, sizeof(char) * (array_slot + 1)); for (fixup_idx = 0; fixup_idx < fixup_elems; fixup_idx++) { MVMSpeshIns *ins = to_fixup[fixup_idx]; ins->operands[3].lit_i64 = (uintptr_t)line_report_store; } if (array_slot == 0) { MVM_free(line_report_store); } MVM_free(to_fixup); }
/* Considers logged types and, if they are stable, adds facts and a guard. */ static void log_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins, MVMSpeshPlanned *p, MVMSpeshAnn *deopt_one_ann, MVMSpeshAnn *logged_ann) { /* See if we have stable type information. For now, we need consistent * types, since a mis-match will force a deopt. In the future we may be * able to do Basic Block Versioning inspired tricks, like producing two * different code paths ahead when there are a small number of options. */ MVMObject *agg_type = NULL; MVMuint32 agg_type_count = 0; MVMuint32 agg_type_object = 0; MVMuint32 agg_concrete = 0; MVMuint32 i; for (i = 0; i < p->num_type_stats; i++) { MVMSpeshStatsByType *ts = p->type_stats[i]; MVMuint32 j; for (j = 0; j < ts->num_by_offset; j++) { if (ts->by_offset[j].bytecode_offset == logged_ann->data.bytecode_offset) { /* Go over the logged types. */ MVMuint32 num_types = ts->by_offset[j].num_types; MVMuint32 k; for (k = 0; k < num_types; k++) { /* If it's inconsistent with the aggregated type so far, * then first check if the type we're now seeing is either * massively more popular or massively less popular. If * massively less, disregard this one. If massively more, * disregard the previous one. Otherwise, tot up the type * object vs. concrete. */ MVMObject *cur_type = ts->by_offset[j].types[k].type; MVMuint32 count = ts->by_offset[j].types[k].count; if (agg_type) { if (agg_type != cur_type) { if (count > 100 * agg_type_count) { /* This one is hugely more popular. */ agg_type = cur_type; agg_type_count = 0; agg_concrete = 0; agg_type_object = 0; } else if (agg_type_count > 100 * count) { /* This one is hugely less popular. */ continue; } else { /* Unstable types. */ return; } } } else { agg_type = cur_type; } agg_type_count += count; if (ts->by_offset[j].types[k].type_concrete) agg_concrete++; else agg_type_object++; } /* No need to consider searching after this offset. */ break; } } } if (agg_type) { MVMSpeshIns *guard; MVMSpeshAnn *ann; MVMuint16 guard_op; /* Generate a new version. We'll use this version for the original, * unguarded, value, which we know is the instruction right before * the guard, so that makes things rather simple. Thus the facts we * will set go on the original register */ MVMSpeshOperand guard_reg = ins->operands[0]; MVMSpeshOperand preguard_reg = MVM_spesh_manipulate_new_version(tc, g, ins->operands[0].reg.orig); MVMSpeshFacts *pre_facts = &g->facts[preguard_reg.reg.orig][preguard_reg.reg.i]; MVMSpeshFacts *facts = &g->facts[guard_reg.reg.orig][guard_reg.reg.i]; ins->operands[0] = preguard_reg; pre_facts->writer = ins; /* Add facts and choose guard op. */ facts->type = agg_type; facts->flags |= MVM_SPESH_FACT_KNOWN_TYPE; if (agg_concrete && !agg_type_object) { facts->flags |= MVM_SPESH_FACT_CONCRETE; guard_op = MVM_OP_sp_guardconc; } else if (agg_type_object && !agg_concrete) { facts->flags |= MVM_SPESH_FACT_TYPEOBJ; guard_op = MVM_OP_sp_guardtype; } else { guard_op = MVM_OP_sp_guard; } /* Insert guard instruction. */ guard = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); guard->info = MVM_op_get_op(guard_op); guard->operands = MVM_spesh_alloc(tc, g, 4 * sizeof(MVMSpeshOperand)); guard->operands[0] = guard_reg; guard->operands[1] = preguard_reg; guard->operands[2].lit_i16 = MVM_spesh_add_spesh_slot_try_reuse(tc, g, (MVMCollectable *)agg_type->st); guard->operands[3].lit_ui32 = g->deopt_addrs[2 * deopt_one_ann->data.deopt_idx]; if (ins->next) MVM_spesh_manipulate_insert_ins(tc, bb, ins, guard); else MVM_spesh_manipulate_insert_ins(tc, bb->linear_next, NULL, guard); facts->writer = guard; MVM_spesh_usages_add_by_reg(tc, g, preguard_reg, guard); /* Move deopt annotation to the guard instruction. */ ann = ins->annotations; if (ann == deopt_one_ann) { ins->annotations = ann->next; } else { while (ann) { if (ann->next == deopt_one_ann) { ann->next = deopt_one_ann->next; break; } ann = ann->next; } } deopt_one_ann->next = NULL; guard->annotations = deopt_one_ann; /* Copy deopt usages to the preguard register. */ { MVMSpeshDeoptUseEntry *due = facts->usage.deopt_users; while (due) { MVM_spesh_usages_add_deopt_usage(tc, g, pre_facts, due->deopt_idx); due = due->next; } } /* Add entry in log guards table, and mark facts as depending on it. */ if (g->num_log_guards % 16 == 0) { MVMSpeshLogGuard *orig_log_guards = g->log_guards; g->log_guards = MVM_spesh_alloc(tc, g, (g->num_log_guards + 16) * sizeof(MVMSpeshLogGuard)); if (orig_log_guards) memcpy(g->log_guards, orig_log_guards, g->num_log_guards * sizeof(MVMSpeshLogGuard)); } g->log_guards[g->num_log_guards].ins = guard; g->log_guards[g->num_log_guards].bb = ins->next ? bb : bb->linear_next; facts->log_guards = MVM_spesh_alloc(tc, g, sizeof(MVMint32)); facts->log_guards[0] = g->num_log_guards; facts->num_log_guards++; g->num_log_guards++; } }