/* * jit-dump.c - Functions for dumping JIT structures, for debugging. * * Copyright (C) 2004 Southern Storm Software, Pty Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "jit-internal.h" #include "jit-rules.h" #include #include #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #if defined(JIT_BACKEND_INTERP) #include "jit-interp.h" #endif /*@ @cindex jit-dump.h @*/ /*@ * @deftypefun void jit_dump_type ({FILE *} stream, jit_type_t type) * Dump the name of a type to a stdio stream. * @end deftypefun @*/ void jit_dump_type(FILE *stream, jit_type_t type) { const char *name; type = jit_type_remove_tags(type); if(!type || !stream) { return; } switch(type->kind) { case JIT_TYPE_VOID: name = "void"; break; case JIT_TYPE_SBYTE: name = "sbyte"; break; case JIT_TYPE_UBYTE: name = "ubyte"; break; case JIT_TYPE_SHORT: name = "short"; break; case JIT_TYPE_USHORT: name = "ushort"; break; case JIT_TYPE_INT: name = "int"; break; case JIT_TYPE_UINT: name = "uint"; break; case JIT_TYPE_NINT: name = "nint"; break; case JIT_TYPE_NUINT: name = "nuint"; break; case JIT_TYPE_LONG: name = "long"; break; case JIT_TYPE_ULONG: name = "ulong"; break; case JIT_TYPE_FLOAT32: name = "float32"; break; case JIT_TYPE_FLOAT64: name = "float64"; break; case JIT_TYPE_NFLOAT: name = "nfloat"; break; case JIT_TYPE_STRUCT: { fprintf(stream, "struct<%u>", (unsigned int)(jit_type_get_size(type))); return; } /* Not reached */ case JIT_TYPE_UNION: { fprintf(stream, "union<%u>", (unsigned int)(jit_type_get_size(type))); return; } /* Not reached */ case JIT_TYPE_SIGNATURE: name = "signature"; break; case JIT_TYPE_PTR: name = "ptr"; break; default: name = ""; break; } fputs(name, stream); } /* * Format an integer value of arbitrary precision. */ static char *format_integer(char *buf, int is_neg, jit_ulong value) { buf += 64; *(--buf) = '\0'; if(value == 0) { *(--buf) = '0'; } else { while(value != 0) { *(--buf) = '0' + (int)(value % 10); value /= 10; } } if(is_neg) { *(--buf) = '-'; } return buf; } /*@ * @deftypefun void jit_dump_value ({FILE *} stream, jit_function_t func, jit_value_t value, const char *prefix) * Dump the name of a value to a stdio stream. If @code{prefix} is not * NULL, then it indicates a type prefix to add to the value name. * If @code{prefix} is NULL, then this function intuits the type prefix. * @end deftypefun @*/ void jit_dump_value(FILE *stream, jit_function_t func, jit_value_t value, const char *prefix) { jit_pool_block_t block; unsigned int block_size; unsigned int posn; /* Bail out if we have insufficient informaition for the dump */ if(!stream || !func || !(func->builder) || !value) { return; } /* Handle constants and non-local variables */ if(value->is_constant) { jit_constant_t const_value; char buf[64]; char *name; const_value = jit_value_get_constant(value); switch((jit_type_promote_int (jit_type_normalize(const_value.type)))->kind) { case JIT_TYPE_INT: { if(const_value.un.int_value < 0) { name = format_integer (buf, 1, (jit_ulong)(jit_uint) (-(const_value.un.int_value))); } else { name = format_integer (buf, 0, (jit_ulong)(jit_uint) (const_value.un.int_value)); } } break; case JIT_TYPE_UINT: { name = format_integer (buf, 0, (jit_ulong)(const_value.un.uint_value)); } break; case JIT_TYPE_LONG: { if(const_value.un.long_value < 0) { name = format_integer (buf, 1, (jit_ulong)(-(const_value.un.long_value))); } else { name = format_integer (buf, 0, (jit_ulong)(const_value.un.long_value)); } } break; case JIT_TYPE_ULONG: { name = format_integer(buf, 0, const_value.un.ulong_value); } break; case JIT_TYPE_FLOAT32: { jit_snprintf(buf, sizeof(buf), "%f", (double)(const_value.un.float32_value)); name = buf; } break; case JIT_TYPE_FLOAT64: { jit_snprintf(buf, sizeof(buf), "%f", (double)(const_value.un.float64_value)); name = buf; } break; case JIT_TYPE_NFLOAT: { jit_snprintf(buf, sizeof(buf), "%f", (double)(const_value.un.nfloat_value)); name = buf; } break; default: { name = ""; } break; } fputs(name, stream); return; } else if(value->is_local && value->block->func != func) { /* Accessing a local variable in an outer function frame */ int scope = 0; while(func && func->builder && func != value->block->func) { ++scope; func = func->nested_parent; } fprintf(stream, "{%d}", scope); if(!func || !(func->builder)) { return; } } /* Intuit the prefix if one was not supplied */ if(!prefix) { switch(jit_type_normalize(jit_value_get_type(value))->kind) { case JIT_TYPE_VOID: prefix = "v"; break; case JIT_TYPE_SBYTE: prefix = "i"; break; case JIT_TYPE_UBYTE: prefix = "i"; break; case JIT_TYPE_SHORT: prefix = "i"; break; case JIT_TYPE_USHORT: prefix = "i"; break; case JIT_TYPE_INT: prefix = "i"; break; case JIT_TYPE_UINT: prefix = "i"; break; case JIT_TYPE_LONG: prefix = "l"; break; case JIT_TYPE_ULONG: prefix = "l"; break; case JIT_TYPE_FLOAT32: prefix = "f"; break; case JIT_TYPE_FLOAT64: prefix = "d"; break; case JIT_TYPE_NFLOAT: prefix = "D"; break; case JIT_TYPE_STRUCT: prefix = "s"; break; case JIT_TYPE_UNION: prefix = "u"; break; default: prefix = "?"; break; } } /* Get the position of the value within the function's value pool */ block = func->builder->value_pool.blocks; block_size = func->builder->value_pool.elem_size * func->builder->value_pool.elems_per_block; posn = 1; while(block != 0) { if(((char *)value) >= block->data && ((char *)value) < (block->data + block_size)) { posn += (((char *)value) - block->data) / func->builder->value_pool.elem_size; break; } posn += func->builder->value_pool.elems_per_block; block = block->next; } /* Dump the prefix and the position, as the value's final name */ fprintf(stream, "%s%u", prefix, posn); } /* * Dump a temporary value, prefixed by its type. */ static void dump_value(FILE *stream, jit_function_t func, jit_value_t value, int type) { /* Normalize the type, so that it reflects JIT_OPCODE_DEST_xxx values */ if((type & JIT_OPCODE_SRC1_MASK) != 0) { type >>= 4; } if((type & JIT_OPCODE_SRC2_MASK) != 0) { type >>= 8; } /* Dump the value, prefixed appropriately */ switch(type) { case JIT_OPCODE_DEST_INT: { jit_dump_value(stream, func, value, "i"); } break; case JIT_OPCODE_DEST_LONG: { jit_dump_value(stream, func, value, "l"); } break; case JIT_OPCODE_DEST_FLOAT32: { jit_dump_value(stream, func, value, "f"); } break; case JIT_OPCODE_DEST_FLOAT64: { jit_dump_value(stream, func, value, "d"); } break; case JIT_OPCODE_DEST_NFLOAT: { jit_dump_value(stream, func, value, "D"); } break; case JIT_OPCODE_DEST_ANY: { /* Intuit the prefix from the value if the type is "any" */ jit_dump_value(stream, func, value, 0); } break; } } /*@ * @deftypefun void jit_dump_insn ({FILE *} stream, jit_function_t func, jit_value_t value) * Dump the contents of an instruction to a stdio stream. * @end deftypefun @*/ void jit_dump_insn(FILE *stream, jit_function_t func, jit_insn_t insn) { const char *name; const char *infix_name; int opcode, flags; jit_nint reg; /* Bail out if we have insufficient information for the dump */ if(!stream || !func || !insn) { return; } /* Get the opcode details */ opcode = insn->opcode; if(opcode < JIT_OP_NOP || opcode >= JIT_OP_NUM_OPCODES) { fprintf(stream, "unknown opcode %d\n", opcode); return; } name = jit_opcodes[opcode].name; flags = jit_opcodes[opcode].flags; infix_name = 0; /* Dump branch, call, or register information */ if((flags & JIT_OPCODE_IS_BRANCH) != 0) { if(opcode == JIT_OP_BR) { fprintf(stream, "goto .L%ld", (long)(jit_insn_get_label(insn))); return; } fprintf(stream, "if "); } else if((flags & JIT_OPCODE_IS_CALL) != 0) { if(insn->value1) fprintf(stream, "%s %s", name, (const char *)(insn->value1)); else fprintf(stream, "%s 0x08%lx", name, (long)(jit_nuint)(insn->dest)); return; } else if((flags & JIT_OPCODE_IS_CALL_EXTERNAL) != 0) { if(insn->value1) fprintf(stream, "%s %s (0x%08lx)", name, (const char *)(insn->value1), (long)(jit_nuint)(insn->dest)); else fprintf(stream, "%s 0x08%lx", name, (long)(jit_nuint)(insn->dest)); return; } else if((flags & JIT_OPCODE_IS_REG) != 0) { reg = jit_value_get_nint_constant(jit_insn_get_value2(insn)); fputs(name, stream); putc('(', stream); jit_dump_value(stream, func, jit_insn_get_value1(insn), 0); fputs(", ", stream); fputs(_jit_reg_info[(int)reg].name, stream); putc(')', stream); return; } else if((flags & JIT_OPCODE_IS_ADDROF_LABEL) != 0) { fprintf(stream, "address_of_label .L%ld", (long)(jit_insn_get_label(insn))); return; } /* Output the destination information */ if((flags & JIT_OPCODE_DEST_MASK) != JIT_OPCODE_DEST_EMPTY && !jit_insn_dest_is_value(insn)) { dump_value(stream, func, jit_insn_get_dest(insn), flags & JIT_OPCODE_DEST_MASK); fprintf(stream, " = "); } /* Dump the details of the operation */ switch(flags & JIT_OPCODE_OPER_MASK) { case JIT_OPCODE_OPER_ADD: infix_name = " + "; break; case JIT_OPCODE_OPER_SUB: infix_name = " - "; break; case JIT_OPCODE_OPER_MUL: infix_name = " * "; break; case JIT_OPCODE_OPER_DIV: infix_name = " / "; break; case JIT_OPCODE_OPER_REM: infix_name = " % "; break; case JIT_OPCODE_OPER_NEG: infix_name = "-"; break; case JIT_OPCODE_OPER_AND: infix_name = " & "; break; case JIT_OPCODE_OPER_OR: infix_name = " | "; break; case JIT_OPCODE_OPER_XOR: infix_name = " ^ "; break; case JIT_OPCODE_OPER_NOT: infix_name = "~"; break; case JIT_OPCODE_OPER_EQ: infix_name = " == "; break; case JIT_OPCODE_OPER_NE: infix_name = " != "; break; case JIT_OPCODE_OPER_LT: infix_name = " < "; break; case JIT_OPCODE_OPER_LE: infix_name = " <= "; break; case JIT_OPCODE_OPER_GT: infix_name = " > "; break; case JIT_OPCODE_OPER_GE: infix_name = " >= "; break; case JIT_OPCODE_OPER_SHL: infix_name = " << "; break; case JIT_OPCODE_OPER_SHR: infix_name = " >> "; break; case JIT_OPCODE_OPER_SHR_UN: infix_name = " >>> "; break; case JIT_OPCODE_OPER_COPY: infix_name = ""; break; case JIT_OPCODE_OPER_ADDRESS_OF: infix_name = "&"; break; } if(infix_name) { if((flags & JIT_OPCODE_SRC2_MASK) != 0) { /* Binary operation with a special operator name */ dump_value(stream, func, jit_insn_get_value1(insn), flags & JIT_OPCODE_SRC1_MASK); fputs(infix_name, stream); dump_value(stream, func, jit_insn_get_value2(insn), flags & JIT_OPCODE_SRC2_MASK); } else { /* Unary operation with a special operator name */ fputs(infix_name, stream); dump_value(stream, func, jit_insn_get_value1(insn), flags & JIT_OPCODE_SRC1_MASK); } } else { /* Not a special operator, so use the opcode name */ if(!jit_strncmp(name, "br_", 3)) { name += 3; } fputs(name, stream); if((flags & (JIT_OPCODE_SRC1_MASK | JIT_OPCODE_SRC2_MASK)) != 0) { putc('(', stream); if(jit_insn_dest_is_value(insn)) { dump_value(stream, func, jit_insn_get_dest(insn), flags & JIT_OPCODE_DEST_MASK); fputs(", ", stream); } dump_value(stream, func, jit_insn_get_value1(insn), flags & JIT_OPCODE_SRC1_MASK); if((flags & JIT_OPCODE_SRC2_MASK) != 0) { fputs(", ", stream); dump_value(stream, func, jit_insn_get_value2(insn), flags & JIT_OPCODE_SRC2_MASK); } putc(')', stream); } } /* Dump the "then" information on a conditional branch */ if((flags & JIT_OPCODE_IS_BRANCH) != 0) { fprintf(stream, " then goto .L%ld", (long)(jit_insn_get_label(insn))); } } #if defined(JIT_BACKEND_INTERP) extern jit_opcode_info_t const _jit_interp_opcodes[JIT_OP_NUM_INTERP_OPCODES]; /* * Dump the interpreted bytecode representation of a function. */ static void dump_interp_code(FILE *stream, void **pc, void **end) { int opcode; const jit_opcode_info_t *info; while(pc < end) { /* Fetch the next opcode */ opcode = (int)(jit_nint)(*pc); /* Dump the address of the opcode */ fprintf(stream, "\t%08lX: ", (long)(jit_nint)pc); ++pc; /* Get information about this opcode */ if(opcode < JIT_OP_NUM_OPCODES) { info = &(jit_opcodes[opcode]); } else { info = &(_jit_interp_opcodes[opcode - JIT_OP_NUM_OPCODES]); } /* Dump the name of the opcode */ fputs(info->name, stream); /* Dump additional parameters from the opcode stream */ switch(info->flags & JIT_OPCODE_INTERP_ARGS_MASK) { case JIT_OPCODE_NINT_ARG: { fprintf(stream, " %ld", (long)(jit_nint)(*pc)); ++pc; } break; case JIT_OPCODE_NINT_ARG_TWO: { fprintf(stream, " %ld, %ld", (long)(jit_nint)(pc[0]), (long)(jit_nint)(pc[1])); pc += 2; } break; case JIT_OPCODE_CONST_LONG: { jit_ulong value; jit_memcpy(&value, pc, sizeof(jit_ulong)); pc += (sizeof(jit_ulong) + sizeof(void *) - 1) / sizeof(void *); fprintf(stream, " 0x%lX%08lX", (long)((value >> 32) & jit_max_uint), (long)(value & jit_max_uint)); } break; case JIT_OPCODE_CONST_FLOAT32: { jit_float32 value; jit_memcpy(&value, pc, sizeof(jit_float32)); pc += (sizeof(jit_float32) + sizeof(void *) - 1) / sizeof(void *); fprintf(stream, " %f", (double)value); } break; case JIT_OPCODE_CONST_FLOAT64: { jit_float64 value; jit_memcpy(&value, pc, sizeof(jit_float64)); pc += (sizeof(jit_float64) + sizeof(void *) - 1) / sizeof(void *); fprintf(stream, " %f", (double)value); } break; case JIT_OPCODE_CONST_NFLOAT: { jit_nfloat value; jit_memcpy(&value, pc, sizeof(jit_nfloat)); pc += (sizeof(jit_nfloat) + sizeof(void *) - 1) / sizeof(void *); fprintf(stream, " %f", (double)value); } break; case JIT_OPCODE_CALL_INDIRECT_ARGS: { fprintf(stream, " %ld", (long)(jit_nint)(pc[1])); pc += 2; } break; default: { if((info->flags & (JIT_OPCODE_IS_BRANCH | JIT_OPCODE_IS_ADDROF_LABEL)) != 0) { fprintf(stream, " %08lX", (long)(jit_nint)((pc - 1) + (jit_nint)(*pc))); ++pc; } else if((info->flags & JIT_OPCODE_IS_CALL) != 0) { fprintf(stream, " 0x%lX", (long)(jit_nint)(*pc)); ++pc; } else if((info->flags & JIT_OPCODE_IS_CALL_EXTERNAL) != 0) { fprintf(stream, " 0x%lX, %ld", (long)(jit_nint)(pc[1]), (long)(jit_nint)(pc[2])); pc += 3; } } break; } /* Terminate the current disassembly line */ putc('\n', stream); } } #else /* !JIT_BACKEND_INTERP */ /* * Dump the native object code representation of a function. * Can only dump to stdout or stderr at the moment. */ static void dump_object_code(FILE *stream, void *start, void *end) { char cmdline[BUFSIZ]; unsigned char *pc = (unsigned char *)start; FILE *file; #if JIT_WIN32_PLATFORM /* * NOTE: If libjit is compiled on cygwin with -mno-cygwin flag then * fopen("/tmp/foo.s", ...) will use the root folder of the current * drive. That is the full file name will be like "c:/tmp/foo". But * the ``as'' and ``objdump'' utilities still use the cygwin's root. * So "as /tmp/foo.s" will look for "c:/cygwin/tmp/foo.s". To avoid * this ambiguity the file name has to contian the drive spec (e.g. * fopen("c:/tmp/foo.s", ...) and "as c;/tmp/foo.s"). Here we assume * that the TMP or TEMP environment variables always contain it. */ char s_path[BUFSIZ]; char o_path[BUFSIZ]; char *tmp_dir = getenv("TMP"); if(tmp_dir == NULL) { tmp_dir = getenv("TEMP"); if(tmp_dir == NULL) { tmp_dir = "c:/tmp"; } } sprintf(s_path, "%s/libjit-dump.s", tmp_dir); sprintf(o_path, "%s/libjit-dump.o", tmp_dir); #else const char *s_path = "/tmp/libjit-dump.s"; const char *o_path = "/tmp/libjit-dump.o"; #endif file = fopen(s_path, "w"); if(!file) { return; } fflush(stream); while(pc < (unsigned char *)end) { fprintf(file, ".byte %d\n", (int)(*pc)); ++pc; } fclose(file); sprintf(cmdline, "as %s -o %s", s_path, o_path); system(cmdline); sprintf(cmdline, "objdump --adjust-vma=%ld -d %s%s", (long)(jit_nint)start, o_path, (stream == stderr ? " 1>&2" : "")); system(cmdline); unlink(s_path); unlink(o_path); putc('\n', stream); fflush(stream); } #endif /* !JIT_BACKEND_INTERP */ /*@ * @deftypefun void jit_dump_function ({FILE *} stream, jit_function_t func, {const char *} name) * Dump the three-address instructions within a function to a stdio stream. * The @code{name} is attached to the output as a friendly label, but * has no other significance. * * If the function has not been compiled yet, then this will dump the * three address instructions from the build process. Otherwise it will * disassemble and dump the compiled native code. * @end deftypefun @*/ void jit_dump_function(FILE *stream, jit_function_t func, const char *name) { jit_block_t block; jit_insn_iter_t iter; jit_insn_t insn; int prev_block; jit_type_t signature; unsigned int param; unsigned int num_params; jit_value_t value; /* Bail out if we don't have sufficient information to dump */ if(!stream || !func) { return; } /* Output the function header */ if(name) fprintf(stream, "function %s(", name); else fprintf(stream, "function 0x%08lX(", (long)(jit_nuint)func); signature = func->signature; num_params = jit_type_num_params(signature); if(func->builder) { value = jit_value_get_struct_pointer(func); if(value || func->nested_parent) { /* We have extra hidden parameters */ putc('[', stream); if(func->nested_parent) { fputs("parent_frame", stream); if(value) { fputs(", ", stream); } } if(value) { jit_dump_value(stream, func, value, 0); fputs(" : struct_ptr", stream); } putc(']', stream); if(num_params > 0) { fputs(", ", stream); } } for(param = 0; param < num_params; ++param) { if(param != 0) { fputs(", ", stream); } value = jit_value_get_param(func, param); if(value) { jit_dump_value(stream, func, value, 0); } else { fputs("???", stream); } fputs(" : ", stream); jit_dump_type(stream, jit_type_get_param(signature, param)); } } else { for(param = 0; param < num_params; ++param) { if(param != 0) { fputs(", ", stream); } jit_dump_type(stream, jit_type_get_param(signature, param)); } } fprintf(stream, ") : "); jit_dump_type(stream, jit_type_get_return(signature)); putc('\n', stream); /* Should we dump the three address code or the native code? */ if(func->builder) { /* Output each of the three address blocks in turn */ block = 0; prev_block = 0; while((block = jit_block_next(func, block)) != 0) { /* Output the block's label, if it has one */ if(prev_block && block->label == jit_label_undefined) { /* A new block was started, but it doesn't have a label yet */ if(_jit_block_get_last(block) != 0) { block->label = (func->builder->next_label)++; } } if(block->label != jit_label_undefined) { fprintf(stream, ".L%ld:\n", (long)(block->label)); } prev_block = 1; /* Dump the instructions in the block */ jit_insn_iter_init(&iter, block); while((insn = jit_insn_iter_next(&iter)) != 0) { putc('\t', stream); jit_dump_insn(stream, func, insn); putc('\n', stream); } if(block->ends_in_dead) { fputs("\tends_in_dead\n", stream); } } } else if(func->is_compiled) { void *end = _jit_cache_get_end_method (func->context->cache, func->entry_point); #if defined(JIT_BACKEND_INTERP) /* Dump the interpreter's bytecode representation */ jit_function_interp_t interp; interp = (jit_function_interp_t)(func->entry_point); fprintf(stream, "\t%08lX: prolog(0x%lX, %d, %d, %d)\n", (long)(jit_nint)interp, (long)(jit_nint)func, (int)(interp->args_size), (int)(interp->frame_size), (int)(interp->working_area)); dump_interp_code(stream, (void **)(interp + 1), (void **)end); #else dump_object_code(stream, func->entry_point, end); #endif } /* Output the function footer */ fprintf(stream, "end\n\n"); }