// SPDX-License-Identifier: MIT OR Apache-2.0

Оптимизация field-cache — руководство пользователя

Зонтичный план 123 (активны V1-V5). Обновлено 2026-06-02.

Что она делает

Компилятор Nova автоматически кэширует чтения @field и вызовы @<pure_method>() в телах методов, устраняя избыточные разыменования указателей self->X в генерируемом .c-выводе. Методы на горячем пути (ReadBuffer, StringBuilder, итераторы HashMap) обычно получают снижение числа разыменований указателей на 15-30% в debug-сборках под -O0.

Оптимизация прозрачна — гарантируется семантическая эквивалентность. Вы можете отключить её в любой момент с помощью переменных окружения (см. «Аварийные люки» ниже).

Что кэшируется

Совместно работают четыре уровня:

D217 V1 — прямой кэш полей (Plan 123.1)

Для ro-полей, читаемых 2+ раза → кэшируются в начале тела метода:

fn Point @sum_squared() -> int {
    @x * @x + @y * @y      // Before: 4 pointer derefs
}
// After D217 V1:
//   ro _at_x = @x; ro _at_y = @y; _at_x * _at_x + _at_y * _at_y

D218 V2 — LICM-вынос из цикла (Plan 123.2)

Инвариантные чтения полей внутри циклов → выносятся непосредственно перед циклом:

fn Buf @sum_n(n int) -> int {
    mut total = 0
    for i in 0..n {
        total = total + @data[i] + @size   // @size invariant
    }
    total
}
// D218 hoists @size immediately before for-loop.

D219 V3 — кэш чистых вызовов (Plan 123.3)

Вызовы @<pure_method>() 2+ раза → кэшируются:

#pure
fn Vec3 @magnitude_sq() -> int => @x * @x + @y * @y + @z * @z

fn Vec3 @double() -> int {
    @magnitude_sq() + @magnitude_sq()   // Cached single call.
}

D217 V4 — кэш цепочек (Plan 123.4)

Вложенные доступы @a.b.c 2+ раза → кэшируются:

fn Outer @check() -> int {
    @inner.cfg.limit + @inner.cfg.limit + @inner.cfg.limit
    // D217 V4 caches @inner.cfg.limit once.
}

Как посмотреть решения кэша

Флаг CLI --explain-cache у nova check:

nova check src/buffer.nv --explain-cache

Пример вывода:

=== src/buffer.nv ===
  fn ReadBuffer @try_read_u32_le — 4 cache(s):
    D217 field cache: data, pos
    D219 pure-call cache: len
    D217 V4 chain cache: @header.signature

field-cache total: 1 method(s) affected, 4 cache(s) inserted

Аварийные люки

Отключить всё кэширование:

NOVA_FIELD_CACHE=0 nova build

Отключить отдельные уровни:

NOVA_FIELD_CACHE_LICM=0    # disable D218 LICM
NOVA_FIELD_CACHE_PURE=0    # disable D219 pure-call
NOVA_FIELD_CACHE_CHAIN=0   # disable D217 V4 chain

Настроить пороги:

NOVA_FIELD_CACHE_THRESHOLD=3        # default 2 (D217 V1)
NOVA_FIELD_CACHE_LICM_THRESHOLD=3   # default 2 (D218)
NOVA_FIELD_CACHE_PURE_THRESHOLD=3   # default 2 (D219)
NOVA_FIELD_CACHE_CHAIN_THRESHOLD=3  # default 2 (D217 V4)

Потолок кэшей на функцию (бюджет кадра стека):

NOVA_FIELD_CACHE_MAX=12   # default 8 — total across all 4 layers

Ожидаемая производительность

  • Сборки -O0: снижение числа разыменований указателей на горячих путях на 15-30%.
  • Сборки -O2: меньший выигрыш (C-компилятор уже делает NoAlias-based CSE). Всё же измеримый из-за детерминированной эмиссии Nova.
  • Кросс-платформенность: идентичный вывод AST на Windows MSVC / Linux clang / macOS clang.
  • Влияние на кадр стека: ≤ 8 кэш-локальных на функцию × 8 байт ≈ 64 байта.

Семантическая эквивалентность

Все 4 уровня — это чистые преобразования AST→AST. Отключение любого уровня (или всех) даёт идентичное наблюдаемое поведение:

  • stdout / stderr идентичны.
  • Паника возбуждается в тех же условиях.
  • Эффекты файловой системы / сети идентичны.
  • Поведение GC идентично.

Проверено дифференциальным тестированием (все фикстуры nova_tests/plan123_* проходят PASS идентично при включённом и отключённом кэше).

Ссылки на spec

  • D217 (Plan 123.1) — базовый кэш полей + расширение цепочек V4.
  • D218 (Plan 123.2) — семантика LICM.
  • D219 (Plan 123.3) — кэш чистых вызовов.
  • D24 (Plan 33.1+33.2) — инфраструктура чистоты #pure.

Followup’ы и будущие версии

  • V5 (Plan 123.5, эта) — LSP code-lens (отложено) + флаг CLI.
  • V6 (Plan 123.6) — телеметрия + production-раскатка + полный набор флагов CLI.
  • V7 (Plan 123.7) — межпроцедурный анализ (IPA) для точной инвалидации.