Skip to content

fix(jit): ARM64 bundle and tail encodings; v_random honours its seed (Refs #989) - #1215

Merged
dmitrii-f-t27 merged 1 commit into
mainfrom
spec/trinity-s12-jit
Oct 1, 2026
Merged

dmitrii-f-t27 merged 1 commit into
mainfrom
spec/trinity-s12-jit

Conversation

@dmitrii-f-t27

@dmitrii-f-t27 dmitrii-f-t27 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

S12 of #988 (Refs #989). While probing the capability cards' acceptance commands for the capability index, the VM example printed this for cos(v0, unbind(bind(v0, v1), v1)):

Result: f0 = -1.0000 (expected ~1.0)

Two defects were behind it, and the tests had been bent around both.

1. Two ARM64 encodings in src/jit_arm64.zig

encoder emitted is effect
cmgt_16b_zero 0x4E20A800 CMLT #0 (opcode 01010) the SIMD bundle returns 0 for every positive sum: 371 of 1024 trits wrong on random input
ldrsbReg 0x38E06800 LDRSB Wt, which zeroes bits 63:32 the scalar tails do 64-bit MUL/ADD/CMP/SCVTF on it, so a −1 trit past the last full 16 becomes 4294967295

The second one broke the fused cosine for every vector with a negative trit in its tail. Since 59049 % 16 = 9, that means every MAX_TRITS vector, and it is why the VM's v_cosine called two identical vectors −1.0. It also broke the bundle tail.

The fixes are 0x4E208800 (CMGT) and 0x38A06800 (LDRSB Xt). SMULL, which the dot products use, reads the same low 32 bits, so nothing that already worked can change.

The existing tests used all-ones vectors or one fixed vector, and the bundle test checked no result at all ("to be verified via integration tests"). Each JIT file now has a differential test against a scalar reference, over random trits and ten dimensions (1 to 59049, with and without a tail):

  • src/jit_arm64.zig: dot (scalar, SIMD, hybrid), hamming, fused cosine, bind (direct, SIMD), bundle. It fails with either encoding restored.
  • src/jit_x86_64.zig and src/jit.zig: dot, bind, bundle. Every x86-64 kernel already agreed; the test fails on a planted setg/setl swap.

Off its own architecture each test skips (error.SkipZigTest) instead of returning early as a pass.

2. v_random ignored its seed

execVRandom had _ = seed; // TODO: actually use seed and drew seed 0. So v_random 111 and v_random 222 were the same vector, and every bind/unbind program bound a vector with itself. It now uses the seed.

The tests that had been bent around both

where workaround now
src/vm.zig, two tests vm.jit_enabled = false; // Disable JIT (has bug in cosineSimilarity) JIT on
src/e2e_test.zig, VM roundtrip @abs(f0) > 0.3 ("JIT engine may return negative cosine (known sign issue)") f0 > 0.7. The expected value is √(2/3) ≈ 0.82, because v1 is zero on a third of its trits
src/e2e_test.zig, VERDICT check 4 @abs(f0) > 0.99 f0 > 0.99
examples/vm.zig "expected ~1.0" "expected ~0.82"; it prints 0.8155

src/vsa_jit.zig: for a zero vector the fused kernel computes 0/0 = NaN, the only way it can produce NaN. It now returns 0, as the three-dot fallback and vsa.cosineSimilarity do.

The gate

What skips now depends on the machine, so specs/reproduce/headless.t27 sets its floors per architecture:

passed skipped at most
x86_64 2773 3
aarch64 2763 13

The judge picks the floors by the record's host and refuses to judge another architecture (ARCH); the self-check plants both architectures and a third.

reproduce.yml now runs the headless profile on both architectures, aarch64 on ubuntu-24.04-arm, with one artifact each. With x86_64 alone, no runner could execute the ARM64 kernels; this is how both encodings survived.

Measured: Linux aarch64 in a container with empty caches gives build 94/94 with 46/46 installs, and test 135/135 steps with 2763/2776 passed and 13 skipped, no violation. x86_64 is this PR's CI.

{
  "version": 1,
  "head_sha": "acaad5eeef91846f921eea84eaff50198231830d",
  "summary": "The ARM64 JIT computed wrong bundles and cosines and the VM drew one random vector for every seed; both are fixed, the tests that had been bent around them assert the right values again, and the profile now runs on aarch64 as well as x86_64.",
  "changes": [
    "Fixed two encodings in src/jit_arm64.zig: CMGT #0 was emitted as CMLT #0, and LDRSB loaded into W under 64-bit tail arithmetic.",
    "Added differential tests against a scalar reference to src/jit_arm64.zig, src/jit_x86_64.zig and src/jit.zig, over random trits and dimensions with and without a tail; each skips off its own architecture.",
    "Made v_random use its seed, mapped the fused cosine's 0/0 to 0, and restored the VM and e2e assertions that had disabled the JIT or taken the absolute value of the cosine.",
    "Made the headless floors per architecture and ran the profile on ubuntu-24.04-arm as well as ubuntu-latest."
  ],
  "tests": [
    {
      "command": "zig test src/jit_arm64.zig on Linux aarch64",
      "status": "passed",
      "result": "25 of 25 tests, including the differential test; with CMLT restored it reports bundle_simd wrong at every dimension, with LDRSB Wt restored it reports cosine and bundle tails wrong.",
      "evidence": "Ubuntu 24.04 aarch64 container, Zig 0.15.2, run locally before the commit"
    },
    {
      "command": "zig test src/jit_x86_64.zig and src/jit.zig -target x86_64-linux-gnu, run in an amd64 container",
      "status": "passed",
      "result": "6 of 6 and 7 of 7 tests; a planted setg/setl swap in jit.zig makes the differential test report bundle wrong at nine dimensions.",
      "evidence": "Run locally under emulation before the commit"
    },
    {
      "command": "python3 tools/reproduce.py headless --zig zig-0.15.2 in ubuntu:24.04 aarch64 with empty caches",
      "status": "passed",
      "result": "Build 94/94 with 46/46 installs; test 135/135 steps, 2763/2776 passed and 13 skipped, 66 run steps with tests and one empty, no compile failure, both commands exit 0; no violation against the aarch64 floors.",
      "evidence": "Linux aarch64 container, head acaad5eeef91846f921eea84eaff50198231830d, fresh zig caches, 2 min 36 s"
    }
  ],
  "limitations": [
    "The x86_64 floors (2773 passed, 3 skipped) are derived from the counts of the added tests and the previous x86_64 run; this PR's x86_64 job measures them.",
    "Most older tests in src/jit_arm64.zig and src/jit_x86_64.zig still return early off their architecture and count as passed; the new differential tests skip instead.",
    "The aarch64 leg depends on GitHub's ubuntu-24.04-arm runners."
  ],
  "tags": [
    "Engineering",
    "Verification",
    "CI"
  ],
  "blog": {
    "title": "One opcode bit made two identical vectors look opposite",
    "summary": "An ARM64 CMLT where a CMGT was meant, a 32-bit load under 64-bit arithmetic, and a random instruction that ignored its seed, hidden for months by tests bent around them.",
    "outline": [
      "The symptom: the VM said cos(v, v) = -1.0, and the tests had been relaxed to accept it.",
      "The causes: two ARM64 encodings and a seed that was never read, found by testing every kernel against a scalar reference.",
      "The gate: differential tests that skip off their architecture, floors per architecture, and an ARM64 runner."
    ]
  }
}

Dmitrii Fedorov (@dmitrii-f-t27)

…(Refs #989)

S12 of #988. Probing the capability acceptance commands, the VM example printed
"f0 = -1.0000 (expected ~1.0)" for cos(v0, unbind(bind(v0, v1), v1)). Two defects
were behind it, and the tests had been bent around both.

src/jit_arm64.zig, two encodings:
- cmgt_16b_zero emitted 0x4E20A800, which is CMLT #0, so the SIMD bundle returned 0
  for every positive sum (371 of 1024 trits wrong on random input). Now 0x4E208800.
- ldrsbReg emitted LDRSB Wt, which zeroes bits 63:32, while the scalar tails do 64-bit
  MUL, ADD, CMP and SCVTF on the result: a -1 trit past the last full 16 became
  4294967295. The fused cosine was wrong for every vector with a negative trit in its
  tail -- 59049 % 16 = 9, so every MAX_TRITS vector -- and so was the bundle tail. Now
  LDRSB Xt; SMULL, which the dot products use, reads the same low 32 bits.
The existing tests used all-ones or one fixed vector, and the bundle test checked no
result. Each JIT file now has a differential test against a scalar reference over random
trits and ten dimensions with and without a tail. It fails with either encoding restored;
the x86-64 ones fail on a planted setg/setl swap. On x86-64 every kernel already agreed.

src/vsa_jit.zig: the fused kernel's 0/0 for a zero vector is NaN; return 0, as the
three-dot fallback and vsa.cosineSimilarity do.

src/vm.zig: v_random ignored its immediate ("TODO: actually use seed") and drew seed 0,
so v_random 111 and v_random 222 were one vector, and every bind/unbind program bound a
vector with itself. It uses the seed now. Tests that worked around the cosine bug are
restored: two VM tests ran with the JIT disabled ("has bug in cosineSimilarity"), and
two e2e checks took |f0| ("known sign issue"). They assert the sign again, the roundtrip
f0 > 0.7 (sqrt(2/3), about 0.82, since v1 is zero on a third of its trits).
examples/vm.zig expects about 0.82 and prints 0.8155.

specs/reproduce/headless.t27: what skips depends on the machine, so the floors are per
architecture -- x86_64 2773 passed and at most 3 skipped, aarch64 2763 and 13 -- and the
judge picks them by the record's host and refuses another architecture (ARCH).
reproduce.yml runs the profile on both, aarch64 on ubuntu-24.04-arm, one artifact each.

Исправлены кодировки ARM64 для bundle и хвостов; v_random учитывает seed.

S12 эпика #988. При проверке acceptance-команд пример VM напечатал «f0 = -1.0000
(expected ~1.0)» для cos(v0, unbind(bind(v0, v1), v1)). За этим стояли две ошибки, и
тесты были подогнаны под обе.

src/jit_arm64.zig, две кодировки:
- cmgt_16b_zero выдавал 0x4E20A800, то есть CMLT #0, и SIMD-bundle возвращал 0 для
  любой положительной суммы (371 из 1024 тритов неверно на случайных данных). Теперь
  0x4E208800.
- ldrsbReg выдавал LDRSB Wt, который обнуляет биты 63:32, а скалярные хвосты делают над
  результатом 64-битные MUL, ADD, CMP и SCVTF: трит -1 после последней полной шестнадцатки
  становился 4294967295. Fused cosine ошибался для любого вектора с отрицательным тритом в
  хвосте -- 59049 % 16 = 9, то есть для каждого вектора MAX_TRITS, -- как и хвост bundle.
  Теперь LDRSB Xt; SMULL, которым пользуются скалярные произведения, читает те же младшие
  32 бита.
Прежние тесты брали векторы из единиц или один фиксированный вектор, а тест bundle вообще не
проверял результат. В каждом JIT-файле теперь есть дифференциальный тест против скалярного
эталона на случайных тритах и десяти размерностях с хвостом и без. Он падает при возврате
любой из двух кодировок; x86-64 тесты падают на подложенной перестановке setg/setl. На
x86-64 все ядра и так совпадали.

src/vsa_jit.zig: 0/0 fused-ядра для нулевого вектора -- это NaN; возвращаем 0, как запасной
путь с тремя скалярными произведениями и vsa.cosineSimilarity.

src/vm.zig: v_random игнорировал аргумент («TODO: actually use seed») и брал seed 0, так
что v_random 111 и v_random 222 были одним вектором, и каждая программа bind/unbind
связывала вектор сам с собой. Теперь seed учитывается. Тесты, обходившие ошибку косинуса,
возвращены: два теста VM шли с выключенным JIT («has bug in cosineSimilarity»), две
проверки e2e брали |f0| («known sign issue»). Они снова проверяют знак, roundtrip --
f0 > 0.7 (sqrt(2/3), около 0.82, потому что v1 нулевой на трети тритов).
examples/vm.zig ждёт около 0.82 и печатает 0.8155.

specs/reproduce/headless.t27: что пропускается, зависит от машины, поэтому нижние границы
заданы по архитектурам -- x86_64: 2773 пройдено и не больше 3 пропусков, aarch64: 2763 и
13, -- судья выбирает их по host записи и отказывается судить другую архитектуру (ARCH).
reproduce.yml гоняет профиль на обеих, aarch64 на ubuntu-24.04-arm, по артефакту на каждую.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the status:in-progress 🔵 Agent working label Oct 1, 2026
@dmitrii-f-t27
dmitrii-f-t27 merged commit 419226c into main Oct 1, 2026
26 of 30 checks passed
@github-actions github-actions Bot added status:completed Done and removed status:in-progress 🔵 Agent working labels Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant