Cross-Compiling Pyvorin Edge Kernels for ARM64

June 2, 2026 | 18 min read

Introduction

Most Pyvorin Edge deployments target ARM64 Linux devices (Raspberry Pi, NVIDIA Jetson, custom boards), but CI/CD often runs on x86_64. Cross-compilation lets you build .so artifacts on an x86 host and ship them to the edge without requiring native ARM64 build nodes.

Toolchain Setup

On Ubuntu/Debian install the cross-compiler and standard library headers for ARM64:

sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross

Verify the compiler is on PATH:

aarch64-linux-gnu-gcc --version
# aarch64-linux-gnu-gcc (Ubuntu 13.2.0) 13.2.0

Cross-Compiling a Kernel

Use the same flags as a native build, but swap the compiler prefix. The -march flag should match your target SoC. For a generic ARMv8-A device:

aarch64-linux-gnu-gcc \
    -shared -fPIC -O3 -march=armv8-a+fp+simd \
    -o libvecadd_arm64.so vec_add_neon.c

For a Cortex-A72 specific optimization (Raspberry Pi 4, Jetson Nano):

aarch64-linux-gnu-gcc \
    -shared -fPIC -O3 -mcpu=cortex-a72+fp+simd -mtune=cortex-a72 \
    -o libvecadd_arm64.so vec_add_neon.c

Verifying Compiled Artifacts

Always confirm the architecture of the output before deployment. The file utility reads the ELF header and reports the target machine.

file libvecadd_arm64.so
# libvecadd_arm64.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked

Also inspect exported symbols to ensure the ABI contract matches:

aarch64-linux-gnu-nm -D libvecadd_arm64.so | grep vec_add_f32
# 0000000000000730 T vec_add_f32

Docker buildx with --platform linux/arm64

Docker buildx is the most reliable way to cross-compile in CI. You write a multi-stage Dockerfile that builds inside an ARM64 container running under QEMU, or you use a native cross-compiler image.

Fast Cross-Build Dockerfile (using cross-compiler)

# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM debian:bookworm-slim AS builder
RUN apt-get update && apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross
WORKDIR /src
COPY vec_add_neon.c .
RUN aarch64-linux-gnu-gcc \
    -shared -fPIC -O3 -march=armv8-a+fp+simd \
    -o /out/libvecadd_arm64.so vec_add_neon.c

FROM scratch AS export
COPY --from=builder /out/libvecadd_arm64.so /

Build and Extract

docker buildx build \
    --platform linux/arm64 \
    --target export \
    --output type=local,dest=./artifacts \
    .

Manifest Verification After Cross-Compile

After cross-compilation, generate a manifest with CompilerBridge and include the SHA-256 hash of the .so. The edge runtime verifies this hash before loading.

import hashlib
import json
from pathlib import Path

from pyvorin_edge.compiler_bridge import CompilerBridge

so_path = Path("./artifacts/libvecadd_arm64.so")

# Compute hash
hasher = hashlib.sha256()
with open(so_path, "rb") as fh:
    while chunk := fh.read(8192):
        hasher.update(chunk)
so_hash = hasher.hexdigest()

# Generate ABI + manifest
bridge = CompilerBridge()
abi = bridge.generate_abi(so_path, "vec_add_f32")

manifest = {
    "version": "1.0.0",
    "platform": "linux/arm64",
    "modules": [
        {
            "name": "vec_add_f32",
            "path": str(so_path.name),
            "abi": abi.to_dict(),
            "sha256": so_hash,
        }
    ],
}

with open("manifest.json", "w") as fh:
    json.dump(manifest, fh, indent=2)

print(json.dumps(manifest, indent=2))

CI/CD Pipeline Example (GitHub Actions)

The workflow below cross-compiles on every push, verifies the ELF architecture, generates the manifest, and uploads artifacts.

name: Cross-Compile ARM64 Kernels

on:
  push:
    branches: [main]
    paths:
      - "kernels/**/*.c"
      - ".github/workflows/cross-compile.yml"

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
        with:
          platforms: arm64

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Cross-compile with buildx
        run: |
          docker buildx build \
            --platform linux/arm64 \
            --target export \
            --output type=local,dest=./artifacts \
            -f kernels/Dockerfile .

      - name: Verify ELF architecture
        run: |
          for so in ./artifacts/*.so; do
            echo "Checking $so"
            file "$so" | grep -q "ARM aarch64" || exit 1
          done

      - name: Install Python dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ./edge_sdk

      - name: Generate manifest
        run: python kernels/generate_manifest.py

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: arm64-kernels
          path: |
            artifacts/*.so
            manifest.json

Runtime Verification on the Edge Device

When the edge agent boots, it loads the manifest and verifies each module before calling ModuleLoader.load(). The verify_signature() method in module_host/loader.py performs the SHA-256 check.

from pyv_edge_agent.module_host.loader import ModuleLoader
import json

manifest = json.load(open("manifest.json"))
loader = ModuleLoader()

for mod in manifest["modules"]:
    loader.load(mod["path"], ABIContract.from_manifest(mod["abi"]))
    if not loader.verify_signature(mod["sha256"]):
        raise RuntimeError(f"Integrity check failed for {mod['name']}")
    print(f"{mod['name']} verified and loaded.")

Summary

Cross-compilation for ARM64 is straightforward with aarch64-linux-gnu-gcc or Docker buildx. Always verify the ELF architecture with file, generate a manifest with SHA-256 hashes, and check those hashes at runtime via ModuleLoader.verify_signature(). Automate the entire flow in GitHub Actions for reproducible edge deployments.