kanat is too fat
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

457 lines
13 KiB

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)) {
const geo = generate('0');
traces['0'].geo = geo;
traces['0'].lod1 = lod(geo, 1);
traces['0'].lod2 = lod(geo, 2);
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 === Math.floor(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 = isNaN(instruction.retcyc) ? 0 : 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', 'at': raster_tile, '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;
}
function lod(geo) {
let x11, x12, y1, x21, x22, y2;
let merged_rows = 0;
let merged_quads = 0;
let row_y;
const merge_count = 100;
const merged_vertices = [];
for (let i = 0; i < geo.pos.length; i += 2) {
const at_x = geo.pos[i + 0];
const at_y = geo.pos[i + 1];
const w = geo.size[i + 0];
const h = geo.size[i + 1];
// TODO: error metric, make sure nothing "sticks out" too much
if (merged_quads === 0) {
x11 = x21 = at_x;
y2 = row_y = at_y + h;
}
if (merged_rows === 0) {
y1 = at_y;
if (merged_quads === 0) {
x12 = x22 = at_x;
}
}
x12 = Math.max(x12, at_x + w);
x22 = Math.max(x22, at_x + w);
++merged_quads;
if (at_y != row_y) {
merged_quads = 0;
++merged_rows;
}
if (merged_rows === merge_count) {
merged_vertices.push(
x11, y1, x12, y1,
x21, y2, x22, y2
);
console.log(`(${x11}, ${y1}) - (${x12}, ${y1}) - (${x21}, ${y2}) - (${x22}, ${y2})`);
merged_rows = 0;
}
}
}