Files
Sprinter-SDCC/toolchain/size_check.py
T
2026-09-16 10:01:43 +03:00

113 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""
size_check.py — размерный регресс-тест: сравнение _CODE тестов с эталоном
docs/size_baseline.tsv.
Использование:
python3 toolchain/size_check.py # сверка с эталоном
python3 toolchain/size_check.py --update # перезаписать эталон
Логика: собираем _CODE из .map каждой программы под tests/, сравниваем с
эталоном. Любое отличие печатается; выход 1 — если есть РОСТ (случайное
разжирение libc), 0 — если только уменьшения/без изменений. Новые
программы (нет в эталоне) — предупреждение, добавить через --update.
Запускать после `make all`.
ТОЛЬКО tests/, без отдельного репозитория Examples: регресс ловит разжирение libc, а для этого
хватает мелких тестов — каждый тянет свой кусок библиотеки и пересобирается
за секунды. Крупные приложения (`../Examples/mdview` компилируется минутами)
не показывают ничего сверх этого и лишь удлиняют цикл, поэтому и из
`make all` они убраны (см. корневой Makefile).
"""
import glob
import os
import re
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BASELINE = os.path.join(ROOT, "docs", "size_baseline.tsv")
def collect():
"""Имя программы → размер _CODE (байт) по свежим .map."""
sizes = {}
pats = [
os.path.join(ROOT, "tests", "*", ".sprinter-cc-*", "*.map"),
]
for pat in pats:
for map_path in glob.glob(pat):
name = os.path.splitext(os.path.basename(map_path))[0]
# .map должен лежать в своей папке .sprinter-cc-<name>
if os.path.basename(os.path.dirname(map_path)) != ".sprinter-cc-" + name:
continue
with open(map_path, encoding="utf-8", errors="replace") as f:
for line in f:
m = re.match(r"^_CODE\s+([0-9A-Fa-f]+)\s+([0-9A-Fa-f]+)", line)
if m:
sizes[name] = int(m.group(2), 16)
break
return sizes
def load_baseline():
base = {}
if os.path.exists(BASELINE):
with open(BASELINE) as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
name, size = line.split("\t")
base[name] = int(size)
return base
def save_baseline(sizes):
with open(BASELINE, "w") as f:
f.write("# Эталон размеров _CODE (байт); обновление: "
"python3 toolchain/size_check.py --update\n")
for name in sorted(sizes):
f.write(f"{name}\t{sizes[name]}\n")
def main():
update = "--update" in sys.argv
sizes = collect()
if not sizes:
print("size_check: .map не найдены — сначала `make all`")
return 2
if update:
save_baseline(sizes)
print(f"size_check: эталон обновлён ({len(sizes)} программ) → "
f"{os.path.relpath(BASELINE, ROOT)}")
return 0
base = load_baseline()
if not base:
print("size_check: эталона нет — создать: size_check.py --update")
return 2
grew = 0
for name in sorted(set(sizes) | set(base)):
new, old = sizes.get(name), base.get(name)
if new is None:
print(f" {name}: исчез из сборки (был {old} Б)")
elif old is None:
print(f" {name}: НОВЫЙ ({new} Б) — добавить в эталон (--update)")
elif new != old:
mark = "РОСТ" if new > old else "ок, меньше"
print(f" {name}: {old} -> {new} ({new - old:+d} Б) [{mark}]")
if new > old:
grew += 1
if grew:
print(f"size_check: {grew} программ(ы) ВЫРОСЛИ — проверить правки libc")
return 1
print(f"size_check: OK ({len(sizes)} программ, роста нет)")
return 0
if __name__ == "__main__":
sys.exit(main())