···17171818- [Why Ant?](#why-ant)
1919- [Installation](#installation)
2020+- [Benchmarks](#benchmarks)
2021- [Spec conformance](#spec-conformance)
2122- [Building Ant](#building-ant)
2223- [Security](#security)
···25262627## Why Ant?
27282828-| | Ant | Node | Bun | Deno |
2929-| ------------------- | ----------- | --------- | -------- | --------- |
3030-| Binary size | **~9 MB** | ~120 MB | ~60 MB | ~90 MB |
3131-| Cold start | **~3-5 ms** | ~30-50 ms | ~5-10 ms | ~20-30 ms |
3232-| Engine | Ant Silver | V8 | JSC | V8 |
3333-| JIT | โ | โ | โ | โ |
3434-| WinterTC conformant | โ | partial | โ | โ |
2929+| | Ant | Node | Bun | Deno |
3030+| ------------------- | ---------- | ------- | ------ | ------ |
3131+| Binary size | **~9 MB** | ~120 MB | ~60 MB | ~90 MB |
3232+| Cold start | **~5 ms** | ~31 ms | ~13 ms | ~25 ms |
3333+| Engine | Ant Silver | V8 | JSC | V8 |
3434+| JIT | โ | โ | โ | โ |
3535+| WinterTC conformant | โ | partial | โ | โ |
35363637Ant is designed for environments where size and startup time matter: serverless functions, edge computing, embedded systems, CLI tools, and anywhere you'd want JavaScript but can't afford a 50MB+ runtime.
3738···5455| js-zoo (ES2016+) | ~86% | |
5556| js-zoo (overall) | **88%** | 1211/1368 passing |
5657| test262 | ~50% | Improving, focus is on real-world coverage |
5858+5959+## Benchmarks
6060+6161+### Cold start
6262+6363+Measures the time to import [Hono](https://hono.dev), register routes, and exit. Each runtime loads he same `bench-coldstart.js` script from `examples/npm/hono/` that creates a Hono app with two routes, prints "ready", and calls `process.exit(0)`. No HTTP server is actually started, this isolates module resolution and initialization overhead.
6464+6565+Measured with hyperfine (10 warmup runs, 100 timed runs):
6666+6767+```bash
6868+hyperfine --warmup 10 --runs 100 \
6969+ 'ant examples/npm/hono/bench-coldstart.js' \
7070+ 'node examples/npm/hono/bench-coldstart.js' \
7171+ 'bun examples/npm/hono/bench-coldstart.js' \
7272+ 'deno run --allow-read --allow-env examples/npm/hono/bench-coldstart.js'
7373+```
7474+7575+| Runtime | Mean | Min | Max | Relative |
7676+| ------- | ---------- | ------- | -------- | ------------ |
7777+| **Ant** | **5.7 ms** | 5.0 ms | 7.3 ms | **1.00** |
7878+| Bun | 12.8 ms | 11.6 ms | 16.4 ms | 2.24ร slower |
7979+| Deno | 24.8 ms | 22.2 ms | 29.4 ms | 4.32ร slower |
8080+| Node | 31.1 ms | 27.1 ms | 151.7 ms | 5.41ร slower |
8181+8282+<details>
8383+<summary>Environment</summary>
8484+8585+| Detail | Value |
8686+| -------- | --------------------------------- |
8787+| Hardware | Apple M4 Pro, 24 GB RAM, 14 cores |
8888+| OS | macOS 15.7.5 (arm64) |
8989+| Ant | 0.9.1 |
9090+| Node | 25.9.0 |
9191+| Bun | 1.3.13 |
9292+| Deno | 2.7.12 |
9393+9494+</details>
57955896## Building Ant
5997
+10
examples/npm/hono/bench-coldstart.js
···11+import { Hono } from 'hono';
22+33+const app = new Hono();
44+55+app.get('/', c => c.text('hello hono!'));
66+app.get('/json', c => c.json({ message: 'hello' }));
77+88+// signal that startup is complete, then exit
99+console.log('ready');
1010+process.exit(0);
+52-28
include/arena.h
···1616#include <errno.h>
1717#endif
18181919-#define ANT_ARENA_MIN (32 * 1024)
2020-#define ANT_ARENA_MAX (64ULL * 1024 * 1024 * 1024)
2121-2222-#define ANT_ARENA_THRESHOLD (256ULL * 1024 * 1024)
2323-#define ARENA_GROW_INCREMENT (8ULL * 1024 * 1024)
2424-#define ANT_CLOSURE_ARENA_MAX (2ULL * 1024 * 1024 * 1024)
2525-2626-// preferred base for mmap on platforms where the kernel may hand out
2727-// addresses above the 47-bit NaN-boxing ceiling. We pick 0x100000000
2828-// inside the 47-bit range and above the typical text/data segments.
2929-#define ANT_MMAP_HINT ((void *)0x100000000ULL)
3030-3119typedef struct {
3220 uint8_t *base;
3321 size_t committed;
···3927 void *free_list;
4028} ant_fixed_arena_t;
41293030+#define ANT_ARENA_MIN (32 * 1024)
3131+#define ANT_ARENA_MAX (64ULL * 1024 * 1024 * 1024)
3232+3333+#define ANT_ARENA_THRESHOLD (256ULL * 1024 * 1024)
3434+#define ARENA_GROW_INCREMENT (8ULL * 1024 * 1024)
3535+#define ANT_CLOSURE_ARENA_MAX (2ULL * 1024 * 1024 * 1024)
3636+3737+// the kernel can hand out mmap addresses above the 47-bit NaN-boxing ceiling.
3838+// ant_mmap_low() probes the low VA range with MAP_FIXED_NOREPLACE
3939+// before falling back to an unpinned mmap. only needed on Linux
4040+#ifdef __linux__s
4141+4242+#ifndef MAP_FIXED_NOREPLACE
4343+#define MAP_FIXED_NOREPLACE 0x100000
4444+#endif
4545+4646+#define ANT_MMAP_LOW_START 0x10000000000ULL
4747+#define ANT_MMAP_LOW_END 0x7F0000000000ULL
4848+#define ANT_MMAP_LOW_STEP 0x10000000000ULL
4949+5050+static inline void *ant_mmap_low(size_t size, int prot, int extra_flags) {
5151+ int flags = MAP_PRIVATE | MAP_ANON | extra_flags;
5252+5353+ for (
5454+ uintptr_t addr = ANT_MMAP_LOW_START;
5555+ addr + size <= ANT_MMAP_LOW_END;
5656+ addr += ANT_MMAP_LOW_STEP
5757+ ) {
5858+ void *p = mmap((void *)addr, size, prot, flags | MAP_FIXED_NOREPLACE, -1, 0);
5959+ if (p != MAP_FAILED) return p;
6060+ }
6161+6262+ void *p = mmap(NULL, size, prot, flags, -1, 0);
6363+ return (p == MAP_FAILED) ? NULL : p;
6464+}
6565+6666+#endif
6767+4268#ifdef _WIN32
43694470static inline void *ant_arena_reserve(size_t max_size) {
···7096#else
71977298static inline void *ant_arena_reserve(size_t max_size) {
7373- void *p = mmap(ANT_MMAP_HINT, max_size, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
7474- if (p == MAP_FAILED) return NULL;
7575- if ((uintptr_t)p >> 47) {
7676- munmap(p, max_size);
7777- p = mmap(NULL, max_size, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
7878- if (p == MAP_FAILED) return NULL;
7979- }
8080- return mantissa_chk(p, "mmap");
9999+#ifdef __linux__
100100+ void *p = ant_mmap_low(max_size, PROT_NONE, 0);
101101+#else
102102+ void *p = mmap(NULL, max_size, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
103103+ if (p == MAP_FAILED) p = NULL;
104104+#endif
105105+ return p ? mantissa_chk(p, "mmap") : NULL;
81106}
8210783108static inline int ant_arena_commit(void *base, size_t old_size, size_t new_size) {
···105130}
106131107132static inline void *ant_os_alloc(size_t size) {
108108- void *p = mmap(ANT_MMAP_HINT, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
109109- if (p == MAP_FAILED) return NULL;
110110- if ((uintptr_t)p >> 47) {
111111- munmap(p, size);
112112- p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
113113- if (p == MAP_FAILED) return NULL;
114114- }
115115- return mantissa_chk(p, "mmap");
133133+#ifdef __linux__
134134+ void *p = ant_mmap_low(size, PROT_READ | PROT_WRITE, 0);
135135+#else
136136+ void *p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
137137+ if (p == MAP_FAILED) p = NULL;
138138+#endif
139139+ return p ? mantissa_chk(p, "mmap") : NULL;
116140}
117141118142static inline int ant_arena_decommit(void *base, size_t old_size, size_t new_size) {