on master help sign in
Add tools/hoon --no-debug, for timings
cdf53c8Claude
docs/compiler.md+1−1
@@ -216,7 +216,7 @@ Note 4 has examples of each.
216
216
217## Speed
217## Speed
218
218
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).
220
220
221The 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.
221The 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.
222
222
docs/development.md+2−2
@@ -125,14 +125,14 @@ git clone https://github.com/urbit/tools /root/src/tools
125| `tools/type-arithmetic-guards-test` | Runs restricted atom-variable, unification and projection guards under eval and on the ship |
125| `tools/type-arithmetic-guards-test` | Runs restricted atom-variable, unification and projection guards under eval and on the ship |
126| `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 |
126| `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 |
127| `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 |
127| `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 |
129| `tools/doc-blocks` | Checks the doc's `jibb` blocks against the `doc-*` golden cases: `tools/doc-blocks docs/spec` |
129| `tools/doc-blocks` | Checks the doc's `jibb` blocks against the `doc-*` golden cases: `tools/doc-blocks docs/spec` |
130| `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` |
130| `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` |
131| `tools/testdata/selftest` | Tests `tools/golden` and `tools/hoon` against a stub library, and `tools/doc-blocks` against Markdown it writes |
131| `tools/testdata/selftest` | Tests `tools/golden` and `tools/hoon` against a stub library, and `tools/doc-blocks` against Markdown it writes |
132| `tools/testdata/ship-selftest` | Tests `tools/ship`'s test generator and runner against a stub library on the ship |
132| `tools/testdata/ship-selftest` | Tests `tools/ship`'s test generator and runner against a stub library on the ship |
133| `tools/testdata/clay-fixtures-selftest` | Tests temporary Clay fixture cleanup and signal handling without a ship |
133| `tools/testdata/clay-fixtures-selftest` | Tests temporary Clay fixture cleanup and signal handling without a ship |
134
134
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.
136
136
137## Before pushing
137## Before pushing
138
138
docs/status.md+1−1
@@ -121,7 +121,7 @@ Besides the plan's list:
121- 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.
121- 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.
122- `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.
122- `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.
123- 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.
123- 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).
125
125
126### History
126### History
127
127
tools/hoon+8−2
@@ -6,7 +6,9 @@
6 tools/hoon -f expr.hoon
6 tools/hoon -f expr.hoon
7
7
8The program is `!: => <library> <expression>`, run by `urbit eval`,
8The 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
10result is printed as Hoon prints it, without colours; --raw prints a
12result is printed as Hoon prints it, without colours; --raw prints a
11cord's or tape's text instead. Errors and crashes are printed with
13cord's or tape's text instead. Errors and crashes are printed with
12their positions mapped back to the library or the expression, and an
14their positions mapped back to the library or the expression, and an
@@ -102,6 +104,10 @@ def main(argv):
102 "expression to use; repeatable")
104 "expression to use; repeatable")
103 ap.add_argument('--timeout', type=float, default=600, metavar='SECONDS',
105 ap.add_argument('--timeout', type=float, default=600, metavar='SECONDS',
104 help='how long urbit eval may run (default: 600)')
106 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")
105 ap.add_argument('--save-program', metavar='FILE',
111 ap.add_argument('--save-program', metavar='FILE',
106 help='also write the generated Hoon program to FILE')
112 help='also write the generated Hoon program to FILE')
107 opts = ap.parse_args(argv)
113 opts = ap.parse_args(argv)
@@ -119,7 +125,7 @@ def main(argv):
119 expr, name = opts.expr, '(expression)'
125 expr, name = opts.expr, '(expression)'
120 if not expr.strip():
126 if not expr.strip():
121 raise ue.ToolError('the expression is empty')
127 raise ue.ToolError('the expression is empty')
122− prog = ue.library_program(opts.lib)
128+ prog = ue.library_program(opts.lib, debug=opts.debug)
123 for spec in opts.cord:
129 for spec in opts.cord:
124 line, cname = cord_binding(spec)
130 line, cname = cord_binding(spec)
125 prog.add(line, '(--cord %s)' % cname, 'harness')
131 prog.add(line, '(--cord %s)' % cname, 'harness')
tools/testdata/selftest+6
@@ -574,6 +574,12 @@ def check_hoon(c, tmp):
574 'a crash from -f FILE: %r' % out)
574 'a crash from -f FILE: %r' % out)
575 status, out = hoon('--lib', STUB, '-f', '-', stdin=b'(mul 6 7)')
575 status, out = hoon('--lib', STUB, '-f', '-', stdin=b'(mul 6 7)')
576 c.expect((status, out) == (0, '42\n'), 'stdin: %r' % out)
576 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))
577
583
578
584
579def check_doc_blocks(c, tmp):
585def 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:
18- A failure prints its trace to stdout and "eval: bail: %exit" (or
18- A failure prints its trace to stdout and "eval: bail: %exit" (or
19 %meme, ...) to stderr, and still exits with status 0.
19 %meme, ...) to stderr, and still exits with status 0.
20- Type errors carry no source positions unless the program turns on
20- 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
22 /::[l c].[l c]> at compile time and /:<[l c].[l c]> at run time, and
24 /::[l c].[l c]> at compile time and /:<[l c].[l c]> at run time, and
23 a syntax error as {l c}. Lines and columns count from 1, columns in
25 a syntax error as {l c}. Lines and columns count from 1, columns in
24 bytes.
26 bytes.
@@ -173,14 +175,16 @@ class Program:
173 return None, line
175 return None, line
174
176
175
177
176−def library_program(lib_path):
178+def library_program(lib_path, debug=True):
177 """A program that starts `!: => <library>`, for adding what follows.
179 """A program that starts `!: => <library>`, for adding what follows.
178
180
179 `!:` turns on debug spots, so errors and crash traces have positions;
181 `!:` turns on debug spots, so errors and crash traces have positions;
180 `=>` makes the library's core the subject of what's added next.
182 `=>` 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.
181 """
185 """
182 prog = Program()
186 prog = Program()
183− prog.add('!:\n=>', '(prefix)', 'prefix')
187+ prog.add('!:\n=>' if debug else '=>', '(prefix)', 'prefix')
184 prog.add(read_text(lib_path), display_path(lib_path), 'lib')
188 prog.add(read_text(lib_path), display_path(lib_path), 'lib')
185 return prog
189 return prog
186
190