diff options
| author | 2026-10-12 08:46:23 +0330 | |
|---|---|---|
| committer | 2026-10-12 08:46:23 +0330 | |
| commit | ecc350ca5c2eb50be8309c5f14f8bf0d87db9a02 (patch) | |
| tree | 70e0af76a1b13affff7c6cc8aac7b3e6ef960478 /editors/vscode/client/wasmproc.js | |
a
Diffstat (limited to 'editors/vscode/client/wasmproc.js')
| -rw-r--r-- | editors/vscode/client/wasmproc.js | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/editors/vscode/client/wasmproc.js b/editors/vscode/client/wasmproc.js new file mode 100644 index 0000000..0aa674a --- /dev/null +++ b/editors/vscode/client/wasmproc.js @@ -0,0 +1,96 @@ +// runs the language server as WebAssembly (server/lsp.wasm, built by +// src/tools/mklspwasm.sh): the microcore with the server's image inside. it presents +// the same surface as vmProcess: stdin to write, stdout and stderr to listen to, +// an exit event and kill(). it is much faster than the javascript vm, because +// the server's 64-bit words are native here and not bigints. +// +// the module is synchronous to load and to run, so the server starts in the +// extension host's own thread. a step runs until the server waits for input, +// then returns, the way vmProcess does. +// +// opts.log(level, text) gets what happens: the module loaded, each halt, and the +// messages the server writes to its error stream. +const fs = require('fs'); +const { EventEmitter } = require('events'); + +const CHUNK = 65536; // the most a single feed can carry (wasm_chunk_cap) + +function wasmProcess(file, opts = {}) { + const log = opts.log || (() => {}); + const t0 = Date.now(); + const bytes = fs.readFileSync(file); + const mod = new WebAssembly.Module(bytes); + log('message', 'compiled ' + file + ' (' + bytes.length + ' bytes) in ' + (Date.now() - t0) + ' ms'); + + const proc = new EventEmitter(); + const out = new EventEmitter(); + const err = new EventEmitter(); + let outBytes = [], msgBytes = []; + let dead = false, steps = 0, bytesIn = 0, bytesOut = 0; + + const inst = new WebAssembly.Instance(mod, { + env: { + js_putc: c => { outBytes.push(c & 255); }, + js_msg: c => { msgBytes.push(c & 255); }, + }, + }); + const x = inst.exports; + if (x.wasm_chunk_cap() < CHUNK) throw new Error('lsp.wasm has a smaller input chunk than this host expects'); + + const flush = () => { + if (outBytes.length) { + const chunk = Buffer.from(outBytes); + outBytes = []; + bytesOut += chunk.length; + out.emit('data', chunk); + } + if (msgBytes.length) { + const text = Buffer.from(msgBytes).toString('utf8'); + msgBytes = []; + log('problem', 'server says: ' + text.trim().slice(0, 500)); + err.emit('data', Buffer.from(text, 'utf8')); + } + }; + const after = status => { + steps++; + flush(); + if (status !== 3 && !dead) { // it halted: the server has exited + dead = true; + log(status === 0 ? 'message' : 'problem', 'the server halted with status ' + status + ' after ' + steps + ' steps, ' + bytesIn + ' bytes in, ' + bytesOut + ' out'); + proc.emit('exit', status); + } + }; + + proc.stdin = { + writable: true, + write(buf) { + if (dead) { log('problem', 'write to a halted server dropped (' + buf.length + ' bytes)'); return false; } + bytesIn += buf.length; + const chunkAt = x.wasm_chunk(); + let off = 0; + while (off < buf.length) { + const n = Math.min(CHUNK, buf.length - off); + new Uint8Array(x.memory.buffer).set(buf.subarray(off, off + n), chunkAt); + if (x.wasm_feed(n) !== 0) { log('problem', 'the server input is full; dropped ' + (buf.length - off) + ' bytes'); return false; } + off += n; + } + after(x.wasm_resume()); + return true; + }, + }; + proc.stdout = out; + proc.stderr = err; + proc.kill = () => { + if (dead) return; + dead = true; + log('message', 'server killed after ' + steps + ' steps'); + proc.emit('exit', null); + }; + + const t1 = Date.now(); + after(x.wasm_start()); + log('message', 'server ran to its first wait in ' + (Date.now() - t1) + ' ms'); + return proc; +} + +module.exports = { wasmProcess }; |
