Skip to content

lakefile: whole-archive sparkle C externs into precompiled .so#54

Merged
junjihashimoto merged 1 commit into
mainfrom
fix/sparkle-shared-lib-jit-symbols
Jun 13, 2026
Merged

lakefile: whole-archive sparkle C externs into precompiled .so#54
junjihashimoto merged 1 commit into
mainfrom
fix/sparkle-shared-lib-jit-symbols

Conversation

@junjihashimoto

Copy link
Copy Markdown
Contributor

Fixes the `tutorial-notebooks` CI step on main, which has been
breaking with:

```
symbol lookup error:
/home/runner/work/sparkle/sparkle/.lake/build/lib/libsparkle_Sparkle.so:
undefined symbol: sparkle_jit_load
error: Lean exited with code 127

  • Notebooks.Gen.Ch03_Sequential
    ```

What happened

PR #53 (`xlean-mcp + JIT-in-kernel + Real-Time Collaboration`)
added `precompileModules := true` to `lean_lib Sparkle` so the
xlean WASM kernel could dlsym the Lean-side boxing wrappers from a
shared library at notebook run time.

That worked for everything except symbols that no Lean wrapper
currently calls directly — like `sparkle_jit_load`, which is
only ever dlsym'd via `Sparkle.Core.JIT.JIT.load`'s boxing
wrapper. When Lake linked `libsparkle_Sparkle.so` against the C
extern archives (`libsparkle_barrier.a`, `libsparkle_jit.a`),
the linker's default `--as-needed` / `--gc-sections` pass
discarded every symbol nothing was statically referencing. PR #53
itself slipped through CI because none of its own files triggered
`JIT.load`; PR #53 + the existing Ch03 tutorial chapter does.

Fix

Wrap the two extern archives with `-Wl,--whole-archive ...
-Wl,--no-whole-archive` in `Sparkle`'s `moreLinkArgs`, so the
linker retains every symbol they contain. Pair with
`-L ./.lake/build/c_src` to point at the directory the
`extern_lib` blocks emit those archives into.

Verification

  • `lake build Sparkle` — succeeds (was failing on every
    `*:dynlib` target with "libsparkle_barrier.a not found"
    until `-L` was added).
  • `lake build TutorialNotebooks` — succeeds, including the
    `Notebooks.Gen.Ch03_Sequential` chapter that triggered the
    original failure.
  • `lake exe svparser-test` — `42 passed, 0 failed` (no
    regression on the prior PRs' SVParser fixes).

CI for this branch should be green.

PR #53's `precompileModules := true` on `lean_lib Sparkle` made Lake
build `libsparkle_Sparkle.so` alongside the oleans.  That .so was
linked against the C extern_lib archives (`libsparkle_barrier.a`,
`libsparkle_jit.a`) but the linker's default `--as-needed` pass
discarded every symbol no Lean wrapper currently *calls* directly —
including `sparkle_jit_load`, which the dlsym-loaded
`JIT.load` boxing wrapper looks up at run time.

Tutorial-notebooks CI consequently broke with:

  symbol lookup error: .lake/build/lib/libsparkle_Sparkle.so:
    undefined symbol: sparkle_jit_load

while building `Notebooks.Gen.Ch03_Sequential` (the new chapter
that exercises JIT.load).

Fix: pass `-Wl,--whole-archive` around the two extern archives in
`moreLinkArgs`, so the linker retains every symbol from them.  The
`-L ./.lake/build/c_src` path points at the directory the
`extern_lib` blocks emit those archives into.

`lake build Sparkle`, `lake build TutorialNotebooks`, and
`lake exe svparser-test` (42/0) all succeed locally with this
change.
@junjihashimoto junjihashimoto merged commit d9b9a63 into main Jun 13, 2026
3 checks passed
@junjihashimoto junjihashimoto deleted the fix/sparkle-shared-lib-jit-symbols branch June 13, 2026 05:43
junjihashimoto added a commit that referenced this pull request Jun 14, 2026
The downstream-smoke CI added in this branch turned red on all
three OSes the first time it ran, surfacing a real bug in PR #54's
\`-Wl,--whole-archive\` linker hack:

- **Linux**: \`-L ./.lake/build/c_src\` resolves against the
  downstream consumer's cwd (\`my-blinky/.lake/build/c_src\`),
  not Sparkle's package dir, so the archives weren't found.
- **macOS / Windows**: Apple ld64 and lld-link don't understand
  \`--whole-archive\` / \`--no-whole-archive\` at all.

The actual fix (independently identified in PR #55) is to force
default visibility on the FFI export functions in the .c sources
themselves, via:

    #pragma GCC visibility push(default)
    ...
    #pragma GCC visibility pop

That overrides leanc's mandatory \`-fvisibility=hidden\` per-TU
and is portable across all three OSes (clang/gcc/MSVC all accept
the GCC visibility pragma; on macOS it maps to the dylib's
exported-symbol list, on Windows it's a no-op but the underlying
linker behaviour does the right thing anyway because lld-link
keeps externally-referenced \`__declspec(dllexport)\`-ish symbols
by default).

With this, drop the entire \`moreLinkArgs\` block from
\`lean_lib Sparkle\`.

Verified locally with a clean \`.lake\` + a downstream Lake project
that pins Sparkle by path:

    blinky 16 cycles: [0x0#4, 0x1#4, ..., 0xf#4]

Co-authored-by: @wvhulle (#55 author — same diagnosis, same fix)
junjihashimoto added a commit that referenced this pull request Jun 14, 2026
The downstream-smoke CI added in this branch turned red on all
three OSes the first time it ran, surfacing a real bug in PR #54's
\`-Wl,--whole-archive\` linker hack:

- **Linux**: \`-L ./.lake/build/c_src\` resolves against the
  downstream consumer's cwd (\`my-blinky/.lake/build/c_src\`),
  not Sparkle's package dir, so the archives weren't found.
- **macOS / Windows**: Apple ld64 and lld-link don't understand
  \`--whole-archive\` / \`--no-whole-archive\` at all.

The actual fix (independently identified in PR #55) is to force
default visibility on the FFI export functions in the .c sources
themselves, via:

    #pragma GCC visibility push(default)
    ...
    #pragma GCC visibility pop

That overrides leanc's mandatory \`-fvisibility=hidden\` per-TU
and is portable across all three OSes (clang/gcc/MSVC all accept
the GCC visibility pragma; on macOS it maps to the dylib's
exported-symbol list, on Windows it's a no-op but the underlying
linker behaviour does the right thing anyway because lld-link
keeps externally-referenced \`__declspec(dllexport)\`-ish symbols
by default).

With this, drop the entire \`moreLinkArgs\` block from
\`lean_lib Sparkle\`.

Verified locally with a clean \`.lake\` + a downstream Lake project
that pins Sparkle by path:

    blinky 16 cycles: [0x0#4, 0x1#4, ..., 0xf#4]

Co-authored-by: @wvhulle (#55 author — same diagnosis, same fix)
junjihashimoto added a commit that referenced this pull request Jul 1, 2026
lakefile: whole-archive sparkle C externs into precompiled .so
junjihashimoto added a commit that referenced this pull request Jul 1, 2026
The downstream-smoke CI added in this branch turned red on all
three OSes the first time it ran, surfacing a real bug in PR #54's
\`-Wl,--whole-archive\` linker hack:

- **Linux**: \`-L ./.lake/build/c_src\` resolves against the
  downstream consumer's cwd (\`my-blinky/.lake/build/c_src\`),
  not Sparkle's package dir, so the archives weren't found.
- **macOS / Windows**: Apple ld64 and lld-link don't understand
  \`--whole-archive\` / \`--no-whole-archive\` at all.

The actual fix (independently identified in PR #55) is to force
default visibility on the FFI export functions in the .c sources
themselves, via:

    #pragma GCC visibility push(default)
    ...
    #pragma GCC visibility pop

That overrides leanc's mandatory \`-fvisibility=hidden\` per-TU
and is portable across all three OSes (clang/gcc/MSVC all accept
the GCC visibility pragma; on macOS it maps to the dylib's
exported-symbol list, on Windows it's a no-op but the underlying
linker behaviour does the right thing anyway because lld-link
keeps externally-referenced \`__declspec(dllexport)\`-ish symbols
by default).

With this, drop the entire \`moreLinkArgs\` block from
\`lean_lib Sparkle\`.

Verified locally with a clean \`.lake\` + a downstream Lake project
that pins Sparkle by path:

    blinky 16 cycles: [0x0#4, 0x1#4, ..., 0xf#4]

Co-authored-by: @wvhulle (#55 author — same diagnosis, same fix)
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