import { pads } from '../lib.js' interface CPU { wm: { exports: Record any> } wasm_memory: WebAssembly.Memory } export function stats_to_string(cpu: CPU): string { return print_misc_stats(cpu) + print_instruction_counts(cpu) } function print_misc_stats(cpu: CPU): string { let text = '' const stat_names = [ 'COMPILE', 'COMPILE_SKIPPED_NO_NEW_ENTRY_POINTS', 'COMPILE_WRONG_ADDRESS_SPACE', 'COMPILE_CUT_OFF_AT_END_OF_PAGE', 'COMPILE_WITH_LOOP_SAFETY', 'COMPILE_PAGE', 'COMPILE_PAGE/COMPILE', 'COMPILE_BASIC_BLOCK', 'COMPILE_DUPLICATED_BASIC_BLOCK', 'COMPILE_WASM_BLOCK', 'COMPILE_WASM_LOOP', 'COMPILE_DISPATCHER', 'COMPILE_ENTRY_POINT', 'COMPILE_WASM_TOTAL_BYTES', 'COMPILE_WASM_TOTAL_BYTES/COMPILE_PAGE', 'RUN_INTERPRETED', 'RUN_INTERPRETED_NEW_PAGE', 'RUN_INTERPRETED_PAGE_HAS_CODE', 'RUN_INTERPRETED_PAGE_HAS_ENTRY_AFTER_PAGE_WALK', 'RUN_INTERPRETED_NEAR_END_OF_PAGE', 'RUN_INTERPRETED_DIFFERENT_STATE', 'RUN_INTERPRETED_DIFFERENT_STATE_CPL3', 'RUN_INTERPRETED_DIFFERENT_STATE_FLAT', 'RUN_INTERPRETED_DIFFERENT_STATE_IS32', 'RUN_INTERPRETED_DIFFERENT_STATE_SS32', 'RUN_INTERPRETED_MISSED_COMPILED_ENTRY_RUN_INTERPRETED', 'RUN_INTERPRETED_STEPS', 'RUN_FROM_CACHE', 'RUN_FROM_CACHE_STEPS', 'RUN_FROM_CACHE_STEPS/RUN_FROM_CACHE', 'RUN_FROM_CACHE_STEPS/RUN_INTERPRETED_STEPS', 'DIRECT_EXIT', 'INDIRECT_JUMP', 'INDIRECT_JUMP_NO_ENTRY', 'NORMAL_PAGE_CHANGE', 'NORMAL_FALLTHRU', 'NORMAL_FALLTHRU_WITH_TARGET_BLOCK', 'NORMAL_BRANCH', 'NORMAL_BRANCH_WITH_TARGET_BLOCK', 'CONDITIONAL_JUMP', 'CONDITIONAL_JUMP_PAGE_CHANGE', 'CONDITIONAL_JUMP_EXIT', 'CONDITIONAL_JUMP_FALLTHRU', 'CONDITIONAL_JUMP_FALLTHRU_WITH_TARGET_BLOCK', 'CONDITIONAL_JUMP_BRANCH', 'CONDITIONAL_JUMP_BRANCH_WITH_TARGET_BLOCK', 'DISPATCHER_SMALL', 'DISPATCHER_LARGE', 'LOOP', 'LOOP_SAFETY', 'CONDITION_OPTIMISED', 'CONDITION_UNOPTIMISED', 'CONDITION_UNOPTIMISED_PF', 'CONDITION_UNOPTIMISED_UNHANDLED_L', 'CONDITION_UNOPTIMISED_UNHANDLED_LE', 'FAILED_PAGE_CHANGE', 'SAFE_READ_FAST', 'SAFE_READ_SLOW_PAGE_CROSSED', 'SAFE_READ_SLOW_NOT_VALID', 'SAFE_READ_SLOW_NOT_USER', 'SAFE_READ_SLOW_IN_MAPPED_RANGE', 'SAFE_WRITE_FAST', 'SAFE_WRITE_SLOW_PAGE_CROSSED', 'SAFE_WRITE_SLOW_NOT_VALID', 'SAFE_WRITE_SLOW_NOT_USER', 'SAFE_WRITE_SLOW_IN_MAPPED_RANGE', 'SAFE_WRITE_SLOW_READ_ONLY', 'SAFE_WRITE_SLOW_HAS_CODE', 'SAFE_READ_WRITE_FAST', 'SAFE_READ_WRITE_SLOW_PAGE_CROSSED', 'SAFE_READ_WRITE_SLOW_NOT_VALID', 'SAFE_READ_WRITE_SLOW_NOT_USER', 'SAFE_READ_WRITE_SLOW_IN_MAPPED_RANGE', 'SAFE_READ_WRITE_SLOW_READ_ONLY', 'SAFE_READ_WRITE_SLOW_HAS_CODE', 'PAGE_FAULT', 'TLB_MISS', 'MAIN_LOOP', 'MAIN_LOOP_IDLE', 'DO_MANY_CYCLES', 'CYCLE_INTERNAL', 'INVALIDATE_ALL_MODULES_NO_FREE_WASM_INDICES', 'INVALIDATE_MODULE_WRITTEN_WHILE_COMPILED', 'INVALIDATE_MODULE_UNUSED_AFTER_OVERWRITE', 'INVALIDATE_MODULE_DIRTY_PAGE', 'INVALIDATE_PAGE_HAD_CODE', 'INVALIDATE_PAGE_HAD_ENTRY_POINTS', 'DIRTY_PAGE_DID_NOT_HAVE_CODE', 'RUN_FROM_CACHE_EXIT_SAME_PAGE', 'RUN_FROM_CACHE_EXIT_NEAR_END_OF_PAGE', 'RUN_FROM_CACHE_EXIT_DIFFERENT_PAGE', 'CLEAR_TLB', 'FULL_CLEAR_TLB', 'TLB_FULL', 'TLB_GLOBAL_FULL', 'MODRM_SIMPLE_REG', 'MODRM_SIMPLE_REG_WITH_OFFSET', 'MODRM_SIMPLE_CONST_OFFSET', 'MODRM_COMPLEX', 'SEG_OFFSET_OPTIMISED', 'SEG_OFFSET_NOT_OPTIMISED', 'SEG_OFFSET_NOT_OPTIMISED_ES', 'SEG_OFFSET_NOT_OPTIMISED_FS', 'SEG_OFFSET_NOT_OPTIMISED_GS', 'SEG_OFFSET_NOT_OPTIMISED_NOT_FLAT', ] let j = 0 const stat_values: Record = {} for (let i = 0; i < stat_names.length; i++) { const name = stat_names[i] let value: number | string if (name.includes('/')) { j++ // skip profiler_stat_get const [left, right] = name.split('/') value = stat_values[left] / stat_values[right] } else { const stat = (stat_values[name] = cpu.wm.exports[ 'profiler_stat_get' ](i - j) as number) value = stat >= 100e6 ? Math.round(stat / 1e6) + 'm' : stat >= 100e3 ? Math.round(stat / 1e3) + 'k' : stat } text += name + '=' + value + '\n' } text += '\n' const tlb_entries = cpu.wm.exports[ 'get_valid_tlb_entries_count' ]() as number const global_tlb_entries = cpu.wm.exports[ 'get_valid_global_tlb_entries_count' ]() as number const nonglobal_tlb_entries = tlb_entries - global_tlb_entries text += 'TLB_ENTRIES=' + tlb_entries + ' (' + global_tlb_entries + ' global, ' + nonglobal_tlb_entries + ' non-global)\n' text += 'WASM_TABLE_FREE=' + cpu.wm.exports['jit_get_wasm_table_index_free_list_count']() + '\n' text += 'JIT_CACHE_SIZE=' + cpu.wm.exports['jit_get_cache_size']() + '\n' text += 'FLAT_SEGMENTS=' + cpu.wm.exports['has_flat_segmentation']() + '\n' text += 'wasm memory size: ' + (cpu.wasm_memory.buffer.byteLength >> 20) + 'm\n' text += 'Config:\n' text += 'JIT_DISABLED=' + cpu.wm.exports['get_jit_config'](0) + '\n' text += 'MAX_PAGES=' + cpu.wm.exports['get_jit_config'](1) + '\n' text += 'JIT_USE_LOOP_SAFETY=' + Boolean(cpu.wm.exports['get_jit_config'](2)) + '\n' text += 'MAX_EXTRA_BASIC_BLOCKS=' + cpu.wm.exports['get_jit_config'](3) + '\n' return text } function print_instruction_counts(cpu: CPU): string { return [ print_instruction_counts_offset(cpu, false, false, false, false), print_instruction_counts_offset(cpu, true, false, false, false), print_instruction_counts_offset(cpu, false, true, false, false), print_instruction_counts_offset(cpu, false, false, true, false), print_instruction_counts_offset(cpu, false, false, false, true), ].join('\n\n') } interface InstructionCount { opcode: number count: number is_mem: boolean fixed_g: number } function print_instruction_counts_offset( cpu: CPU, compiled: boolean, jit_exit: boolean, unguarded_register: boolean, wasm_size: boolean, ): string { let text = '' const counts: InstructionCount[] = [] const label = compiled ? 'compiled' : jit_exit ? 'jit exit' : unguarded_register ? 'unguarded register' : wasm_size ? 'wasm size' : 'executed' for (let opcode = 0; opcode < 0x100; opcode++) { for (let fixed_g = 0; fixed_g < 8; fixed_g++) { for (const is_mem of [false, true]) { const count = cpu.wm.exports['get_opstats_buffer']( compiled, jit_exit, unguarded_register, wasm_size, opcode, false, is_mem, fixed_g, ) as number counts.push({ opcode, count, is_mem, fixed_g }) const count_0f = cpu.wm.exports['get_opstats_buffer']( compiled, jit_exit, unguarded_register, wasm_size, opcode, true, is_mem, fixed_g, ) as number counts.push({ opcode: 0x0f00 | opcode, count: count_0f, is_mem, fixed_g, }) } } } let total = 0 const prefixes = new Set([ 0x26, 0x2e, 0x36, 0x3e, 0x64, 0x65, 0x66, 0x67, 0xf0, 0xf2, 0xf3, ]) for (const { count, opcode } of counts) { if (!prefixes.has(opcode)) { total += count } } if (total === 0) { return '' } const per_opcode = new Uint32Array(0x100) const per_opcode0f = new Uint32Array(0x100) for (const { opcode, count } of counts) { if ((opcode & 0xff00) === 0x0f00) { per_opcode0f[opcode & 0xff] += count } else { per_opcode[opcode & 0xff] += count } } text += '------------------\n' text += 'Total: ' + total + '\n' const factor = total > 1e7 ? 1000 : 1 const max_count = Math.max( ...counts.map(({ count }) => Math.round(count / factor)), ) const pad_length = String(max_count).length text += `Instruction counts ${label} (in ${factor}):\n` for (let i = 0; i < 0x100; i++) { text += i.toString(16).padStart(2, '0') + ':' + pads(Math.round(per_opcode[i] / factor), pad_length) if (i % 16 === 15) text += '\n' else text += ' ' } text += '\n' text += `Instruction counts ${label} (0f, in ${factor}):\n` for (let i = 0; i < 0x100; i++) { text += (i & 0xff).toString(16).padStart(2, '0') + ':' + pads(Math.round(per_opcode0f[i] / factor), pad_length) if (i % 16 === 15) text += '\n' else text += ' ' } text += '\n' const top_counts = counts .filter(({ count }) => count) .sort(({ count: count1 }, { count: count2 }) => count2 - count1) for (const { opcode, is_mem, fixed_g, count } of top_counts.slice(0, 200)) { const opcode_description = opcode.toString(16) + '_' + fixed_g + (is_mem ? '_m' : '_r') text += opcode_description + ':' + ((count / total) * 100).toFixed(2) + ' ' } text += '\n' return text }