refactor(codemode): flatten the VM into src and remove two-engine leftovers (#63377)

This commit is contained in:
James Long
2026-11-12 12:39:15 -06:00
committed by GitHub
Unverified
parent 3cfe63a182
commit a73657f3f9
115 changed files with 239 additions and 306 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
# @opencode/codemode
- `interpreter-support.md` is a checklist, not a changelog: check off newly added support and add unchecked items for significant gaps. Leave it alone for fixes and refactors.
- Program values are `Value` (`src/vm/runtime/objects.ts`). Host values are `unknown` and enter only through `fromHost` and `fromJson`; do not widen program-facing signatures to `unknown`.
- Program values are `Value` (`src/runtime/objects.ts`). Host values are `unknown` and enter only through `fromHost` and `fromJson`; do not widen program-facing signatures to `unknown`.
- Each built-in object kind is one `Obj` subclass (`Wrapper` or `Opaque`) that overrides the hooks it needs. Ask the object instead of adding `instanceof` ladders.
- The core VM, compiler, prelude, snapshot, and stdlib modules must not import `effect`; Effect lives only in the host boundary and driver (`src/runtime/`, `src/openapi/`, and the top-level tool/runtime entrypoints).
+1 -1
View File
@@ -246,7 +246,7 @@ Every value crossing in either direction is converted, never shared: arguments c
copies, and a function inside a result is callable the same way. A global that shadows a built-in or another
extension throws at `CodeMode.make`.
Functions inside a result have no data form, so a suspended VM run loses them by default: after a resume, calling one
Functions inside a result have no data form, so a suspended run loses them by default: after a resume, calling one
throws a `TypeError`. An extension can preserve them across suspend and resume with `snapshot` hooks. `encode` receives
a value one of the extension's functions returned, as the host function returned it, and returns plain data (JSON
values and `Uint8Array`s); on resume, `decode` receives that data once per value and returns a host value of the same
+1 -1
View File
@@ -6,7 +6,7 @@
import { numberOfDFGCompiles, reoptimizationRetryCount } from "bun:jsc"
import { Effect, Schema } from "effect"
import { CodeMode, Tool } from "../src/index.js"
import { Vm } from "../src/vm/vm.js"
import { Vm } from "../src/vm.js"
const cases: ReadonlyArray<readonly [name: string, code: string]> = [
[
+5 -5
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env bun
// The bytecode disassembler: pcs, ops, constants, handler and finally tables, local names, source lines.
import { sourceLocation } from "../src/vm/runtime/model.js"
import type { Code } from "../src/vm/code.js"
import { compileSource } from "../src/vm/compiler/compile.js"
import { sourceLocation } from "../src/runtime/model.js"
import type { Code } from "../src/code.js"
import { compileSource } from "../src/compiler/compile.js"
import {
BinOpSymbols,
CheckBaseKind,
@@ -13,8 +13,8 @@ import {
OpNames,
PutKind,
UnaryOpSymbols,
} from "../src/vm/opcodes.js"
import { parseProgram } from "../src/vm/runtime/execute.js"
} from "../src/opcodes.js"
import { parseProgram } from "../src/runtime/execute.js"
const formatFlags = (flags: number): string => {
const names: Array<string> = []
+9 -9
View File
@@ -1,15 +1,15 @@
#!/usr/bin/env bun
// Compiles the built-in sources (the prelude, the VM's internal helpers) ahead of time into src/vm/prelude/bytecode.json,
// which a process loads instead of parsing and compiling them (see src/vm/prelude/bytecode.ts). Run it after changing
// either source or the compiler; test/vm/prelude-bytecode.test.ts fails until then.
// Compiles the built-in sources (the prelude, the VM's internal helpers) ahead of time into src/prelude/bytecode.json,
// which a process loads instead of parsing and compiling them (see src/prelude/bytecode.ts). Run it after changing
// either source or the compiler; test/prelude-bytecode.test.ts fails until then.
import { parse } from "acorn"
import { Source } from "../src/vm/runtime/source.js"
import { saveCodes } from "../src/vm/prelude/bytecode.js"
import { code } from "../src/vm/prelude/source.js"
import { compileSource } from "../src/vm/compiler/compile.js"
import { INTERNAL_SOURCE } from "../src/vm/internal-prelude.js"
import { Source } from "../src/runtime/source.js"
import { saveCodes } from "../src/prelude/bytecode.js"
import { code } from "../src/prelude/source.js"
import { compileSource } from "../src/compiler/compile.js"
import { INTERNAL_SOURCE } from "../src/internal-prelude.js"
export const compileBuiltins = () =>
Object.fromEntries(
@@ -23,5 +23,5 @@ export const compileBuiltins = () =>
)
if (import.meta.main) {
await Bun.write(new URL("../src/vm/prelude/bytecode.json", import.meta.url), `${JSON.stringify(compileBuiltins())}\n`)
await Bun.write(new URL("../src/prelude/bytecode.json", import.meta.url), `${JSON.stringify(compileBuiltins())}\n`)
}
+2 -2
View File
@@ -5,8 +5,8 @@
import { Effect } from "effect"
import { CodeMode } from "../src/index.js"
import { Interpreter } from "../src/vm/runtime/interpreter.js"
import { createBuiltins } from "../src/vm/runtime/intrinsics.js"
import { Interpreter } from "../src/runtime/interpreter.js"
import { createBuiltins } from "../src/runtime/intrinsics.js"
import { ToolRuntime } from "../src/tool-runtime.js"
const index = process.argv.indexOf("--iterations")
@@ -1,4 +1,4 @@
import type { Diagnostic } from "../codemode.js"
import type { Diagnostic } from "./codemode.js"
import { normalizeError, locate } from "./runtime/errors.js"
import { MAX_PENDING_PROMISES } from "./runtime/limits.js"
import { type AstNode, IteratorSymbol, rangeError, typeError } from "./runtime/model.js"
+6 -6
View File
@@ -3,11 +3,11 @@ import type { Extension } from "./extension.js"
import { scanTools } from "./scan.js"
import { type Services, type ToolDescription, ToolRuntime } from "./tool-runtime.js"
import type { Tools } from "./tools.js"
import { fromJson } from "./vm/data.js"
import { finishExecution, resumeProgram, type Running, type Setup, startProgram, SuspendError } from "./vm/runtime/execute.js"
import { extensionGlobals } from "./vm/runtime/extensions.js"
import { globalNames } from "./vm/runtime/globals.js"
import { fromBytes, type Snapshot, SnapshotError, type SnapshotHeader, toBytes } from "./vm/snapshot/snapshot.js"
import { fromJson } from "./data.js"
import { finishExecution, resumeProgram, type Running, type Setup, startProgram, SuspendError } from "./runtime/execute.js"
import { extensionGlobals } from "./runtime/extensions.js"
import { globalNames } from "./runtime/globals.js"
import { fromBytes, type Snapshot, SnapshotError, type SnapshotHeader, toBytes } from "./snapshot/snapshot.js"
export { fromBytes, scanTools, SnapshotError, SuspendError, toBytes }
export type { Snapshot, SnapshotHeader }
@@ -173,7 +173,7 @@ export type Runtime<R = never> = {
}
/**
* One run of a program on the VM. `run()` runs it in the current fiber until it ends or is suspended, and owns its host
* One run of a program. `run()` runs it in the current fiber until it ends or is suspended, and owns its host
* calls: interrupting `run()` interrupts them. A program runs once: running it again is a defect.
*/
export type Program<R = never, E = never> = {
@@ -1,5 +1,3 @@
export * as Data from "./data.js"
import type { Json } from "./snapshot/codec.js"
import type { Interpreter } from "./runtime/interpreter.js"
import { Arr, define, Obj, type Value } from "./runtime/objects.js"
+1 -1
View File
@@ -13,7 +13,7 @@ import type { Effect } from "effect"
* promise of an asynchronous global and by throwing from a synchronous one. Functions inside a global object are
* called the same way, named by their path (`fs.readFile`).
*
* Programs can also `import` or `require` a global by its Node module name; see `interpreter/modules.ts`.
* Programs can also `import` or `require` a global by its Node module name; see `runtime/modules.ts`.
*/
export type Extension<Globals extends Readonly<Record<string, unknown>> = Readonly<Record<string, unknown>>> = {
readonly name: string
@@ -1,4 +1,4 @@
import type { Diagnostic } from "../codemode.js"
import type { Diagnostic } from "./codemode.js"
import { fromJson, type Json } from "./data.js"
import { startExtensionCall } from "./runtime/extensions.js"
import type { Interpreter } from "./runtime/interpreter.js"
@@ -7,7 +7,7 @@ import { Arr, PromiseObj, type Value } from "./runtime/objects.js"
import { promiseLike } from "./runtime/promises.js"
import type { Completion } from "./runtime/signals.js"
import { type ExtensionRequest, HostCall, type HostRequest, Resolving } from "./scheduler.js"
import type { ExtensionInvocation } from "../tool-runtime.js"
import type { ExtensionInvocation } from "./tool-runtime.js"
import type { Code } from "./code.js"
import { internalCode, tryFastBoundaryOrJson, VmIntrinsic } from "./internal-prelude.js"
import { type FrameMode, Mode, type Vm } from "./vm.js"
@@ -1,4 +1,4 @@
import type { DiagnosticKind } from "../codemode.js"
import type { DiagnosticKind } from "./codemode.js"
import type { ErrorType } from "./runtime/intrinsics.js"
/** Uninitialized lexical binding sentinel (temporal dead zone). */
@@ -33,7 +33,7 @@ import {
} from "../runtime/objects.js"
import { builtinCursor, requireIteratorObject } from "../runtime/operations.js"
import { containsOpaqueReference, describeValue, isOpaque } from "../runtime/references.js"
import { compareText } from "../../tool-runtime.js"
import { compareText } from "../tool-runtime.js"
import { Invoke } from "../runtime/signals.js"
// The prelude's privileged helpers: leaf built-ins (they never call program code) bound only in the prelude's scope.
@@ -1,7 +1,7 @@
import { Cause, type Context, Effect, Exit, Fiber } from "effect"
import type { Json } from "../data.js"
import type { HostCall } from "../scheduler.js"
import { type ToolRuntime, toolExpression } from "../../tool-runtime.js"
import { type ToolRuntime, toolExpression } from "../tool-runtime.js"
import { locate } from "./errors.js"
import { PendingThrow } from "./model.js"
import type { Vm } from "../vm.js"
@@ -1,6 +1,6 @@
import type { Diagnostic } from "../../codemode.js"
import { ToolError } from "../../tool-error.js"
import { ToolRuntimeError } from "../../tool-runtime.js"
import type { Diagnostic } from "../codemode.js"
import { ToolError } from "../tool-error.js"
import { ToolRuntimeError } from "../tool-runtime.js"
import { type AstNode, formatLocation, PendingThrow, Throw, sourceLocation } from "./model.js"
import { containsRuntimeReference } from "./references.js"
import { createErrorValue, type ErrorType, isErrorType } from "./intrinsics.js"
@@ -1,8 +1,8 @@
import { parse, type Program } from "acorn"
import { Cause, Deferred, Effect, Option, Schema } from "effect"
import type { DataValue, Diagnostic, Outcome, ResolvedExecutionLimits, Result, Suspended } from "../../codemode.js"
import type { DataValue, Diagnostic, Outcome, ResolvedExecutionLimits, Result, Suspended } from "../codemode.js"
import { fromBytes, header, type Snapshot, SnapshotError, toBytes } from "../snapshot/snapshot.js"
import { ToolRuntime } from "../../tool-runtime.js"
import { ToolRuntime } from "../tool-runtime.js"
import { normalizeError } from "./errors.js"
import { createBuiltins } from "./intrinsics.js"
import { Interpreter } from "./interpreter.js"
@@ -47,17 +47,6 @@ export const finishExecution = <E, R>(outcome: Effect.Effect<Outcome, E, R>): Ef
}
})
export const executeProgram = <R>(
code: string,
prepared: ToolRuntime.Prepared<R>,
limits: ResolvedExecutionLimits,
hooks: ToolRuntime.Hooks<R>,
globals?: Globals<R>,
bindings?: Globals<R>,
lossy = false,
): Effect.Effect<Result, never, R> =>
finishExecution(startProgram({ prepared, limits, hooks, globals, lossy }, code, bindings).run())
/** A program ready to run on the VM: nothing runs until `run`. */
export const startProgram = <R>(
setup: Setup<R>,
@@ -1,6 +1,6 @@
import { Effect } from "effect"
import { type Extension, isAsync } from "../../extension.js"
import { hooked } from "../../tool-runtime.js"
import { type Extension, isAsync } from "../extension.js"
import { hooked } from "../tool-runtime.js"
import { errorMessage, errorType } from "./errors.js"
import type { Interpreter } from "./interpreter.js"
import { createErrorValue } from "./intrinsics.js"
@@ -15,7 +15,7 @@ import { headersGlobal } from "../stdlib/headers.js"
import { iteratorGlobals } from "../stdlib/iterator.js"
import { coercion } from "../stdlib/value.js"
import { base64Global, cryptoGlobal, structuredCloneGlobal } from "../stdlib/web.js"
import { ToolReference } from "../../tool-runtime.js"
import { ToolReference } from "../tool-runtime.js"
import { errorGlobal } from "./errors.js"
import { errorTypes } from "./intrinsics.js"
import { constants, constructor, methods, native, receiver } from "./native.js"
@@ -1,4 +1,4 @@
import type { ToolRuntime } from "../../tool-runtime.js"
import type { ToolRuntime } from "../tool-runtime.js"
import type { ExtensionRequest } from "../scheduler.js"
import type { HostBridge } from "../snapshot/codec.js"
import { Vm } from "../vm.js"
@@ -1,6 +1,6 @@
import type { Node } from "acorn"
import type { ErrorType } from "./intrinsics.js"
import type { DiagnosticKind } from "../../codemode.js"
import type { DiagnosticKind } from "../codemode.js"
import type { ErrorObj, Value } from "./objects.js"
/** Any parsed node; the compiler narrows on `type`, and diagnostics read `loc`. */
@@ -1,4 +1,4 @@
import { ToolReference } from "../../tool-runtime.js"
import { ToolReference } from "../tool-runtime.js"
import type { Builtins } from "./intrinsics.js"
import { checkArrayLength } from "./limits.js"
import {
@@ -1,5 +1,5 @@
import type { Expression, MemberExpression, NewExpression, Super } from "acorn"
import { ToolReference } from "../../tool-runtime.js"
import { ToolReference } from "../tool-runtime.js"
import { type AstNode, invalidData, typeError } from "./model.js"
import { checkStringLength } from "./limits.js"
import { locate } from "./errors.js"
@@ -1,4 +1,4 @@
import { ToolReference } from "../../tool-runtime.js"
import { ToolReference } from "../tool-runtime.js"
import { invalidData } from "./model.js"
import { Callable, getOwn, isRuntimeReference, Obj, Opaque, ownKeys, type Value } from "./objects.js"
+2 -2
View File
@@ -1,8 +1,8 @@
import type { AnyNode, BlockStatement, ModuleDeclaration, Pattern, Program, Statement } from "acorn"
import { Result } from "effect"
import type { Diagnostic } from "./codemode.js"
import { parseProgram } from "./vm/runtime/execute.js"
import { formatLocation } from "./vm/runtime/model.js"
import { parseProgram } from "./runtime/execute.js"
import { formatLocation } from "./runtime/model.js"
/**
* Extracts every `tools.<path>` referenced by a Code Mode program, in first-seen order. Fails on syntax errors,
@@ -1,5 +1,5 @@
import type { Json } from "./data.js"
import type { ExtensionInvocation } from "../tool-runtime.js"
import type { ExtensionInvocation } from "./tool-runtime.js"
import { locate } from "./runtime/errors.js"
import type { AstNode } from "./runtime/model.js"
import type { Callable, Cursor, GeneratorObj, Obj, PromiseObj, Value } from "./runtime/objects.js"
@@ -1,4 +1,4 @@
import packageJson from "../../package.json" with { type: "json" }
import packageJson from "../package.json" with { type: "json" }
import type { Interruption } from "./runtime/signals.js"
import { staticNames, unchanged } from "./runtime/intrinsic-names.js"
import { Fn, Obj } from "./runtime/objects.js"
@@ -39,8 +39,8 @@ import { deleted, OrderedTable } from "../runtime/table.js"
import { AsyncFromSyncIterator } from "../generators.js"
import { HostCall, Resolving } from "../scheduler.js"
import { TextDecoderObj } from "../stdlib/bytes.js"
import { ToolError, toolError } from "../../tool-error.js"
import { ToolReference, ToolRuntimeError } from "../../tool-runtime.js"
import { ToolError, toolError } from "../tool-error.js"
import { ToolReference, ToolRuntimeError } from "../tool-runtime.js"
// A graph codec for a run's state: every object reachable from the roots becomes one record, so identity, sharing,
// and cycles survive, and every value inside a record is tagged JSON. Built-ins, AST nodes, bytecode, and the VM's
@@ -57,7 +57,7 @@ export type Json = null | boolean | number | string | ReadonlyArray<Json> | { re
* - `["s", name]` an interpreter symbol;
* - `["i", name]` a built-in or host global, unchanged since the runtime was made;
* - `["a", source, index]` an AST node (a call site, an error's location);
* - `["x", data]` something the engine names itself (the VM's compiled code, by source and function index);
* - `["x", data]` something the VM names itself (compiled code, by source and function index);
* - `["r", id]` another record.
*/
export type Encoded = string | number | boolean | null | ReadonlyArray<Json>
@@ -110,9 +110,9 @@ export type EncodeEnv = {
readonly lost: Array<string>
readonly strict: boolean
readonly hosts?: HostBridge | undefined
/** More classes to save, an engine's own (the VM's frames and closures), after the built-in ones. */
/** More classes to save (the VM's frames and closures), after the built-in ones. */
readonly specs?: ReadonlyArray<ClassSpec> | undefined
/** The data an engine names an object or symbol by, saved as `["x", data]`, instead of copying it. */
/** The data the VM names an object or symbol by, saved as `["x", data]`, instead of copying it. */
readonly external?: ((value: object | symbol) => Json | undefined) | undefined
}
@@ -1,5 +1,5 @@
import type { Interpreter } from "../runtime/interpreter.js"
import type { ToolCall } from "../../tool-runtime.js"
import type { ToolCall } from "../tool-runtime.js"
import { type Encoded, type Record, SnapshotError } from "./codec.js"
export { SnapshotError }
@@ -16,7 +16,7 @@ export const fromBytes = (bytes: Uint8Array): unknown => {
}
}
/** The snapshot format this build writes and reads: the bytecode VM's (see `src/vm/snapshot.ts`). */
/** The snapshot format this build writes and reads (see `src/snapshot.ts`). */
export const FORMAT = 2
/**
@@ -23,7 +23,7 @@ import {
import { describeValue, rejectCircularInsertion } from "../runtime/references.js"
import { requireCallback, withPrimitives } from "../runtime/callback.js"
import type { Interpreter } from "../runtime/interpreter.js"
import { compareText } from "../../tool-runtime.js"
import { compareText } from "../tool-runtime.js"
const arrayLikeSource = (source: Value): { readonly length: number; readonly source: Obj } => {
// JS would treat a promise as an empty array-like; that would hide a missing `await`.
@@ -1,6 +1,6 @@
import { type Method, methods } from "../runtime/native.js"
import { entries, get, Arr, Obj, type Value } from "../runtime/objects.js"
import { ToolReference } from "../../tool-runtime.js"
import { ToolReference } from "../tool-runtime.js"
import { containsOpaqueReference } from "../runtime/references.js"
import type { Interpreter } from "../runtime/interpreter.js"
@@ -22,7 +22,7 @@ import { primitivePrototype } from "../runtime/intrinsics.js"
import { describeValue, rejectCircularInsertion } from "../runtime/references.js"
import { invokeMethod } from "../runtime/callback.js"
import type { Interpreter } from "../runtime/interpreter.js"
import { ToolReference } from "../../tool-runtime.js"
import { ToolReference } from "../tool-runtime.js"
// ToObject for enumeration.
export const enumerableSource = <R>(ctx: Interpreter<R>, label: string, value: Value, node?: AstNode): Obj => {
@@ -24,7 +24,7 @@ import {
import { createErrorValue, isErrorType } from "../runtime/intrinsics.js"
import { describeValue } from "../runtime/references.js"
import type { Interpreter } from "../runtime/interpreter.js"
import { ToolReference } from "../../tool-runtime.js"
import { ToolReference } from "../tool-runtime.js"
// WebIDL DOMString conversion: a missing argument is a TypeError, anything else stringifies. Invalid input is a
// TypeError as well; browsers throw a DOMException named InvalidCharacterError, which CodeMode does not have.
+1 -1
View File
@@ -1,5 +1,5 @@
import { Cause, Effect, Exit, Formatter, Schema } from "effect"
import type { Json } from "./vm/data.js"
import type { Json } from "./data.js"
import type { DiagnosticKind } from "./codemode.js"
import { toolError } from "./tool-error.js"
import {
@@ -1,5 +1,5 @@
import { parse, type Expression, type MemberExpression, type NewExpression } from "acorn"
import type { Diagnostic } from "../codemode.js"
import type { Diagnostic } from "./codemode.js"
import { conversions } from "./runtime/callback.js"
import { locate, materialize } from "./runtime/errors.js"
import type { Interpreter } from "./runtime/interpreter.js"
@@ -81,7 +81,7 @@ import type { HostCall, Stop } from "./scheduler.js"
import { compiledPrelude } from "./prelude/prelude.js"
import { constructRegExp } from "./stdlib/regexp.js"
import { enumerableSource } from "./stdlib/object.js"
import { ToolReference } from "../tool-runtime.js"
import { ToolReference } from "./tool-runtime.js"
import { registerAsyncIntrinsics, type SettlingTarget, VmScheduler } from "./async.js"
import type { Code } from "./code.js"
import { type CaptureOptions, captureVm, type RestoreOptions, restoreVm } from "./snapshot.js"
@@ -56,7 +56,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
@@ -69,7 +69,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
@@ -4,7 +4,7 @@ import path from "node:path"
// The snapshot build id hashes only the package's own bytecode, codecs, and built-ins, so it is the same in every
// process, whatever it imported first.
const root = path.join(import.meta.dir, "../..")
const root = path.join(import.meta.dir, "..")
const output = async (command: Array<string>) => {
const child = Bun.spawn(command, { cwd: root, stdout: "pipe", stderr: "pipe" })
@@ -15,7 +15,7 @@ const output = async (command: Array<string>) => {
describe("build id", () => {
test("the build id is the same whatever a process imported first", async () => {
const fixture = path.join(import.meta.dir, "../fixtures/build-id.ts")
const fixture = path.join(import.meta.dir, "fixtures/build-id.ts")
const ids = await Promise.all(
[[], ["--preload", "./test/test262/run.ts"], ["--preload", "./src/index.ts"]].map((preload) =>
output(["bun", ...preload, fixture]),
@@ -28,6 +28,6 @@ describe("build id", () => {
// A process with the test262 harness loaded saves the run; the child that resumes it has not loaded it.
test("a run saved with the test262 harness loaded resumes in another process", async () => {
await output(["bun", "test", "--preload", "./test/test262/run.ts", "test/vm/suspend.test.ts", "-t", "another process"])
await output(["bun", "test", "--preload", "./test/test262/run.ts", "test/suspend.test.ts", "-t", "another process"])
}, 15_000)
})
@@ -1,13 +1,13 @@
import { describe, expect, test } from "bun:test"
import { createBuiltins } from "../../src/vm/runtime/intrinsics.js"
import { Interpreter } from "../../src/vm/runtime/interpreter.js"
import type { ToolRuntime } from "../../src/tool-runtime.js"
import { CodeBuilder, CodeSizeOverflow, hashOf } from "../../src/vm/code.js"
import { compileSource } from "../../src/vm/compiler/compile.js"
import { CodeFlags, EMPTY_CELLS, Op, packWord, PutKind } from "../../src/vm/opcodes.js"
import { Mode } from "../../src/vm/vm.js"
import { parseVmSource, type RunOptions, runProgram } from "./run.js"
import { verifyAll, verifyCode } from "./verify.js"
import { createBuiltins } from "../src/runtime/intrinsics.js"
import { Interpreter } from "../src/runtime/interpreter.js"
import type { ToolRuntime } from "../src/tool-runtime.js"
import { CodeBuilder, CodeSizeOverflow, hashOf } from "../src/code.js"
import { compileSource } from "../src/compiler/compile.js"
import { CodeFlags, EMPTY_CELLS, Op, packWord, PutKind } from "../src/opcodes.js"
import { Mode } from "../src/vm.js"
import { parseVmSource, type RunOptions, runProgram } from "./run-program.js"
import { verifyAll, verifyCode } from "./verify-bytecode.js"
const emptyTools = {
calls: [],
@@ -534,8 +534,9 @@ describe("per-function wide jumps past 65,535 words and snapshot round-trips", (
mainRes: [21, 10, expectedPadA * 2, 378, 13, 7, "late-reject", "ok"],
gSteps: [1, 10, 20, expectedGenPad + 4, 18],
})
})
}, 15_000)
// Compiling a >65,535-word program and round-tripping 8 full snapshots takes ~8-9s on loaded Windows runners.
test("top-level script >65,535 words runs correctly and survives snapshot round-trips at pc > 65,535", () => {
const src = `
function hoistedTop(n) { return n + 42 }
@@ -602,5 +603,5 @@ describe("per-function wide jumps past 65,535 words and snapshot round-trips", (
expect(roundTripped).toEqual({ ok: true, value: expected, toolCalls: [] })
expect(trips).toBeGreaterThan(2)
expect(sawHighPc).toBe(true)
})
}, 15_000)
})
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Effect, Schema } from "effect"
import { CodeMode, Tool } from "./lib/index.js"
import { CodeMode, Tool } from "../src/index.js"
// Callback acceptance is one gate shared by array methods, sort, string replacers,
// Array.from mappers, Map/Set/URLSearchParams forEach, and promise reactions:
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Cause, Effect, Schema } from "effect"
import { CodeMode, Extension, Tool, toolError } from "./lib/index.js"
import { CodeMode, Extension, Tool, toolError } from "../src/index.js"
const run = (tool: Tool.Tool<never>) =>
Effect.runPromise(CodeMode.make({ tools: { host: { call: tool } } }).execute("return await tools.host.call({})"))
@@ -14,7 +14,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
+1 -1
View File
@@ -44,7 +44,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Effect, Schema } from "effect"
import { CodeMode, Tool } from "./lib/index.js"
import { CodeMode, Tool } from "../src/index.js"
// Key enumeration: Object.keys and for...in share one surface over plain objects, arrays
// (index strings), and tool references (namespace/tool names from the supplied tools), so a
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
// One failure is one program error object, and rethrowing it keeps the diagnostic it started with.
const run = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools: {} }))
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Context, Effect, Schema } from "effect"
import { CodeMode, Extension, Tool } from "./lib/index.js"
import { CodeMode, Extension, Tool } from "../src/index.js"
const held: Array<unknown> = []
const config = { retries: 3, nested: { deep: true } }
+3 -3
View File
@@ -1,8 +1,8 @@
// Prints this process's snapshot build id, from a runtime made with as few of the package's modules as it takes, so
// the id can be compared across entry points (`bun --preload <module> build-id.ts`).
import { Interpreter } from "../../src/vm/runtime/interpreter.js"
import { createBuiltins } from "../../src/vm/runtime/intrinsics.js"
import { buildId } from "../../src/vm/snapshot.js"
import { Interpreter } from "../../src/runtime/interpreter.js"
import { createBuiltins } from "../../src/runtime/intrinsics.js"
import { buildId } from "../../src/snapshot.js"
import { ToolRuntime } from "../../src/tool-runtime.js"
new Interpreter({ tools: ToolRuntime.make(ToolRuntime.prepare({}), undefined, {}), builtins: createBuiltins() })
+3 -3
View File
@@ -2,9 +2,9 @@
// prelude frame was on the stack.
import { Effect } from "effect"
import { CodeMode } from "../../src/index.js"
import { CodeFlags } from "../../src/vm/opcodes.js"
import { setRoundTrip } from "../../src/vm/runtime/execute.js"
import { Vm } from "../../src/vm/vm.js"
import { CodeFlags } from "../../src/opcodes.js"
import { setRoundTrip } from "../../src/runtime/execute.js"
import { Vm } from "../../src/vm.js"
setRoundTrip(true)
@@ -14,7 +14,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const execute = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools: {} }))
@@ -12,7 +12,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const execute = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools: {} }))
@@ -30,7 +30,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const execute = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools: {} }))
@@ -28,7 +28,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
@@ -12,7 +12,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const execute = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools: {} }))
@@ -26,7 +26,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
-1
View File
@@ -1 +0,0 @@
export * from "../../src/index.js"
+4 -4
View File
@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test"
import { Cause, Deferred, Effect, Exit, Fiber, Schema } from "effect"
import { CodeMode, Tool } from "./lib/index.js"
import { MAX_ARRAY_LENGTH, MAX_PENDING_PROMISES, MAX_STRING_LENGTH } from "../src/vm/runtime/limits.js"
import { CodeMode, Tool } from "../src/index.js"
import { MAX_ARRAY_LENGTH, MAX_PENDING_PROMISES, MAX_STRING_LENGTH } from "../src/runtime/limits.js"
const runtime = CodeMode.make({ tools: {} })
@@ -40,7 +40,7 @@ describe("one built-in cannot build an unbounded value", () => {
})
// split and matchAll share the same result check but must build ten million items first to reach it,
// which is too slow for CI.
// which is too slow for CI; flat() over MAX_ARRAY_LENGTH (10M) elements still needs a 15s budget on Windows.
test("arrays: constructor, length, Array.from, concat, flat", async () => {
expect(await value(`return Array(${A}).length`)).toBe(A)
for (const code of [
@@ -54,7 +54,7 @@ describe("one built-in cannot build an unbounded value", () => {
"RangeError: Invalid array length",
)
}
})
}, 15_000)
test("promises: too many pending at once, while settled ones do not count", async () => {
const n = MAX_PENDING_PROMISES
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Effect, Schema } from "effect"
import { CodeMode, Tool } from "./lib/index.js"
import { CodeMode, Tool } from "../src/index.js"
// `new` is supported syntax; only the callee decides whether construction succeeds. A callee without
// construction support is a TypeError naming it, like JS, rather than an unsupported-syntax diagnostic
@@ -37,7 +37,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
+1 -1
View File
@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test"
import { Effect, Layer, Option } from "effect"
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"
import { CodeMode, OpenAPI, Tool } from "./lib/index.js"
import { CodeMode, OpenAPI, Tool } from "../src/index.js"
import { inputTypeScript, outputTypeScript } from "../src/tool-schema.js"
const baseUrl = "http://localhost:4096"
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
// Runs a CodeMode program with no host tools and returns the CodeMode.Result. These tests pin the
// JS-parity behaviors for the "99% of ordinary defensive JavaScript just works" goal: cases where
+2 -2
View File
@@ -1,5 +1,5 @@
import { setRoundTrip } from "../src/vm/runtime/execute.js"
import { nativeRule } from "../src/vm/runtime/objects.js"
import { setRoundTrip } from "../src/runtime/execute.js"
import { nativeRule } from "../src/runtime/objects.js"
// Every test run checks that natives are made only with the intrinsics.
nativeRule.check = true
@@ -1,10 +1,10 @@
import { describe, expect, test } from "bun:test"
import { compileBuiltins } from "../../script/prelude.js"
import saved from "../../src/vm/prelude/bytecode.json" with { type: "json" }
import { compiledPrelude } from "../../src/vm/prelude/prelude.js"
import { hashOf } from "../../src/vm/code.js"
import { getInternalPrelude } from "../../src/vm/internal-prelude.js"
import { moduleUrl, runProgram } from "./run.js"
import { compileBuiltins } from "../script/prelude.js"
import saved from "../src/prelude/bytecode.json" with { type: "json" }
import { compiledPrelude } from "../src/prelude/prelude.js"
import { hashOf } from "../src/code.js"
import { getInternalPrelude } from "../src/internal-prelude.js"
import { moduleUrl, runProgram } from "./run-program.js"
// The built-in sources are compiled ahead of time (script/prelude.ts); a process loads that bytecode instead.
@@ -25,8 +25,8 @@ describe("precompiled built-ins", () => {
}
// A fresh process runs a program that calls into the prelude, and its source is still unparsed.
const script = `
import { runProgram } from ${moduleUrl("run.js")}
import { compiledPrelude } from ${moduleUrl("../../src/vm/prelude/prelude.js")}
import { runProgram } from ${moduleUrl("run-program.js")}
import { compiledPrelude } from ${moduleUrl("../src/prelude/prelude.js")}
const result = runProgram("return [3, 1, 2].map((x) => x * 2).sort((a, b) => a - b)")
console.log(JSON.stringify([result.ok && result.value, typeof compiledPrelude().codes[0].sourceObj.tree]))`
const child = Bun.spawn(["bun", "-e", script], { stdout: "pipe", stderr: "inherit" })
@@ -14,7 +14,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const execute = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools: {}, limits: { timeoutMs: 1_000 } }))
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Cause, Deferred, Effect, Exit, Schema } from "effect"
import { CodeMode, Tool, toolError } from "./lib/index.js"
import { CodeMode, Tool, toolError } from "../src/index.js"
// Wave 5 acceptance suite: first-class promise values. Un-awaited tool calls start eagerly as
// host work, `await` settles them, Promise.all/allSettled/race/resolve/reject are
@@ -41,7 +41,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
@@ -1,16 +1,16 @@
import { describe, expect, test } from "bun:test"
import path from "node:path"
import { parseProgram } from "../../src/vm/runtime/execute.js"
import { Interpreter } from "../../src/vm/runtime/interpreter.js"
import { createBuiltins } from "../../src/vm/runtime/intrinsics.js"
import { getOwn, Native, nativeRule, Obj, remove, set } from "../../src/vm/runtime/objects.js"
import { CodeMode } from "../lib/index.js"
import { decode, encode } from "../../src/vm/snapshot/codec.js"
import { Lost, PendingThrow } from "../../src/vm/runtime/model.js"
import { normalizeError } from "../../src/vm/runtime/errors.js"
import { parseProgram } from "../src/runtime/execute.js"
import { Interpreter } from "../src/runtime/interpreter.js"
import { createBuiltins } from "../src/runtime/intrinsics.js"
import { getOwn, Native, nativeRule, Obj, remove, set } from "../src/runtime/objects.js"
import { CodeMode } from "../src/index.js"
import { decode, encode } from "../src/snapshot/codec.js"
import { Lost, PendingThrow } from "../src/runtime/model.js"
import { normalizeError } from "../src/runtime/errors.js"
import { Effect } from "effect"
import { ToolRuntime } from "../../src/tool-runtime.js"
import { nodeId, sourceOf } from "../../src/vm/runtime/source.js"
import { ToolRuntime } from "../src/tool-runtime.js"
import { nodeId, sourceOf } from "../src/runtime/source.js"
// Groundwork for suspending and resuming a run: everything the state refers to has a stable name.
@@ -281,7 +281,7 @@ describe("heap as data", () => {
describe("prelude", () => {
test("a run saved and restored while prelude built-ins run continues in their frames", async () => {
const child = Bun.spawn(["bun", path.join(import.meta.dir, "../fixtures/prelude-roundtrip.ts")], {
const child = Bun.spawn(["bun", path.join(import.meta.dir, "fixtures/prelude-roundtrip.ts")], {
stdout: "pipe",
stderr: "inherit",
})
@@ -320,7 +320,6 @@ describe("lost values", () => {
unchanged: () => false,
anchorOf: () => undefined,
nodeId: () => undefined,
astArray: () => undefined,
lost,
strict: false,
})
@@ -328,7 +327,6 @@ describe("lost values", () => {
named: () => undefined,
anchor: () => undefined,
node: () => undefined,
astArray: () => undefined,
interruption: () => {
throw new Error("no interruption is saved")
},
@@ -1,18 +1,19 @@
import path from "node:path"
import { pathToFileURL } from "node:url"
import { parse, type Program } from "acorn"
import type { DataValue, Diagnostic, Result } from "../../src/codemode.js"
import { normalizeError } from "../../src/vm/runtime/errors.js"
import { createBuiltins } from "../../src/vm/runtime/intrinsics.js"
import { Interpreter } from "../../src/vm/runtime/interpreter.js"
import { formatLocation, PendingThrow, sourceLocation } from "../../src/vm/runtime/model.js"
import type { Value } from "../../src/vm/runtime/objects.js"
import { Interruption } from "../../src/vm/runtime/signals.js"
import { Source } from "../../src/vm/runtime/source.js"
import type { ToolRuntime } from "../../src/tool-runtime.js"
import type { Snapshot } from "../../src/vm/snapshot/snapshot.js"
import type { Program } from "acorn"
import type { DataValue, Diagnostic, Result } from "../src/codemode.js"
import { normalizeError } from "../src/runtime/errors.js"
import { parseProgram } from "../src/runtime/execute.js"
import { createBuiltins } from "../src/runtime/intrinsics.js"
import { Interpreter } from "../src/runtime/interpreter.js"
import { PendingThrow } from "../src/runtime/model.js"
import type { Value } from "../src/runtime/objects.js"
import { Interruption } from "../src/runtime/signals.js"
import type { Source } from "../src/runtime/source.js"
import type { ToolRuntime } from "../src/tool-runtime.js"
import type { Snapshot } from "../src/snapshot/snapshot.js"
/** A quoted `file:` URL for a module relative to `test/vm`, to import from a script run with `bun -e` on any platform. */
/** A quoted `file:` URL for a module relative to `test`, to import from a script run with `bun -e` on any platform. */
export const moduleUrl = (relative: string) =>
JSON.stringify(pathToFileURL(path.join(import.meta.dir, relative)).href)
@@ -36,7 +37,6 @@ export type RunOptions = {
export const parseVmSource = (
code: string,
id = "main",
):
| { readonly ok: true; readonly program: Program; readonly source: Source }
| { readonly ok: false; readonly error: Diagnostic } => {
@@ -44,38 +44,8 @@ export const parseVmSource = (
return { ok: false, error: { kind: "ParseError", message: "Code cannot be empty." } }
}
try {
const program = parse(code, {
ecmaVersion: "latest",
sourceType: "script",
allowReturnOutsideFunction: true,
allowAwaitOutsideFunction: true,
allowImportExportEverywhere: true,
locations: true,
})
const exported = program.body.find((statement) => statement.type.startsWith("Export"))
if (exported) {
const location = sourceLocation(exported)
return {
ok: false,
error: {
kind: "ParseError",
message: `SyntaxError: Exports aren't available. Return the result instead.${formatLocation(location)}`,
location,
},
}
}
return { ok: true, program, source: new Source(id, code, program) }
return parseProgram(code)
} catch (error) {
if (error instanceof SyntaxError && "loc" in error) {
const loc = (error as SyntaxError & { readonly loc: { readonly line: number; readonly column: number } }).loc
const location = { line: loc.line, column: loc.column + 1 }
const reason = error.message.replace(/ \(\d+:\d+\)$/, "")
const text = `${reason.endsWith(".") ? reason : `${reason}.`} Code must be plain JavaScript written as a program body.`
return {
ok: false,
error: { kind: "ParseError", message: `SyntaxError: ${text}${formatLocation(location)}`, location },
}
}
if (error instanceof RangeError) {
return { ok: false, error: normalizeError(error) }
}
@@ -173,49 +143,3 @@ export const runProgram = (code: string, options?: RunOptions): Result => {
}
}
}
const WARMUP_SCRIPT = `
function fib(n) { return n < 2 ? n : fib(n - 1) + fib(n - 2) }
let sum = 0
for (let i = 0; i < 400; i++) sum = (sum + i * 3) % 10007
const closures = []
for (let i = 0; i < 16; i++) {
let s = "v" + i
let o = { x: i, y: s }
closures.push(() => o.x + s.length)
}
for (const fn of closures) sum += fn()
const arr = Array.from({ length: 64 }, (_, i) => (i * 17) % 64)
const mapped = arr.map((x) => x * 2).filter((x) => x % 3 !== 0)
sum += mapped.reduce((a, b) => a + b, 0)
const sorted = mapped.slice(0, 32).sort((a, b) => a - b)
sum += sorted.length + fib(8)
outer: for (let i = 0; i < 3; i++) {
try {
for (const x of [1, 2, 3]) {
try {
if (x === 2) continue
if (i === 1 && x === 3) break outer
sum += x
} finally { sum += 1 }
}
} catch (e) { sum -= 1 } finally { sum += 2 }
}
const { a = 1, b: [c = 2, ...rest] = [3, 4, 5] } = { b: [10, 20, 30] }
return sum + a + c + rest.length
`
let warmed = false
/**
* Startup training program (J3): exercises the VM dispatch loop with diverse types
* (numbers, strings, objects, arrays, closures, cells, try/catch/finally, iteration, destructuring)
* so JSC's DFG/FTL tiers up `Vm.prototype.exec` with polymorphic value profiles.
*/
export const warmupVm = (rounds = 8): void => {
if (warmed) return
warmed = true
for (let i = 0; i < rounds; i++) {
runProgram(WARMUP_SCRIPT)
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Effect, Result } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
describe("CodeMode.scanTools", () => {
test("extracts dotted, bracketed and nested static tool calls in first-seen order", () => {
@@ -71,7 +71,7 @@
*/
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"
import { CodeMode } from "../src/index.js"
const value = async (code: string) => {
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))

Some files were not shown because too many files have changed in this diff Show More