aboutsummaryrefslogtreecommitdiff
path: root/editors/vscode/client/wasmproc.js
blob: 0aa674a1f45ccf16df148ff6020ff2e8cf683494 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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 };