static void build_cfg(MVMThreadContext *tc, MVMSpeshGraph *g, MVMStaticFrame *sf, MVMint32 *existing_deopts, MVMint32 num_existing_deopts) { MVMSpeshBB *cur_bb, *prev_bb; MVMSpeshIns *last_ins; MVMint64 i; MVMint32 bb_idx; /* Temporary array of all MVMSpeshIns we create (one per instruction). * Overestimate at size. Has the flat view, matching the bytecode. */ MVMSpeshIns **ins_flat = MVM_calloc(g->bytecode_size / 2, sizeof(MVMSpeshIns *)); /* Temporary array where each byte in the input bytecode gets a 32-bit * integer. This is used for two things: * A) When we make the MVMSpeshIns for an instruction starting at the * byte, we put the instruction index (into ins_flat) in the slot, * shifting it by 2 bits to the left. We will use this to do fixups. * B) The first bit is "I have an incoming branch" - that is, start of * a basic block. The second bit is "I can branch" - that is, end of * a basic block. It's possible to have both bits set. * Anything that's just a zero has no instruction starting there. */ MVMuint32 *byte_to_ins_flags = MVM_calloc(g->bytecode_size, sizeof(MVMuint32)); /* Instruction to basic block mapping. Initialized later. */ MVMSpeshBB **ins_to_bb = NULL; /* Make first pass through the bytecode. In this pass, we make MVMSpeshIns * nodes for each instruction and set the start/end of block bits. Also * set handler targets as basic block starters. */ MVMCompUnit *cu = sf->body.cu; MVMuint8 *pc = g->bytecode; MVMuint8 *end = g->bytecode + g->bytecode_size; MVMuint32 ins_idx = 0; MVMuint8 next_bbs = 1; /* Next iteration (here, first) starts a BB. */ for (i = 0; i < g->num_handlers; i++) byte_to_ins_flags[g->handlers[i].goto_offset] |= MVM_CFG_BB_START; while (pc < end) { /* Look up op info. */ MVMuint16 opcode = *(MVMuint16 *)pc; MVMuint8 *args = pc + 2; MVMuint8 arg_size = 0; const MVMOpInfo *info = get_op_info(tc, cu, opcode); /* Create an instruction node, add it, and record its position. */ MVMSpeshIns *ins_node = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); ins_flat[ins_idx] = ins_node; byte_to_ins_flags[pc - g->bytecode] |= ins_idx << 2; /* Did previous instruction end a basic block? */ if (next_bbs) { byte_to_ins_flags[pc - g->bytecode] |= MVM_CFG_BB_START; next_bbs = 0; } /* Also check we're not already a BB start due to being a branch * target, in which case we should ensure our prior is marked as * a BB end. */ else { if (byte_to_ins_flags[pc - g->bytecode] & MVM_CFG_BB_START) { MVMuint32 hunt = pc - g->bytecode; while (!byte_to_ins_flags[--hunt]); byte_to_ins_flags[hunt] |= MVM_CFG_BB_END; } } /* Store opcode */ ins_node->info = info; /* Go over operands. */ ins_node->operands = MVM_spesh_alloc(tc, g, info->num_operands * sizeof(MVMSpeshOperand)); for (i = 0; i < info->num_operands; i++) { MVMuint8 flags = info->operands[i]; MVMuint8 rw = flags & MVM_operand_rw_mask; switch (rw) { case MVM_operand_read_reg: case MVM_operand_write_reg: ins_node->operands[i].reg.orig = GET_UI16(args, arg_size); arg_size += 2; break; case MVM_operand_read_lex: case MVM_operand_write_lex: ins_node->operands[i].lex.idx = GET_UI16(args, arg_size); ins_node->operands[i].lex.outers = GET_UI16(args, arg_size + 2); arg_size += 4; break; case MVM_operand_literal: { MVMuint32 type = flags & MVM_operand_type_mask; switch (type) { case MVM_operand_int8: ins_node->operands[i].lit_i8 = GET_I8(args, arg_size); arg_size += 1; break; case MVM_operand_int16: ins_node->operands[i].lit_i16 = GET_I16(args, arg_size); arg_size += 2; break; case MVM_operand_int32: ins_node->operands[i].lit_i32 = GET_I32(args, arg_size); arg_size += 4; break; case MVM_operand_int64: ins_node->operands[i].lit_i64 = MVM_BC_get_I64(args, arg_size); arg_size += 8; break; case MVM_operand_num32: ins_node->operands[i].lit_n32 = GET_N32(args, arg_size); arg_size += 4; break; case MVM_operand_num64: ins_node->operands[i].lit_n64 = MVM_BC_get_N64(args, arg_size); arg_size += 8; break; case MVM_operand_callsite: ins_node->operands[i].callsite_idx = GET_UI16(args, arg_size); arg_size += 2; break; case MVM_operand_coderef: ins_node->operands[i].coderef_idx = GET_UI16(args, arg_size); arg_size += 2; break; case MVM_operand_str: ins_node->operands[i].lit_str_idx = GET_UI32(args, arg_size); arg_size += 4; break; case MVM_operand_ins: { /* Stash instruction offset. */ MVMuint32 target = GET_UI32(args, arg_size); ins_node->operands[i].ins_offset = target; /* This is a branching instruction, so it's a BB end. */ byte_to_ins_flags[pc - g->bytecode] |= MVM_CFG_BB_END; /* Its target is a BB start, and any previous instruction * we already passed needs marking as a BB end. */ byte_to_ins_flags[target] |= MVM_CFG_BB_START; if (target > 0 && target < pc - g->bytecode) { while (!byte_to_ins_flags[--target]); byte_to_ins_flags[target] |= MVM_CFG_BB_END; } /* Next instruction is also a BB start. */ next_bbs = 1; arg_size += 4; break; } case MVM_operand_spesh_slot: ins_node->operands[i].lit_i16 = GET_I16(args, arg_size); arg_size += 2; break; default: MVM_exception_throw_adhoc(tc, "Spesh: unknown operand type %d in graph building (op %s)", (int)type, ins_node->info->name); } } break; } } /* We specially handle the jumplist case, which needs to mark all of * the possible places we could jump to in the following instructions * as starts of basic blocks. It is, in itself, the end of one. Note * we jump to the instruction after the n jump points if none match, * so that is marked too. */ if (opcode == MVM_OP_jumplist) { MVMint64 n = MVM_BC_get_I64(args, 0); for (i = 0; i <= n; i++) byte_to_ins_flags[(pc - g->bytecode) + 12 + i * 6] |= MVM_CFG_BB_START; byte_to_ins_flags[pc - g->bytecode] |= MVM_CFG_BB_END; } /* Invocations, returns, and throws are basic block ends. */ switch (opcode) { case MVM_OP_invoke_v: case MVM_OP_invoke_i: case MVM_OP_invoke_n: case MVM_OP_invoke_s: case MVM_OP_invoke_o: case MVM_OP_return_i: case MVM_OP_return_n: case MVM_OP_return_s: case MVM_OP_return_o: case MVM_OP_return: case MVM_OP_throwdyn: case MVM_OP_throwlex: case MVM_OP_throwlexotic: case MVM_OP_throwcatdyn: case MVM_OP_throwcatlex: case MVM_OP_throwcatlexotic: case MVM_OP_die: case MVM_OP_rethrow: case MVM_OP_resume: byte_to_ins_flags[pc - g->bytecode] |= MVM_CFG_BB_END; next_bbs = 1; break; } /* Final instruction is basic block end. */ if (pc + 2 + arg_size == end) byte_to_ins_flags[pc - g->bytecode] |= MVM_CFG_BB_END; /* Caculate next instruction's PC. */ pc += 2 + arg_size; /* If this is a deopt point opcode... */ if (!existing_deopts && (info->deopt_point & MVM_DEOPT_MARK_ONE)) add_deopt_annotation(tc, g, ins_node, pc, MVM_SPESH_ANN_DEOPT_ONE_INS); if (!existing_deopts && (info->deopt_point & MVM_DEOPT_MARK_ALL)) add_deopt_annotation(tc, g, ins_node, pc, MVM_SPESH_ANN_DEOPT_ALL_INS); if (!existing_deopts && (info->deopt_point & MVM_DEOPT_MARK_OSR)) add_deopt_annotation(tc, g, ins_node, pc, MVM_SPESH_ANN_DEOPT_OSR); /* Go to next instruction. */ ins_idx++; } /* Annotate instructions that are handler-significant. */ for (i = 0; i < g->num_handlers; i++) { MVMSpeshIns *start_ins = ins_flat[byte_to_ins_flags[g->handlers[i].start_offset] >> 2]; MVMSpeshIns *end_ins = ins_flat[byte_to_ins_flags[g->handlers[i].end_offset] >> 2]; MVMSpeshIns *goto_ins = ins_flat[byte_to_ins_flags[g->handlers[i].goto_offset] >> 2]; MVMSpeshAnn *start_ann = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshAnn)); MVMSpeshAnn *end_ann = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshAnn)); MVMSpeshAnn *goto_ann = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshAnn)); start_ann->next = start_ins->annotations; start_ann->type = MVM_SPESH_ANN_FH_START; start_ann->data.frame_handler_index = i; start_ins->annotations = start_ann; end_ann->next = end_ins->annotations; end_ann->type = MVM_SPESH_ANN_FH_END; end_ann->data.frame_handler_index = i; end_ins->annotations = end_ann; goto_ann->next = goto_ins->annotations; goto_ann->type = MVM_SPESH_ANN_FH_GOTO; goto_ann->data.frame_handler_index = i; goto_ins->annotations = goto_ann; } /* Annotate instructions that are inline start/end points. */ for (i = 0; i < g->num_inlines; i++) { MVMSpeshIns *start_ins = ins_flat[byte_to_ins_flags[g->inlines[i].start] >> 2]; MVMSpeshIns *end_ins = ins_flat[byte_to_ins_flags[g->inlines[i].end] >> 2]; MVMSpeshAnn *start_ann = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshAnn)); MVMSpeshAnn *end_ann = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshAnn)); start_ann->next = start_ins->annotations; start_ann->type = MVM_SPESH_ANN_INLINE_START; start_ann->data.inline_idx = i; start_ins->annotations = start_ann; end_ann->next = end_ins->annotations; end_ann->type = MVM_SPESH_ANN_INLINE_END; end_ann->data.inline_idx = i; end_ins->annotations = end_ann; } /* Now for the second pass, where we assemble the basic blocks. Also we * build a lookup table of instructions that start a basic block to that * basic block, for the final CFG construction. We make the entry block a * special one, containing a noop; it will have any exception handler * targets linked from it, so they show up in the graph. */ g->entry = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshBB)); g->entry->first_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); g->entry->first_ins->info = get_op_info(tc, cu, 0); g->entry->last_ins = g->entry->first_ins; g->entry->idx = 0; cur_bb = NULL; prev_bb = g->entry; last_ins = NULL; ins_to_bb = MVM_calloc(ins_idx, sizeof(MVMSpeshBB *)); ins_idx = 0; bb_idx = 1; for (i = 0; i < g->bytecode_size; i++) { MVMSpeshIns *cur_ins; /* Skip zeros; no instruction here. */ if (!byte_to_ins_flags[i]) continue; /* Get current instruction. */ cur_ins = ins_flat[byte_to_ins_flags[i] >> 2]; /* Start of a basic block? */ if (byte_to_ins_flags[i] & MVM_CFG_BB_START) { /* Should not already be in a basic block. */ if (cur_bb) { MVM_spesh_graph_destroy(tc, g); MVM_exception_throw_adhoc(tc, "Spesh: confused during basic block analysis (in block)"); } /* Create it, and set first instruction and index. */ cur_bb = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshBB)); cur_bb->first_ins = cur_ins; cur_bb->idx = bb_idx; bb_idx++; /* Record instruction -> BB start mapping. */ ins_to_bb[ins_idx] = cur_bb; /* Link it to the previous one. */ prev_bb->linear_next = cur_bb; } /* Should always be in a BB at this point. */ if (!cur_bb) { MVM_spesh_graph_destroy(tc, g); MVM_exception_throw_adhoc(tc, "Spesh: confused during basic block analysis (no block)"); } /* Add instruction into double-linked per-block instruction list. */ if (last_ins) { last_ins->next = cur_ins; cur_ins->prev = last_ins; } last_ins = cur_ins; /* End of a basic block? */ if (byte_to_ins_flags[i] & MVM_CFG_BB_END) { cur_bb->last_ins = cur_ins; prev_bb = cur_bb; cur_bb = NULL; last_ins = NULL; } ins_idx++; } g->num_bbs = bb_idx; /* Finally, link the basic blocks up to form a CFG. Along the way, any of * the instruction operands get the target BB stored. */ cur_bb = g->entry; while (cur_bb) { /* If it's the first block, it's a special case; successors are the * real successor and all exception handlers. */ if (cur_bb == g->entry) { cur_bb->num_succ = 1 + g->num_handlers; cur_bb->succ = MVM_spesh_alloc(tc, g, cur_bb->num_succ * sizeof(MVMSpeshBB *)); cur_bb->succ[0] = cur_bb->linear_next; for (i = 0; i < g->num_handlers; i++) { MVMuint32 offset = g->handlers[i].goto_offset; cur_bb->succ[i + 1] = ins_to_bb[byte_to_ins_flags[offset] >> 2]; } } /* Otherwise, consider the last instruction, to see how we leave the BB. */ else { switch (cur_bb->last_ins->info->opcode) {
static void bytecode_dump_frame_internal(MVMThreadContext *tc, MVMStaticFrame *frame, MVMSpeshCandidate *maybe_candidate, MVMuint8 *frame_cur_op, char ***frame_lexicals, char **oo, MVMuint32 *os, MVMuint32 *ol) { /* since "references" are not a thing in C, keep a local copy of these * and update the passed-in pointers at the end of the function */ char *o = *oo; MVMuint32 s = *os; MVMuint32 l = *ol; MVMuint32 i, j, k; /* mostly stolen from validation.c */ MVMStaticFrame *static_frame = frame; MVMuint32 bytecode_size = maybe_candidate ? maybe_candidate->bytecode_size : static_frame->body.bytecode_size; MVMuint8 *bytecode_start = maybe_candidate ? maybe_candidate->bytecode : static_frame->body.bytecode; MVMuint8 *bytecode_end = bytecode_start + bytecode_size; /* current position in the bytestream */ MVMuint8 *cur_op = bytecode_start; /* positions in the bytestream that are starts of ops and goto targets */ MVMuint8 *labels = MVM_calloc(1, bytecode_size); MVMuint32 *jumps = MVM_calloc(1, sizeof(MVMuint32) * bytecode_size); char **lines = MVM_malloc(sizeof(char *) * bytecode_size); MVMuint32 *linelocs = MVM_malloc(sizeof(MVMuint32) * bytecode_size); MVMuint32 lineno = 0; MVMuint32 lineloc; MVMuint16 op_num; const MVMOpInfo *op_info; MVMuint32 operand_size = 0; unsigned char op_rw; unsigned char op_type; unsigned char op_flags; MVMOpInfo tmp_extop_info; /* stash the outer output buffer */ MVMuint32 sP = s; MVMuint32 lP = l; char *oP = o; char *tmpstr; char mark_this_line = 0; MVMCompUnit *cu = static_frame->body.cu; while (cur_op < bytecode_end - 1) { /* allocate a line buffer */ s = 200; l = 0; o = MVM_calloc(s, sizeof(char)); lineloc = cur_op - bytecode_start; /* mark that this line starts at this point in the bytestream */ linelocs[lineno] = lineloc; /* mark that this point in the bytestream is an op boundary */ labels[lineloc] |= MVM_val_op_boundary; mark_this_line = 0; if (frame_cur_op) { if (frame_cur_op == cur_op || frame_cur_op == cur_op + 2) { mark_this_line = 1; } } if (mark_this_line) { a("-> "); } else { a(" "); } op_num = *((MVMint16 *)cur_op); cur_op += 2; if (op_num < MVM_OP_EXT_BASE) { op_info = MVM_op_get_op(op_num); if (op_info) a("%-18s ", op_info->name); else a("invalid OP "); } else { MVMint16 ext_op_num = op_num - MVM_OP_EXT_BASE; if (0 <= ext_op_num && ext_op_num < cu->body.num_extops) { MVMExtOpRecord r = cu->body.extops[ext_op_num]; MVMuint8 j; memset(&tmp_extop_info, 0, sizeof(MVMOpInfo)); tmp_extop_info.name = MVM_string_utf8_encode_C_string(tc, r.name); memcpy(tmp_extop_info.operands, r.operand_descriptor, 8); for (j = 0; j < 8; j++) if (tmp_extop_info.operands[j]) tmp_extop_info.num_operands++; else break; op_info = &tmp_extop_info; a("%-12s ", tmp_extop_info.name); MVM_free((void *)tmp_extop_info.name); tmp_extop_info.name = NULL; } else { a("Extension op %d out of range", (int)op_num); } } if (!op_info) continue; for (i = 0; i < op_info->num_operands; i++) { if (i) a(", "); op_flags = op_info->operands[i]; op_rw = op_flags & MVM_operand_rw_mask; op_type = op_flags & MVM_operand_type_mask; if (op_rw == MVM_operand_literal) { switch (op_type) { case MVM_operand_int8: operand_size = 1; a("%"PRId8, GET_I8(cur_op, 0)); break; case MVM_operand_int16: operand_size = 2; a("%"PRId16, GET_I16(cur_op, 0)); break; case MVM_operand_int32: operand_size = 4; a("%"PRId32, GET_I32(cur_op, 0)); break; case MVM_operand_int64: operand_size = 8; a("%"PRId64, MVM_BC_get_I64(cur_op, 0)); break; case MVM_operand_uint8: operand_size = 1; a("%"PRIu8, GET_I8(cur_op, 0)); break; case MVM_operand_uint16: operand_size = 2; a("%"PRIu16, GET_I16(cur_op, 0)); break; case MVM_operand_uint32: operand_size = 4; a("%"PRIu32, GET_I32(cur_op, 0)); break; case MVM_operand_uint64: operand_size = 8; a("%"PRIu64, MVM_BC_get_I64(cur_op, 0)); break; case MVM_operand_num32: operand_size = 4; a("%f", GET_N32(cur_op, 0)); break; case MVM_operand_num64: operand_size = 8; a("%f", MVM_BC_get_N64(cur_op, 0)); break; case MVM_operand_callsite: operand_size = 2; a("Callsite_%"PRIu16, GET_UI16(cur_op, 0)); break; case MVM_operand_coderef: operand_size = 2; a("Frame_%"PRIu16, GET_UI16(cur_op, 0)); break; case MVM_operand_str: operand_size = 4; if (GET_UI32(cur_op, 0) < cu->body.num_strings) { tmpstr = MVM_string_utf8_encode_C_string( tc, MVM_cu_string(tc, cu, GET_UI32(cur_op, 0))); /* XXX C-string-literal escape the \ and ' and line breaks and non-ascii someday */ a("'%s'", tmpstr); MVM_free(tmpstr); } else a("invalid string index: %d", GET_UI32(cur_op, 0)); break; case MVM_operand_ins: operand_size = 4; /* luckily all the ins operands are at the end of op operands, so I can wait to resolve the label to the end. */ if (GET_UI32(cur_op, 0) < bytecode_size) { labels[GET_UI32(cur_op, 0)] |= MVM_val_branch_target; jumps[lineno] = GET_UI32(cur_op, 0); } break; case MVM_operand_obj: /* not sure what a literal object is */ operand_size = 4; break; case MVM_operand_spesh_slot: operand_size = 2; a("sslot(%d)", GET_UI16(cur_op, 0)); break; default: fprintf(stderr, "what is an operand of type %d??\n", op_type); abort(); /* never reached, silence compiler warnings */ } } else if (op_rw == MVM_operand_read_reg || op_rw == MVM_operand_write_reg) { /* register operand */ MVMuint8 frame_has_inlines = maybe_candidate && maybe_candidate->num_inlines ? 1 : 0; MVMuint16 *local_types = frame_has_inlines ? maybe_candidate->local_types : frame->body.local_types; MVMuint16 num_locals = frame_has_inlines ? maybe_candidate->num_locals : frame->body.num_locals; operand_size = 2; a("loc_%u_%s", GET_REG(cur_op, 0), get_typename(local_types[GET_REG(cur_op, 0)])); } else if (op_rw == MVM_operand_read_lex || op_rw == MVM_operand_write_lex) { /* lexical operand */ MVMuint16 idx, frames, m; MVMStaticFrame *applicable_frame = static_frame; operand_size = 4; idx = GET_UI16(cur_op, 0); frames = GET_UI16(cur_op, 2); m = frames; while (m > 0) { applicable_frame = applicable_frame->body.outer; m--; } /* inefficient, I know. should use a hash. */ for (m = 0; m < cu->body.num_frames; m++) { if (get_frame(tc, cu, m) == applicable_frame) { char *lexname = frame_lexicals ? frame_lexicals[m][idx] : "lex??"; a("lex_Frame_%u_%s_%s", m, lexname, get_typename(applicable_frame->body.lexical_types[idx])); } } } cur_op += operand_size; } lines[lineno++] = o; } { MVMuint32 *linelabels = MVM_calloc(lineno, sizeof(MVMuint32)); MVMuint32 byte_offset = 0; MVMuint32 line_number = 0; MVMuint32 label_number = 1; MVMuint32 *annotations = MVM_calloc(lineno, sizeof(MVMuint32)); for (; byte_offset < bytecode_size; byte_offset++) { if (labels[byte_offset] & MVM_val_branch_target) { /* found a byte_offset where a label should be. now crawl up through the lines to find which line starts there */ while (line_number < lineno && linelocs[line_number] != byte_offset) line_number++; if (line_number < lineno) linelabels[line_number] = label_number++; } } o = oP; l = lP; s = sP; i = 0; /* resolve annotation line numbers */ for (j = 0; j < frame->body.num_annotations; j++) { MVMuint32 ann_offset = GET_UI32(frame->body.annotations_data, j*12); for (; i < lineno; i++) { if (linelocs[i] == ann_offset) { annotations[i] = j + 1; break; } } } for (j = 0; j < lineno; j++) { if (annotations[j]) { MVMuint16 shi = GET_UI16(frame->body.annotations_data + 4, (annotations[j] - 1)*12); tmpstr = MVM_string_utf8_encode_C_string( tc, MVM_cu_string(tc, cu, shi < cu->body.num_strings ? shi : 0)); a(" annotation: %s:%u\n", tmpstr, GET_UI32(frame->body.annotations_data, (annotations[j] - 1)*12 + 8)); MVM_free(tmpstr); } if (linelabels[j]) a(" label_%u:\n", linelabels[j]); a("%05d %s", j, lines[j]); MVM_free(lines[j]); if (jumps[j]) { /* horribly inefficient for large frames. again, should use a hash */ line_number = 0; while (line_number < lineno && linelocs[line_number] != jumps[j]) line_number++; if (line_number < lineno) a("label_%u(%05u)", linelabels[line_number], line_number); else a("label (invalid: %05u)", jumps[j]); } a("\n"); } MVM_free(lines); MVM_free(jumps); MVM_free(linelocs); MVM_free(linelabels); MVM_free(labels); MVM_free(annotations); } *oo = o; *os = s; *ol = l; }