import { describe, expect, test } from "bun:test" import { Effect } from "effect" import { CodeMode } from "../../src/index.js" import { createBuiltins } from "../../src/vm/runtime/intrinsics.js" import { Interpreter } from "../../src/vm/runtime/interpreter.js" import { type Snapshot, SnapshotError } from "../../src/vm/snapshot/snapshot.js" import { ToolRuntime } from "../../src/tool-runtime.js" import { hashOf } from "../../src/vm/code.js" import { compileSource } from "../../src/vm/compiler/compile.js" import { moduleUrl, parseVmSource, runProgram } from "./run.js" import { buildId } from "../../src/vm/snapshot.js" // The VM's snapshots: a run saved and restored into a fresh runtime anywhere it slices goes on as if it never stopped. const programs: Record = { "closures over per-iteration bindings": ` const fns = [] for (let i = 0; i < 5; i++) fns.push(() => i * 2) let total = 0 for (const f of fns) total += f() return [fns.map((f) => f()), total]`, "recursion several frames deep": ` function fib(n) { return n < 2 ? n : fib(n - 1) + fib(n - 2) } const walk = (node, depth) => node === null ? depth : walk(node.next, depth + 1) return [fib(12), walk({ next: { next: { next: null } } }, 0)]`, "finally levels, labels and pending returns": ` const out = [] function f(x) { try { try { if (x > 1) return "r" + x; out.push("t" + x) } finally { out.push("inner" + x) } } finally { out.push("outer" + x) for (let i = 0; i < 2; i++) out.push("loop" + i) } return "n" + x } outer: for (let i = 0; i < 4; i++) { for (let j = 0; j < 3; j++) { try { if (j === 1) continue outer; if (i === 3) break outer } finally { out.push(i + ":" + j) } } } return [f(1), f(2), out]`, "a custom iterator in mid-iteration, and destructuring": ` const log = [] const it = { i: 0, [Symbol.iterator]() { return this }, next() { this.i++; return { value: this.i, done: this.i > 4 } }, return() { log.push("closed"); return {} } } for (const x of it) { log.push(x); if (x === 3) break } const [a, b = a * 10, ...rest] = [1, undefined, 3, 4] const { p, q: { r = 5 } = {}, ...others } = { p: 1, s: 2, t: 3 } return [log, a, b, rest, p, r, others]`, "a tagged template keeps its strings object": ` const seen = new Set() const tag = (strings, ...values) => { seen.add(strings); return strings.raw.join("|") + values.join(",") } let text = "" for (let i = 0; i < 4; i++) text += tag\`a\${i}b\` return [text, seen.size]`, "prelude callbacks and sorts are interrupted and continue": ` const xs = Array.from({ length: 30 }, (_, i) => (i * 7) % 30) const sorted = [...xs].sort((a, b) => a - b) const groups = Object.groupBy(xs, (x) => (x % 3 === 0 ? "three" : "other")) const mapped = xs.map((x) => x * 2).filter((x) => x % 4 === 0).reduce((a, b) => a + b, 0) return [sorted, groups.three.length, mapped, new Map(xs.map((x) => [x, x])).size]`, "JSON with toJSON, replacers and revivers": ` const value = { a: [1, { b: 2, toJSON() { return { c: 3 } } }], d: new Date(0) } const text = JSON.stringify(value, (k, v) => (typeof v === "number" ? v + 1 : v), 2) return [text, JSON.parse('{"a":[1,2,{"b":3}]}', (k, v) => (typeof v === "number" ? v * 10 : v))]`, "errors keep their types and locations": ` const caught = [] for (let i = 0; i < 3; i++) { try { if (i === 0) null.x; if (i === 1) undefinedName; if (i === 2) throw new RangeError("r" + i) } catch (e) { caught.push(e.name + ": " + e.message) } } return caught`, "implicit conversions run program code": ` let calls = 0 const o = { valueOf() { calls++; return 2 }, toString() { calls++; return "k" } } const t = {} t[o] = 1 return [o + 1, o * o, \`\${o}\`, t.k, calls, String(o).padStart(3, "-")]`, "logs and changed built-ins survive": ` Array.prototype.extra = function () { return this.length } Object.freeze(Math) for (let i = 0; i < 3; i++) console.log("line", i) return [[1, 2, 3].extra(), Object.isFrozen(Math)]`, "the program's result is copied out after a slice in the boundary walk": ` const rows = [] for (let i = 0; i < 5; i++) rows.push({ i, toJSON() { return { row: this.i } } }) return rows`, } describe("round trips", () => { for (const [name, code] of Object.entries(programs)) { test(name, () => { const straight = runProgram(code) expect(straight.ok).toBe(true) let trips = 0 const tripped = runProgram(code, { roundTrip: { every: 1, max: 1000 }, onTrip: () => trips++ }) expect(tripped).toEqual(straight) expect(trips).toBeGreaterThan(0) }) } test("a failing program fails the same way, at the same place", () => { const code = `let s = 0\nfor (let i = 0; i < 10; i++) s += i\nconst o = null\nreturn o.missing` const straight = runProgram(code) expect(straight.ok).toBe(false) expect(runProgram(code, { roundTrip: { every: 1 } })).toEqual(straight) }) }) /** A runtime the way `execute.ts` makes one, with no tools. */ const runtime = () => new Interpreter({ tools: ToolRuntime.make(ToolRuntime.prepare({}), undefined, {}), builtins: createBuiltins(), }) as Interpreter const parse = (code: string) => { const parsed = parseVmSource(code) if (!parsed.ok) throw new Error(parsed.error.message) return parsed.source } const restoreOptions = { parse, interruption: () => { throw new Error("no interruption") }, } /** A snapshot as JSON, to tamper with. */ type Saved = { readonly format: number readonly header: Snapshot["header"] readonly records: Array<{ readonly t: string; readonly f: Array }> } /** A VM sliced once inside a loop, and its state. */ const sliced = (code = `let n = 0\nfor (let i = 0; i < 10; i++) { try { n += i } finally { n += 0 } }\nreturn n`) => { const vm = runtime().vm vm.start(parse(code)) vm.tripEvery = 3 expect(vm.run().kind).toBe("slice") const { snapshot, lost } = vm.capture({ strict: true }) expect(lost).toEqual([]) return JSON.parse(JSON.stringify(snapshot)) as Saved } const frameOf = (snapshot: ReturnType) => snapshot.records.find((record) => record.t === "Frame")! /** A saved frame's registers, a record of their own. */ const regsOf = (snapshot: ReturnType) => { const ref = frameOf(snapshot).f[4] as [string, number] return (snapshot.records[ref[1]] as unknown as { v: Array }).v } const refusal = (snapshot: unknown) => { try { runtime().vm.restore(snapshot, restoreOptions) } catch (error) { expect(error).toBeInstanceOf(SnapshotError) return (error as Error).message } throw new Error("the snapshot was restored") } describe("snapshots", () => { test("hold code by source and function index, with the source's bytecode hash, and never bytecode", () => { const snapshot = sliced() expect(snapshot.format).toBe(2) const source = snapshot.header.sources[0]! expect(source.id).toBe("main") expect(source.bytecode).toBe(hashOf(compileSource(parse(source.code)))) const frame = frameOf(snapshot) expect(frame.f[1]).toEqual(["x", ["code", "main", 0]]) expect(JSON.stringify(snapshot)).not.toContain('"code":[') }) test("a restored VM runs to the same result", () => { const vm = runtime().vm vm.restore(sliced(), restoreOptions) const stop = vm.run() expect(stop).toEqual({ kind: "done", completion: { ok: true, value: 45 } }) }) test("capturing changes nothing the run sees", () => { const vm = runtime().vm vm.start(parse(`let n = 0\nfor (let i = 0; i < 10; i++) n += i\nreturn n`)) vm.tripEvery = 2 for (let stop = vm.run(); ; stop = vm.run()) { if (stop.kind !== "slice") { expect(stop).toEqual({ kind: "done", completion: { ok: true, value: 45 } }) break } vm.capture({ strict: true }) } }) test("the build id is stable", () => { expect(buildId()).toBe(buildId()) }) test("are refused by another build, or when a source compiles to other bytecode", () => { expect(refusal({ ...sliced(), header: { ...sliced().header, build: "0.0.0+0" } })).toContain( "another Code Mode build", ) const snapshot = sliced() const sources = [{ ...snapshot.header.sources[0]!, bytecode: "0000000000000000" }] expect(refusal({ ...snapshot, header: { ...snapshot.header, sources } })).toContain("other bytecode") }) test("check every frame against its code before anything runs", () => { const tamper = (change: (frame: Array) => void) => { const snapshot = sliced() change(frameOf(snapshot).f) return refusal(snapshot) } expect(tamper((frame) => (frame[2] = 100_000))).toContain("pc") expect(tamper((frame) => (frame[2] = 1.5))).toContain("pc") expect(tamper((frame) => (frame[3] = 0))).toContain("stack pointer") expect(tamper((frame) => (frame[7] = 99))).toContain("mode") expect(tamper((frame) => (frame[1] = ["x", ["code", "main", 99]]))).toContain("unknown external value") }) test("a hole on the operand stack is refused", () => { const vm = runtime().vm vm.start( parse(`function f(a, b) { return a + b }\nlet s = 0\nfor (let i = 0; i < 3; i++) s = s + f(i, 1)\nreturn s`), ) vm.tripEvery = 1 expect(vm.run().kind).toBe("slice") const snapshot = JSON.parse(JSON.stringify(vm.capture({ strict: true }).snapshot)) const regs = regsOf(snapshot) const code = compileSource(parse(snapshot.header.sources[0].code))[0]! expect(regs.length).toBeGreaterThan(code.locals) regs[regs.length - 1] = ["x", "hole"] expect(refusal(snapshot)).toContain("hole") }) test("a finally tag out of range is refused", () => { const snapshot = sliced() const code = compileSource(parse(snapshot.header.sources[0]!.code))[0]! regsOf(snapshot)[code.finallies[code.finallyOffsets[0]!]!] = 50 expect(refusal(snapshot)).toContain("finally tag") }) }) describe("suspend", () => { test("a sync program suspended mid-loop resumes where it stopped, in a new runtime", async () => { const runtime = CodeMode.make({ tools: {}, engine: "vm" }) const code = ` const seen = [] let n = 0 for (let i = 0; i < 400_000; i++) { n = (n + i) % 1_000_003; if (i % 100_000 === 0) seen.push(i) } return [n, seen]` const expected = await Effect.runPromise(runtime.execute(code)) const program = runtime.program(code) const run = Effect.runPromise(program.run()) await new Promise((done) => setTimeout(done, 5)) const suspended = await Effect.runPromise(program.suspend()) if (suspended.ok !== "suspended") throw new Error("the run ended before it could be suspended") expect(await run).toBe(suspended) expect(await Effect.runPromise(runtime.resume(suspended.state).run())).toEqual(expected) }) }) describe("determinism", () => { test("another process compiles the same bytecode and has the same build id", async () => { const codes = Object.values(programs) const local = { hashes: codes.map((code) => hashOf(compileSource(parse(code)))), build: (runtime(), buildId()) } const script = ` import { hashOf } from ${moduleUrl("../../src/vm/code.js")} import { compileSource } from ${moduleUrl("../../src/vm/compiler/compile.js")} import { parseVmSource } from ${moduleUrl("run.js")} import { buildId } from ${moduleUrl("../../src/vm/snapshot.js")} import { createBuiltins } from ${moduleUrl("../../src/vm/runtime/intrinsics.js")} import { Interpreter } from ${moduleUrl("../../src/vm/runtime/interpreter.js")} import { ToolRuntime } from ${moduleUrl("../../src/tool-runtime.js")} new Interpreter({ tools: ToolRuntime.make(ToolRuntime.prepare({}), undefined, {}), builtins: createBuiltins() }) const codes = ${JSON.stringify(codes)} console.log(JSON.stringify({ hashes: codes.map((code) => hashOf(compileSource(parseVmSource(code).source))), build: buildId() }))` const child = Bun.spawn(["bun", "-e", script], { stdout: "pipe", stderr: "inherit" }) expect(JSON.parse(await new Response(child.stdout).text())).toEqual(local) expect(await child.exited).toBe(0) }) })