| 1 | // |
| 2 | // m3_info.c |
| 3 | // |
| 4 | // Created by Steven Massey on 4/27/19. |
| 5 | // Copyright © 2019 Steven Massey. All rights reserved. |
| 6 | // |
| 7 | |
| 8 | #include "m3_env.h" |
| 9 | #include "m3_info.h" |
| 10 | #include "m3_compile.h" |
| 11 | |
| 12 | #ifdef DEBUG |
| 13 | |
| 14 | // a central function you can be breakpoint: |
| 15 | void ExceptionBreakpoint (cstr_t i_exception, cstr_t i_message) |
| 16 | { |
| 17 | printf ("\nexception: '%s' @ %s\n" , i_exception, i_message); |
| 18 | return; |
| 19 | } |
| 20 | |
| 21 | |
| 22 | typedef struct OpInfo |
| 23 | { |
| 24 | IM3OpInfo info; |
| 25 | m3opcode_t opcode; |
| 26 | } |
| 27 | OpInfo; |
| 28 | |
| 29 | void m3_PrintM3Info () |
| 30 | { |
| 31 | printf ("\n-- m3 configuration --------------------------------------------\n" ); |
| 32 | // printf (" sizeof M3CodePage : %zu bytes (%d slots) \n", sizeof (M3CodePage), c_m3CodePageNumSlots); |
| 33 | printf (" sizeof M3MemPage : %u bytes \n" , d_m3MemPageSize); |
| 34 | printf (" sizeof M3Compilation : %zu bytes \n" , sizeof (M3Compilation)); |
| 35 | printf (" sizeof M3Function : %zu bytes \n" , sizeof (M3Function)); |
| 36 | printf ("----------------------------------------------------------------\n\n" ); |
| 37 | } |
| 38 | |
| 39 | |
| 40 | void * v_PrintEnvModuleInfo (IM3Module i_module, u32 * io_index) |
| 41 | { |
| 42 | printf (" module [%u] name: '%s'; funcs: %d \n" , * io_index++, i_module->name, i_module->numFunctions); |
| 43 | |
| 44 | return NULL; |
| 45 | } |
| 46 | |
| 47 | |
| 48 | void m3_PrintRuntimeInfo (IM3Runtime i_runtime) |
| 49 | { |
| 50 | printf ("\n-- m3 runtime -------------------------------------------------\n" ); |
| 51 | |
| 52 | printf (" stack-size: %zu \n\n" , i_runtime->numStackSlots * sizeof (m3slot_t)); |
| 53 | |
| 54 | u32 moduleIndex = 0; |
| 55 | ForEachModule (i_runtime, (ModuleVisitor) v_PrintEnvModuleInfo, & moduleIndex); |
| 56 | |
| 57 | printf ("----------------------------------------------------------------\n\n" ); |
| 58 | } |
| 59 | |
| 60 | |
| 61 | cstr_t GetTypeName (u8 i_m3Type) |
| 62 | { |
| 63 | if (i_m3Type < 5) |
| 64 | return c_waTypes [i_m3Type]; |
| 65 | else |
| 66 | return "?" ; |
| 67 | } |
| 68 | |
| 69 | |
| 70 | // TODO: these 'static char string []' aren't thread-friendly. though these functions are |
| 71 | // mainly for simple diagnostics during development, it'd be nice if they were fully reliable. |
| 72 | |
| 73 | cstr_t SPrintFuncTypeSignature (IM3FuncType i_funcType) |
| 74 | { |
| 75 | static char string [256]; |
| 76 | |
| 77 | sprintf (string, "(" ); |
| 78 | |
| 79 | for (u32 i = 0; i < i_funcType->numArgs; ++i) |
| 80 | { |
| 81 | if (i != 0) |
| 82 | strcat (string, ", " ); |
| 83 | |
| 84 | strcat (string, GetTypeName (d_FuncArgType(i_funcType, i))); |
| 85 | } |
| 86 | |
| 87 | strcat (string, ") -> " ); |
| 88 | |
| 89 | for (u32 i = 0; i < i_funcType->numRets; ++i) |
| 90 | { |
| 91 | if (i != 0) |
| 92 | strcat (string, ", " ); |
| 93 | |
| 94 | strcat (string, GetTypeName (d_FuncRetType(i_funcType, i))); |
| 95 | } |
| 96 | |
| 97 | return string; |
| 98 | } |
| 99 | |
| 100 | |
| 101 | size_t SPrintArg (char * o_string, size_t i_stringBufferSize, voidptr_t i_sp, u8 i_type) |
| 102 | { |
| 103 | int len = 0; |
| 104 | |
| 105 | * o_string = 0; |
| 106 | |
| 107 | if (i_type == c_m3Type_i32) |
| 108 | len = snprintf (o_string, i_stringBufferSize, "%" PRIi32, * (i32 *) i_sp); |
| 109 | else if (i_type == c_m3Type_i64) |
| 110 | len = snprintf (o_string, i_stringBufferSize, "%" PRIi64, * (i64 *) i_sp); |
| 111 | #if d_m3HasFloat |
| 112 | else if (i_type == c_m3Type_f32) |
| 113 | len = snprintf (o_string, i_stringBufferSize, "%" PRIf32, * (f32 *) i_sp); |
| 114 | else if (i_type == c_m3Type_f64) |
| 115 | len = snprintf (o_string, i_stringBufferSize, "%" PRIf64, * (f64 *) i_sp); |
| 116 | #endif |
| 117 | |
| 118 | len = M3_MAX (0, len); |
| 119 | |
| 120 | return len; |
| 121 | } |
| 122 | |
| 123 | |
| 124 | cstr_t SPrintValue (void * i_value, u8 i_type) |
| 125 | { |
| 126 | static char string [100]; |
| 127 | SPrintArg (string, 100, (m3stack_t) i_value, i_type); |
| 128 | return string; |
| 129 | } |
| 130 | |
| 131 | |
| 132 | cstr_t SPrintFunctionArgList (IM3Function i_function, m3stack_t i_sp) |
| 133 | { |
| 134 | int ret; |
| 135 | static char string [256]; |
| 136 | |
| 137 | char * s = string; |
| 138 | ccstr_t e = string + sizeof(string) - 1; |
| 139 | |
| 140 | ret = snprintf (s, e-s, "(" ); |
| 141 | s += M3_MAX (0, ret); |
| 142 | |
| 143 | u64 * argSp = (u64 *) i_sp; |
| 144 | |
| 145 | IM3FuncType funcType = i_function->funcType; |
| 146 | if (funcType) |
| 147 | { |
| 148 | u32 numArgs = funcType->numArgs; |
| 149 | |
| 150 | for (u32 i = 0; i < numArgs; ++i) |
| 151 | { |
| 152 | u8 type = d_FuncArgType(funcType, i); |
| 153 | |
| 154 | ret = snprintf (s, e-s, "%s: " , c_waTypes [type]); |
| 155 | s += M3_MAX (0, ret); |
| 156 | |
| 157 | s += SPrintArg (s, e-s, argSp + i, type); |
| 158 | |
| 159 | if (i != numArgs - 1) { |
| 160 | ret = snprintf (s, e-s, ", " ); |
| 161 | s += M3_MAX (0, ret); |
| 162 | } |
| 163 | } |
| 164 | } |
| 165 | else printf ("null signature" ); |
| 166 | |
| 167 | ret = snprintf (s, e-s, ")" ); |
| 168 | s += M3_MAX (0, ret); |
| 169 | |
| 170 | return string; |
| 171 | } |
| 172 | |
| 173 | static |
| 174 | OpInfo find_operation_info (IM3Operation i_operation) |
| 175 | { |
| 176 | OpInfo opInfo = { NULL, 0 }; |
| 177 | |
| 178 | if (!i_operation) return opInfo; |
| 179 | |
| 180 | // TODO: find also extended opcodes |
| 181 | for (u32 i = 0; i <= 0xff; ++i) |
| 182 | { |
| 183 | IM3OpInfo oi = GetOpInfo (i); |
| 184 | |
| 185 | if (oi->type != c_m3Type_unknown) |
| 186 | { |
| 187 | for (u32 o = 0; o < 4; ++o) |
| 188 | { |
| 189 | if (oi->operations [o] == i_operation) |
| 190 | { |
| 191 | opInfo.info = oi; |
| 192 | opInfo.opcode = i; |
| 193 | break; |
| 194 | } |
| 195 | } |
| 196 | } |
| 197 | else break; |
| 198 | } |
| 199 | |
| 200 | return opInfo; |
| 201 | } |
| 202 | |
| 203 | |
| 204 | #undef fetch |
| 205 | #define fetch(TYPE) (* (TYPE *) ((*o_pc)++)) |
| 206 | |
| 207 | #define d_m3Decoder(FUNC) void Decode_##FUNC (char * o_string, u8 i_opcode, IM3Operation i_operation, IM3OpInfo i_opInfo, pc_t * o_pc) |
| 208 | |
| 209 | d_m3Decoder (Call) |
| 210 | { |
| 211 | void * function = fetch (void *); |
| 212 | i32 stackOffset = fetch (i32); |
| 213 | |
| 214 | sprintf (o_string, "%p; stack-offset: %d" , function, stackOffset); |
| 215 | } |
| 216 | |
| 217 | |
| 218 | d_m3Decoder (Entry) |
| 219 | { |
| 220 | IM3Function function = fetch (IM3Function); |
| 221 | |
| 222 | // only prints out the first registered name for the function |
| 223 | sprintf (o_string, "%s" , m3_GetFunctionName(function)); |
| 224 | } |
| 225 | |
| 226 | |
| 227 | d_m3Decoder (f64_Store) |
| 228 | { |
| 229 | if (i_operation == i_opInfo->operations [0]) |
| 230 | { |
| 231 | u32 operand = fetch (u32); |
| 232 | u32 offset = fetch (u32); |
| 233 | |
| 234 | sprintf (o_string, "offset= slot:%d + immediate:%d" , operand, offset); |
| 235 | } |
| 236 | |
| 237 | // sprintf (o_string, "%s", function->name); |
| 238 | } |
| 239 | |
| 240 | |
| 241 | d_m3Decoder (Branch) |
| 242 | { |
| 243 | void * target = fetch (void *); |
| 244 | sprintf (o_string, "%p" , target); |
| 245 | } |
| 246 | |
| 247 | d_m3Decoder (BranchTable) |
| 248 | { |
| 249 | u32 slot = fetch (u32); |
| 250 | |
| 251 | o_string += sprintf (o_string, "slot: %" PRIu32 "; targets: " , slot); |
| 252 | |
| 253 | // IM3Function function = fetch2 (IM3Function); |
| 254 | |
| 255 | i32 targets = fetch (i32); |
| 256 | |
| 257 | for (i32 i = 0; i < targets; ++i) |
| 258 | { |
| 259 | pc_t addr = fetch (pc_t); |
| 260 | o_string += sprintf (o_string, "%" PRIi32 "=%p, " , i, addr); |
| 261 | } |
| 262 | |
| 263 | pc_t addr = fetch (pc_t); |
| 264 | sprintf (o_string, "def=%p " , addr); |
| 265 | } |
| 266 | |
| 267 | |
| 268 | d_m3Decoder (Const) |
| 269 | { |
| 270 | u64 value = fetch (u64); i32 offset = fetch (i32); |
| 271 | sprintf (o_string, " slot [%d] = %" PRIu64, offset, value); |
| 272 | } |
| 273 | |
| 274 | |
| 275 | #undef fetch |
| 276 | |
| 277 | void DecodeOperation (char * o_string, u8 i_opcode, IM3Operation i_operation, IM3OpInfo i_opInfo, pc_t * o_pc) |
| 278 | { |
| 279 | #define d_m3Decode(OPCODE, FUNC) case OPCODE: Decode_##FUNC (o_string, i_opcode, i_operation, i_opInfo, o_pc); break; |
| 280 | |
| 281 | switch (i_opcode) |
| 282 | { |
| 283 | // d_m3Decode (0xc0, Const) |
| 284 | d_m3Decode (0xc5, Entry) |
| 285 | d_m3Decode (c_waOp_call, Call) |
| 286 | d_m3Decode (c_waOp_branch, Branch) |
| 287 | d_m3Decode (c_waOp_branchTable, BranchTable) |
| 288 | d_m3Decode (0x39, f64_Store) |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | // WARNING/TODO: this isn't fully implemented. it blindly assumes each word is a Operation pointer |
| 293 | // and, if an operation happens to missing from the c_operations table it won't be recognized here |
| 294 | void dump_code_page (IM3CodePage i_codePage, pc_t i_startPC) |
| 295 | { |
| 296 | m3log (code, "code page seq: %d" , i_codePage->info.sequence); |
| 297 | |
| 298 | pc_t pc = i_startPC ? i_startPC : GetPageStartPC (i_codePage); |
| 299 | pc_t end = GetPagePC (i_codePage); |
| 300 | |
| 301 | m3log (code, "---------------------------------------------------------------------------------------" ); |
| 302 | |
| 303 | while (pc < end) |
| 304 | { |
| 305 | pc_t operationPC = pc; |
| 306 | IM3Operation op = (IM3Operation) (* pc++); |
| 307 | |
| 308 | OpInfo i = find_operation_info (op); |
| 309 | |
| 310 | if (i.info) |
| 311 | { |
| 312 | char infoString [8*1024] = { 0 }; |
| 313 | |
| 314 | DecodeOperation (infoString, i.opcode, op, i.info, & pc); |
| 315 | |
| 316 | m3log (code, "%p | %20s %s" , operationPC, i.info->name, infoString); |
| 317 | } |
| 318 | else |
| 319 | m3log (code, "%p | %p" , operationPC, op); |
| 320 | |
| 321 | } |
| 322 | |
| 323 | m3log (code, "---------------------------------------------------------------------------------------" ); |
| 324 | |
| 325 | m3log (code, "free-lines: %d" , i_codePage->info.numLines - i_codePage->info.lineIndex); |
| 326 | } |
| 327 | |
| 328 | |
| 329 | void dump_type_stack (IM3Compilation o) |
| 330 | { |
| 331 | /* Reminders about how the stack works! :) |
| 332 | -- args & locals remain on the type stack for duration of the function. Denoted with a constant 'A' and 'L' in this dump. |
| 333 | -- the initial stack dumps originate from the CompileLocals () function, so these identifiers won't/can't be |
| 334 | applied until this compilation stage is finished |
| 335 | -- constants are not statically represented in the type stack (like args & constants) since they don't have/need |
| 336 | write counts |
| 337 | |
| 338 | -- the number shown for static args and locals (value in wasmStack [i]) represents the write count for the variable |
| 339 | |
| 340 | -- (does Wasm ever write to an arg? I dunno/don't remember.) |
| 341 | -- the number for the dynamic stack values represents the slot number. |
| 342 | -- if the slot index points to arg, local or constant it's denoted with a lowercase 'a', 'l' or 'c' |
| 343 | |
| 344 | */ |
| 345 | |
| 346 | // for the assert at end of dump: |
| 347 | i32 regAllocated [2] = { (i32) IsRegisterAllocated (o, 0), (i32) IsRegisterAllocated (o, 1) }; |
| 348 | |
| 349 | // display whether r0 or fp0 is allocated. these should then also be reflected somewhere in the stack too. |
| 350 | d_m3Log(stack, "\n" ); |
| 351 | d_m3Log(stack, " " ); |
| 352 | printf ("%s %s " , regAllocated [0] ? "(r0)" : " " , regAllocated [1] ? "(fp0)" : " " ); |
| 353 | printf("\n" ); |
| 354 | |
| 355 | for (u32 p = 1; p <= 2; ++p) |
| 356 | { |
| 357 | d_m3Log(stack, " " ); |
| 358 | |
| 359 | for (u32 i = 0; i < o->stackIndex; ++i) |
| 360 | { |
| 361 | if (i > 0 and i == o->stackFirstDynamicIndex) |
| 362 | printf ("#" ); |
| 363 | |
| 364 | if (i == o->block.blockStackIndex) |
| 365 | printf (">" ); |
| 366 | |
| 367 | const char * type = c_waCompactTypes [o->typeStack [i]]; |
| 368 | |
| 369 | const char * location = "" ; |
| 370 | |
| 371 | i32 slot = o->wasmStack [i]; |
| 372 | |
| 373 | if (IsRegisterSlotAlias (slot)) |
| 374 | { |
| 375 | bool isFp = IsFpRegisterSlotAlias (slot); |
| 376 | location = isFp ? "/f" : "/r" ; |
| 377 | |
| 378 | regAllocated [isFp]--; |
| 379 | slot = -1; |
| 380 | } |
| 381 | else |
| 382 | { |
| 383 | if (slot < o->slotFirstDynamicIndex) |
| 384 | { |
| 385 | if (slot >= o->slotFirstConstIndex) |
| 386 | location = "c" ; |
| 387 | else if (slot >= o->function->numRetAndArgSlots) |
| 388 | location = "L" ; |
| 389 | else |
| 390 | location = "a" ; |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | char item [100]; |
| 395 | |
| 396 | if (slot >= 0) |
| 397 | sprintf (item, "%s%s%d" , type, location, slot); |
| 398 | else |
| 399 | sprintf (item, "%s%s" , type, location); |
| 400 | |
| 401 | if (p == 1) |
| 402 | { |
| 403 | size_t s = strlen (item); |
| 404 | |
| 405 | sprintf (item, "%d" , i); |
| 406 | |
| 407 | while (strlen (item) < s) |
| 408 | strcat (item, " " ); |
| 409 | } |
| 410 | |
| 411 | printf ("|%s " , item); |
| 412 | |
| 413 | } |
| 414 | printf ("\n" ); |
| 415 | } |
| 416 | |
| 417 | // for (u32 r = 0; r < 2; ++r) |
| 418 | // d_m3Assert (regAllocated [r] == 0); // reg allocation & stack out of sync |
| 419 | |
| 420 | u16 maxSlot = GetMaxUsedSlotPlusOne (o); |
| 421 | |
| 422 | if (maxSlot > o->slotFirstDynamicIndex) |
| 423 | { |
| 424 | d_m3Log (stack, " -" ); |
| 425 | |
| 426 | for (u16 i = o->slotFirstDynamicIndex; i < maxSlot; ++i) |
| 427 | printf ("----" ); |
| 428 | |
| 429 | printf ("\n" ); |
| 430 | |
| 431 | d_m3Log (stack, " slot |" ); |
| 432 | for (u16 i = o->slotFirstDynamicIndex; i < maxSlot; ++i) |
| 433 | printf ("%3d|" , i); |
| 434 | |
| 435 | printf ("\n" ); |
| 436 | d_m3Log (stack, " alloc |" ); |
| 437 | |
| 438 | for (u16 i = o->slotFirstDynamicIndex; i < maxSlot; ++i) |
| 439 | { |
| 440 | printf ("%3d|" , o->m3Slots [i]); |
| 441 | } |
| 442 | |
| 443 | printf ("\n" ); |
| 444 | } |
| 445 | d_m3Log(stack, "\n" ); |
| 446 | } |
| 447 | |
| 448 | |
| 449 | static const char * GetOpcodeIndentionString (i32 blockDepth) |
| 450 | { |
| 451 | blockDepth += 1; |
| 452 | |
| 453 | if (blockDepth < 0) |
| 454 | blockDepth = 0; |
| 455 | |
| 456 | static const char * s_spaces = "......................................................................................." ; |
| 457 | const char * indent = s_spaces + strlen (s_spaces); |
| 458 | indent -= (blockDepth * 2); |
| 459 | if (indent < s_spaces) |
| 460 | indent = s_spaces; |
| 461 | |
| 462 | return indent; |
| 463 | } |
| 464 | |
| 465 | |
| 466 | const char * get_indention_string (IM3Compilation o) |
| 467 | { |
| 468 | return GetOpcodeIndentionString (o->block.depth+4); |
| 469 | } |
| 470 | |
| 471 | |
| 472 | void log_opcode (IM3Compilation o, m3opcode_t i_opcode) |
| 473 | { |
| 474 | i32 depth = o->block.depth; |
| 475 | if (i_opcode == c_waOp_end or i_opcode == c_waOp_else) |
| 476 | depth--; |
| 477 | |
| 478 | m3log (compile, "%4d | 0x%02x %s %s" , o->numOpcodes++, i_opcode, GetOpcodeIndentionString (depth), GetOpInfo(i_opcode)->name); |
| 479 | } |
| 480 | |
| 481 | |
| 482 | void log_emit (IM3Compilation o, IM3Operation i_operation) |
| 483 | { |
| 484 | OpInfo i = find_operation_info (i_operation); |
| 485 | |
| 486 | d_m3Log(emit, "" ); |
| 487 | if (i.info) |
| 488 | { |
| 489 | printf ("%p: %s\n" , GetPagePC (o->page), i.info->name); |
| 490 | } |
| 491 | else printf ("not found: %p\n" , i_operation); |
| 492 | } |
| 493 | |
| 494 | #endif // DEBUG |
| 495 | |
| 496 | |
| 497 | # if d_m3EnableOpProfiling |
| 498 | |
| 499 | typedef struct M3ProfilerSlot |
| 500 | { |
| 501 | cstr_t opName; |
| 502 | u64 hitCount; |
| 503 | } |
| 504 | M3ProfilerSlot; |
| 505 | |
| 506 | static M3ProfilerSlot s_opProfilerCounts [d_m3ProfilerSlotMask + 1] = {}; |
| 507 | |
| 508 | void ProfileHit (cstr_t i_operationName) |
| 509 | { |
| 510 | u64 ptr = (u64) i_operationName; |
| 511 | |
| 512 | M3ProfilerSlot * slot = & s_opProfilerCounts [ptr & d_m3ProfilerSlotMask]; |
| 513 | |
| 514 | if (slot->opName) |
| 515 | { |
| 516 | if (slot->opName != i_operationName) |
| 517 | { |
| 518 | m3_Abort ("profiler slot collision; increase d_m3ProfilerSlotMask" ); |
| 519 | } |
| 520 | } |
| 521 | |
| 522 | slot->opName = i_operationName; |
| 523 | slot->hitCount++; |
| 524 | } |
| 525 | |
| 526 | |
| 527 | void m3_PrintProfilerInfo () |
| 528 | { |
| 529 | M3ProfilerSlot dummy; |
| 530 | M3ProfilerSlot * maxSlot = & dummy; |
| 531 | |
| 532 | do |
| 533 | { |
| 534 | maxSlot->hitCount = 0; |
| 535 | |
| 536 | for (u32 i = 0; i <= d_m3ProfilerSlotMask; ++i) |
| 537 | { |
| 538 | M3ProfilerSlot * slot = & s_opProfilerCounts [i]; |
| 539 | |
| 540 | if (slot->opName) |
| 541 | { |
| 542 | if (slot->hitCount > maxSlot->hitCount) |
| 543 | maxSlot = slot; |
| 544 | } |
| 545 | } |
| 546 | |
| 547 | if (maxSlot->opName) |
| 548 | { |
| 549 | fprintf (stderr, "%13llu %s\n" , maxSlot->hitCount, maxSlot->opName); |
| 550 | maxSlot->opName = NULL; |
| 551 | } |
| 552 | } |
| 553 | while (maxSlot->hitCount); |
| 554 | } |
| 555 | |
| 556 | # else |
| 557 | |
| 558 | void m3_PrintProfilerInfo () {} |
| 559 | |
| 560 | # endif |
| 561 | |
| 562 | |