Repository navigation
QLISP v2.8.2 — кэш раскрытия макросов, DEBUG-TRACE, tail-call optimization
QLISP 2.8.2 — Release Notes
Тег: v2.8.2 · Платформы: Linux x86_64, Windows x86_64
Бинарники — сборка CI (Linux: GCC/Ninja/OpenBLAS/OpenMP; Windows: MSYS2 MinGW64), коммит d716a6a ветки main компиляторного репозитория (CI run 37146790204, зелёный). Состав: qlisp (REPL/интерпретатор + JIT), qlisp-lsp, qvalent.
| Файл | Платформа | Состав |
|---|---|---|
qlisp-linux-x86_64 |
Linux x86_64 | REPL/интерпретатор + JIT |
qlisp-lsp-linux-x86_64 |
Linux x86_64 | Language Server |
qvalent-linux-x86_64 |
Linux x86_64 | пакетный менеджер |
qlisp-windows-x86_64.zip |
Windows x86_64 | qlisp.exe, qlisp-lsp.exe, qvalent.exe |
SHA256SUMS — контрольные суммы всех файлов.
Что нового
Кэш раскрытия макросов (FIXED)
Класс-A утечки scratch: let* или любой макрос в цикле давал ~1 КБ на оценку, линейно (дифференциал: 22 МБ у let против 217 МБ у let* на 200k итераций). Причина: eval_cons звал macroexpand_1 на каждой оценке, а expand() чеканил всё дерево раскрытия заново в root-арене. Кэш в MacroExpander: одно раскрытие на уникальную форму-источник, hit по контенту (у EVAL адреса scratch-форм перерабатываются), запись держит root-клон источника, инвалидация на defmacro*/SLOT-SET, macolet-протяжения вне кэша. После фикса: let* 22.7 МБ против let 22.6 МБ — дифференциал схлопнулся.
DEBUG-TRACE — хронология событий владения
Четыре новых примитива и модуль src/core/trace.{hpp,cpp}: ограниченное кольцо на 4096 записей (сам трейс не течёт; выключенный — один relaxed load), категории scope/clone/anchor/expand/macro.
(DEBUG-TRACE) ;; → nil (выключен по умолчанию)
(DEBUG-TRACE t) ;; → T (все категории)
(DEBUG-TRACE '(scope clone anchor)) ;; → T (только указанные)
(TRACE-STATS)
;; → (("recorded" . 9) ("dropped" . 0) ("stored" . 9) ("capacity" . 4096)
;; ("scope" . 5) ("clone" . 2) ("anchor" . 2) ("expand" . 0) ("macro" . 0))
(TRACE-DUMP) ;; список строк "<tick> <cat> <text>", oldest-first
(TRACE-CLEAR) ;; → nilВключение также через QLISP_TRACE=scope,clone,anchor,expand,macro. Точки эмиссии: reset_scratch (границы форм), clone_to (мост disposable→stable), take/release якорей, кэш раскрытия, defmacro/defcompiler-macro. Счётчики вынесены в TRACE-STATS, а не в mem-report — форма mem-report зашита в существующие тесты.
Tail-call optimization для self-хвостовых вызовов
Хвостовой вызов пере-биндит кадр вместо того, чтобы класть новый (параметры копируются в heap-стор, итерация освобождается через restore). Гейт: при живых tape-узлах TCO для итерации выключается; let/let* разворачиваются в транполине до 8 уровней, глубже — честный фоллбэк.
Замер (RSS, рекурсивный train-эпох): было 34.8/51.2/67.5 МБ на N=100/300/500 против плоского while ~25 МБ → стало 27.4/27.4/27.4 МБ. Хвостовая рекурсия на 200 000: (tc-sum 200000 0) → 20000100000, без роста стека.
Вне среза: взаимные хвостовые вызовы, нативная рекурсия JIT, хвосты глубже let/if/progn (cond, when).
Известная проблема этой версии
&optional со значением по умолчанию теряет его: (defun f (&optional (k 0)) ...) биндит k в nil ((+ k 1) → «argument is not a number»). Причина — нормализация списка параметров в defun/defuse отбрасывала default-форму. Исправлено в 2.8.3.
Тесты: 78 сюитов в tests/ (+9 в test/); новые expansion_cache.qlsp (7), debug_trace.qlsp (7), tail_call.qlsp (18). Корпус и аудитор зелёные; ASan/LSan чисто на затронутых сценариях.