tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

binding-failure-investigation.md (11476B)


Current integration is a stable compiler change, not a third backend. See binding elimination; separate-backend stages below are superseded historical experiments.

Binding compiler timeout: isolation, unchanged retries, and completed measurements

This investigates the cold-compilation roadblock.

Outcome: the original timeout did not reproduce. The unchanged candidate completed a bounded instrumented retry, all ten planned repeated cold compilations, full-result arithmetic benchmarks, and the selected existing regression suites. No optimizer change or increased process limit was needed. The accepted compiler remains untouched and the candidate remains unpublished.

The original timeout's exact cause is unknown. The evidence points away from a deterministic failure or large inherent overhead in the transformation, but does not prove a particular scheduler, filesystem, or resource-contention cause.

1. Isolate the suspected unfinished module

The failed run had completed receipts for native_math, kernels, and layouts, but not pipelines. test/binding_failure_isolate.py exported those dependencies from both original runs and compared every exported function tree. There were no differences between the accepted and candidate dependency functions.

The harness then compiled all 53 pipelines definitions with immutable prebuilt accepted dependencies, separating module compilation from the rest of the cold graph build. Both compilers completed under a 55-second process guard:

Tool Wall time Cumulative allocation
Accepted 11.069 s 71.266 GB
Candidate 11.113 s 71.943 GB

All 53 resulting exported trees are identical between the two tools. Thus the isolated module neither stalls nor shows a changed function-construction result. The planned prefix bisection was unnecessary because the entire module passed. These are single isolation measurements, not repeated performance medians.

2. Repeat the original cold workload with retained progress evidence

test/binding_failure_cold.py copies the exact source bytes from the failed workspace and uses the same candidate bundle. It retains:

It runs one fresh-store pair in candidate-first order. The original 240-second CLI compilation timeout and 270-second enclosing process guard are unchanged. The one-capability and 2 GiB limits are unchanged too.

Tool Result Wall time Mutator + GC CPU time
Candidate Complete 26.638 s 23.572 s
Accepted Complete 24.775 s 22.969 s

The accepted bundle is byte-for-byte equal to the original successful accepted build, despite its earlier 136-second elapsed time. Both retry bundles also match their respective outputs in every subsequent repeated compilation.

What this says about the original timeout

The first accepted build took about 136.13 seconds wall time but 25.21 seconds mutator + GC CPU time. The original run therefore already exhibited a large wall-versus-CPU gap even without the candidate pass. That is consistent with an environmental delay rather than expensive additional compiler computation.

The killed candidate's original process output was not retained: the old monitor raised its guard exception before reading its temporary output file. We cannot recover a comparable candidate CPU profile or identify its exact wait state. The new diagnostic monitor preserves logs/progress without changing the runtime or weakening guards.

Consequently, the supported conclusion is “not reproduced under unchanged code and limits”, not “a specific environmental cause has been proved.” No speculative changes to gate ordering, traversal, equality or the reducer were made to obtain the passing retries.

3. Complete the planned repeated comparison

The original test/binding_compiler_measure.py ran unchanged in a fresh output directory. All ten cold compilations completed: five per tool, randomized order, fresh output stores. First repetitions are excluded from the medians. The same source workspace, runtime, one-capability setting and heap guard are used for both tools.

Cold compilation overhead

Metric Accepted median Candidate median Candidate / accepted
Wall time 27.656 s 27.262 s 0.986
Mutator + GC CPU time 23.459 s 23.475 s 1.001
Cumulative allocation 148.826 GB 149.580 GB 1.0051

There is approximately 0.51% additional compilation allocation, with no convincing CPU or wall-time difference. Measured wall times after excluding the first repetition range from 25.31–28.50 seconds for the accepted compiler and 25.11–28.46 seconds for the candidate. These totals include loading, graph IO, compilation and packaging; they are not isolated analysis-pass timings.

The original incomplete report remains preserved as a failure observation. It has not been overwritten or retroactively treated as a completed sample.

Actual recursive arithmetic

These are now the real recursive ordered-adder and multiplication programs from the arithmetic investigation, not just isolated Boolean truth-table functions. The source compiler's emitted ordered and orderedMultiply trees are exactly equal to the accepted compiler's corresponding manually inlined trees.

Runtime profiles cover invocation through sharing-aware full forcing, excluding rendering. There are five fresh-process invocations per tool/workload, randomized order, with the first excluded. Every complete result is checked independently. No reused CLI --repeat thunk is measured.

Workload Accepted allocation Candidate allocation Allocation reduction Wall-time change
16 random 53-bit operand multiplications 697.267 MB 623.766 MB 10.54% 10.82% lower
16 random 53-bit operand additions 27.053 MB 24.199 MB 10.55% 10.14% lower
1,024 exclusive-branch cases 16.428 MB 15.592 MB 5.09% 1.98% higher
Shared traversal control 4.718 MB 4.718 MB Identical 2.08% higher

Multiplication median wall times are 98.01 ms versus 87.41 ms. Addition and the nonnumeric controls run in only a few milliseconds, so their small timing movements deserve caution. Allocation reductions are the stronger evidence; the unchanged sharing control remains exact in both function tree and allocation.

There are 40 recorded runtime samples and 30 additional check invocations/events, including independent small-number arithmetic outputs, partial-input demand controls, and four expected bounded closure/selected-branch timeouts. The 18 whole-function controls retain the previously established pattern: three become the expected inline trees and 15 remain exact.

These measurements establish gains on this bounded workload, not a universal speedup or a completed binary64 implementation. Public numeric encodings remain uncommitted.

4. Existing regression coverage

The actual candidate's source_order, source_stable, and source_unit artifacts were exported from its private preparation store. They are not replacement published fixtures.

The existing abstraction harness expects a --generation directory layout. A small artifact-only adapter exposes the candidate bundle and preparation store in that layout and includes an explicit NOT-A-GENERATION.json marker. No compiler generation is claimed. An independent exact-tree comparison confirms that the harness used this candidate's compileStableModuleDefinitions worker, not the accepted reference worker accidentally.

An additional identity audit checks the candidate stable module against the retained stable module. Sixteen exports remain exact, including stableBracket, stableEliminate, stableTermApply, stableFix, and stableI. Only the expected three compiler-entry exports change:

The constructor/K and eta machinery therefore has not silently changed as a side effect of building the candidate. This exact-tree audit is not presented as a rerun of every separate constructor-K test harness.

Current decision boundary

The subsequent source-owned integration now makes this pass an explicit opt-in experimental backend, with separate profile and compiler-owned certificates. Its self-rebuild screen and repeated regression checks pass; formal acceptance and default promotion remain unclaimed.

The cold-compilation block is no longer reproducible, and the completed evidence supports keeping this narrow optimization candidate. It now realizes the earlier manual-inlining arithmetic gains with small measured compilation-allocation cost.

It is not promoted. Remaining decisions include the intended function-tree representation contract, production ownership of selector/RHS certificates, reviewable source integration and the appropriate compiler acceptance checkpoint. The earlier broader test explicitly recorded finite partial-function tree changes; passing these tests is not universal intensional equivalence.

Before adoption, source freezing, fresh self-hosted generation-pair verification, independent audits, smokes, retention and publication obligations still apply. No source commit was made, no accepted artifact was replaced, and none of those acceptance gates is implied by the private build or regression results.

Evidence and reproduction

Combined new evidence: bench/binding-failure-results.json. Historical incomplete evidence: bench/binding-compiler-results.json.

Raw directories:

New bounded diagnostic harnesses:

HOST=$(nix build .# --no-link --print-out-paths)/bin/tricu
python3 test/binding_failure_isolate.py "$HOST" \
  --directory /tmp/binding-isolate-new
python3 test/binding_failure_cold.py "$HOST" \
  --directory /tmp/binding-cold-new
python3 test/binding_compiler_measure.py "$HOST" \
  --candidate /tmp/tricu-binding-compiler-first \
  --directory /tmp/binding-measure-new

The two diagnostic harnesses explicitly select the prior private candidate and failed workspace by default; use their --candidate and --failed options to select relocated evidence. All output directories must be fresh. Recorded source hashes and artifact identities distinguish unchanged retries from a different candidate.