From f53d6053653ff1f12ade883a6b988db881485a00 Mon Sep 17 00:00:00 2001 From: lucannez64 <58551020+lucannez64@users.noreply.github.com> Date: Thu, 15 Oct 2026 23:22:15 +0200 Subject: [PATCH] Keep Iridium LLVM headers and runtime library on the same major --- docs/TESTING.md | 3 ++ docs/TROUBLESHOOTING.md | 1 + patches/metal--deps--indium.patch | 50 +++++++++++++++++++ tools/build-riot-air-check.py | 9 +++- tools/build-riot-indium-runtime.py | 17 +++++-- tools/riot-llvm-version-offline-check.py | 61 ++++++++++++++++++++++++ 6 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 tools/riot-llvm-version-offline-check.py diff --git a/docs/TESTING.md b/docs/TESTING.md index 7a78652..13482df 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -11,8 +11,11 @@ python3 scripts/prepare.py --check-only python3 tools/riot-launcher-offline-check.py python3 tools/riot-memory-installer-offline-check.py python3 tools/riot-foundation-installer-offline-check.py +python3 tools/riot-llvm-version-offline-check.py ``` +The LLVM version check compiles Iridium's actual dynamic loader against small stub libraries. It verifies that a matching major initializes, older/newer majors produce a version mismatch, and a missing library fails initialization. It does not start Darling. The native AIR translator (`python3 tools/build-riot-air-check.py --native`) selects both headers and the shared library from the same `llvm-config`; its build prints those paths so shader results can be attributed to the tested LLVM installation. + The patch check uses temporary base indexes and does not reset or modify the source checkout. The launcher check uses stub commands and never starts or contacts Riot or Darling. It checks installed-client and first-time-installer launches, strips inherited `DYLD_INSERT_LIBRARIES` even with the old VPX opt-in set, and preserves Darling runtime library/framework paths. The memory-installer check imports the installer and exercises its policy (default source is the prepared artifact; the artifact is never a destination of a different library) in temporary directories. The Foundation-installer check does the same for the overlay installer: it seeds a temporary framework from a stub SDK, replaces `Versions/C/Foundation`, keeps the standard symlinks, stages a backup, treats a re-run as current and refuses a non-Mach-O source. The installer handoff mock covers 28 scenarios, including HTTP 409, interrupted verification and preserving active downloads. ## CoreText font-list and AppKit resize regressions diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index e538f7b..f54b731 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -135,6 +135,7 @@ A separate multi-window path stopped the active async presenter when another dif - **OpenDirectory 32-bit ivar errors:** keep `TARGET_i386=OFF`; the supported client is x86-64. - **Undefined empty Objective-C collection symbols:** retain the patched macOS 10.15 Objective-C runtime code generation. - **LLVM branch opcode errors:** retain the Indium LLVM 22/23 compatibility patch; do not replace its branch handling with an unconditional version-specific opcode. +- **Scalar float errors or opcode 71 after updating:** `Unsupported LLVM value: float 0.000000e+00` (also `1.000000e+00`) and `Unhandled instruction opcode: 71` can result from compiling Iridium with LLVM 22 headers and loading LLVM 23. The LLVM C API changed enum values between those majors. The build now selects headers and the shared library together; initialization rejects a mismatched major with `Iridium LLVM version mismatch`. Close the game and both clients, then run `git pull` and `./scripts/update.sh` to rebuild and reinstall both the base runtime and per-user overrides. If the same errors persist, include the update's `Iridium LLVM headers:` line and a newly generated `metal-errors.log` with the report. - **Every game shader fails to compile on LLVM 23** (r3dlog full of `Failed to compile shader`, then `Material Missing Pipeline` and a crash before the loading screen; `/tmp/metal-errors.log` shows `Unsupported LLVM value: <3 x float> zeroinitializer`): LLVM 23 represents vector splats as vector-typed `ConstantFP`/`ConstantInt`, which Iridium did not translate. Indium now splats them itself. Rebuild and reinstall the Metal/Indium pair ([setup](SETUP.md#metal-and-indium-overlays)). The offline check `python3 tools/build-riot-air-check.py --native` translates shader libraries without the game. - **Build process killed:** lower parallelism, for example `JOBS=2 ./scripts/build.sh` or `--parallel 1` for an incremental component build. - **Incremental helper cannot find `flags.make` or `link.txt`:** use Unix Makefiles, not Ninja. diff --git a/patches/metal--deps--indium.patch b/patches/metal--deps--indium.patch index 8f34335..faa1a83 100644 --- a/patches/metal--deps--indium.patch +++ b/patches/metal--deps--indium.patch @@ -8228,3 +8228,53 @@ new file mode 100644 + } + } +} +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 8d95863..603dc3d 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -159,9 +159,10 @@ if (NOT DARLING) + endif() + + execute_process( +- COMMAND llvm-config --link-shared --libnames ++ COMMAND "${LLVM_TOOLS_BINARY_DIR}/llvm-config" --link-shared --libnames + OUTPUT_VARIABLE HOST_LLVM_LIBNAME + OUTPUT_STRIP_TRAILING_WHITESPACE ++ COMMAND_ERROR_IS_FATAL ANY + ) + + target_compile_definitions(iridium PRIVATE +diff --git a/src/iridium/dynamic-llvm.cpp b/src/iridium/dynamic-llvm.cpp +index 1b5876e..03af560 100644 +--- a/src/iridium/dynamic-llvm.cpp ++++ b/src/iridium/dynamic-llvm.cpp +@@ -1,4 +1,7 @@ + #include ++#include ++#include ++#include + + #ifdef DARLING + #include +@@ -19,7 +22,20 @@ bool Iridium::DynamicLLVM::init() { + libraryHandle = dlopen(HOST_LLVM_LIBNAME, RTLD_LAZY); + #endif + +- return !!libraryHandle; ++ if (!libraryHandle) return false; ++ // LLVM's C API enum values changed in LLVM 23. A different runtime major ++ // misclassifies constants and branch instructions even when symbols resolve. ++ using GetVersion = void (*)(unsigned*, unsigned*, unsigned*); ++ auto getVersion = reinterpret_cast(resolveSymbol("LLVMGetVersion")); ++ unsigned major = 0, minor = 0, patch = 0; ++ if (getVersion) getVersion(&major, &minor, &patch); ++ if (major != LLVM_VERSION_MAJOR) { ++ finit(); ++ throw std::runtime_error("Iridium LLVM version mismatch: built with LLVM " + ++ std::to_string(LLVM_VERSION_MAJOR) + ", loaded LLVM " + std::to_string(major) + ++ " from " HOST_LLVM_LIBNAME "; rebuild and reinstall the runtime with matching LLVM headers and library"); ++ } ++ return true; + }; + + void Iridium::DynamicLLVM::finit() { diff --git a/tools/build-riot-air-check.py b/tools/build-riot-air-check.py index 0ca61fd..cd870f0 100644 --- a/tools/build-riot-air-check.py +++ b/tools/build-riot-air-check.py @@ -7,8 +7,15 @@ import sys root = Path(__file__).resolve().parent.parent if '--native' in sys.argv: indium = root / 'build/darling-src/src/external/metal/deps/indium' + llvm_include = subprocess.check_output(['llvm-config', '--includedir'], text=True).strip() + llvm_libdir = Path(subprocess.check_output(['llvm-config', '--libdir'], text=True).strip()) + llvm_names = shlex.split(subprocess.check_output(['llvm-config', '--link-shared', '--libnames'], text=True)) + if len(llvm_names) != 1: + raise SystemExit('Iridium requires one shared LLVM library, got: ' + repr(llvm_names)) + llvm_library = llvm_libdir / llvm_names[0] + print(f'Iridium LLVM headers: {llvm_include}; library: {llvm_library}', flush=True) subprocess.run(['clang++', '-O2', '-std=c++17', - '-DHOST_LLVM_LIBNAME="libLLVM-22.so"', + '-DHOST_LLVM_LIBNAME="' + str(llvm_library) + '"', '-I' + llvm_include, '-I' + str(indium / 'include'), '-I' + str(indium / 'private-include'), str(root / 'tools/riot-air-translate-check.cpp'), *[str(indium / 'src/iridium' / (name + '.cpp')) diff --git a/tools/build-riot-indium-runtime.py b/tools/build-riot-indium-runtime.py index a115c2a..a630cc5 100644 --- a/tools/build-riot-indium-runtime.py +++ b/tools/build-riot-indium-runtime.py @@ -20,9 +20,20 @@ for line in (folder / 'CMakeFiles/iridium.dir/flags.make').read_text().splitline iridium_flags['CXX_INCLUDES'] = current_includes(iridium_flags['CXX_INCLUDES']) # RIOT_IRIDIUM_SYSTEM_LLVM=1 ignores the pin and builds against the host LLVM, as a fresh checkout does. if (pinned_llvm / 'lib/libLLVM-22.so').exists() and os.environ.get('RIOT_IRIDIUM_SYSTEM_LLVM') != '1': - iridium_flags['CXX_INCLUDES'].insert(0, '-I' + str(pinned_llvm / 'include')) - iridium_flags['CXX_DEFINES'] = [value for value in iridium_flags['CXX_DEFINES'] if not value.startswith('-DHOST_LLVM_LIBNAME=')] - iridium_flags['CXX_DEFINES'].append('-DHOST_LLVM_LIBNAME="' + str(pinned_llvm / 'lib/libLLVM-22.so') + '"') + llvm_include = pinned_llvm / 'include' + llvm_library = pinned_llvm / 'lib/libLLVM-22.so' +else: + # Do not combine cached CMake include paths with a newer llvm-config library. + llvm_include = Path(subprocess.check_output(['llvm-config', '--includedir'], text=True).strip()) + llvm_libdir = Path(subprocess.check_output(['llvm-config', '--libdir'], text=True).strip()) + llvm_names = shlex.split(subprocess.check_output(['llvm-config', '--link-shared', '--libnames'], text=True)) + if len(llvm_names) != 1: + raise SystemExit('Iridium requires one shared LLVM library, got: ' + repr(llvm_names)) + llvm_library = llvm_libdir / llvm_names[0] +iridium_flags['CXX_INCLUDES'].insert(0, '-I' + str(llvm_include)) +iridium_flags['CXX_DEFINES'] = [value for value in iridium_flags['CXX_DEFINES'] if not value.startswith('-DHOST_LLVM_LIBNAME=')] +iridium_flags['CXX_DEFINES'].append('-DHOST_LLVM_LIBNAME="' + str(llvm_library) + '"') +print(f'Iridium LLVM headers: {llvm_include}; library: {llvm_library}', flush=True) iridium_link = shlex.split((folder / 'CMakeFiles/iridium.dir/link.txt').read_text()) for name in ('air', 'dynamic-llvm', 'iridium', 'spirv'): obj = root / f'build/riot-iridium-{name}.o' diff --git a/tools/riot-llvm-version-offline-check.py b/tools/riot-llvm-version-offline-check.py new file mode 100644 index 0000000..036670b --- /dev/null +++ b/tools/riot-llvm-version-offline-check.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Check Iridium's LLVM major-version guard without Darling or a game process.""" +from pathlib import Path +import re +import subprocess +import tempfile + +root = Path(__file__).resolve().parent.parent +indium = root / 'build/darling-src/src/external/metal/deps/indium' +include = Path(subprocess.check_output(['llvm-config', '--includedir'], text=True).strip()) +header = (include / 'llvm/Config/llvm-config.h').read_text() +major = int(re.search(r'^#define LLVM_VERSION_MAJOR (\d+)', header, re.MULTILINE)[1]) +with tempfile.TemporaryDirectory(prefix='iridium-llvm-version-') as directory: + folder = Path(directory) + library_source = folder / 'llvm.c' + library_source.write_text(''' +void LLVMGetVersion(unsigned* major, unsigned* minor, unsigned* patch) { + *major = TEST_LLVM_MAJOR; *minor = 0; *patch = 0; +} +''') + probe = folder / 'probe.cpp' + probe.write_text(''' +#include +#include +#include +int main() { + try { + if (!Iridium::DynamicLLVM::init()) return 2; + Iridium::DynamicLLVM::finit(); + puts("LLVM version matched"); + return 0; + } catch (const std::exception& error) { + fprintf(stderr, "%s\\n", error.what()); + return 1; + } +} +''') + # Keep the loaded path fixed so the same compiled probe tests both majors. + library = folder / 'libLLVM-test.so' + executable = folder / 'probe' + subprocess.run([ + 'clang++', '-std=c++17', '-Wno-deprecated-declarations', + '-DHOST_LLVM_LIBNAME="' + str(library) + '"', '-I' + str(include), + '-I' + str(indium / 'include'), '-I' + str(indium / 'private-include'), + str(probe), str(indium / 'src/iridium/dynamic-llvm.cpp'), + '-ldl', '-o', str(executable), + ], check=True) + for loaded in (major, major - 1, major + 1): + subprocess.run(['cc', '-shared', '-fPIC', f'-DTEST_LLVM_MAJOR={loaded}', + str(library_source), '-o', str(library)], check=True) + result = subprocess.run([str(executable)], text=True, capture_output=True) + if loaded == major: + assert result.returncode == 0, result.stderr + else: + assert result.returncode == 1, result.stderr + assert f'built with LLVM {major}, loaded LLVM {loaded}' in result.stderr, result.stderr + print(f'PASS: headers {major}, runtime {loaded}', flush=True) + library.unlink() + result = subprocess.run([str(executable)], capture_output=True) + assert result.returncode == 2 + print('PASS: missing library fails initialization')