Skip to content

fix: x86 SMP bitcode/code-model mismatch (smp_semaphore/mutex/threads)#30

Open
avrabe wants to merge 2 commits intomainfrom
fix/smp-x86-bitcode-mismatch
Open

fix: x86 SMP bitcode/code-model mismatch (smp_semaphore/mutex/threads)#30
avrabe wants to merge 2 commits intomainfrom
fix/smp-x86-bitcode-mismatch

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 1, 2026

Summary

Three CI checks have been failing for at least the last several PRs:

  • `smp_semaphore (qemu_x86_64, SMP)`
  • `smp_mutex (qemu_x86_64, SMP)`
  • `smp_threads (qemu_x86_64, SMP)`

All three fail at the same step — building `libgale_ffi.a` for `x86_64-unknown-none`:

```
error: failed to load bitcode of module
"core-fcc82a7537a7267a.core.bb170dea1013a3a3-cgu.0.rcgu.o"
error: could not compile gale-ffi (lib) due to 1 previous error
```

Root cause

Reproduced locally with rustc 1.95.0. The CI log truncates one line earlier than the actual cause; the smoking gun is a warning that precedes the error:

```
warning: linking module flags 'Code Model':
IDs have conflicting values: 'i32 2' from , and 'i32 1' from gale_ffi.5b37a6aa01c132b8-cgu.0
```

The chain:

  • `gale/ffi/Cargo.toml` sets `lto = true` for `[profile.release]` (fat LTO).
  • `gale/zephyr/CMakeLists.txt` sets `-Ccode-model=small` for x86 (correct — Zephyr maps the kernel low).
  • The precompiled `core` rustlib for `x86_64-unknown-none` was built with the target's default `code-model=kernel` and ships with embedded bitcode tagged that way.
  • rustc fat-LTO loads `core`'s bitcode and tries to merge it with `gale-ffi`'s bitcode. LLVM's "Code Model" module flag is `Error`-level: 1 (small) and 2 (kernel) cannot coexist, so the bitcode load aborts.

Cortex-M doesn't trip on this because we don't override `code-model` on Cortex-M; the default matches the precompiled `core`.

Fix

Switch x86 builds from the `release` profile to the existing `release-lto` profile (which has `lto = false`), but only when the cross-language-LTO upper block hasn't already set it. `release-lto` already `inherits = "release"` so all other tuning (`opt-level="z"`, `codegen-units=1`, `panic="abort"`, `overflow-checks=true`) is preserved.

Scoped inside `if(CONFIG_X86)` so Cortex-M is byte-identical to before — the merged-in PR #27 publication-relevant LTO numbers are not at risk.

```diff
diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt
@@ -183,6 +183,29 @@ if(CONFIG_X86)
...

  • if(GALE_CARGO_PROFILE_DIR STREQUAL "release")
  • set(GALE_CARGO_PROFILE_ARGS "--profile" "release-lto")
  • set(GALE_CARGO_PROFILE_DIR "release-lto")
  • message(STATUS "Gale: x86 — using release-lto profile to avoid core bitcode/code-model mismatch")
  • endif()
    endif()
    ```

Test plan

  • Reproduced the failure locally (rustc 1.95.0, x86_64-unknown-none target, with the exact CI feature set)
  • Confirmed fix resolves locally — same cargo invocation builds clean producing 10 MB libgale_ffi.a
  • Cortex-M cargo build (thumbv7m-none-eabi) still builds clean — change is `if(CONFIG_X86)`-scoped
  • CI on this PR validates the three SMP tests now pass

Local repro / validation command (no Zephyr SDK needed):

```
cd gale/ffi && env RUSTFLAGS="-Crelocation-model=static -Ccode-model=small" \
GALE_MAX_SEMS=32 GALE_MAX_WAITERS=16 \
cargo build --profile release-lto --target x86_64-unknown-none \
--no-default-features \
--features sem,mutex,condvar,msgq,pipe,stack,timer,...,rbtree
```

🤖 Generated with Claude Code

@codecov
Copy link
Copy Markdown

codecov Bot commented May 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Three SMP tests on qemu_x86_64 (smp_semaphore, smp_mutex, smp_threads)
have been failing in the libgale_ffi.a build step with:

  warning: linking module flags 'Code Model':
    IDs have conflicting values: 'i32 2' from , and 'i32 1' from gale_ffi...
  error: failed to load bitcode of module "core-...rcgu.o"

Root cause: the precompiled `core` rustlib for x86_64-unknown-none
ships bitcode tagged with code-model=kernel (=2, the target's default).
We pass -Ccode-model=small (=1) so addresses match Zephyr's lower-2GB
kernel mapping. With ffi/Cargo.toml's release profile `lto = true`
(fat LTO), rustc tries to merge the two bitcode modules and LLVM
rejects the link.

Fix: switch x86 to the release-lto profile (lto=false), which already
exists for cross-language LTO. rustc then emits a regular static
archive without doing fat LTO, so no bitcode merge is attempted. All
other release tuning (opt-level=z, codegen-units=1, panic=abort,
overflow-checks=true) is preserved via `inherits = "release"`.

Cortex-M targets are unaffected — the change is scoped inside
`if(CONFIG_X86)`. Their precompiled core ships with a matching
code-model, so fat LTO continues to work there. Reproduced the
failure locally with rustc 1.95.0 + the exact CI cargo invocation;
confirmed the fix builds clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avrabe avrabe force-pushed the fix/smp-x86-bitcode-mismatch branch from 20fee98 to aff4da0 Compare May 3, 2026 07:06
The previous fix on this branch (release-lto profile) only resolved
the bitcode-merge half of the problem. CI showed it built libgale_ffi.a
clean but then failed at link with a different error:

    ld.bfd: discarded output section: `.got.plt'
    ld.bfd: final link failed
    collect2: error: ld returned 1 exit status

Both errors root-cause to the same thing: rustup's precompiled `core`
rustlib for x86_64-unknown-none was built with the target's defaults
(code-model=kernel, PIC), which don't match the flags Zephyr's kernel
needs (code-model=small, static relocation, no PIE).

  - Fat LTO (release profile) → bitcode merge rejects code-model
    mismatch.
  - lto=false (release-lto profile) → bitcode merge avoided, but the
    precompiled core's GOT/PLT references hit the non-PIC link script
    and ld.bfd refuses to discard the now-non-empty .got.plt section.

Proper fix: rebuild `core` from source with our flags via -Zbuild-std,
so the rebuilt core picks up RUSTFLAGS=-Ccode-model=small
-Crelocation-model=static and is both bitcode-compatible AND PIC-free.
Reverts to the original release profile (fat LTO works again) and
adds:

  - GALE_CARGO_EXTRA_ARGS = "-Zbuild-std=core" (threaded into all 42
    cargo build invocations in CMakeLists.txt)
  - RUSTC_BOOTSTRAP=1 in GALE_CARGO_ENV (to permit unstable cargo
    flag on stable rustc)
  - rustup component add rust-src in the SMP test job

Scoped inside if(CONFIG_X86); Cortex-M codegen is byte-identical to
before.

Also addresses the underlying root-cause comment at zephyr-tests.yml
about the SMP build failures (the runtime hang remains a separate
known-issue tracked in the workflow comment).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant