Skip to content

Commit 9c4583b

Browse files
committed
feat: restructure CLI around subcommands (run/test/disasm/build), hard-break the old mode flags
--test, --disasm, --emit-gbc <path>, and --emit-gbc-module <path> were flags dressed up as different modes (run vs. run-test-blocks vs. compile-only-and-disassemble vs. compile-only-and-write-an-artifact) rather than ordinary settings. gengo bundle already exists as a real subcommand for exactly this reason; extended that same argv[1]-dispatch pattern to run/test/disasm/build, routed through the existing flag-parsing loop so every modifier flag (--heap, --cap, --mount, --modules, --max-ops, --backend, --no-fusion, --net-listen-allow, --net-dial-allow, --verify-source) keeps working identically under every subcommand with no duplicated parsing logic. gengo <script.gengo> (no subcommand) is unchanged -- still the default, implicit run. gengo run/test/disasm/build <script> are the new explicit forms; build takes -o/--output <path> (required) and --lib for the linkable-module variant. Hard break, not a soft deprecation: the old flags are gone entirely. Hitting one now fails with a specific migration message instead of being silently misparsed as a script path -- the existing arg loop had no "unknown flag" branch at all, so simply deleting the flag blocks would have made `gengo --test script.gengo` try to open a file literally named --test. Updated every doc referencing the removed flags (docs/cli.md, docs/language.md, docs/changelog.md, docs/known-limitations.md) in the same pass. No CI workflow, git hook, or tooling script used any of the four removed flags to begin with, confirmed via a full grep sweep before starting. Verified under standard, -Dpreset=stress, and -Dgc_stress=true builds.
1 parent f02856c commit 9c4583b

6 files changed

Lines changed: 181 additions & 69 deletions

File tree

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,39 @@ This changelog tracks notable language/runtime changes by implementation date.
44

55
## 2026-08-21
66

7+
### Change — CLI restructured around subcommands (`run`/`test`/`disasm`/`build`)
8+
9+
`--test`, `--disasm`, `--emit-gbc <path>`, and `--emit-gbc-module <path>` were
10+
flags dressed up as different modes (run vs. run-test-blocks vs.
11+
compile-only-and-disassemble vs. compile-only-and-write-an-artifact) rather
12+
than ordinary settings — the same shape `gengo bundle` (already a real
13+
subcommand) exists specifically to avoid. Replaced with explicit
14+
subcommands, mirroring `bundle`'s existing `argv[1]`-dispatch pattern but
15+
routed through the same flag-parsing loop so every modifier flag (`--heap`,
16+
`--cap`, `--mount`, `--modules`, `--max-ops`, `--backend`, `--no-fusion`,
17+
`--net-listen-allow`, `--net-dial-allow`, `--verify-source`) keeps working
18+
identically under every subcommand with no duplicated parsing logic:
19+
20+
- `gengo <script.gengo>` (no subcommand) is unchanged — still the default,
21+
implicit `run`.
22+
- `gengo run <script.gengo>` — new explicit alias for the same thing.
23+
- `gengo test <script.gengo> [--profile]` — replaces `--test`/`--profile`.
24+
- `gengo disasm <script.gengo>` — replaces `--disasm`.
25+
- `gengo build <script.gengo> -o <path> [--lib]` — replaces
26+
`--emit-gbc <path>` (default) / `--emit-gbc-module <path>` (`--lib`).
27+
28+
Hard break, not a soft deprecation: `--test`/`--disasm`/`--emit-gbc`/
29+
`--emit-gbc-module` are gone entirely. Hitting one now fails with a specific
30+
migration message (e.g. `--test was removed — use \`gengo test <script>\`
31+
instead`) rather than being silently misparsed as a script path — the
32+
existing arg loop had no "unknown flag" branch at all, so simply deleting
33+
these blocks would have made `gengo --test script.gengo` try to open a file
34+
literally named `--test`. `--help` and every doc/example referencing the
35+
removed flags (`docs/cli.md`, `docs/language.md`, `docs/changelog.md`,
36+
`docs/known-limitations.md`) updated in the same pass; no CI workflow,
37+
git hook, or tooling script used any of the four removed flags to begin
38+
with (confirmed via a full grep sweep before starting).
39+
740
### Fix — engine_slots handle-allocation race, and opt-in GBC source-staleness verification
841

942
Two follow-ups from digging further into the engine C-API concurrency work

docs/changelog.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030

3131
### Tooling (unreleased)
3232

33-
- **`gengo --test --profile`** — reports each `test` block's instruction count and peak heap bytes/stack depth/live object count, plus a final peak-across-all-blocks summary line, so integrators can size `engine_init_with_config`'s resource ceilings from measured workload data instead of guessing. Does not affect pass/fail behavior or the exit code; does cost real speed (forces per-instruction accounting on for the run), so it's a diagnostic flag, not something to leave on by default.
34-
- **`gengo --emit-gbc path` / running a `.gbc` directly** — an early GBC (Gengo Bytecode Cache) implementation: `--emit-gbc` compiles a script and writes a `.gbc` artifact instead of running it; a `.gbc` file passed as the script argument (recognized by magic bytes) loads and runs directly, skipping parsing and compilation. Covers plain and generic functions (real parameter/return types, so interface conformance checks keep working on a loaded function; a generic function's type parameters erase to `any` on the wire, matching their own runtime semantics — constraint checking is unaffected, since it runs at each call site's own compile time against the caller's concrete type arguments, not by inspecting a loaded callee's stored types), `std`/native calls, and struct, named-type, `variant`, `interface`, `enum`, and task/actor declarations (including range/cycle/clamp-constrained named types, a named type's `default` value and `scale`, a module/type-scope `predicate`, variants with shared fields and record/single-payload/no-payload arms, enums with explicit representation values, auto-incremented ordinals, and enum subtypes, spawning/sending/receiving through a task type loaded from a `.gbc` (running one now correctly drives the same task scheduler a normally-compiled program uses, not a one-shot run with no scheduler at all), and a predicate declared inside a function body that closes over the function's own locals, re-capturing fresh on every call exactly as it does compiled normally (this last one turned out to already work once checked directly — a closure with real captures is always built by ordinary `make_closure` bytecode at the point it's declared, never handed to the writer as a literal constant object, so there was nothing to reject in the first place). See `dev-docs/design/gbc-spec.md` and GitHub issue #5 for the full format and remaining scope; the CLI does not yet check a `.gbc`'s recorded source hash against the current source before running it, so regenerate one by hand when its source changes.
33+
- **`gengo test --profile`** — reports each `test` block's instruction count and peak heap bytes/stack depth/live object count, plus a final peak-across-all-blocks summary line, so integrators can size `engine_init_with_config`'s resource ceilings from measured workload data instead of guessing. Does not affect pass/fail behavior or the exit code; does cost real speed (forces per-instruction accounting on for the run), so it's a diagnostic flag, not something to leave on by default.
34+
- **`gengo build script.gengo -o path` / running a `.gbc` directly** — an early GBC (Gengo Bytecode Cache) implementation: `build` compiles a script and writes a `.gbc` artifact instead of running it; a `.gbc` file passed as the script argument (recognized by magic bytes) loads and runs directly, skipping parsing and compilation. Covers plain and generic functions (real parameter/return types, so interface conformance checks keep working on a loaded function; a generic function's type parameters erase to `any` on the wire, matching their own runtime semantics — constraint checking is unaffected, since it runs at each call site's own compile time against the caller's concrete type arguments, not by inspecting a loaded callee's stored types), `std`/native calls, and struct, named-type, `variant`, `interface`, `enum`, and task/actor declarations (including range/cycle/clamp-constrained named types, a named type's `default` value and `scale`, a module/type-scope `predicate`, variants with shared fields and record/single-payload/no-payload arms, enums with explicit representation values, auto-incremented ordinals, and enum subtypes, spawning/sending/receiving through a task type loaded from a `.gbc` (running one now correctly drives the same task scheduler a normally-compiled program uses, not a one-shot run with no scheduler at all), and a predicate declared inside a function body that closes over the function's own locals, re-capturing fresh on every call exactly as it does compiled normally (this last one turned out to already work once checked directly — a closure with real captures is always built by ordinary `make_closure` bytecode at the point it's declared, never handed to the writer as a literal constant object, so there was nothing to reject in the first place). See `dev-docs/design/gbc-spec.md` and GitHub issue #5 for the full format and remaining scope; pass `--verify-source path` when running a `.gbc` to check it against the current source (`SourceGraphStale` on drift) — omitted by default, so a `.gbc` remains runnable standalone without its source present.
3535

3636
### Fixes (unreleased)
3737

docs/cli.md

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,28 @@ this checkout from source and have not installed it, substitute
77
## Invocation
88

99
```text
10-
gengo [options] [script.gengo]
10+
gengo [run|test|disasm|build|bundle] [options] [script.gengo]
1111
```
1212

13-
Run a source file:
13+
Run a source file — no subcommand needed, `run` is implicit:
1414

1515
```bash
1616
gengo hello.gengo
1717
```
1818

19+
`gengo run hello.gengo` does exactly the same thing; the subcommand is there
20+
for scripts/docs that want to be explicit, or to sit alongside `test`/
21+
`disasm`/`build` as a sibling. The other subcommands switch what "compile
22+
this script" actually does instead of running it normally:
23+
24+
| Command | Meaning |
25+
|---|---|
26+
| `run <script>` | Compile and run (the default — also what a bare `gengo <script>` does). |
27+
| `test <script>` | Run top-level `test` blocks rather than ordinary script execution. A failed test exits unsuccessfully. |
28+
| `disasm <script>` | Compile and print a bytecode disassembly without running the script. This is an implementation-debugging aid, not language semantics. |
29+
| `build <script> -o path` | Compile the script and write a GBC (Gengo Bytecode Cache) artifact to `path`; do not run. Add `--lib` to write a linkable GBC module artifact instead. See below. |
30+
| `bundle` | Package a script and its imports into a zip archive. See `gengo bundle --help`. |
31+
1932
Use `-e` or `--eval` for a short program. It cannot be combined with a script
2033
path:
2134

@@ -29,14 +42,18 @@ script from standard input instead.
2942

3043
## Options
3144

45+
Modifier flags below work identically with or without a subcommand — they
46+
apply the same way to `gengo script.gengo`, `gengo test script.gengo`,
47+
`gengo build script.gengo -o out.gbc`, and so on.
48+
3249
| Option | Meaning |
3350
|---|---|
3451
| `--help`, `-h` | Print the option summary and exit. |
3552
| `--version` | Print the CLI version and exit. |
36-
| `--disasm` | Compile and print a bytecode disassembly without running the script. This is an implementation-debugging aid, not language semantics. |
37-
| `--emit-gbc path` | Compile the script and write a GBC (Gengo Bytecode Cache) artifact to `path`; do not run. See below. |
38-
| `--test` | Run top-level `test` blocks rather than ordinary script execution. A failed test exits unsuccessfully. |
39-
| `--profile` | With `--test`, print each block's instruction count and peak heap bytes/stack depth/live object count, plus a final peak-across-all-blocks summary line. Does not affect pass/fail behavior or the exit code. Forces per-instruction instruction counting on for the run, which costs real speed — a diagnostic aid, not something to leave on by default. |
53+
| `-o`, `--output path` | `build`-only: the artifact path to write. Required with `build`. |
54+
| `--lib` | `build`-only: write a linkable GBC module artifact instead of a normal one. |
55+
| `--verify-source path` | When running a `.gbc` directly, reject it (`SourceGraphStale`) if it no longer matches this source file. See below. |
56+
| `--profile` | `test`-only: print each block's instruction count and peak heap bytes/stack depth/live object count, plus a final peak-across-all-blocks summary line. Does not affect pass/fail behavior or the exit code. Forces per-instruction instruction counting on for the run, which costs real speed — a diagnostic aid, not something to leave on by default. |
4057
| `--cap name` | Enable one named capability. Repeat for several capabilities. See `capabilities.md`; no capability is enabled merely by importing it. `cap:ffi` requires `--cap ffi` and is native-CLI only. |
4158
| `--cap net=scope1,scope2` | Scope the `net` capability instead of granting it unscoped. Scopes are `dial` and `listen`, comma-separated (`--cap net=dial`, `--cap net=listen`, `--cap net=dial,listen`). Bare `--cap net` (no `=`) still means dial-only, unchanged from before scopes existed — upgrading never silently grants listen. This general `name=scope1,scope2` syntax is available for any capability that defines scopes, not just `net`. |
4259
| `--net-listen-allow pattern[:port]` | Add an allow rule to `net.listen`'s bind policy, which defaults to deny-all (see `security.md`). Repeatable. `pattern` accepts the same shapes as the embedding API's policy rules (`"*"`, exact IPv4/IPv6, CIDR, hostname wildcard); an optional `:port` suffix restricts to one port (bracket the pattern for a literal IPv6 address with a port, e.g. `"[::1]:8080"`). With no rules, `--cap net=listen` alone still makes `net.listen(...)` compile and import but refuse every call. |
@@ -78,13 +95,14 @@ for traversal, symlink, and host-platform limits.
7895

7996
## GBC (Bytecode Cache)
8097

81-
`--emit-gbc path` compiles a script and writes a `.gbc` artifact instead of
82-
running it. A `.gbc` file passed as the script argument runs directly,
83-
skipping parsing and compilation entirely — the file is recognized by its
84-
magic bytes, not its extension, though naming it `.gbc` is the convention:
98+
`gengo build script.gengo -o path` compiles a script and writes a `.gbc`
99+
artifact instead of running it. A `.gbc` file passed as the script argument
100+
runs directly, skipping parsing and compilation entirely — the file is
101+
recognized by its magic bytes, not its extension, though naming it `.gbc` is
102+
the convention:
85103

86104
```bash
87-
gengo --emit-gbc app.gbc app.gengo # compile once
105+
gengo build app.gengo -o app.gbc # compile once
88106
gengo app.gbc # run the cached artifact, repeatedly
89107
```
90108

@@ -105,16 +123,19 @@ This is early — the current implementation covers a first milestone (see
105123
attached via ordinary bytecode execution rather than embedded as a
106124
constant, both work), or a generic function declaration (generic struct
107125
and variant types alone round-trip correctly; the rejection triggers only
108-
when the script declares a `func` with type parameters). `--emit-gbc`
109-
fails with a clear error naming the limitation rather than producing a
110-
broken artifact.
111-
- The artifact records a hash of the source it was compiled from, but the
112-
CLI does not yet check it against the current source file before running
113-
a `.gbc` — nothing currently stops you from running a `.gbc` that no
114-
longer matches its `.gengo` source. Treat a `.gbc` as something you
115-
regenerate whenever its source changes, not as a transparent, self-invalidating
116-
cache yet.
117-
- `--emit-gbc` is not currently supported when running the WASI build.
126+
when the script declares a `func` with type parameters). `build` fails
127+
with a clear error naming the limitation rather than producing a broken
128+
artifact.
129+
- The artifact always records a hash of the source it was compiled from, but
130+
running a `.gbc` doesn't check it by default — nothing stops you from
131+
running a `.gbc` that no longer matches its `.gengo` source unless you ask
132+
for the check. Pass `--verify-source path` (naming the original `.gengo`
133+
file) when running a `.gbc` to opt in: a mismatch fails with
134+
`SourceGraphStale` instead of silently executing stale bytecode.
135+
```bash
136+
gengo --verify-source app.gengo app.gbc
137+
```
138+
- `gengo build` is not currently supported when running the WASI build.
118139

119140
## REPL
120141

docs/known-limitations.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ The relevant specifications include
3434

3535
| Area | Current behaviour | Practical consequence |
3636
|---|---|---|
37-
| `--emit-gbc` source hash | The artifact records a hash of the source it was compiled from, but the CLI does not yet verify it before running the artifact. | Regenerate the `.gbc` whenever its `.gengo` source changes; do not rely on automatic invalidation. |
38-
| `--emit-gbc` on WASI | `--emit-gbc` is not supported in the WASI build. | Use the native CLI for GBC emission; run the artifact from WASI if needed. |
37+
| `gengo build` source hash | The artifact always records a hash of the source it was compiled from, but running a `.gbc` only verifies it against `--verify-source path` when that flag is given — a `.gbc` must remain runnable standalone with no source present, so verification can't be mandatory. | Pass `--verify-source path` when the original `.gengo` file is available and drift should fail loudly (`SourceGraphStale`); otherwise regenerate the `.gbc` by hand whenever its source changes. |
38+
| `gengo build` on WASI | `gengo build` is not supported in the WASI build. | Use the native CLI for GBC emission; run the artifact from WASI if needed. |
3939

4040
## Security and Operations
4141

docs/language.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1918,10 +1918,10 @@ test "range check" {
19181918

19191919
**Normal execution skips test blocks.** Running `gengo script.gengo` executes the script body but ignores all `test` blocks entirely. No test code runs, and no test infrastructure is paid for.
19201920

1921-
**`--test` activates them:**
1921+
**`gengo test` activates them:**
19221922

19231923
```bash
1924-
gengo script.gengo --test
1924+
gengo test script.gengo
19251925
```
19261926

19271927
Each test block runs in order. Results go to stderr:

0 commit comments

Comments
 (0)