Foundry/tt/tools/rebuild_tensix_program.sh

135 lines
6.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2026 David (Corey) Herrera
#
# rebuild_tensix_program.sh
# Rebuild a Tensix PROGRAM DIRECTORY from ITS OWN provenance records.
#
# Usage: rebuild_tensix_program.sh <program-dir> [output-dir]
# Env: KERNELS where the named sources live (default ../kernels beside this script)
# Output defaults to <program-dir>, i.e. rebuild in place.
#
# ------------------------------------------------------------------------------
# WHY THIS EXISTS, AND WHY IT IS NOT A SECOND SOURCE OF TRUTH
# ------------------------------------------------------------------------------
# Until 2026-08-19 the recipe for each of the 21 shipped programs lived only in prose --
# spread across kernel headers, four test Makefiles and DEV_NOTES -- and for THREE of them
# (matmul_assert0, matmul_assert1, reduce_fp32) it was written down nowhere at all. So
# "rebuild the program that is stale" meant reconstructing a command line by reading, and
# guessing one is not a theoretical risk: a rebuild of `rope` with invented compile-time
# args linked a smaller, functionally different kernel that reported the CONTROL as stale
# (DEV_NOTES.md 5), because a short `CTARGS` list folds to a literal 0 under SFPI
# GCC 15.1.0 with no diagnostic.
#
# `regen_tensix_program_header.sh` refuses to own the recipes for exactly that reason:
# inventing one recipe for all of them would be a second source of truth. This script does
# not invent anything. It READS the recipe out of the `.prov` record the builder wrote
# beside each image, which is the recipe that produced the image and therefore the first
# source of truth, not a second one. If the directory has no records it says so and stops.
#
# THE CHECK THAT MAKES IT TRUSTWORTHY IS NOT ITS OWN: rebuild into a scratch directory and
# `cmp` against the original. A faithful rebuild of an unchanged program is byte-identical
# on all ten images -- that is how the 21-program audit established which directories were
# really stale and which merely looked it.
#
# Status: added 2026-08-19. Proven by rebuilding `rope` -- the byte-identity control --
# from its own records: all ten images identical.
# Issues: None
# Todo: None
#
set -euo pipefail
HERE=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
TT=$(cd -- "$HERE/.." && pwd)
KERNELS=${KERNELS:-$TT/kernels}
SOURCE_DIR=${1:?usage: rebuild_tensix_program.sh <program-dir> [output-dir]}
OUT=${2:-$SOURCE_DIR}
SOURCE_DIR=$(cd -- "$SOURCE_DIR" && pwd)
# `recipe` is one line of `key=value` words; `source` is one word. Both are the format
# tools/tensix_image_provenance.py writes, read here with awk so this script needs nothing
# but a shell.
recipe_value() {
awk -v want="$2" '$1 == "recipe" {
for (i = 2; i <= NF; i++) {
equals = index($i, "=")
if (substr($i, 1, equals - 1) == want) { print substr($i, equals + 1); exit }
}
}' "$1"
}
record_source() { awk '$1 == "source" { print $2; exit }' "$1"; }
for image in brisc ncrisc trisc0; do
if [ ! -f "$SOURCE_DIR/$image.elf.prov" ]; then
echo "rebuild: $SOURCE_DIR has no $image.elf.prov -- this directory does not record" \
"how it was built, so it cannot be rebuilt from itself. Build it with" \
"build_tensix_program.sh and its documented recipe once; every rebuild after" \
"that comes from here." >&2
exit 1
fi
done
READER_SRC=$(record_source "$SOURCE_DIR/brisc.elf.prov")
WRITER_SRC=$(record_source "$SOURCE_DIR/ncrisc.elf.prov")
COMPUTE_SRC=$(record_source "$SOURCE_DIR/trisc0.elf.prov")
READER_CTARGS=$(recipe_value "$SOURCE_DIR/brisc.elf.prov" ctargs)
WRITER_CTARGS=$(recipe_value "$SOURCE_DIR/ncrisc.elf.prov" ctargs)
COMPUTE_CTARGS=$(recipe_value "$SOURCE_DIR/trisc0.elf.prov" ctargs)
CB_TABLE_NAME=$(recipe_value "$SOURCE_DIR/trisc0.elf.prov" cb_table)
WATCHER=$(recipe_value "$SOURCE_DIR/brisc.elf.prov" watcher)
LIGHTWEIGHT_ASSERTS=$(recipe_value "$SOURCE_DIR/brisc.elf.prov" lightweight_asserts)
RELOCATE_KERNEL_TEXT=$(recipe_value "$SOURCE_DIR/brisc.elf.prov" relocate_kernel_text)
# Empty for every program built before KERNEL_DEFINES existed, which is what makes those
# rebuilds byte-identical rather than merely equivalent.
#
# `EXTRA_KERNEL_DEFINES` is the ONE knob this script takes, and it is APPENDED rather than
# substituted: a rebuild always applies what the directory recorded, so it can never come
# out as something other than what it was. Appending is how a VARIANT directory is derived
# from a shipped one -- the output records the combined string, so the variant rebuilds
# itself faithfully from then on and needs the knob exactly once.
KERNEL_DEFINES=$(recipe_value "$SOURCE_DIR/trisc0.elf.prov" defines)
if [ -n "${EXTRA_KERNEL_DEFINES:-}" ]; then
KERNEL_DEFINES="${KERNEL_DEFINES:+$KERNEL_DEFINES }$EXTRA_KERNEL_DEFINES"
echo "=== NOT A FAITHFUL REBUILD: EXTRA_KERNEL_DEFINES=$EXTRA_KERNEL_DEFINES appended," \
"so $OUT is a VARIANT of $(basename "$SOURCE_DIR"), not a copy of it" >&2
fi
# A program is ONE build: a kernel's text is linked at its own firmware's
# __fw_export_text_end, and watcher changes the firmware's size. Three images that disagree
# about it are three builds wearing one directory's name, so this refuses rather than
# picking the reader's answer for all five.
for image in ncrisc trisc0 trisc1 trisc2; do
[ -f "$SOURCE_DIR/$image.elf.prov" ] || continue
for key in watcher lightweight_asserts defines; do
mine=$(recipe_value "$SOURCE_DIR/$image.elf.prov" "$key")
theirs=$(recipe_value "$SOURCE_DIR/brisc.elf.prov" "$key")
if [ "$mine" != "$theirs" ]; then
echo "rebuild: $SOURCE_DIR disagrees with itself -- brisc has $key=$theirs and" \
"$image has $key=$mine. A program is one build; these are two." >&2
exit 1
fi
done
done
CB_TABLE=""
[ -n "$CB_TABLE_NAME" ] && CB_TABLE=$KERNELS/cb_tables/$CB_TABLE_NAME
echo "=== rebuilding $(basename "$SOURCE_DIR") into $OUT from its own provenance"
printf ' reader %-38s CTARGS=%s\n' "$READER_SRC" "$READER_CTARGS"
printf ' compute %-38s CTARGS=%s\n' "$COMPUTE_SRC" "$COMPUTE_CTARGS"
printf ' writer %-38s CTARGS=%s\n' "$WRITER_SRC" "$WRITER_CTARGS"
printf ' CB_TABLE=%s WATCHER=%s LIGHTWEIGHT_ASSERTS=%s RELOCATE_KERNEL_TEXT=%s\n' \
"${CB_TABLE_NAME:-<none>}" "$WATCHER" "$LIGHTWEIGHT_ASSERTS" "$RELOCATE_KERNEL_TEXT"
printf ' KERNEL_DEFINES=%s\n' "${KERNEL_DEFINES:-<none>}"
mkdir -p "$OUT"
env READER_CTARGS="$READER_CTARGS" COMPUTE_CTARGS="$COMPUTE_CTARGS" \
WRITER_CTARGS="$WRITER_CTARGS" CB_TABLE="$CB_TABLE" \
WATCHER="$WATCHER" LIGHTWEIGHT_ASSERTS="$LIGHTWEIGHT_ASSERTS" \
RELOCATE_KERNEL_TEXT="$RELOCATE_KERNEL_TEXT" \
KERNEL_DEFINES="$KERNEL_DEFINES" \
bash "$TT/build_tensix_program.sh" \
"$KERNELS/$READER_SRC" "$KERNELS/$COMPUTE_SRC" "$KERNELS/$WRITER_SRC" "$OUT"