/* * cfg.h: Has a Control Flow Graph struct definition shared by parser.c and compiler.c. */ #ifndef LLRB_CFG_H #define LLRB_CFG_H #include <stdbool.h> #include "llvm-c/Core.h" struct llrb_basic_block { // Fields set by parser: unsigned int start; // Start index of ISeq body's iseq_encoded. unsigned int end; // End index of ISeq body's iseq_encoded. unsigned int incoming_size; // Size of incoming_starts. unsigned int *incoming_starts; // Start indices of incoming basic blocks. This buffer is freed by llrb_destruct_cfg. bool traversed; // Prevents infinite loop in `llrb_set_incoming_blocks_by` and used by compiler to judge reachable or not. // Fields set by compiler: LLVMBasicBlockRef ref; // LLVM's actual BasicBlock reference. This value is always available after `llrb_init_cfg_for_compile` is called. LLVMValueRef phi; // Phi node to collect incoming values. This will be created if incoming_size > 1 and compiled time's stack size > 0. bool compiled; // Prevents infinite loop in `llrb_compile_basic_block`. }; // Holds Control-Flow-Graph-like data structure. Actually it's a buffer of graph nodes. struct llrb_cfg { struct llrb_basic_block* blocks; // This buffer is freed by llrb_destruct_cfg. unsigned int size; }; // Used by llrb_dump_cfg. #include "cruby.h" #include "cruby_extra/insns.inc" #include "cruby_extra/insns_info.inc" // Not using `rb_iseq_original_iseq` to avoid unnecessary memory allocation. extern int rb_vm_insn_addr2insn(const void *addr); static void llrb_disasm_insns(const struct rb_iseq_constant_body *body, unsigned int start, unsigned int end) { for (unsigned int i = start; i <= end;) { int insn = rb_vm_insn_addr2insn((void *)body->iseq_encoded[i]); fprintf(stderr, " %04d %-27s [%-4s] ", i, insn_name(insn), insn_op_types(insn)); for (int j = 1; j < insn_len(insn); j++) { VALUE op = body->iseq_encoded[i+j]; switch (insn_op_type(insn, j-1)) { case TS_NUM: fprintf(stderr, "%-4ld ", (rb_num_t)op); break; case TS_OFFSET: fprintf(stderr, "%"PRIdVALUE" ", (VALUE)(i + j + op + 1)); break; } } fprintf(stderr, "\n"); i += insn_len(insn); } fprintf(stderr, "\n"); } static void llrb_dump_catch_table(const struct iseq_catch_table *ct) { if (!ct) return; fprintf(stderr, "-- LLRB: catch table (size=%d)----------------\n", ct->size); for (unsigned int i = 0; i < ct->size; i++) { const struct iseq_catch_table_entry *entry = &ct->entries[i]; switch (entry->type) { case CATCH_TYPE_RESCUE: fprintf(stderr, "CATCH_TYPE_RESCUE"); break; case CATCH_TYPE_ENSURE: fprintf(stderr, "CATCH_TYPE_ENSURE"); break; case CATCH_TYPE_RETRY: fprintf(stderr, "CATCH_TYPE_RETRY"); break; case CATCH_TYPE_BREAK: fprintf(stderr, "CATCH_TYPE_BREAK"); break; case CATCH_TYPE_REDO: fprintf(stderr, "CATCH_TYPE_REDO"); break; case CATCH_TYPE_NEXT: fprintf(stderr, "CATCH_TYPE_NEXT"); break; } fprintf(stderr, ": start=%d, end=%d, cont=%d, sp=%d, iseq=%lx\n", entry->start, entry->end, entry->cont, entry->sp, (VALUE)entry->iseq); } fprintf(stderr, "\n"); } // NOTE: insns(|_info).inc has some static functions, and llrb_dump_cfg uses all those functions. // Thus we must call this function somewhere in all included files to pass compilation. `if (0)` is placed for such a purpose. static void llrb_dump_cfg(const struct rb_iseq_constant_body *body, const struct llrb_cfg *cfg) { fprintf(stderr, "\n== LLRB: cfg ================================\n"); for (unsigned int i = 0; i < cfg->size; i++) { struct llrb_basic_block *block = cfg->blocks + i; fprintf(stderr, "BasicBlock[%d-%d]", block->start, block->end); if (block->incoming_size > 0) fprintf(stderr, " <- "); if (!block->traversed) fprintf(stderr, " UNREACHABLE"); for (unsigned int j = 0; j < block->incoming_size; j++) { fprintf(stderr, "%d", block->incoming_starts[j]); if (j != block->incoming_size-1) { fprintf(stderr, ", "); } } fprintf(stderr, "\n"); llrb_disasm_insns(body, block->start, block->end); } llrb_dump_catch_table(body->catch_table); } #endif // LLRB_CFG_H