/** * \file * Copyright (C) 2016 Xamarin Inc * Licensed under the MIT license. See LICENSE file in the project root for full license information. */ /* inspired by BinaryGraphPrinter.java of Graal */ #include "mini.h" #if !defined(DISABLE_LOGGING) && !defined(DISABLE_JIT) && !defined(HOST_WIN32) #include #include #include #include #include #include #include #include #include #include #include #if 0 #define CFG_DEBUG #endif #ifdef CFG_DEBUG #ifdef HAVE_C99_SUPPORT #define cfg_debug(format, ...) g_debug(format, __VA_ARGS__) #else #define cfg_debug(...) g_debug(__VA_ARGS__) #endif #else #ifdef HAVE_C99_SUPPORT #define cfg_debug(format, ...) do {} while (0) #else #define cfg_debug(...) do {} while (0) #endif #endif static ConstantPoolEntry* create_cp_entry (MonoCompile *cfg, void *data, pool_type pt) { ConstantPoolEntry *entry = (ConstantPoolEntry *) mono_mempool_alloc0 (cfg->mempool, sizeof (ConstantPoolEntry)); entry->pt = pt; entry->data = data; return entry; } static void write_pool (MonoCompile *cfg, ConstantPoolEntry *entry); static int create_socket (const char *hostname, const int port) { int sockfd = 0; struct sockaddr_in serv_addr; if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0) { g_warning ("cfg_dump: could not create socket"); return -1; } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons (port); serv_addr.sin_addr.s_addr = inet_addr (hostname); if (connect (sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { g_warning ("cfg_dump: Connect Failed: %s", strerror (errno)); return -2; } return sockfd; } static void write_byte (MonoCompile *cfg, unsigned char b) { write (cfg->gdump_ctx->fd, &b, 1); } static void write_short (MonoCompile *cfg, short s) { short swap = htons (s); write (cfg->gdump_ctx->fd, &swap, 2); } static void write_int (MonoCompile *cfg, int v) { int swap = htonl (v); write (cfg->gdump_ctx->fd, &swap, 4); } static void write_string (MonoCompile *cfg, const char *str) { size_t len = strnlen (str, 0x2000); write_int (cfg, (int) len); gunichar2 *u = u8to16 (str); for (int i = 0; i < len; i++) write_short (cfg, u[i]); } static void add_pool_entry (MonoCompile *cfg, ConstantPoolEntry *entry) { int *cp_id= (int *) mono_mempool_alloc0 (cfg->mempool, sizeof (int)); *cp_id = cfg->gdump_ctx->next_cp_id; g_hash_table_insert (cfg->gdump_ctx->constant_pool, entry, cp_id); write_byte (cfg, POOL_NEW); write_short (cfg, cfg->gdump_ctx->next_cp_id++); switch (entry->pt) { case PT_STRING: write_byte (cfg, POOL_STRING); write_string (cfg, (char *) entry->data); break; case PT_METHOD: { MonoMethod *method = (MonoMethod *) entry->data; write_byte (cfg, POOL_METHOD); write_pool (cfg, create_cp_entry (cfg, (void *) method->klass, PT_KLASS)); write_pool (cfg, create_cp_entry (cfg, (void *) method->name, PT_STRING)); write_pool (cfg, create_cp_entry (cfg, (void *) method->signature, PT_SIGNATURE)); write_int (cfg, (int) method->flags); write_int (cfg, -1); // don't transmit bytecode. break; } case PT_KLASS: { MonoClass *klass = (MonoClass *) entry->data; write_byte (cfg, POOL_KLASS); write_string (cfg, klass->name); write_byte (cfg, KLASS); break; } case PT_SIGNATURE: { write_byte (cfg, POOL_SIGNATURE); MonoMethodSignature *sig = (MonoMethodSignature *) entry->data; write_short (cfg, sig->param_count); for (int i = 0; i < sig->param_count; i++) { GString *sbuf = g_string_new (NULL); mono_type_get_desc (sbuf, sig->params [i], TRUE); write_pool (cfg, create_cp_entry (cfg, (void *) sbuf->str, PT_STRING)); g_string_free (sbuf, TRUE); } GString *sbuf = g_string_new (NULL); mono_type_get_desc (sbuf, sig->ret, TRUE); write_pool (cfg, create_cp_entry (cfg, (void *) sbuf->str, PT_STRING)); g_string_free (sbuf, TRUE); break; } case PT_OPTYPE: { MonoInst *insn = (MonoInst *) entry->data; write_byte (cfg, POOL_NODE_CLASS); write_string (cfg, mono_inst_name (insn->opcode)); GString *insndesc = mono_print_ins_index_strbuf (-1, insn); int len = strnlen (insndesc->str, 0x2000); #define CUTOFF 40 if (len > CUTOFF) { insndesc->str[CUTOFF] = '\0'; insndesc->str[CUTOFF - 1] = '.'; insndesc->str[CUTOFF - 2] = '.'; } write_string (cfg, insndesc->str); if (len > CUTOFF) insndesc->str[CUTOFF] = ' '; g_string_free (insndesc, TRUE); // one predecessor write_short (cfg, 1); write_byte (cfg, 0); write_pool (cfg, create_cp_entry (cfg, (void *) "predecessor", PT_STRING)); write_pool (cfg, create_cp_entry (cfg, (void *) NULL, PT_INPUTTYPE)); // make NUM_SUCCESSOR successor edges, not everyone will be used. #define NUM_SUCCESSOR 5 write_short (cfg, NUM_SUCCESSOR); for (int i = 0; i < NUM_SUCCESSOR; i++) { char *str = g_strdup ("successor1"); str[9] = '0' + i; write_byte (cfg, 0); write_pool (cfg, create_cp_entry (cfg, (void *) str, PT_STRING)); } break; } case PT_INPUTTYPE: { write_byte (cfg, POOL_ENUM); write_pool (cfg, create_cp_entry (cfg, (void *) NULL, PT_ENUMKLASS)); write_int (cfg, 0); break; } case PT_ENUMKLASS: { write_byte (cfg, POOL_KLASS); write_string (cfg, "InputType"); write_byte (cfg, ENUM_KLASS); write_int (cfg, 1); write_pool (cfg, create_cp_entry (cfg, (void *) "fixed", PT_STRING)); break; } } } static void write_pool (MonoCompile *cfg, ConstantPoolEntry *entry) { if (!entry || !entry->data) { write_byte (cfg, POOL_NULL); return; } short *cp_index = (short *) g_hash_table_lookup (cfg->gdump_ctx->constant_pool, entry); if (cp_index == NULL) add_pool_entry (cfg, entry); else { switch (entry->pt) { case PT_STRING: write_byte (cfg, POOL_STRING); break; case PT_METHOD: write_byte (cfg, POOL_METHOD); break; case PT_ENUMKLASS: write_byte (cfg, POOL_KLASS); break; case PT_KLASS: write_byte (cfg, POOL_KLASS); break; case PT_SIGNATURE: write_byte (cfg, POOL_SIGNATURE); break; case PT_OPTYPE: write_byte (cfg, POOL_NODE_CLASS); break; case PT_INPUTTYPE: write_byte (cfg, POOL_ENUM); break; } write_short (cfg, *cp_index); } } void mono_cfg_dump_begin_group (MonoCompile *cfg) { if (cfg->gdump_ctx == NULL) return; write_byte (cfg, BEGIN_GROUP); char *title = (char *) mono_mempool_alloc0 (cfg->mempool, 0x2000); sprintf (title, "%s::%s", cfg->method->klass->name, cfg->method->name); write_pool (cfg, create_cp_entry (cfg, (void *) title, PT_STRING)); write_pool (cfg, create_cp_entry (cfg, (void *) cfg->method->name, PT_STRING)); write_pool (cfg, create_cp_entry (cfg, (void *) cfg->method, PT_METHOD)); write_int (cfg, 0); // TODO: real bytecode index. } void mono_cfg_dump_close_group (MonoCompile *cfg) { if (cfg->gdump_ctx == NULL) return; write_byte (cfg, CLOSE_GROUP); cfg->gdump_ctx = NULL; } static int label_instructions (MonoCompile *cfg) { MonoBasicBlock *bb; int instruction_count = 0; for (bb = cfg->bb_entry; bb; bb = bb->next_bb) { cfg_debug ("bb: %d (in: %d, out: %d)", bb->block_num, bb->in_count, bb->out_count); MonoInst *insn; for (insn = bb->code; insn; insn = insn->next) { instruction_count++; void *id = g_hash_table_lookup (cfg->gdump_ctx->insn2id, insn); if (id != NULL) // already in the table. continue; int *new_id = (int *) mono_mempool_alloc0 (cfg->mempool, sizeof (int)); *new_id = cfg->gdump_ctx->next_insn_id++; g_hash_table_insert (cfg->gdump_ctx->insn2id, insn, new_id); #ifdef CFG_DEBUG GString *insndesc = mono_print_ins_index_strbuf (-1, insn); cfg_debug ("> insn%002d: %s", *new_id, insndesc->str); g_string_free (insndesc, TRUE); #endif } } return instruction_count; } static void write_instructions (MonoCompile *cfg, int instruction_count) { MonoBasicBlock *bb; write_int (cfg, instruction_count); for (bb = cfg->bb_entry; bb; bb = bb->next_bb) { MonoInst *insn; cfg_debug ("== bb: %d (in: %d, out: %d) ==", bb->block_num, bb->in_count, bb->out_count); for (insn = bb->code; insn; insn = insn->next) { int i; int *id = (int *) g_hash_table_lookup (cfg->gdump_ctx->insn2id, insn); g_assert (id); write_int (cfg, *id); // hardcoded node class: only one input and NUM_SUCCESSOR successors write_pool (cfg, create_cp_entry (cfg, (void *) insn, PT_OPTYPE)); write_byte (cfg, cfg->bb_entry->code != insn); // properties write_short (cfg, 2); // property #1 GString *insndesc = mono_print_ins_index_strbuf (-1, insn); cfg_debug ("dumping node [%2d]: %s", *id, insndesc->str); write_pool (cfg, create_cp_entry (cfg, (void *) "fullname", PT_STRING)); write_byte (cfg, PROPERTY_POOL); write_pool (cfg, create_cp_entry (cfg, (void *) insndesc->str, PT_STRING)); g_string_free (insndesc, TRUE); // property #2 write_pool (cfg, create_cp_entry (cfg, (void *) "category", PT_STRING)); write_byte (cfg, PROPERTY_POOL); if (bb->in_count > 1 && bb->code == insn) write_pool (cfg, create_cp_entry (cfg, (void *) "merge", PT_STRING)); else if (bb->code == insn) write_pool (cfg, create_cp_entry (cfg, (void *) "begin", PT_STRING)); else if (MONO_IS_COND_BRANCH_OP (insn)) write_pool (cfg, create_cp_entry (cfg, (void *) "controlSplit", PT_STRING)); else if (MONO_IS_PHI (insn)) write_pool (cfg, create_cp_entry (cfg, (void *) "phi", PT_STRING)); else if (!MONO_INS_HAS_NO_SIDE_EFFECT (insn)) write_pool (cfg, create_cp_entry (cfg, (void *) "state", PT_STRING)); else write_pool (cfg, create_cp_entry (cfg, (void *) "fixed", PT_STRING)); // end of properties write_int (cfg, -1); // never set predecessor. int *next_id; if (insn->next != NULL) { next_id = (int *) g_hash_table_lookup (cfg->gdump_ctx->insn2id, insn->next); g_assert (next_id); cfg_debug ("\tsuccessor' : [%2d]", *next_id); write_int (cfg, *next_id); for (i = 1; i < NUM_SUCCESSOR; i++) write_int (cfg, -1); } else { g_assert (bb->out_count < NUM_SUCCESSOR); for (i = 0; (i < bb->out_count) && (i < NUM_SUCCESSOR); i++) { if (bb->out_bb[i]->code == NULL) write_int (cfg, -1); else { next_id = (int *) g_hash_table_lookup (cfg->gdump_ctx->insn2id, bb->out_bb[i]->code); if (next_id) cfg_debug ("\tsuccessor'': [%2d]", *next_id); write_int (cfg, next_id ? *next_id : -1); } } for (; i < NUM_SUCCESSOR; i++) write_int (cfg, -1); } } } } static void write_blocks (MonoCompile *cfg) { int block_size = 0; MonoBasicBlock *bb; for (bb = cfg->bb_entry; bb; bb = bb->next_bb) block_size++; write_int (cfg, block_size); for (bb = cfg->bb_entry; bb; bb = bb->next_bb) { int insn_size = 0; MonoInst *insn = NULL; write_int (cfg, bb->block_num); for (insn = bb->code; insn; insn = insn->next) insn_size++; write_int (cfg, insn_size); for (insn = bb->code; insn; insn = insn->next) { int *id = (int *) g_hash_table_lookup (cfg->gdump_ctx->insn2id, insn); g_assert (id); write_int (cfg, *id); } write_int (cfg, bb->out_count); for (int i = 0; i < bb->out_count; i++) write_int (cfg, bb->out_bb[i]->block_num); } } static guint instruction_hash (MonoInst *insn) { guint res = 0; res = insn->opcode << 0x00; res ^= insn->type << 0x04; res ^= insn->flags << 0x08; res ^= insn->dreg << 0x0c; res ^= insn->sreg1 << 0x10; res ^= insn->sreg2 << 0x14; res ^= insn->sreg3 << 0x18; res ^= (gsize) insn->next; res ^= (gsize) insn->prev; res ^= (gsize) insn; return res; } static gboolean instruction_equal (gconstpointer v1, gconstpointer v2) { MonoInst *i1 = (MonoInst *) v1; MonoInst *i2 = (MonoInst *) v2; if (i1->opcode != i2->opcode || i1->type != i2->type || i1->flags != i2->flags) return FALSE; if (i1->dreg != i2->dreg || i1->sreg1 != i2->sreg1 || i1->sreg2 != i2->sreg2 || i1->sreg3 != i2->sreg3) return FALSE; if (i1->next != i2->next || i1->prev != i2->prev) return FALSE; return TRUE; } static guint constant_pool_hash (ConstantPoolEntry *entry) { switch (entry->pt) { case PT_STRING: return g_str_hash (entry->data); case PT_METHOD: { MonoMethod *method = (MonoMethod *) entry->data; return g_str_hash (method->name) ^ g_str_hash (method->klass); } case PT_KLASS: return g_str_hash (((MonoClass *) entry->data)->name); case PT_OPTYPE: return instruction_hash ((MonoInst *) entry->data); case PT_SIGNATURE: { MonoMethodSignature *sig = (MonoMethodSignature *) entry->data; guint ret = GPOINTER_TO_UINT (sig->ret); for (int i = 0; i < sig->param_count; i++) { ret ^= GPOINTER_TO_UINT (sig->params [i]) << (i + 1); } return ret; } case PT_INPUTTYPE: // TODO: singleton. case PT_ENUMKLASS: return GPOINTER_TO_UINT (entry->data); } g_assert (FALSE); return FALSE; } static gboolean constant_pool_equal (gconstpointer v1, gconstpointer v2) { ConstantPoolEntry *e1 = (ConstantPoolEntry *) v1; ConstantPoolEntry *e2 = (ConstantPoolEntry *) v2; if (e1->pt != e2->pt) return FALSE; switch (e1->pt) { case PT_STRING: return g_str_equal (e1->data, e2->data); case PT_OPTYPE: return instruction_equal (e1->data, e2->data); case PT_METHOD: // TODO: implement proper equal. case PT_KLASS: case PT_SIGNATURE: return constant_pool_hash (e1) == constant_pool_hash (e2); case PT_INPUTTYPE: // TODO: singleton. case PT_ENUMKLASS: return TRUE; } g_assert (FALSE); return FALSE; } static gboolean cfg_dump_method_inited = FALSE; static const char *cfg_dump_method_name; void mono_cfg_dump_create_context (MonoCompile *cfg) { cfg->gdump_ctx = NULL; if (!cfg_dump_method_inited) { cfg_dump_method_name = g_getenv ("MONO_JIT_DUMP_METHOD"); cfg_dump_method_inited = TRUE; } if (!cfg_dump_method_name) return; const char *name = cfg_dump_method_name; if ((strchr (name, '.') > name) || strchr (name, ':')) { MonoMethodDesc *desc = mono_method_desc_new (name, TRUE); gboolean failed = !mono_method_desc_full_match (desc, cfg->method); mono_method_desc_free (desc); if (failed) return; } else if (strcmp (cfg->method->name, name) != 0) return; g_debug ("cfg_dump: create context for \"%s::%s\"", cfg->method->klass->name, cfg->method->name); int fd = create_socket (DEFAULT_HOST, DEFAULT_PORT); if (fd < 0) { g_warning ("cfg_dump: couldn't create socket: %s::%d", DEFAULT_HOST, DEFAULT_PORT); return; } MonoGraphDumper *ctx = (MonoGraphDumper *) mono_mempool_alloc0 (cfg->mempool, sizeof (MonoGraphDumper)); ctx->fd = fd; ctx->constant_pool = g_hash_table_new ((GHashFunc) constant_pool_hash, constant_pool_equal); ctx->insn2id = g_hash_table_new ((GHashFunc) instruction_hash, instruction_equal); ctx->next_cp_id = 1; ctx->next_insn_id = 0; cfg->gdump_ctx = ctx; } void mono_cfg_dump_ir (MonoCompile *cfg, const char *phase_name) { if (cfg->gdump_ctx == NULL) return; cfg_debug ("=== DUMPING PASS \"%s\" ===", phase_name); write_byte (cfg, BEGIN_GRAPH); write_pool (cfg, create_cp_entry (cfg, (void *) phase_name, PT_STRING)); int instruction_count = label_instructions (cfg); write_instructions (cfg, instruction_count); write_blocks (cfg); } #else /* !defined(DISABLE_LOGGING) && !defined(DISABLE_JIT) && !defined(HOST_WIN32) */ void mono_cfg_dump_create_context (MonoCompile *cfg) { } void mono_cfg_dump_begin_group (MonoCompile *cfg) { } void mono_cfg_dump_close_group (MonoCompile *cfg) { } void mono_cfg_dump_ir (MonoCompile *cfg, const char *phase_name) { } #endif /* !defined(DISABLE_LOGGING) && !defined(DISABLE_JIT) && !defined(HOST_WIN32) */