Compare commits

6 Commits

Author SHA1 Message Date
956d42d008 Create assembler driver script for recording word addresses 2026-03-20 12:40:09 +00:00
87d8345017 Run profiler while prelude is loading 2026-03-19 22:03:46 +00:00
d39fe580fc Implement sampling profiler 2026-03-19 22:03:36 +00:00
ba8c99a123 Mirror IP and RSP regs in memory
This will allow the profiler to sample the current return stack and
instruction pointer.  It can't just use the ip and rsp globals
directly, as I had initially planned, since these cannot be sent
in between threads.
2026-03-19 22:01:51 +00:00
cc8ae742f0 Add more opcodes to assembler 2026-03-19 22:01:33 +00:00
812443d6ee Support exports of globals in assembler 2026-03-19 19:53:50 +00:00
5 changed files with 161 additions and 47 deletions

119
asm.js
View File

@@ -144,49 +144,52 @@ const types = {
}; };
const opcodes = { const opcodes = {
"block": 0x02, "block": 0x02,
"loop": 0x03, "loop": 0x03,
"if": 0x04, "if": 0x04,
"else": 0x05, "else": 0x05,
"end": 0x0b, "end": 0x0b,
"br": 0x0c, "br": 0x0c,
"br_if": 0x0d, "br_if": 0x0d,
"call": 0x10, "call": 0x10,
"call_indirect": 0x11, "call_indirect": 0x11,
"drop": 0x0a, "drop": 0x0a,
"local.get": 0x20, "local.get": 0x20,
"local.set": 0x21, "local.set": 0x21,
"local.tee": 0x22, "local.tee": 0x22,
"global.get": 0x23, "global.get": 0x23,
"global.set": 0x24, "global.set": 0x24,
"i32.load": 0x28, "i32.load": 0x28,
"i32.load8_u": 0x2d, "i32.load8_u": 0x2d,
"i32.store": 0x36, "i32.store": 0x36,
"i32.store8": 0x3a, "i32.store8": 0x3a,
"i32.const": 0x41, "i32.const": 0x41,
"i64.const": 0x42, "i64.const": 0x42,
"i32.eqz": 0x45, "i32.eqz": 0x45,
"i32.eq": 0x46, "i32.eq": 0x46,
"i32.ne": 0x47, "i32.ne": 0x47,
"i32.lt_s": 0x48, "i32.lt_s": 0x48,
"i32.lt_u": 0x49, "i32.lt_u": 0x49,
"i32.gt_s": 0x4a, "i32.gt_s": 0x4a,
"i32.gt_u": 0x4b, "i32.gt_u": 0x4b,
"i32.le_s": 0x4c, "i32.le_s": 0x4c,
"i32.le_u": 0x4d, "i32.le_u": 0x4d,
"i32.ge_s": 0x4e, "i32.ge_s": 0x4e,
"i32.ge_u": 0x4f, "i32.ge_u": 0x4f,
"i32.add": 0x6a, "i32.add": 0x6a,
"i32.sub": 0x6b, "i32.sub": 0x6b,
"i32.mul": 0x6c, "i32.mul": 0x6c,
"i32.div_s": 0x6d, "i32.div_s": 0x6d,
"i32.rem_s": 0x6f, "i32.rem_s": 0x6f,
"i32.and": 0x71, "i32.and": 0x71,
"i32.or": 0x72, "i32.or": 0x72,
"i32.xor": 0x73, "i32.xor": 0x73,
"i32.shl": 0x74, "i32.shl": 0x74,
"i32.shr_s": 0x75, "i32.shr_s": 0x75,
"i32.shr_u": 0x76, "i32.shr_u": 0x76,
"i64.or": 0x84,
"i64.shl": 0x86,
"i64.extend_i32_u": 0xad,
// Threads instructions // Threads instructions
"memory.atomic.notify": [ 0xfe, 0x00 ], "memory.atomic.notify": [ 0xfe, 0x00 ],
@@ -194,6 +197,7 @@ const opcodes = {
"i32.atomic.load": [ 0xfe, 0x10 ], "i32.atomic.load": [ 0xfe, 0x10 ],
"i32.atomic.load8_u": [ 0xfe, 0x12 ], "i32.atomic.load8_u": [ 0xfe, 0x12 ],
"i32.atomic.store": [ 0xfe, 0x17 ], "i32.atomic.store": [ 0xfe, 0x17 ],
"i64.atomic.store": [ 0xfe, 0x18 ],
"i32.atomic.store8": [ 0xfe, 0x19 ], "i32.atomic.store8": [ 0xfe, 0x19 ],
}; };
@@ -886,8 +890,9 @@ const Section = Object.freeze({
}); });
const Kind = Object.freeze({ const Kind = Object.freeze({
FUNC: 0x00, FUNC: 0x00,
MEM: 0x02, MEM: 0x02,
GLOBAL: 0x03,
}); });
export class Assembler { export class Assembler {
@@ -942,8 +947,28 @@ export class Assembler {
} }
action_export(action) { action_export(action) {
const index = Object.keys(this.funcs).indexOf(action.name); const func_index = Object.keys(this.funcs).indexOf(action.name);
this.exports[action.name] = { kind: Kind.FUNC, index }; if (func_index != -1) {
this.exports[action.name] = {
kind: Kind.FUNC,
index: func_index,
};
return;
}
const global_index = Object.keys(this.globals).indexOf(action.name);
if (global_index != -1) {
this.exports[action.name] = {
kind: Kind.GLOBAL,
index: global_index,
};
return;
}
console.error(
`ERROR: Unable to resolve export ${action.name} `
+ "(only functions and globals currently supported)"
);
} }
action_func(action) { action_func(action) {

15
emu.js
View File

@@ -51,6 +51,19 @@ class Emulator {
document.addEventListener('keydown', (e) => this.handle_keydown(e)); document.addEventListener('keydown', (e) => this.handle_keydown(e));
window.addEventListener('resize', () => this.handle_resize()); window.addEventListener('resize', () => this.handle_resize());
this.prof = new Worker("prof.js");
this.prof.onmessage = (e) => {
const blob = new Blob(
[JSON.stringify(e.data)],
{ type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "wipforth-profile.json";
a.click();
URL.revokeObjectURL(url);
};
this.print("Assembling kernel "); this.print("Assembling kernel ");
const dots = setInterval(() => this.print("."), DOT_INTERVAL_MS); const dots = setInterval(() => this.print("."), DOT_INTERVAL_MS);
this.worker = new Worker('boot.js', { type: 'module' }); this.worker = new Worker('boot.js', { type: 'module' });
@@ -59,6 +72,7 @@ class Emulator {
clearInterval(dots); clearInterval(dots);
this.print(" done\n"); this.print(" done\n");
this.worker.postMessage({ type: "boot" }); this.worker.postMessage({ type: "boot" });
this.prof.postMessage({ type: "start", mem: this.mem });
}; };
fetch('prelude.f') fetch('prelude.f')
@@ -85,6 +99,7 @@ class Emulator {
if (!this.input_enable) { if (!this.input_enable) {
const sysready = Atomics.load(this.mem_u8, SYSREADY); const sysready = Atomics.load(this.mem_u8, SYSREADY);
if (sysready != 0) { if (sysready != 0) {
this.prof.postMessage({ type: "stop" });
this.input_enable = true; this.input_enable = true;
this.flush_output(); this.flush_output();
document.getElementById('cursor').classList.add('blinking'); document.getElementById('cursor').classList.add('blinking');

48
prof.js Normal file
View File

@@ -0,0 +1,48 @@
const INTERVAL_MS = 1;
const RS_TOP_ADDR = 0x10000;
const PROF_DATA_ADDR = 0x58;
const PROF_DATA_IDX = PROF_DATA_ADDR / 8;
let mem_8;
let mem_64;
let sampler;
const samples = [];
function sample() {
const data = Atomics.load(mem_64, PROF_DATA_IDX);
const ip = Number(data & 0xffffffffn);
const rsp = Number(data >> 32n);
samples.push({ ip, rs_bytes: mem_8.slice(rsp, RS_TOP_ADDR) });
}
function i32(bytes) {
return bytes[0]
| (bytes[1] << 8)
| (bytes[2] << 16)
| (bytes[3] << 24);
}
function postproc({ ip, rs_bytes }) {
const rs = [];
for (let i = 0; i < rs_bytes.length; i += 4)
rs.push(i32(rs_bytes.slice(i, i + 4)));
rs.reverse();
return { ip, rs };
}
self.onmessage = (e) => {
switch (e.data.type) {
case "start":
console.log("Starting profiler");
mem_8 = new Uint8Array(e.data.mem.buffer);
mem_64 = new BigUint64Array(e.data.mem.buffer);
ip = e.data.ip;
rsp = e.data.rsp;
sampler = setInterval(sample, INTERVAL_MS);
break;
case "stop":
clearInterval(sample);
console.log("Stopped profiler");
self.postMessage(samples.map(postproc));
}
};

View File

@@ -9,6 +9,9 @@
.def RXHEAD 48h .def RXHEAD 48h
.def RXTAIL 4Ch .def RXTAIL 4Ch
;; Mirror of registers for profiler to sample
.def PROF_DATA 58h
.def DICT_START 0200h .def DICT_START 0200h
.def RSP_INIT 10000h .def RSP_INIT 10000h
@@ -2073,6 +2076,13 @@ KERNEL_DEFS_END:
.func trampoline .func trampoline
loop iter loop iter
global.get fn call_indirect codeword codewords global.get fn call_indirect codeword codewords
i32.const PROF_DATA
global.get ip i64.extend_i32_u
global.get rsp i64.extend_i32_u
i64.const 32 i64.shl i64.or
i64.atomic.store 3 0
global.get run br_if iter global.get run br_if iter
end end

16
words.js Normal file
View File

@@ -0,0 +1,16 @@
import { Assembler } from "./asm.js";
const asm = new Assembler();
for await (const chunk of Deno.stdin.readable) {
asm.push(chunk);
}
asm.wasm();
const defs = Object.entries(asm.defs);
while (defs[0][0] != '_DUP')
defs.shift();
while (defs.at(-1)[0] != 'WNF_HANDLER')
defs.pop();
const words = Object.fromEntries(defs.filter(([k,v]) => !k.startsWith("_")));
console.log(JSON.stringify(words));