From ce05a37338c593d1cc7393f7ce699def797a3668 Mon Sep 17 00:00:00 2001 From: Douglas Whittingham Date: Fri, 21 Aug 2026 12:54:22 -1000 Subject: [PATCH] module-template: fix C-backend module builds on x86-64 clang/gcc Building a C-backend module through module-template fails on x86-64 Linux/macOS with clang or gcc. Three errors, two independent causes. 1) The generated CPU header is never resolved. Generated sources do: #ifndef DOLRECOMP_CPU_HEADER #define DOLRECOMP_CPU_HEADER "cpu/cpu.h" #endif #include DOLRECOMP_CPU_HEADER "cpu/cpu.h" is DolRecomp's standalone layout. A module compiles against GXRuntime, where the same header is core/cpu.h, and none of module-template's four include directories provides a cpu/ subdir: generated.h:15:10: fatal error: 'cpu/cpu.h' file not found Nothing in RecompCore or moderngekko-port sets the override, so set it in module-template, which is the one place that knows the module is being built against GXRuntime. 2) The x86-64-v3 dispatch path references code that is not there. selected_dispatch() calls dolrecomp_call__x86_64_v3(), which only exists when the module was generated with that variant (dolrecomp --targets ... x86-64-v3). moderngekko-port does not pass --targets, so it is normally absent: module_export.c:23: error: invalid cpu feature string for builtin module_export.c:24: error: invalid cpu feature string for builtin module_export.c:35: error: call to undeclared function 'dolrecomp_call__x86_64_v3' clang also rejects "movbe" and "lzcnt" as __builtin_cpu_supports feature strings, which gcc accepts. Compile the whole v3 path only under DOLRECOMP_MODULE_HAVE_X86_64_V3 and drop the two feature strings clang does not accept. Both went unnoticed because the configurations built regularly fold the code away before the compiler sees it: under MSVC and on non-x86-64 targets the __x86_64__ && (__GNUC__ || __clang__) guard is false, so host_has_x86_64_v3() is a constant 0 and the call is dead-code eliminated. The LLVM backend does not include generated.h, so it never hits (1) either. Verified on x86-64 Linux with clang 18: before, the build stops with the errors above; after, a Luigi's Mansion C-backend module compiles clean (0 errors) and links to a 42.3 MB .so with no undefined v3 symbol. --- module-template/CMakeLists.txt | 4 ++++ module-template/module_export.c | 25 ++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/module-template/CMakeLists.txt b/module-template/CMakeLists.txt index 2757a8d96d..0c177fa14d 100644 --- a/module-template/CMakeLists.txt +++ b/module-template/CMakeLists.txt @@ -103,6 +103,10 @@ set_target_properties(${MODULE_NAME} PROPERTIES set_source_files_properties("${MODULE_TABLES}" PROPERTIES HEADER_FILE_ONLY TRUE GENERATED TRUE) target_compile_definitions(${MODULE_NAME} PRIVATE MODULE_GAME_ID="${GAME_ID}") +# Generated sources default DOLRECOMP_CPU_HEADER to "cpu/cpu.h", which is +# DolRecomp's standalone layout. A module compiles against GXRuntime, where the +# same header is core/cpu.h. The override hook exists for exactly this case. +target_compile_definitions(${MODULE_NAME} PRIVATE DOLRECOMP_CPU_HEADER="core/cpu.h") target_include_directories(${MODULE_NAME} PRIVATE "${GENERATED_DIR}" diff --git a/module-template/module_export.c b/module-template/module_export.c index 2a02ef6863..ad3a0d2e13 100644 --- a/module-template/module_export.c +++ b/module-template/module_export.c @@ -8,6 +8,24 @@ #include "StaticRecompABI.h" +/* The x86-64-v3 dispatch path is compiled only when the module was generated + * with that variant (dolrecomp --targets ... x86-64-v3). moderngekko-port does + * not pass --targets, so normally it is absent, and referencing it breaks the + * build two ways on x86-64 with clang or gcc: + * + * module_export.c:23: error: invalid cpu feature string for builtin + * module_export.c:24: error: invalid cpu feature string for builtin + * module_export.c:35: error: call to undeclared function + * 'dolrecomp_call__x86_64_v3' + * + * clang does not accept "movbe" or "lzcnt" for __builtin_cpu_supports, and the + * v3 entry point is neither declared nor defined. Both went unnoticed because + * the two configurations built regularly fold the whole thing away first: under + * MSVC and on non-x86-64 targets the guard below is false, so the body is a + * constant 0 and the call is dead-code eliminated before the compiler sees it. */ +#if defined(DOLRECOMP_MODULE_HAVE_X86_64_V3) +int dolrecomp_call__x86_64_v3(CPUState* ctx, u32 address); + static int host_has_x86_64_v3(void) { #if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__)) @@ -19,20 +37,21 @@ static int host_has_x86_64_v3(void) __builtin_cpu_supports("avx2") && __builtin_cpu_supports("fma") && __builtin_cpu_supports("bmi") && - __builtin_cpu_supports("bmi2") && - __builtin_cpu_supports("movbe") && - __builtin_cpu_supports("lzcnt"); + __builtin_cpu_supports("bmi2"); } return supported; #else return 0; #endif } +#endif static int selected_dispatch(CPUState* ctx, u32 address) { +#if defined(DOLRECOMP_MODULE_HAVE_X86_64_V3) if (host_has_x86_64_v3()) return dolrecomp_call__x86_64_v3(ctx, address); +#endif return dolrecomp_call(ctx, address); }