Keep Iridium LLVM headers and runtime library on the same major

This commit is contained in:
lucannez64 committed 2026-10-15 23:22:15 +02:00
1 parent 9383dd2963
commit f53d605365
6 files changed
+137 -4

No files matched your search

+3
View File
@@ -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
+1
View File
@@ -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.
+50
View File
@@ -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 <iridium/dynamic-llvm.hpp>
+#include <llvm/Config/llvm-config.h>
+#include <stdexcept>
+#include <string>
#ifdef DARLING
#include <elfcalls.h>
@@ -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<GetVersion>(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() {
+8 -1
View File
@@ -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'))
+14 -3
View File
@@ -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'
+61
View File
@@ -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 <iridium/dynamic-llvm.hpp>
#include <cstdio>
#include <exception>
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')