Skip to content
Draft
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
125 changes: 125 additions & 0 deletions specs/tri/t27b/build_record.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: Apache-2.0
; specs/tri/t27b/build_record.t27 -- when `t27b build` may skip a build because nothing that feeds the
; object changed. Issue gHashTag/t27#10860, epic #10863 (T27F developer tools in t27b and t27c, sped up
; by t27c seal).
; --
; Why. Every `t27b build`, and every rebuild of `t27b watch`, parses, lowers and encodes the whole `use`
; closure, even when the save changed nothing the object is made of. A hot-reloading T27 OS window
; (specs/t27os/hot_reload.t27) restarts whenever its object is rewritten, so a rewrite of identical bytes
; is a restart for nothing.
; --
; Nothing is decided twice. specs/verified/reuse.t27 is the reuse rule: reuse only when every part is
; recorded on both sides and equal and the old verdict passed; MISSING never means SAME; the reason is
; the first part that differs. This spec only says which parts a native object has, in that file's order:
; - spec: the root `.t27` bytes;
; - closure: the bytes of every other spec its `use` lines reach;
; - toolchain: the `t27b` binary itself;
; - config: the flags that change the bytes (`--elf`/`--macho`, `--overflow`);
; - verdict: always PASS, because a record is written only after a build succeeded.
; --
; The record. `<out>` + RECORD_SUFFIX, next to the object, the same shape as specs/t27f/build.t27: `#`
; lines carry what is not a file, every other line is `<sha256> <path>` -- the root first, the closure,
; the `t27b` binary last -- so `shasum -a 256 -c <out>.build` reads it unchanged. The hashes come from the
; system sha256 tool (sha256sum, else `shasum -a 256`); this spec computes none.
; --
; Same bytes, no rewrite. When a part moved but the new object is byte-identical to the one on disk (a
; comment edit), the object is not rewritten and only the record is refreshed, so a hot-reloading app does
; not restart. This replaces, for t27b, the gen-hash route of specs/verified/watch.t27 watch_skip: t27b
; cannot learn a spec's four gen hashes without running t27c's four backends, which costs more than its
; own build, and comparing the object it just built answers the same question exactly.
; --
; `--force` builds regardless of the record.
; phi^2 + 1/phi^2 = 3 | TRINITY

module T27bBuildRecord;

use verified::reuse::PART_MISSING;
use verified::reuse::PART_SAME;
use verified::reuse::PART_DIFF;
use verified::reuse::REUSE;
use verified::reuse::REBUILD_SPEC;
use verified::reuse::REBUILD_CLOSURE;
use verified::reuse::REBUILD_TOOLCHAIN;
use verified::reuse::REBUILD_CONFIG;
use verified::reuse::REBUILD_MISSING;
use verified::reuse::reuse_decision;

/// The record lives at `<out>` + this suffix.
pub const RECORD_SUFFIX : str = ".build";

/// First line of the record.
pub const RECORD_HEAD : str = "# t27b build record 1";

/// The config part: this prefix, then the byte-changing flags.
pub const KEY_FLAGS : str = "# flags ";

/// `--force` was given.
pub const REBUILD_FORCED : u8 = 7;

/// The word printed for each reason, indexed by its value (reuse.t27 0..6, then forced).
pub const REASON_WORDS : str = "reuse spec-changed closure-changed t27b-changed flags-changed verdict no-record forced";

/// One part of the record against now: `found` is false when the record has no line for it, `same` when
/// every line for it checked OK.
pub fn record_part(found: bool, same: bool) -> u8 {
if (found == false) { return PART_MISSING; }
if (same) { return PART_SAME; }
return PART_DIFF;
}

/// REUSE, or the first reason to build: forced, then reuse.t27's order over spec, closure, t27b, flags.
pub fn record_reason(force: bool, spec: u8, closure: u8, toolchain: u8, config: u8) -> u8 {
if (force) { return REBUILD_FORCED; }
return reuse_decision(spec, closure, toolchain, config, true);
}

/// Write the object: only when its bytes differ from the object already on disk (or there is none).
pub fn record_rewrite(same_bytes_on_disk: bool) -> bool {
return same_bytes_on_disk == false;
}

/// Write the record: only after a successful build, so its verdict is always PASS.
pub fn record_write(build_ok: bool) -> bool {
return build_ok;
}

test every_part_the_same_reuses {
assert record_reason(false, PART_SAME, PART_SAME, PART_SAME, PART_SAME) == REUSE;
}

test force_wins_over_a_fresh_record {
assert record_reason(true, PART_SAME, PART_SAME, PART_SAME, PART_SAME) == REBUILD_FORCED;
}

test no_record_rebuilds {
const m : u8 = record_part(false, true);
assert m == PART_MISSING;
assert record_reason(false, m, m, m, m) == REBUILD_MISSING;
}

test a_missing_part_never_counts_as_the_same {
assert record_reason(false, PART_SAME, PART_SAME, PART_MISSING, PART_SAME) == REBUILD_MISSING;
}

test the_first_changed_part_is_the_reason {
const d : u8 = record_part(true, false);
assert d == PART_DIFF;
assert record_reason(false, d, d, d, d) == REBUILD_SPEC;
assert record_reason(false, PART_SAME, d, d, d) == REBUILD_CLOSURE;
assert record_reason(false, PART_SAME, PART_SAME, d, d) == REBUILD_TOOLCHAIN;
assert record_reason(false, PART_SAME, PART_SAME, PART_SAME, d) == REBUILD_CONFIG;
}

test identical_bytes_are_not_rewritten {
assert record_rewrite(true) == false;
assert record_rewrite(false);
}

test a_failed_build_writes_no_record {
assert record_write(false) == false;
assert record_write(true);
}

test forced_is_the_word_after_the_reuse_reasons {
assert record_reason(true, PART_MISSING, PART_MISSING, PART_MISSING, PART_MISSING) == REBUILD_MISSING + 1;
}
Loading