#!/bin/sh set -eu : "${PRE15_DUT:?PRE15_DUT is required}" : "${PRE15_ROOT:?PRE15_ROOT is required}" : "${PRE15_CASE_TMP:?PRE15_CASE_TMP is required}" : "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}" : "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}" . "$PRE15_LIB_DIR/runner.sh" fixture_dir=$PRE15_DUT/tests/pre15/fixtures policy=$fixture_dir/B12-readahead-policy.json kld_builder=$fixture_dir/B12-build-kld.sh artifacts=$PRE15_RUN_DIR/artifacts for tool in awk clang file git nm python3 sha256sum timeout; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B12 host tool: $tool" done pre15_record_fixture b12-policy "$policy" pre15_record_fixture b12-kld-builder "$kld_builder" pre15_record_fixture b12-case "$PRE15_DUT/tests/pre15/cases/B12-readahead.sh" for source in data.c dir.c internal.h; do pre15_record_fixture "b12-$source" "$PRE15_DUT/src/$source" done mkdir -p "$artifacts" pre15_target_reached if python3 -B - "$PRE15_ROOT" "$PRE15_DUT" "$policy" \ "$artifacts/B12-policy-report.json" <<'PY' from __future__ import annotations import csv import hashlib import json from pathlib import Path import re import statistics import subprocess import sys root = Path(sys.argv[1]) dut = Path(sys.argv[2]) policy_path = Path(sys.argv[3]) report_path = Path(sys.argv[4]) policy = json.loads(policy_path.read_text(encoding="ascii")) if policy.get("schema") != 1 or policy.get("candidate") != "P15-087": raise SystemExit("invalid B12 policy identity") def digest(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() source_hashes = { relative: digest(dut / relative) for relative in policy["source_sha256"] } if source_hashes != policy["source_sha256"]: raise SystemExit("B12 production source differs from the accepted source set") changed = subprocess.run( [ "git", "-C", str(root), "diff", "--name-only", policy["baseline"], "--", "repo-pre-15/src", ], check=True, text=True, stdout=subprocess.PIPE, ).stdout.splitlines() expected_changed = [ "repo-pre-15/src/data.c", "repo-pre-15/src/dir.c", "repo-pre-15/src/internal.h", ] if sorted(changed) != expected_changed: raise SystemExit(f"B12 production write set mismatch: {changed}") data = (dut / "src/data.c").read_text(encoding="utf-8") directory = (dut / "src/dir.c").read_text(encoding="utf-8") internal = (dut / "src/internal.h").read_text(encoding="utf-8") if "cluster_read(" in data + directory + internal: raise SystemExit("B12 must not introduce cluster_read") if data.count("breadn(") != 1: raise SystemExit("B12 must contain one backing-vnode breadn call") if not re.search(r"breadn\(dif->devvp,", data): raise SystemExit("B12 breadn is not issued on erofs_device_info.devvp") required_data = ( "#define EROFS_DIR_READAHEAD_BYTES\t(1024 * 1024)", "#define EROFS_DIR_READAHEAD_SLOTS\t(EROFS_DIR_READAHEAD_BYTES / PAGE_SIZE)", "future.m_dif != current.m_dif", "future.m_pa != current.m_pa + step", "vi->datalayout == EROFS_INODE_FLAT_PLAIN", "return (erofs_read_data_impl(sbi, vi, loff, len, 0, bufp));", ) for marker in required_data: if marker not in data: raise SystemExit(f"missing B12 data policy marker: {marker}") if directory.count("erofs_read_data_readahead(") != 1: raise SystemExit("B12 directory hint is not limited to readdir") if "sequential = logical_off == 0;" not in directory: raise SystemExit("B12 random seek no-op predicate is absent") if internal.count("erofs_read_data_readahead(") != 1: raise SystemExit("B12 internal prototype is absent or duplicated") evidence = root / policy["gate_evidence"] result_path = evidence / "result.json" rows_path = evidence / "runs.tsv" if digest(result_path) != policy["gate_result_sha256"]: raise SystemExit("B12 gate result hash mismatch") if digest(rows_path) != policy["gate_rows_sha256"]: raise SystemExit("B12 gate rows hash mismatch") result = json.loads(result_path.read_text(encoding="ascii")) if result.get("status") != "GO" or result.get("b12") != "AUTHORIZED": raise SystemExit("P15-087 did not authorize B12") if result.get("qemu") != "PASS" or not all(result.get("checks", {}).values()): raise SystemExit("P15-087 runtime or semantic checks are incomplete") thresholds = result.get("thresholds", {}) if thresholds != { "maximum_extra_provider_reads_percent": policy["maximum_extra_provider_reads_percent"], "minimum_cold_median_improvement_percent": policy["minimum_cold_median_improvement_percent"], }: raise SystemExit("P15-087 thresholds differ from the frozen B12 policy") rows = [] with rows_path.open(encoding="ascii", newline="") as source: for raw in csv.reader(source, delimiter="\t"): if len(raw) != 10: raise SystemExit(f"invalid B12 gate row: {raw}") rows.append({ "variant": raw[0], "run": raw[1], "mode": raw[3], "elapsed_ns": int(raw[4]), "provider_reads": int(raw[5]), "entry_count": int(raw[7]), "hash": raw[8], "final_cookie": int(raw[9]), }) sequential = [row for row in rows if row["mode"] == "sequential"] random_rows = [row for row in rows if row["mode"] == "random"] baseline = [row for row in sequential if row["variant"] == "baseline"] candidate = [row for row in sequential if row["variant"] == "candidate"] if len(baseline) != 5 or len(candidate) != 5 or len(random_rows) != 2: raise SystemExit("B12 gate does not contain the complete 5+5 and 1+1 sample set") baseline_ns = statistics.median(row["elapsed_ns"] for row in baseline) candidate_ns = statistics.median(row["elapsed_ns"] for row in candidate) improvement = (baseline_ns - candidate_ns) * 100.0 / baseline_ns baseline_reads = statistics.median(row["provider_reads"] for row in baseline) candidate_reads = statistics.median(row["provider_reads"] for row in candidate) extra_reads = (candidate_reads - baseline_reads) * 100.0 / baseline_reads if improvement < policy["minimum_cold_median_improvement_percent"]: raise SystemExit("B12 recomputed cold median improvement misses the gate") if extra_reads > policy["maximum_extra_provider_reads_percent"]: raise SystemExit("B12 recomputed provider-read delta misses the gate") if any( (row["entry_count"], row["hash"], row["final_cookie"]) != (18002, "427bb414efa99dc0", 3880368) for row in sequential ): raise SystemExit("B12 sequential correctness rows differ from the oracle") if { (row["provider_reads"], row["entry_count"], row["hash"], row["final_cookie"]) for row in random_rows } != {(19, 292, "9805eadece9f500e", 1998932)}: raise SystemExit("B12 random path is not an exact no-op pair") report = { "baseline_samples": len(baseline), "candidate_samples": len(candidate), "baseline_median_elapsed_ns": baseline_ns, "candidate_median_elapsed_ns": candidate_ns, "cold_median_improvement_percent": improvement, "extra_provider_reads_percent": extra_reads, "gate": "GO", "qemu_runtime": "PASS", "random_samples": len(random_rows), "source_sha256": source_hashes, "status": "PASS", "window_bytes": policy["maximum_readahead_bytes"], } report_path.write_text( json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="ascii" ) PY then : else pre15_dut_fail 'B12 source, write-set, or frozen G11 policy replay failed' fi pre15_record_fixture b12-policy-report "$artifacts/B12-policy-report.json" module=$PRE15_CASE_TMP/B12-erofs-zstdio0.ko if ! timeout -k 10 600 /bin/sh "$kld_builder" "$PRE15_DUT" \ "$PRE15_FREEBSD_SRC" "$module" "$PRE15_CASE_TMP/kld-work" \ >"$artifacts/B12-kld-build.stdout" 2>"$artifacts/B12-kld-build.stderr"; then pre15_dut_fail 'B12 exact-ABI zstdio0 KLD build failed' fi pre15_record_module "$module" file "$module" >"$artifacts/B12-kld-file.txt" sha256sum "$module" >"$artifacts/B12-kld-sha256.txt" nm -g "$module" | LC_ALL=C sort >"$artifacts/B12-kld-nm-global.txt" nm -u "$module" | LC_ALL=C sort >"$artifacts/B12-kld-nm-u.txt" if ! awk '$NF == "erofs_read_data_readahead" { found = 1 } END { exit !found }' \ "$artifacts/B12-kld-nm-global.txt"; then pre15_dut_fail 'B12 exact-ABI KLD lacks the directory readahead symbol' fi if grep -q ' ZSTD_' "$artifacts/B12-kld-nm-u.txt"; then pre15_dut_fail 'B12 zstdio0 KLD contains an unexpected Zstd symbol' fi printf '%s\n' \ 'B12-policy PASS: exact source/write set and frozen 5+5 G11 statistics' \ 'K zstdio0 PASS: FreeBSD 15 exact-ABI cross-KLD and symbol contract' \ 'TC183 correctness/performance PASS from immutable P15-087 QEMU evidence'