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
|
// runs the language server inside the extension, in the tinyvm javascript vm,
// so nothing has to be built or installed for the server to start. it presents
// the same surface the client uses on a child process: stdin to write, stdout
// and stderr to listen to, an exit event and kill().
//
// the vm is streaming: the server runs until it wants input, the client's bytes
// are fed in and the server goes on from there. each step is synchronous, so a
// long check holds the extension host for as long as it takes.
//
// opts.log(level, text) gets what the vm does: the image it loads, each halt,
// and the messages the server writes to its error stream.
const path = require('path');
const { EventEmitter } = require('events');
function vmProcess(dir, opts = {}) {
const log = opts.log || (() => {});
const { TinyVM } = require(path.join(dir, 'tinyvm.js'));
const image = require(path.join(dir, 'lsp.json'));
const proc = new EventEmitter();
const out = new EventEmitter();
const err = new EventEmitter();
const pending = [];
let dead = false;
let bytesIn = 0, bytesOut = 0, steps = 0;
const vm = new TinyVM({
streaming: true,
memoryWords: image.layout.HEAP_HI, // the initial heap; the machine grows on the heap's request
layout: image.layout,
onOutput: b => { pending.push(b); },
onMessage: m => {
log('problem', 'server says: ' + String(m).slice(0, 500));
err.emit('data', Buffer.from(m, 'utf8'));
},
});
vm.loadGenome(image.words, image.gorg);
log('message', 'loaded the image: ' + image.words.length + ' words, code at ' + image.gorg);
const flush = () => {
if (pending.length) {
const chunk = Buffer.from(pending);
bytesOut += chunk.length;
pending.length = 0;
out.emit('data', chunk);
}
};
const after = status => {
steps++;
flush();
if (status !== 3 && !dead) { // it halted: the server has exited
dead = true;
log(status === 0 ? 'message' : 'problem', 'the vm halted with status ' + status + ' after ' + steps + ' steps, ' + bytesIn + ' bytes in, ' + bytesOut + ' out');
proc.emit('exit', status);
} else if (status === 3 && vm.polling) {
// the server asked whether input is waiting: let the event loop deliver it, then go on
setImmediate(() => { if (!dead && vm.polling) after(vm.resume()); });
}
};
proc.stdin = {
writable: true,
write(buf) {
if (dead) { log('problem', 'write to a halted vm dropped (' + buf.length + ' bytes)'); return false; }
bytesIn += buf.length;
vm.feed(buf);
after(vm.resume());
return true;
},
};
proc.stdout = out;
proc.stderr = err;
proc.kill = () => {
if (dead) return;
dead = true;
log('message', 'vm killed after ' + steps + ' steps');
proc.emit('exit', null);
};
const t0 = Date.now();
after(vm.run(image.gorg));
log('message', 'vm ran to its first wait in ' + (Date.now() - t0) + ' ms');
return proc;
}
module.exports = { vmProcess };
|