A multicore-native TypeScript runtime. Research prototype.
Make parallel execution in TypeScript as natural as asynchronous execution became with async/await.
const results = await parallel.map(records, processRecord);
// runtime handles: partitioning, work stealing, core placement, isolationStatus: M5 "Typed IR + Native Compilation" complete — hand-rolled
ARM64 two-tier JIT; annotated numeric loops run unboxed at Node speed and
compose with the multicore scheduler. M4 "GC + Memory Runtime" complete — generational GC (O(young)
minor pauses), memory limits, runtime telemetry, and a live tscore top
monitor (M1 Multicore Proof, M2 Actors, M3 incl. async/await before it) — custom bytecode interpreter (register machine, per-realm
isolated heaps, mark-sweep GC), work-stealing scheduler, actor runtime.
ARM64 macOS/Linux.
| workload | 2 cores | 4 cores | 8 cores | target (2/4/8) |
|---|---|---|---|---|
| primes | 1.88× | 3.73× | 6.79× | ≥1.7 / 3.0 / 5.5 |
| fnv | 1.84× | 3.65× | 5.69× | ✓ |
| mandelbrot | 1.97× | 3.71× | 6.43× | ✓ |
Output is bit-identical at every worker count (clone-at-spawn semantics). Single-core absolute speed vs V8 is an explicit non-goal at M1 — the claim under test is the multicore programming model; see benchmark methodology.
const counter = actor(() => {
let n = 0;
return {
add: (msg) => { n = n + msg.by; },
get: () => n,
};
});
counter.post({ type: "add", by: 2 }); // fire-and-forget
counter.send({ type: "get" }); // blocking replyEach actor owns an isolated realm (heap + GC) on a dedicated thread with a bounded mailbox; messages cross realms as structured clones, handler failures stay inside the actor. See actors spec.
const hashes = await parallel.map(seeds, hashStream); // spec API, for real
const reply = await db.send({ type: "get-user", id: 42 });
const ch = Channel.create({ capacity: 1024 });
await Channel.send(ch, event); // backpressure: parks when full
await sleep(100);Top-level await, async functions/arrows, async actor handlers, channels portable across realms. See async spec.
const total = task.scope((scope) => {
const a = scope.spawn(() => heavyA());
const b = scope.spawn(() => heavyB());
return a.join() + b.join(); // scope exits only when ALL children did
}, { timeout: 5000 });Child errors cancel siblings and propagate to the parent; cancellation is
cooperative at interpreter safepoints (handle.cancel(), scope timeouts,
Runtime.checkCancellation()), and cascades through nested scopes. See
structured concurrency spec.
Sticky generational mark-sweep: minor pauses are O(young) and independent
of heap size (measured ~2ms minors vs ~10ms majors on a 2M-object live
set). runtime.gc.stats(), --max-heap limits with clean OOM errors,
tscore run --stats, and tscore top — a live table of every running
tscore process. See GC spec.
Hot functions compile to hand-rolled ARM64. The optimizing compiler uses your TS
annotations: function work(n: number) runs unboxed in FP registers —
measured 7× over the interpreter, matching Node on numeric loops, and it
multiplies with parallel.map (mandelbrot ×8 cores: 17ms vs Node's 22ms
single-core). See M5 spec.
cargo run --release -p tscore -- run examples/parallel_primes.tsA real workload, same file on both engines (Monte Carlo portfolio VaR, 400k scenarios):
cargo run --release -p tscore -- run examples/portfolio_var.ts --workers 8 # 0.70s on M5 Max
node examples/portfolio_var.ts # 21.8s./benchmarks/run.shUser guide site (Fumadocs): cd packages/docs && npm install && npm run dev, then open http://localhost:3000/docs.
- Architecture overview
- M1 language subset
- Bytecode
- Concurrency semantics
- Memory & ownership
- Benchmark methodology
AGPL-3.0-only, with additional terms under section 7 reserving the project's name and branding. Commercial use is fine; proprietary forks are not, and a fork you distribute must be renamed. See LICENSE.md.