mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Log once per switch when rt_knob reads a value other than 0, 1 or ab as on.
perf.c rt_knob reads "0" as off, "ab" as the A/B blocks and everything else as on, without a word: "cache_die = off", "crash_filter_guard = false" and "one_per_core = 0 ; x" all read as 1 (the audit's probe, Fv/knob.exe, cut rt_knob out with cfg.c: three 1s and no log line). A player who wrote such a value meant the opposite and had no way to learn it from the log. rt_knob now logs "cfg: NX2NAVI_<name> = '<value>' is not 0, 1 or ab: read as 1" the first time it reads such a value for a switch. The value still reads as 1: reading words as 0 changes what a written setting does and waits for approval with F-8. Most of the 109 call sites keep the result in a static but some read it per call, so the warned names are kept in a table of 32 pointers (the names are string literals) filled by compare-and-swap; 0, 1 and ab pay only one more string compare. New tests/rt_knob_check.py cuts rt_knob and its table out of perf.c and builds them with stand-ins for the settings and the log: 0, 1, ab, empty and unset read as before without a line; off, false and "0 ; a note" read as 1 and log once each however often they are read. docs/building.md describes it. The fault was shown before the change by the audit's probe; rt_knob_check passes, and perf.c compiles with the dev flags and with -Wall -Wextra without new warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -721,6 +721,11 @@ which builds the header alone: a walk before, after or around a frame, one still
|
||||
replay part first, capped by the overlap and the parts, and 100,000 random frames in which the census part never passes
|
||||
the frame and the parts keep their sum. No GPU, no game files.
|
||||
|
||||
A change to how the on/off switches read their value (`runtime/perf.c` `rt_knob`) passes `python tests\rt_knob_check.py`,
|
||||
which cuts the function out of the file with its warning table and builds it with stand-ins for the settings and the
|
||||
log: 0, 1 and ab read as before without a line, and any other value reads as 1 and is logged once per switch. No game
|
||||
files.
|
||||
|
||||
The lifter (`recomp/`) and the CPU headers (`runtime/cpu.h`, `runtime/simd.h`) are checked against Unicorn. Every lifter
|
||||
change passes these, all run with `.venv`:
|
||||
|
||||
|
||||
@@ -939,12 +939,32 @@ int rt_ab(void) {
|
||||
return k == 1 || k == 2;
|
||||
}
|
||||
|
||||
// rt_knob reads "0" as off, "ab" as the A/B blocks and anything else as on, so "off", "false" or "0 ; a note" turn a
|
||||
// switch on. A value other than 0, 1 and ab is logged once per switch; the names are string literals, so the table
|
||||
// holds their pointers (a full table logs no more). The value is still read as on: taking such words as off would
|
||||
// change what a written setting does.
|
||||
#define KNOB_WARNED 32
|
||||
static const char *knob_warned[KNOB_WARNED];
|
||||
|
||||
static void knob_warn(const char *name, const char *key, const char *value) {
|
||||
for (int i = 0; i < KNOB_WARNED; i++) {
|
||||
const char *cur = __atomic_load_n(&knob_warned[i], __ATOMIC_ACQUIRE);
|
||||
if (!cur && __atomic_compare_exchange_n(&knob_warned[i], &cur, name, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
|
||||
rt_log("cfg: %s = '%s' is not 0, 1 or ab: read as 1", key, value);
|
||||
return;
|
||||
}
|
||||
if (cur == name) return; // logged already (another thread may have taken this slot for it just now)
|
||||
}
|
||||
}
|
||||
|
||||
int rt_knob(const char *name, const char *def) {
|
||||
char key[64];
|
||||
snprintf(key, sizeof key, "NX2NAVI_%s", name);
|
||||
const char *e = cfg_get(key);
|
||||
if (!e || !*e) e = def;
|
||||
return !strcmp(e, "ab") ? 2 : strcmp(e, "0") != 0;
|
||||
if (!strcmp(e, "ab")) return 2;
|
||||
if (strcmp(e, "0") && strcmp(e, "1")) knob_warn(name, key, e);
|
||||
return strcmp(e, "0") != 0;
|
||||
}
|
||||
|
||||
// NX2NAVI_ONE_PER_CORE=1: the process keeps the first logical processor of each physical core, so busy threads (the
|
||||
|
||||
108
tests/rt_knob_check.py
Normal file
108
tests/rt_knob_check.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Build and run runtime/perf.c's rt_knob (cut out of the file with its warning table) with stand-ins for the settings and
|
||||
the log: 0, 1 and ab read as before and log nothing; a value such as "off", "false" or "0 ; a note" still reads as 1
|
||||
and is logged once per switch, however often the switch is read.
|
||||
|
||||
No GPU, no game files, any Python.
|
||||
|
||||
Usage: rt_knob_check.py
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
MSYS_BIN = Path(r'C:\msys64\ucrt64\bin')
|
||||
|
||||
HARNESS = r'''
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static const char *vals[][2] = {{"NX2NAVI_A", "0"}, {"NX2NAVI_B", "1"}, {"NX2NAVI_C", "ab"}, {"NX2NAVI_D", "off"},
|
||||
{"NX2NAVI_E", "false"}, {"NX2NAVI_F", "0 ; a note"}, {"NX2NAVI_G", ""}};
|
||||
static const char *cfg_get(const char *key) {
|
||||
for (size_t i = 0; i < sizeof vals / sizeof *vals; i++)
|
||||
if (!strcmp(vals[i][0], key)) return vals[i][1];
|
||||
return NULL;
|
||||
}
|
||||
static int logs;
|
||||
static char last[256];
|
||||
static void rt_log(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(last, sizeof last, fmt, ap);
|
||||
va_end(ap);
|
||||
logs++;
|
||||
}
|
||||
|
||||
%s
|
||||
|
||||
static int fails;
|
||||
static void expect(const char *what, int got, int want) {
|
||||
if (got != want) printf("FAIL %%s: %%d, expected %%d\n", what, got, want), fails++;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
for (int round = 0; round < 3; round++) {
|
||||
expect("0", rt_knob("A", "1"), 0);
|
||||
expect("1", rt_knob("B", "0"), 1);
|
||||
expect("ab", rt_knob("C", "0"), 2);
|
||||
expect("unset, default 1", rt_knob("H", "1"), 1);
|
||||
expect("unset, default 0", rt_knob("H2", "0"), 0);
|
||||
expect("empty, default 0", rt_knob("G", "0"), 0);
|
||||
}
|
||||
expect("lines for 0, 1, ab and defaults", logs, 0);
|
||||
expect("off", rt_knob("D", "0"), 1);
|
||||
expect("a line for off", logs, 1);
|
||||
if (strcmp(last, "cfg: NX2NAVI_D = 'off' is not 0, 1 or ab: read as 1"))
|
||||
printf("FAIL the line: %%s\n", last), fails++;
|
||||
for (int i = 0; i < 5; i++) rt_knob("D", "0");
|
||||
expect("off read again", logs, 1);
|
||||
expect("false", rt_knob("E", "1"), 1);
|
||||
expect("0 with a note", rt_knob("F", "0"), 1);
|
||||
expect("a line each", logs, 3);
|
||||
if (!strstr(last, "'0 ; a note'")) printf("FAIL the line: %%s\n", last), fails++;
|
||||
for (int i = 0; i < 3; i++) rt_knob("E", "1"), rt_knob("F", "0");
|
||||
expect("each once", logs, 3);
|
||||
if (fails) return 1;
|
||||
printf("rt_knob_check: all checks passed\n");
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
def compiler():
|
||||
"""$CC, else MSYS2 UCRT64's clang, else clang on PATH (as tests/diff/common.py picks it, without its Unicorn)."""
|
||||
if os.environ.get('CC'):
|
||||
return os.environ['CC']
|
||||
if os.name == 'nt' and (MSYS_BIN / 'clang.exe').exists():
|
||||
return str(MSYS_BIN / 'clang.exe')
|
||||
return shutil.which('clang') or 'clang'
|
||||
|
||||
|
||||
def cut():
|
||||
"""perf.c from the warning table's #define to the end of rt_knob."""
|
||||
src = (ROOT / 'runtime' / 'perf.c').read_text(encoding='utf-8')
|
||||
m = re.search(r'#define KNOB_WARNED .*?\nint rt_knob\(const char \*name, const char \*def\) \{\n.*?\n\}\n', src, re.S)
|
||||
assert m, 'runtime/perf.c: the rt_knob block (KNOB_WARNED ... rt_knob) was not found'
|
||||
return m.group(0)
|
||||
|
||||
|
||||
def main():
|
||||
work = ROOT / 'build' / 'test' / 'rt_knob'
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
c = work / 'rt_knob_check.c'
|
||||
c.write_text('// Made by tests/rt_knob_check.py from runtime/perf.c.\n' + HARNESS % cut(), encoding='utf-8')
|
||||
exe = work / ('rt_knob_check.exe' if os.name == 'nt' else 'rt_knob_check')
|
||||
env = dict(os.environ)
|
||||
if os.name == 'nt' and MSYS_BIN.exists():
|
||||
env['PATH'] = str(MSYS_BIN) + os.pathsep + env.get('PATH', '')
|
||||
subprocess.check_call([compiler(), '-std=gnu11', '-O1', '-Wall', '-Werror', str(c), '-o', str(exe)], env=env)
|
||||
return subprocess.run([str(exe)], env=env).returncode
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user