Skip to content

QLISP v2.8.2 — кэш раскрытия макросов, DEBUG-TRACE, tail-call optimization

Choose a tag to compare

@FQingLars FQingLars released this 04 Oct 22:21
· 2 commits to main since this release

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 чисто на затронутых сценариях.