aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/workbench/workspace.test.mjs
blob: 799f0560ff24dd9a0e6cd93bd809c2a1c704679d (plain)
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
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { compile } from './web/compiler.mjs';
import { createRuntime } from './web/runtime.mjs';

const wasm = await readFile(new URL('./web/core.wasm', import.meta.url));
const source = factor => `state x = 10
input next = KeyN
text label(0, 0, x * ${factor})
on next.down { x = x + 0.25 }
`;

function begin(core, program, preserve) {
  assert.equal(core.begin_program(program.states.length, program.inputs.length), 0);
  program.states.forEach((state, i) => assert.equal(core.put_state(i, state.initial, preserve ? i : -1), 0));
}

function finish(core, program, during = () => {}) {
  for (const op of program.instructions) {
    assert.equal(core.put_instruction(op.op, op.arg, op.value), 0);
    during();
  }
  for (const handler of program.handlers)
    assert.equal(core.put_handler(handler.event, handler.start, handler.length), 0);
  assert.equal(core.commit_program(), 0);
}

function observe(core, binding) {
  core.begin_evaluation(100);
  const value = core.eval_bounded(binding.start, binding.length);
  assert.equal(core.evaluation_info(0), 0);
  return value;
}

test('Wasm executes old events and output during staged replacement without corrupting either phase', async () => {
  const {instance:{exports:core}} = await WebAssembly.instantiate(wasm);
  core.reset_all();
  const old = compile(source(2)), next = compile(source(3));
  begin(core, old, false); finish(core, old);
  assert.equal(core.configure_evaluation(32, 8), 0);
  const binding = old.views[0].bindings.value;
  assert.equal(observe(core, binding), 20);
  assert.equal(observe(core, binding), 20);
  assert.equal(core.evaluation_info(3), 1);
  begin(core, next, true);
  let value = 10;
  finish(core, next, () => {
    assert.equal(core.evaluation_info(9), 0);
    assert.equal(core.dispatch(1, 0), 0); value += 0.25;
    assert.equal(core.configure_evaluation(32, 8), 0);
    core.forget_evaluation(3);
    assert.equal(observe(core, binding), value * 2);
    assert.equal(core.evaluation_info(3), 0);
  });
  assert.equal(core.evaluation_info(9), 1);
  assert.equal(core.get_state(0), value);
  const replaced = next.views[0].bindings.value;
  assert.equal(observe(core, replaced), value * 3);
  assert.equal(observe(core, replaced), value * 3);
  assert.equal(core.evaluation_info(3), 1);

  const revision = core.info(4);
  begin(core, old, true);
  assert.equal(core.put_instruction(0, 0, Infinity), 5);
  assert.equal(core.commit_program(), 5);
  assert.equal(observe(core, replaced), value * 3);
  core.abort_program();
  assert.equal(core.info(4), revision);
  assert.equal(core.get_state(0), value);
  assert.equal(core.evaluation_info(9), 1);
  assert.equal(observe(core, replaced), value * 3);
  assert.equal(core.memory.buffer.byteLength, 65536);
});

test('workspace accounting reports shared payload and safety tag without double-counting', async () => {
  const runtime = await createRuntime(wasm, {cacheEntries:32});
  runtime.apply(source(2)); runtime.snapshot();
  const stats = runtime.evaluationStats(), memory = runtime.snapshot().memory;
  assert.equal(stats.dedicatedBytes, 48);
  assert.equal(stats.sharedBytes, 1152);
  assert.equal(stats.workspaceTagBytes, 8);
  assert.equal(stats.reservedBytes, stats.dedicatedBytes + stats.sharedBytes);
  const restoreStorage = memory.stagingReservedBytes - memory.programReservedBytes;
  assert.equal(restoreStorage, 264); // 32 values + mask/alignment, only in pending storage.
  assert.equal(memory.reservedEngineBytes, 19072 + stats.dedicatedBytes + stats.workspaceTagBytes + restoreStorage + 2 * 64 * (memory.handlerBytes - 12) + 2 * 80);
  // Each program adds 24 three-byte routes, count/mask and alignment (80 B).
  assert.equal(memory.pointerTargetBytes, 3);
  assert(stats.sharedBytes < memory.stagingReservedBytes);
  assert.equal(stats.reuseAvailable, true);
});