let traces = {}; let config = {}; let raster_tile = {'x': 0, 'y': 0}; let max_stage_cycles = 0; onmessage = (e) => { const msg = e.data; if (msg.type === 'init') { config = msg.config; } else if (msg.type === 'import') { const text = msg.text; if (parse(text)) { traces['0'].geo = generate('0'); delete traces['0'].raw; postMessage({'type': 'trace', 'data': traces['0']}); } } } function parse(text) { // https://github.com/shioyadan/Konata/blob/master/docs/kanata-log-format.md const before = performance.now(); let line_start = 0; let line_index = 0; let c = -1; const instructions = {}; //console.log(text); for (let i = 0; i < text.length; ++i) { const last_percent = Math.round((i - 1) / text.length * 100); const this_percent = Math.round(i / text.length * 100); if (this_percent != last_percent) { postMessage({'type': 'progress_parse', 'data': this_percent}) } if (text[i] === '\n') { // TODO: speed const line_copy = text.substring(line_start, i); const line_parts = line_copy.split(/\t/); if (line_parts.length === 0) { console.error('Parser error: empty line'); } const command = line_parts[0]; switch (command) { case 'C=': { // Specify the number of cycles since the start of simulation. if (!assert_arglen(line_parts, 1, 'C=')) return false; const cycles = Number(line_parts[1]); c = cycles; break; } case 'C': { // Specifies the number of elapsed cycles since the last output of any commands. if (!assert_arglen(line_parts, 1, 'C')) return false; const cycles = Number(line_parts[1]); if (c === -1) { c = 0; } else { c += cycles; } break; } case 'I': { if (!assert_arglen(line_parts, 3, 'I')) return false; const insn_id_in_file = Number(line_parts[1]); const insn_id_in_sim = Number(line_parts[2]); const thread_id = Number(line_parts[3]); instructions[insn_id_in_file] = { 'cycle': c, 'file_id': insn_id_in_file, 'sim_id': insn_id_in_sim, 'thread_id': thread_id, 'text': '', 'popover_text': '', 'lanes': {'0': [], '1': []}, }; break; } case 'L': { if (!assert_arglenmin(line_parts, 3, 'L')) return false; const id = Number(line_parts[1]); const type = Number(line_parts[2]); const text = line_parts[3].replaceAll('\\n', '\n'); if (id in instructions) { if (type === 0) { instructions[id].text += text; } else { instructions[id].popover_text += text; } } else { console.error('Parser error: label for an instruction that does not exist'); } break; } case 'S': { if (!assert_arglen(line_parts, 3, 'S')) return false; const id = Number(line_parts[1]); const lane = Number(line_parts[2]); const stage = line_parts[3]; if (id in instructions) { if (!(lane in instructions[id].lanes)) { instructions[id].lanes[lane] = []; } instructions[id].lanes[lane].push({ 'name': stage, 'c': c, }); } else { console.error('Parser error: pipeline start for an instruction that does not exist'); } break; } case 'E': { if (!assert_arglen(line_parts, 3, 'E')) return false; const id = Number(line_parts[1]); const lane = Number(line_parts[2]); const stage = line_parts[3]; // This command can be ommited break; } case 'R': { if (!assert_arglen(line_parts, 3, 'R')) return false; const id = Number(line_parts[1]); const retire_id = Number(line_parts[2]); const type = Number(line_parts[3]); if (id in instructions) { if (type === 0) { instructions[id].retired = true; } else { instructions[id].retired = false; } instructions[id].retcyc = c; } else { console.error('Parser error: retire for an instruction that does not exist'); } break; } case 'W': { if (!assert_arglen(line_parts, 3, 'W')) return false; const consumer_id = Number(line_parts[1]); const producer_id = Number(line_parts[2]); const type = Number(line_parts[3]); // TODO break; } default: { console.error('Parser error: unexpected command', command); } } line_start = i + 1; line_index += 1; } } traces['0'] = { // The fact these are sorted is used extensively over the code 'raw': Object.values(instructions).sort((a, b) => a.cycle - b.cycle), }; const after = performance.now(); console.log(`Parsed in ${Math.round(after - before)}ms`); return true; } function assert_arglen(args, arglen, command) { if (args.length !== arglen + 1) { console.error(`Parser error: command ${command} requires ${arglen} argument(s)`); return false; } return true; } function assert_arglenmin(args, arglen, command) { if (args.length < arglen + 1) { console.error(`Parser error: command ${command} requires at least ${arglen} argument(s)`); return false; } return true; } let colors = {}; let rasterized = {}; // https://stackoverflow.com/a/17243070 function hsv_to_rgb(h, s, v) { let r, g, b; const i = Math.floor(h * 6); const f = h * 6 - i; const p = v * (1 - s); const q = v * (1 - f * s); const t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: r = v, g = t, b = p; break; case 1: r = q, g = v, b = p; break; case 2: r = p, g = v, b = t; break; case 3: r = p, g = q, b = v; break; case 4: r = t, g = p, b = v; break; case 5: r = v, g = p, b = q; break; } return [ Math.round(r * 255), Math.round(g * 255), Math.round(b * 255) ]; } function get_color(stage_name) { if (stage_name in config.predefined_colors) { return config.predefined_colors[stage_name]; } if (stage_name in colors) { return colors[stage_name]; } colors[stage_name] = hsv_to_rgb(Math.random(), 0.56, 0.56); return colors[stage_name]; } function rasterize_and_pack(text) { // TODO: handle texture is full or stuff don't fit (unlikely) const key = text; if (key in rasterized) { return rasterized[key]; } const u = raster_tile.x * config.w / config.raster_texture_size; const v = raster_tile.y * config.h / config.raster_texture_size; postMessage({'type': 'rasterize', 'data': { 'text': text, 'at': {'x': raster_tile.x, 'y': raster_tile.y}}}); raster_tile.x += 1; if (raster_tile.x === config.raster_texture_size / config.w) { raster_tile.x = 0; raster_tile.y += 1; } rasterized[key] = [u, v]; return [u, v]; } function pack_instruction(instruction, positions, sizes, colors, uvs, starts, y) { starts.push(positions.length); for (let i = 0; i < instruction.lanes['0'].length; ++i) { const stage = instruction.lanes['0'][i]; let stage_cycles; if (i < instruction.lanes['0'].length - 1) { const next_stage = instruction.lanes['0'][i + 1]; stage_cycles = next_stage.c - stage.c; } else { stage_cycles = instruction.retcyc - stage.c; } if (stage_cycles > max_stage_cycles) { max_stage_cycles = stage_cycles; } let [r, g, b] = get_color(stage.name); let a = 255; if (!instruction.retired) { a = 80; } const [u, v] = rasterize_and_pack(stage.name); sizes.push(stage_cycles * config.w + (stage_cycles - 1) * config.padding, 1 * config.h); positions.push(config.w * stage.c + config.padding * (stage.c - 1), config.h * y + config.padding * (y - 1)); colors.push(r, g, b, a); uvs.push(u, v); } return instruction.lanes['0'].length; } function generate(trace_id) { const before = performance.now(); const result = { 'count': 0, }; const positions = []; const sizes = []; const colors = []; const uvs = []; const starts = []; let instructions = {}; if (trace_id in traces) { instructions = traces[trace_id].raw; } let y = 0; for (let i = 0; i < instructions.length; ++i) { const last_percent = Math.round((i - 1) / instructions.length * 100); const this_percent = Math.round(i / instructions.length * 100); if (this_percent !== last_percent) { postMessage({'type': 'progress_generate', 'data': this_percent}) } const instruction = instructions[i]; // TODO: make all quads same size, abuse this fact result.count += pack_instruction(instruction, positions, sizes, colors, uvs, starts, y); if (config.limit > 0 && result.count >= config.limit) { break; } ++y; } postMessage({'type': 'rasterize_numbers', 'up_to': max_stage_cycles}); starts.push(positions.length); console.log(`Total geometry: ${Math.round(result.count * config.bytes_per_quad / 1024 / 1024)}MB`); if (config.show_texture) { result.pos = new Float32Array([0, 0]); result.size = new Float32Array([config.raster_texture_size, config.raster_texture_size]); result.color = new Uint8Array([0, 0, 0, 255]); result.uv = new Float32Array([0, 0]); result.count = 1; result.trace_id = trace_id; result.uploaded = false; } else { result.pos = new Float32Array(positions); result.size = new Float32Array(sizes); result.color = new Uint8Array(colors); result.uv = new Float32Array(uvs); result.trace_id = trace_id; result.uploaded = false; result.start = new Int32Array(starts); } const after = performance.now(); console.log(`Generated geometry in ${Math.round(after - before)}ms`); return result; }