Skip to content

fix(backend): embed the kernel static archive into published K/N klibs (#941) - #942

Merged
michalharakal merged 1 commit into
developfrom
fix/cinterop-embed-archives-941
Aug 10, 2026
Merged

michalharakal merged 1 commit into
developfrom
fix/cinterop-embed-archives-941

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

Full analysis in #941 — flagged as the highest-risk item in #920's iOS track, and a latent bug for today's linux artifacts.

The bug

libskainet_kernels.a was attached via binaries.all { linkerOpts(...) }, which applies only to this project's own binaries. Published -linuxx64/-linuxarm64 klibs carried cinterop bindings but no machine code — any downstream K/N consumer fails to link with unresolved skainet_* symbols. Unhit so far only because no external K/N consumer exists yet.

The fix

  • Embed the archive into the cinterop klib (-staticLibrary/-libraryPath passed per target from Gradle; paths are machine-specific so they don't belong in the .def).
  • Architecture-gated: linuxX64 embeds on a Linux host (or injected -PskainetKernelsX64Dir); linuxArm64 under -PcrossArm64 (or -PskainetKernelsArm64Dir). A macOS host's Mach-O archives are never embedded into linux klibs — those builds keep the old project-local linking and the cinterop task warns loudly that the klib is bindings-only, replacing today's silence.
  • Cinterop tasks depend on the CMake builds (the archive must exist at cinterop time, not just link time).
  • publish.yml: the ubuntu leg additionally cross-builds (gcc-aarch64-linux-gnu via apt) and uploads both linux ELF archives; the macOS publish job resolves them and passes the injection properties — failing loudly if either is missing.
  • With embedding active the in-repo K/N test binaries link purely from the embedded klib code — every test run now exercises exactly the consumer-link scenario.

Verified (Linux host)

  • linuxX64Test green with linkerOpts removed — the test binary got its machine code from the klib
  • linuxArm64Test -PcrossArm64=true under qemu-aarch64: 23/23 green; archive present at default/targets/linux_arm64/included/
  • publishLinuxX64PublicationToMavenLocal: the published -cinterop-skainetKernels.klib contains default/targets/linux_x64/included/libskainet_kernels.a (39.8 KB) — the artifact consumers resolve now carries the code
  • jvmTest green (FFM/resources path untouched)

Needs a maintainer eye: the publish.yml changes can only be fully validated by a real release run (or a tag on a fork). The Gradle-side mechanics are fully verified locally.

This is the prerequisite for publishing usable iosArm64 klibs in #920's Apple track — the same embedding path will carry the iOS archives.

Closes #941
Refs #920

The static archive libskainet_kernels.a was attached to the K/N targets
via binaries.all { linkerOpts(...) }, which applies only to this
project's own binaries. Published -linuxx64/-linuxarm64 klibs therefore
carried cinterop bindings but no machine code: any downstream K/N
consumer failed to link with unresolved skainet_* symbols. Nobody hit
it because no external K/N consumer exists yet — the #920 mobile-kernel
work makes this the foundation everything else stands on.

The archive is now embedded into the cinterop klib via
-staticLibrary/-libraryPath, passed per target from Gradle (paths are
machine-specific and don't belong in the .def). Embedding is gated on a
correct-architecture ELF archive being available:

- linuxX64: Linux host CMake build, or injected -PskainetKernelsX64Dir
- linuxArm64: -PcrossArm64 cross build, or -PskainetKernelsArm64Dir

The injection properties exist for the release workflow: the publish
host is macOS, whose CMake build emits Mach-O that must not be embedded
into linux klibs. publish.yml's ubuntu leg now also cross-builds
(gcc-aarch64-linux-gnu) and uploads both linux static archives, and the
publish job resolves them and passes the properties — failing loudly if
either is missing, since bindings-only klibs are the bug this prevents.

When embedding is active the old linkerOpts wiring is dropped, so the
in-repo K/N test binaries link purely from the embedded klib code —
every test run now exercises exactly the consumer-link scenario.
Bindings-only builds keep linkerOpts for project-local binaries and the
cinterop task warns loudly instead of today's silence. Cinterop tasks
gained dependencies on the CMake build tasks (the archive must exist at
cinterop time, not just link time).

Verified on a Linux host:
- linuxX64Test green with linkerOpts removed (links from embedded klib)
- linuxArm64Test under qemu-aarch64 with -PcrossArm64: 23/23 green,
  archive embedded at default/targets/linux_arm64/included/
- publishLinuxX64PublicationToMavenLocal: the published
  -cinterop-skainetKernels.klib contains
  default/targets/linux_x64/included/libskainet_kernels.a
- jvmTest green (FFM/resources path untouched)

Closes #941
Refs #920
@michalharakal
michalharakal requested a review from aharakal August 10, 2026 11:06
@michalharakal
michalharakal merged commit 2af845c into develop Aug 10, 2026
14 checks passed
@michalharakal
michalharakal deleted the fix/cinterop-embed-archives-941 branch August 10, 2026 12:57
michalharakal added a commit to MacOS/SKaiNET that referenced this pull request Aug 11, 2026
Bump VERSION_NAME 0.38.0 -> 0.39.0 and update all version-carrying docs:
- CHANGELOG: consolidate [Unreleased] under [0.39.0] with a headline summary
  (includes the SKaiNET-developers#947 AAR-publishing CI entry, now merged to develop).
- README: BOM snippet -> 0.39.0, What's New in 0.39.0, Contributors (0.39.0).
- docs/antora.yml: skainet_version attribute 0.38.0 -> 0.39.0 (used in the
  docs' dependency snippets).
- kernel-support-matrix.adoc: regenerated via generateKernelMatrix — the
  Android column now shows native-jni for Q8_0/Q4_0/Q4_K/Q5_K/Q6_K (the new
  JNI backend), replacing panama-vector; version stamp -> 0.39.0.

0.39.0 headline: on-device AI on Android becomes real — the
skainet-backend-jni-cpu JNI NEON backend (~24 tok/s SmolLM2-135M Q8_0 on a
Pixel 8a vs ~3.8 scalar), plus Android streaming GGUF loads (SKaiNET-developers#922), linkable
K/N kernel klibs (SKaiNET-developers#942), the Q4_0 NEON kernel (SKaiNET-developers#939), GGUF loader fail-fast
(SKaiNET-developers#919), and a tensor-storage correctness pass (SKaiNET-developers#927-SKaiNET-developers#931).

Local prep only — not pushed/tagged. Cut off develop after SKaiNET-developers#947 merged, so
the branch already carries the AAR-publishing workflow + pinned NDK.
michalharakal added a commit that referenced this pull request Aug 11, 2026
…ed kernel archives (#959)

iosArm64 + iosSimulatorArm64 + macosArm64 join the module through the
existing wireSkainetKernels cinterop/embedding seam (#941/#942). Three
Apple CMake lanes (macOS host only; CMake first-class iOS support, no
toolchain file) produce the Mach-O static archives — device: iphoneos
SDK, min iOS 12.0; simulator: iphonesimulator SDK, min 14.0; macOS:
arm64, min 11.0, static-only. No -march is passed: the #958 runtime
FEAT_DotProd dispatch serves A12 through M-series from one archive.

Archive-dir precedence mirrors the Linux pair: injected
-PskainetKernelsIosArm64Dir / -PskainetKernelsIosSimulatorArm64Dir /
-PskainetKernelsMacosArm64Dir > macOS-host heuristic > bindings-only
with a loud cinterop warning. The shared nativeTest parity suites gain
iosSimulatorArm64Test / macosArm64Test on macOS hosts; the multiarch
macos-14 PR lane runs both. On non-macOS hosts the whole Apple task
family is disabled — module build + root assemble verified green on
Linux with the targets declared.

Refs #920
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.

Published K/N klibs for skainet-backend-native-cpu carry cinterop bindings but no machine code — consumers cannot link

2 participants