Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ All notable changes to EigenScript are documented here.

## [Unreleased]

### Added
- **`--bundle`: single-file distribution (#413).** `eigenscript
--bundle app.eigs out [--with-tape tape]` copies the runtime binary
and appends an archive — the script, the adjacent `eigs_modules/`
tree, the stdlib `lib/` modules, and optionally a trace tape — into
one executable that runs on a machine with no EigenScript checkout.
Startup detects the trailer magic on the binary's own tail, extracts
to a tempdir (removed at exit), and rewrites argv to run the
extracted script, so every existing resolution rule just works with
no VFS or resolver changes. With a tape attached, `out --replay` is
an **executable bug report**: it replays byte-identically, serving
every nondet input from the tape — and the #411 version contract is
pinned by construction, since the bundle carries the exact binary
that recorded the tape. In bundle mode all arguments belong to the
script (only `--replay` is intercepted); torn archives refuse with
exit 3; re-bundling from a bundle copies only the runtime image.
`tests/test_bundle.sh` (suite [42g], 8 checks) covers script+modules+
stdlib from a bare cwd, arg passthrough, byte-identical replay of the
recorded nondet, no-tape/torn refusals, and re-bundle size stability.
docs/BUNDLE.md documents the format and the AOT (ouroboros) tier as
the native-speed variant of the same single-file story.

### Fixed
- **Builtin calls with a single list argument were O(len(list)) (#546).**
The direct-borrow heuristic that runs after every builtin call scanned
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ LDFLAGS := -pie -Wl,-z,relro,-z,now -lm -lpthread
endif

SRC_DIR := src
SOURCES := $(SRC_DIR)/eigenscript.c $(SRC_DIR)/lexer.c $(SRC_DIR)/parser.c $(SRC_DIR)/builtins.c $(SRC_DIR)/builtins_tensor.c $(SRC_DIR)/hash.c $(SRC_DIR)/arena.c $(SRC_DIR)/state.c $(SRC_DIR)/strbuf.c $(SRC_DIR)/ext_store.c $(SRC_DIR)/fmt.c $(SRC_DIR)/lint.c $(SRC_DIR)/chunk.c $(SRC_DIR)/compiler.c $(SRC_DIR)/vm.c $(SRC_DIR)/jit.c $(SRC_DIR)/trace.c $(SRC_DIR)/eigs_embed.c $(SRC_DIR)/repl.c $(SRC_DIR)/step.c $(SRC_DIR)/main.c
SOURCES := $(SRC_DIR)/eigenscript.c $(SRC_DIR)/lexer.c $(SRC_DIR)/parser.c $(SRC_DIR)/builtins.c $(SRC_DIR)/builtins_tensor.c $(SRC_DIR)/hash.c $(SRC_DIR)/arena.c $(SRC_DIR)/state.c $(SRC_DIR)/strbuf.c $(SRC_DIR)/ext_store.c $(SRC_DIR)/fmt.c $(SRC_DIR)/lint.c $(SRC_DIR)/chunk.c $(SRC_DIR)/compiler.c $(SRC_DIR)/vm.c $(SRC_DIR)/jit.c $(SRC_DIR)/trace.c $(SRC_DIR)/eigs_embed.c $(SRC_DIR)/repl.c $(SRC_DIR)/step.c $(SRC_DIR)/bundle.c $(SRC_DIR)/main.c
BINARY := $(SRC_DIR)/eigenscript

# CLI-only translation units: linked into the binary, never into the
# runtime library (repl.c pulls termios/isatty — banned in the
# freestanding/embed profile, same footing as main.c; step.c is the
# --step tape-stepper, stdio+isatty, same footing).
CLI_ONLY := $(SRC_DIR)/main.c $(SRC_DIR)/repl.c $(SRC_DIR)/step.c
CLI_ONLY := $(SRC_DIR)/main.c $(SRC_DIR)/repl.c $(SRC_DIR)/step.c $(SRC_DIR)/bundle.c

FULL_SOURCES := $(SOURCES) $(SRC_DIR)/ext_http.c $(SRC_DIR)/ext_db.c \
$(SRC_DIR)/model_io.c $(SRC_DIR)/model_infer.c $(SRC_DIR)/model_train.c
Expand Down
71 changes: 71 additions & 0 deletions docs/BUNDLE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# `--bundle` — single-file distribution (#413)

```
eigenscript --bundle app.eigs out [--with-tape tape]
./out [args...] # runs anywhere — no EigenScript checkout needed
./out --replay [args...] # with a tape attached: re-run it EXACTLY
```

`--bundle` copies the running runtime binary and appends an archive:
the script, the adjacent `eigs_modules/` tree (the whole tree —
dependency analysis can't see dynamic imports, and the superset is
cheap), the stdlib `lib/` modules (so `import json` works on a bare
machine), and optionally a trace tape. The result is one executable.

Demo:

```
eigenscript --bundle examples/data_pipeline.eigs pipeline
./pipeline
```

## The tape-carrying variant: an executable bug report

Record a failing run, then ship binary + failure as one file:

```
EIGS_TRACE=fail.tape eigenscript app.eigs # reproduce once, recorded
eigenscript --bundle app.eigs repro --with-tape fail.tape
./repro --replay # replays byte-identically,
# no network, no env, no luck
```

Every nondeterministic input (random, time, env, file reads, HTTP —
see docs/TRACE.md) is served from the tape. The #411 version contract
is pinned *by construction*: the tape names the version that recorded
it, and the bundle carries that exact binary — the pair can't drift.

## How it runs

Startup checks the binary's own tail for the archive trailer. A bundle
extracts to a `mkdtemp` directory and rewrites `argv` to run the
extracted script; the script's directory is the resolve base, so every
existing resolution rule just works (`import name` →
`eigs_modules/name/name.eigs`, `import json` → `lib/json.eigs`). The
tempdir is removed at exit. In bundle mode all arguments belong to the
bundled script — only `--replay` (first argument) is intercepted.

## Format

Appended after the runtime image (little-endian, x86-64 — the same
assumption the JIT makes):

```
entry*: [u32 path_len][path][u64 size][bytes] (first entry = the script)
trailer: [u64 archive_off][u32 count][u32 fmt=1]["EIGSBNDL"]
```

A torn archive (misparsing entry headers) refuses with exit 3 rather
than running garbage. There are no per-entry checksums — a flipped bit
inside one file's *data* is out of scope for fmt 1; the attached tape
still self-identifies via its own `V` header. Re-bundling from a bundle
copies only the runtime image, so archives don't accrete.

## The native-speed tier of the same story

`--bundle` ships at VM speed today. The **AOT compiler in the sibling
`ouroboros` repo** (transpile-to-C, the VM as its byte-exact oracle) is
the native-speed tier of the same deliverable: compile the script to a
real native binary instead of carrying the interpreter. Same
single-file story, ~10–60× faster on numeric code — use it when the
bundle's job is performance rather than reproduction.
Loading
Loading