mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
3fee0ae7be
tcc 0.9.27's lib/bcheck.c uses __malloc_hook, removed in glibc 2.34, so the pinned build failed on Ubuntu 24.04 (lib/bcheck.c:738). Shards never use -b; the build now passes BCHECK_O= unconditionally, keeping every other libtcc1.a object. The bundle smoke also clears LIBRARY_PATH, CC and GCC_EXEC_PREFIX and checks shards import no bounds-checking symbols. Verified: linux_tcc_bundle_smoke_test passes on Ubuntu 24.04 x86_64 (glibc 2.39, gcc + libc6-dev installed, so host headers exist but are not used) and on Steam Runtime sniper (glibc 2.31). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
176 lines
6.7 KiB
Python
176 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Smoke test of the Linux release toolchain's TinyCC bundle.
|
|
|
|
tools/release_stage.py builds the pinned tcc 0.9.27 source and stages it with
|
|
musl's C headers as overlay_toolchain/tcc/. Its unit tests only check pins and
|
|
arguments. This one does what a player's Steam Deck does: it stages the
|
|
bundle exactly as a release would (tcc, libtcc1.a, tcc's and musl's headers,
|
|
the runtime headers the shard includes), compiles an overlay shard with the
|
|
same code shape psxrecomp-game emits, through compile_overlays.py's own tcc
|
|
path (shim preamble, call_by_address rewrite, exports), for PGXP flavor 0 and
|
|
2, using ONLY the bundle (-nostdinc / -nostdlib), then loads the .so the way
|
|
the runtime does and checks overlay_abi() and the exported function.
|
|
|
|
Linux x86_64 only, and it downloads the pinned sources once (or reuses
|
|
PSX_RELEASE_DL_CACHE). Skips (exit 77) elsewhere or without network.
|
|
|
|
python3 runtime/tests/test_linux_tcc_bundle_smoke.py
|
|
ctest -R linux_tcc_bundle_smoke_test
|
|
"""
|
|
import ctypes
|
|
import os
|
|
import platform
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
ROOT = os.path.abspath(os.path.join(HERE, '..', '..'))
|
|
TOOLS = os.path.join(ROOT, 'tools')
|
|
INCLUDE = os.path.join(ROOT, 'runtime', 'include')
|
|
sys.path.insert(0, TOOLS)
|
|
|
|
SKIP = 77
|
|
|
|
SHARD_BODY = r'''/* Generated by PSXRecomp - PlayStation 1 Static Recompiler */
|
|
|
|
#include "psx_runtime.h"
|
|
|
|
#include "overlay_api.h"
|
|
|
|
void func_80114000(CPUState* cpu);
|
|
void func_8008B4A8(CPUState* cpu);
|
|
|
|
void func_80114000(CPUState* cpu)
|
|
{
|
|
#if defined(PSX_ENABLE_BLOCK_CYCLES) && (defined(__GNUC__) || defined(__clang__))
|
|
__attribute__((cleanup(psx_cyc_bb_defer_cleanup))) int _psx_cyc_bb_guard = 1;
|
|
psx_cyc_bb_defer_begin();
|
|
#endif
|
|
if (cpu->pc != 0u) {
|
|
uint32_t _cont = cpu->pc; cpu->pc = 0;
|
|
switch (_cont) {
|
|
case 0x80114008u: goto block_80114008;
|
|
case 0x80114000u: break;
|
|
default: cpu->pc = _cont; psx_native_bad_entry(cpu, 0x80114000u, _cont); return;
|
|
}
|
|
}
|
|
debug_server_log_call_entry(0x80114000u);
|
|
|
|
block_80114000:
|
|
#ifdef PSX_ENABLE_BLOCK_CYCLES
|
|
if (psx_slice_block(cpu, 0x80114000u, 2u, 0)) return;
|
|
#endif
|
|
#ifdef PSX_ENABLE_BLOCK_CYCLES
|
|
psx_icache_fetch(cpu, 0x80114000u);
|
|
#endif
|
|
#ifdef PSX_ENABLE_BLOCK_CYCLES
|
|
psx_cyc_step(cpu, 0x20000000u);
|
|
#endif
|
|
{ uint32_t _pgx1 = cpu->gpr[29]; cpu->gpr[29] = cpu->gpr[29] + -24;
|
|
PGXP_ALU(0x27BDFFE8u, cpu->gpr[29], _pgx1, 0xFFFFFFE8u); }
|
|
#ifdef PSX_ENABLE_BLOCK_CYCLES
|
|
psx_cyc_step(cpu, 0xA0000000u);
|
|
#endif
|
|
{ uint32_t _pgxa = cpu->gpr[29] + 16; g_debug_last_store_pc = 0x80114004u; psx_store_cycle_barrier(); cpu->write_word(cpu->gpr[29] + 16, cpu->gpr[31]);
|
|
PGXP_STORE(0xAFBF0010u, _pgxa, cpu->gpr[31]); }
|
|
func_8008B4A8(cpu);
|
|
|
|
block_80114008:
|
|
#ifdef PSX_ENABLE_BLOCK_CYCLES
|
|
psx_icache_fetch(cpu, 0x80114008u);
|
|
#endif
|
|
{ uint32_t _pgxa = cpu->gpr[29] + 16; cpu->gpr[31] = psx_cyc_load_word(cpu, cpu->gpr[29] + 16, 31, 0x20000000u);
|
|
PGXP_LOAD(0x8FBF0010u, _pgxa, cpu->gpr[31]); }
|
|
#ifdef PSX_ENABLE_BLOCK_CYCLES
|
|
psx_cyc_bb_defer_flush();
|
|
#endif
|
|
psx_check_interrupts_at(cpu, cpu->gpr[31]);
|
|
cpu->pc = cpu->gpr[31]; return;
|
|
;
|
|
}
|
|
'''
|
|
|
|
|
|
def main():
|
|
if platform.system() != 'Linux' or platform.machine() not in ('x86_64', 'AMD64'):
|
|
print('SKIP: the Linux tcc bundle is x86_64 Linux only')
|
|
return SKIP
|
|
import release_stage as rs
|
|
import compile_overlays as co
|
|
pins = rs.TOOLCHAIN_PINS['linux']
|
|
work = tempfile.mkdtemp(prefix='psx_tcc_bundle_')
|
|
dl = os.environ.get('PSX_RELEASE_DL_CACHE') or os.path.join(work, 'dl')
|
|
try:
|
|
toolchain = os.path.join(work, 'stage', 'overlay_toolchain')
|
|
os.makedirs(toolchain)
|
|
try:
|
|
rs.stage_linux_tcc(toolchain, dl, pins, log=lambda *_: None)
|
|
except rs.StageError as e:
|
|
if 'fetch' in str(e):
|
|
print('SKIP: cannot download the pinned sources: %s' % e)
|
|
return SKIP
|
|
raise
|
|
# The runtime headers, staged as stage_toolchain stages them.
|
|
inc = os.path.join(toolchain, 'include')
|
|
os.makedirs(inc)
|
|
for h in os.listdir(INCLUDE):
|
|
if h.endswith(('.h', '.c.inc')):
|
|
shutil.copy2(os.path.join(INCLUDE, h), inc)
|
|
tcc = os.path.join(toolchain, 'tcc', 'tcc')
|
|
for need in (tcc, os.path.join(toolchain, 'tcc', 'libtcc1.a'),
|
|
os.path.join(toolchain, 'tcc', 'COPYING'),
|
|
os.path.join(toolchain, 'tcc', 'libc-include', 'COPYRIGHT'),
|
|
os.path.join(toolchain, 'tcc', 'libc-include', 'stdint.h')):
|
|
assert os.path.isfile(need), 'missing from the bundle: %s' % need
|
|
|
|
co.load_dispatch_preamble(inc)
|
|
fails = 0
|
|
for flavor in (0, 2):
|
|
src = co.patch_generated_c(SHARD_BODY, 0x80114000, 0x1000)
|
|
c_path = os.path.join(work, 'shard_f%d.c' % flavor)
|
|
with open(c_path, 'w') as f:
|
|
f.write(src)
|
|
out = os.path.join(work, 'shard_f%d.so' % flavor)
|
|
env_keep = dict(os.environ)
|
|
os.environ['PATH'] = '/nonexistent' # no host compiler can help
|
|
for v in ('CPATH', 'C_INCLUDE_PATH', 'LIBRARY_PATH', 'CC', 'GCC_EXEC_PREFIX'):
|
|
os.environ.pop(v, None)
|
|
try:
|
|
ok = co._compile_dll_tcc(c_path, out, [inc], flavor, tcc)
|
|
finally:
|
|
os.environ.clear(); os.environ.update(env_keep)
|
|
if not ok or not os.path.isfile(out):
|
|
print('FAIL flavor %d: the bundled tcc did not build the shard' % flavor)
|
|
fails += 1
|
|
continue
|
|
lib = ctypes.CDLL(out, mode=getattr(ctypes, 'RTLD_LOCAL', 0))
|
|
abi = lib.overlay_abi()
|
|
want = co_abi(inc) | (flavor << 16)
|
|
if abi != want:
|
|
print('FAIL flavor %d: overlay_abi() = 0x%X, want 0x%X' % (flavor, abi, want))
|
|
fails += 1
|
|
if not hasattr(lib, 'func_80114000'):
|
|
print('FAIL flavor %d: func_80114000 not exported' % flavor)
|
|
fails += 1
|
|
nm = os.popen('nm -D --undefined-only %s' % out).read()
|
|
if 'bound' in nm or '__bound' in nm:
|
|
print('FAIL flavor %d: the shard references bounds-checking symbols' % flavor)
|
|
fails += 1
|
|
print('ok flavor %d: shard built by the bundled tcc + musl and loaded (abi 0x%X)'
|
|
% (flavor, abi))
|
|
return 1 if fails else 0
|
|
finally:
|
|
shutil.rmtree(work, ignore_errors=True)
|
|
|
|
|
|
def co_abi(inc):
|
|
import re
|
|
with open(os.path.join(inc, 'overlay_api.h')) as f:
|
|
m = re.search(r'#define\s+PSX_OVERLAY_ABI_VERSION\s+(\d+)', f.read())
|
|
return int(m.group(1))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|