List kern_ap_stub_ymm as dispatched in check_isa.py, so that a --cpu v2 build passes its instruction check again.

kernel.c's preemption stub has two bodies since 00f06e66: kern_ap_stub_ymm saves YMM0-15 with vmovdqa and is chosen only when ap_init's cpu_keeps_ymm() finds AVX, OSXSAVE and XCR0 bits 1 and 2, and kern_ap_stub_xmm serves every other processor. tools/check_isa.py's DISPATCHED listed only sha256.c's blocks_ni, so it took the YMM body for an undispatched AVX function and stopped every tools/build_game.py --cpu v2 build. The entry names that check. Players' builds (make_game.py) run no such check and were not affected.

Reproduced before the change: kernel.c built with the ucrt64 clang at -march=x86-64-v2 failed tools/check_isa.py with "kern_ap_stub_ymm ...: vmovdqa x32, vzeroupper x1", and passes now. Checks that passed: the new tests/check_isa_test.py (kernel.c and sha256.c built with -march=x86-64-v2 and without -march pass; an AVX function not in DISPATCHED still fails; it fails before the change) and tools/check_content.py --staged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 17:23:01 -05:00
parent 56db0d485c
commit 6b051eea58
3 changed files with 56 additions and 1 deletions

View File

@@ -108,7 +108,8 @@ of them.
`runtime/fma.c`, a correctly rounded software fma the FP lowering needs, and runs slower. Inline assembly,
target attributes and intrinsics are not bound by `-march`, so a v2 build runs `tools/check_isa.py` over the runtime
objects before it links: any AVX, BMI or other instruction above x86-64-v2 outside a function listed there as
dispatched by a runtime CPU check stops the build.
dispatched by a runtime CPU check stops the build. `python tests\check_isa_test.py` builds the files with such
functions both ways and checks that they pass.
`gen.py` sizes its emit pool from the CPU count and the free memory; `--jobs N` sets it, and so does the environment variable `NX2NAVI_LIFT_JOBS=N`, which the installer's lift step honours too (it has no `--jobs`). When a worker runs out of memory, the pool restarts with half the workers (`emit: out of memory with N workers, retrying with M`), down to one; only an out-of-memory error with one worker fails the step.

53
tests/check_isa_test.py Normal file
View File

@@ -0,0 +1,53 @@
"""tools/check_isa.py on the runtime files that hold instructions above x86-64-v2 behind a runtime CPU check
(runtime/kernel.c kern_ap_stub_ymm, runtime/sha256.c blocks_ni): built as tools/build_game.py --cpu v2 builds the
runtime (-march=x86-64-v2) and as players' builds do (no -march), they pass, and a function with such an instruction
and no entry in DISPATCHED still fails. Needs the MSYS2 UCRT64 clang and an objdump (skipped without them); no game
files.
Run: python tests/check_isa_test.py
"""
import shutil
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / 'tools'))
import check_isa # noqa: E402
UCRT = Path(r'C:\msys64\ucrt64\bin')
CC = next((str(c) for c in (UCRT / 'clang.exe', shutil.which('clang')) if c and Path(c).is_file()), None)
OBJDUMP = check_isa.find_objdump(str(UCRT / 'objdump.exe') if (UCRT / 'objdump.exe').is_file() else None)
FLAGS = ['-O2', '-g', '-fno-strict-aliasing', '-w'] # tools/build_game.py RT_FLAGS, without its warnings
@unittest.skipUnless(CC and OBJDUMP, 'needs clang and objdump')
class CheckIsaTest(unittest.TestCase):
def setUp(self):
td = tempfile.TemporaryDirectory()
self.addCleanup(td.cleanup)
self.tmp = Path(td.name)
def compile(self, src, march, name=None):
out = self.tmp / f'{name or Path(src).stem}{march or ""}.o'
subprocess.run([CC, *FLAGS, *([march] if march else []), '-I', str(ROOT / 'runtime'), '-c', str(src),
'-o', str(out)], check=True, capture_output=True, timeout=600)
return out
def test_the_dispatched_functions_pass(self):
for march in ('-march=x86-64-v2', None):
objs = [self.compile(ROOT / 'runtime' / f, march) for f in ('kernel.c', 'sha256.c')]
self.assertEqual(check_isa.scan(objs, OBJDUMP), {}, march)
def test_an_undispatched_avx_function_fails(self):
src = self.tmp / 'wide.c'
src.write_text('void wide(void) { __asm__ volatile("vzeroupper"); }\n', encoding='utf-8')
hits = check_isa.scan([self.compile(src, '-march=x86-64-v2')], OBJDUMP)
self.assertEqual([f for _, f in hits], ['wide'])
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@@ -37,6 +37,7 @@ WIDE = re.compile(r'%[yz]mm\d|%k[0-7]\b')
# Function -> the runtime check that keeps it off CPUs without the instructions.
DISPATCHED = {
'blocks_ni': 'runtime/sha256.c sha256_blocks: __builtin_cpu_supports("sha") && __builtin_cpu_supports("sse4.1")',
'kern_ap_stub_ymm': 'runtime/kernel.c ap_init: cpu_keeps_ymm() (CPUID AVX and OSXSAVE, XCR0 bits 1 and 2)',
}