on master help sign in
Add tools/hoon --no-debug, for timings

Every eval the dev tools run starts with `!:`, so errors have positions, and that makes checker work 2.2 to 2.6 times slower than on a ship. tools/hoon --no-debug leaves it out. The docs said building the library takes about 8 s; it takes about 27 s now, 24 s without `!:`, and compiler.md says its timings were all measured with `!:`. The selftest checks that --no-debug's program starts without it. Review question 40 (A137). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U4FzmSdubYxmZGDp2VJRAQ

commitcdf53c85322444ef83f4685d126d0a5d36ee538b authorClaude <noreply@anthropic.com> dateMon 2 Nov 2026, 20:16 (11 hours ago) parentce69a3b
6 files changed+25−9
docs/compiler.md+1−1 docs/development.md+2−2 docs/status.md+1−1 tools/hoon+8−2 tools/testdata/selftest+6 tools/urbit_eval.py+7−3
docs/compiler.md+1−1
@@ -216,7 +216,7 @@ Note 4 has examples of each.
216216
217217## Speed
218218
219−Starting `urbit eval` and building the library takes about 8 s. A 2,000-line module of 400 definitions then parses in about a second. Loops over definitions, arms, lines and items are tail calls, so a long module grows no stack; nesting costs a few stack frames a level, and 100,000 nested brackets still parse (note 3).
219+Starting `urbit eval` and building the library takes about 27 s, or 24 s without `!:` (`tools/hoon --no-debug`), measured on 2026-11-02. Every timing in this document was measured with `!:`, which the dev tools add so errors have positions, and which makes checker work 2.2 to 2.6 times slower than on a ship; the comparisons hold, but the absolute figures overstate a ship's time. A 2,000-line module of 400 definitions then parses in about a second. Loops over definitions, arms, lines and items are tail calls, so a long module grows no stack; nesting costs a few stack frames a level, and 100,000 nested brackets still parse (note 3).
220220
221221The experimental deferred constructor path can close a small import-free library transaction when ordinary resolution stalls. It retries definitions whose earlier attempts failed from their original states, retaining only successful attempts. A transaction with at most 64 definitions makes at most 4,096 attempts; it stops after a pass with no progress. This bounds the new attempt count, not each attempt's parsing, type walks or ordinary inference. Deferred definitions are restricted to simple one-component wings, literals, static strings, gates, transparent groups and empty bodies, calls, casts and a restricted boolean branch. Each successful call or cast fit must have exactly equal normalized operands so later substitutions cannot invalidate a face check. A multi-argument call also needs closed argument, sample and product types because positional face checks depend on slot boundaries and whether the result can be a cell. Other fits fail closed. A 512-node screen bounds visited expression nodes and a separate 512-node scan bounds each stability check; neither bounds mold shapes, string-piece checks, parsing, normalization, lookup, or total repeated inference. It accepts only a direct constructor callback with a closed input, records its pending fit under the source-owned inference state, and generalizes each definition against the live environment and pending constraints. Before publication it discharges and replays every edge, rejects unresolved tags or changed replay state, and checks that all molds and values are complete. The reporter reads the committed schemes for definitions added by the transaction. API and store paths, `main`, prepared-prelude recovery, imports, recursive groups, unresolved holes or written capitals, and new projection actions remain outside this full-module path. `%grec` in the constraint or freshness scan is conservatively unsupported. This is not an island-kind, runtime, or watchability check.
222222
docs/development.md+2−2
@@ -125,14 +125,14 @@ git clone https://github.com/urbit/tools /root/src/tools
125125| `tools/type-arithmetic-guards-test` | Runs restricted atom-variable, unification and projection guards under eval and on the ship |
126126| `tools/type-module-scope-test` | Checks the partial module checker’s prelude boundary and duplicate-source rejection against `tests/type-module-scope/expected.txt` under eval |
127127| `tools/clay-fixtures` | Syncs `my-app`, builds its example `/sur` files and marks through Clay, checks selected mold and mark projections and the partial checker, and records Ford and mark subject shapes on the fake ship |
128−| `tools/hoon` | Evaluates Hoon with the library in scope, as `tools/hoon '(golden /a/b/jibb (crip "++ main view"))'`; `--cord name=file` binds a file's text, `-f` reads the expression from a file |
128+| `tools/hoon` | Evaluates Hoon with the library in scope, as `tools/hoon '(golden /a/b/jibb (crip "++ main view"))'`; `--cord name=file` binds a file's text, `-f` reads the expression from a file; `--no-debug` leaves out `!:`, which gives errors their positions but makes the checker 2.2 to 2.6 times slower than on a ship, for timings |
129129| `tools/doc-blocks` | Checks the doc's `jibb` blocks against the `doc-*` golden cases: `tools/doc-blocks docs/spec` |
130130| `tools/ship` | Runs the fake ship: `start`, `stop`, `status`, `log`, `sync <desk>`, `assemble --out <dir>` (no ship needed), `test`, `gen-tests`, `check-commit`, `scry`, `hoon`, `run`, `poke`, `commit`, `mount`, `unmount` |
131131| `tools/testdata/selftest` | Tests `tools/golden` and `tools/hoon` against a stub library, and `tools/doc-blocks` against Markdown it writes |
132132| `tools/testdata/ship-selftest` | Tests `tools/ship`'s test generator and runner against a stub library on the ship |
133133| `tools/testdata/clay-fixtures-selftest` | Tests temporary Clay fixture cleanup and signal handling without a ship |
134134
135−`tools/golden` starts `urbit eval` once and builds the library, which takes about 8 s, then runs every case under `mule`, so a crash fails only its own case.
135+`tools/golden` starts `urbit eval` once and builds the library, which takes about 27 s, then runs every case under `mule`, so a crash fails only its own case.
136136
137137## Before pushing
138138
docs/status.md+1−1
@@ -121,7 +121,7 @@ Besides the plan's list:
121121- The 660-case corpus passed under `urbit eval` in 108.0 s while other checks were running. The fake ship passed all 717 tests, including the source-overlap, structural-overlap, generic-union source and alias-overlap, full-module overlap, deferred-context, generic-recursion, generic-construction, generic-pattern, deferred-inference, empty-mold and cell-branch fixtures, importer, importer/checker and mark/checker bridges, module scope, unification, hole, qath, ask, default-wrapper importer, projection, arithmetic, generic-alias, positional-face, API-evidence, request, closed-gate, todo API, constructor, sub, nested-union, gate-free and direct-gate guards and the unchanged todo report. Desk sync took 111.2 s and the ship's `-test` run took 316.7 s. The importer golden compares 221 projections; `tools/clay-fixtures` builds the example Hoon fixtures through Clay, checks a recursive Ford import's origin, and passes Clay-built types through the Clay-built importer and partial checker.
122122- `tools/testdata/selftest` (22 checks), `tools/testdata/ship-selftest` and `tools/testdata/clay-fixtures-selftest` test the tools themselves; the last checks temporary Clay fixture cleanup and signal handling without a ship.
123123- Two independent audits before 7c739b7, one of which compared the library with a reference parser written in Python from the doc over about 19,000 generated programs. Before ab797a9, a second Python parser, written from the decisions, agreed with the library on 8,000 generated programs and 900 hand-written probes.
124−- Speed: starting `urbit eval` and building the library takes about 8 s. After that, a 2,000-line module of 400 definitions parses in about a second. Loops over definitions, arms, lines and items grow no stack; nesting costs a few stack frames a level, and 100,000 nested brackets still parse (library section 3).
124+- Speed: starting `urbit eval` and building the library takes about 27 s with the dev tools' `!:`, and 24 s without it (`tools/hoon --no-debug`). After that, a 2,000-line module of 400 definitions parses in about a second. Loops over definitions, arms, lines and items grow no stack; nesting costs a few stack frames a level, and 100,000 nested brackets still parse (library section 3).
125125
126126### History
127127
tools/hoon+8−2
@@ -6,7 +6,9 @@
66 tools/hoon -f expr.hoon
77
88The program is `!: => <library> <expression>`, run by `urbit eval`,
9−so every arm of the library, and the standard library, is in scope. The
9+so every arm of the library, and the standard library, is in scope.
10+`!:` gives errors their positions but makes the checker run 2.2 to 2.6
11+times slower than on a ship; --no-debug leaves it out, for timings. The
1012result is printed as Hoon prints it, without colours; --raw prints a
1113cord's or tape's text instead. Errors and crashes are printed with
1214their positions mapped back to the library or the expression, and an
@@ -102,6 +104,10 @@ def main(argv):
102104 "expression to use; repeatable")
103105 ap.add_argument('--timeout', type=float, default=600, metavar='SECONDS',
104106 help='how long urbit eval may run (default: 600)')
107+ ap.add_argument('--no-debug', dest='debug', action='store_false',
108+ help="leave out !:, so the checker runs at a ship's "
109+ "speed, for timings; errors then have no "
110+ "positions")
105111 ap.add_argument('--save-program', metavar='FILE',
106112 help='also write the generated Hoon program to FILE')
107113 opts = ap.parse_args(argv)
@@ -119,7 +125,7 @@ def main(argv):
119125 expr, name = opts.expr, '(expression)'
120126 if not expr.strip():
121127 raise ue.ToolError('the expression is empty')
122− prog = ue.library_program(opts.lib)
128+ prog = ue.library_program(opts.lib, debug=opts.debug)
123129 for spec in opts.cord:
124130 line, cname = cord_binding(spec)
125131 prog.add(line, '(--cord %s)' % cname, 'harness')
tools/testdata/selftest+6
@@ -574,6 +574,12 @@ def check_hoon(c, tmp):
574574 'a crash from -f FILE: %r' % out)
575575 status, out = hoon('--lib', STUB, '-f', '-', stdin=b'(mul 6 7)')
576576 c.expect((status, out) == (0, '42\n'), 'stdin: %r' % out)
577+ program = os.path.join(tmp, 'no-debug.hoon')
578+ status, out = hoon('--lib', STUB, '--no-debug', '--save-program', program,
579+ '(add 2 2)')
580+ c.expect((status, out) == (0, '4\n') and
581+ read(program).startswith(b'=>\n'),
582+ '--no-debug leaves out !: : %d %r' % (status, out))
577583
578584
579585def check_doc_blocks(c, tmp):
tools/urbit_eval.py+7−3
@@ -18,7 +18,9 @@ How `urbit eval` (vere 4.6) behaves, as found by trying it:
1818- A failure prints its trace to stdout and "eval: bail: %exit" (or
1919 %meme, ...) to stderr, and still exits with status 0.
2020- Type errors carry no source positions unless the program turns on
21− debug spots, so every program here starts with `!:`. Spots print as
21+ debug spots, so programs here start with `!:` (tools/hoon --no-debug
22+ leaves it out). It makes the checker run 2.2 to 2.6 times slower
23+ than on a ship. Spots print as
2224 /::[l c].[l c]> at compile time and /:<[l c].[l c]> at run time, and
2325 a syntax error as {l c}. Lines and columns count from 1, columns in
2426 bytes.
@@ -173,14 +175,16 @@ class Program:
173175 return None, line
174176
175177
176−def library_program(lib_path):
178+def library_program(lib_path, debug=True):
177179 """A program that starts `!: => <library>`, for adding what follows.
178180
179181 `!:` turns on debug spots, so errors and crash traces have positions;
180182 `=>` makes the library's core the subject of what's added next.
183+ Without debug, the program starts `=> <library>`: it runs at a ship's
184+ speed, but its errors and traces have no positions.
181185 """
182186 prog = Program()
183− prog.add('!:\n=>', '(prefix)', 'prefix')
187+ prog.add('!:\n=>' if debug else '=>', '(prefix)', 'prefix')
184188 prog.add(read_text(lib_path), display_path(lib_path), 'lib')
185189 return prog
186190