Skip to content

[DOCS] Add "NVX, CPU ISA levels, native & cross builds, and wheel compatibility" documentation page #1946

Description

@oberstet

also see: #1935

Summary

autobahn-python's native story is deliberately sophisticated — NVX (own SIMD C via CFFI) with
fixed -march floors per architecture, a pure-Python auto-fallback, an opt-in native-arch
build (real Gentoo/Arch users), cross-compilation support (real Yocto/Buildroot users),
and a vendored FlatBuffers. Today this is spread across README.md, docs/wheels-inventory.rst,
docs/environments/conda.rst, docs/release-notes.rst, docs/changelog.rst, and docstrings in
src/autobahn/nvx/_compile_args.py. This issue proposes one reference page (RST, under
docs/) consolidating and correcting the material below.

Scope: documentation only — no src/ behaviour change. (One doc correction is needed — see §10.)

Proposed page outline

1. Two-layer model

  • Pure-Python core — every hot path (UTF-8 validation, XOR masking) has a pure-Python
    implementation → runs on any CPU, any architecture, CPython and PyPy. No ISA floor.
    autobahn always runs.
  • NVX — optional CFFI accelerator (_nvx_utf8validator, _nvx_xormasker) for those two paths.
  • and both are tested for WebSocket conformance using autobahn-testsuite and pass every test green (in strict mode)

2. ISA floors of the published native wheels

Target -march floor Meaning
x86-64 x86-64-v2 SSE3/SSSE3/SSE4.1/SSE4.2, POPCNT, CMPXCHG16B (~2009+)
aarch64 armv8-a the 64-bit ARM baseline — every aarch64 CPU qualifies (no sub-floor case)
source build same floors; AUTOBAHN_ARCH_TARGET=native opt-in host-CPU-locked, maximum performance

Authoritative source: src/autobahn/nvx/_compile_args.py / get_compile_args():

$ python -c "from autobahn.nvx._compile_args import get_compile_args; print(get_compile_args())"
['-std=c99', '-Wall', '-Wno-strict-prototypes', '-O3', '-march=x86-64-v2']

3. Native & cross builds (Gentoo/Arch, Yocto/Buildroot)

  • Native distro builds (Gentoo, Arch): AUTOBAHN_ARCH_TARGET=native → -march=native, host-CPU
    optimized, host-locked. Correct only when the build host is also the run host (single-machine
    or per-host package builds).
  • Cross-compilation (Yocto, Buildroot, OpenWrt): building e.g. aarch64 on an x86-64 host. Two
    rules the page must state (and which _compile_args.py already encodes):
    • -march=native is host-only and wrong under cross-compilation — it would encode the
      build host's ISA into a target binary. NVX therefore keeps native strictly opt-in and
      never defaults to it.
    • platform.machine() reports the build host arch, not the target arch, during a
      cross-build — so it must not be used to pick target flags; the vendored/distro toolchain's
      own CFLAGS/-march (Buildroot/Yocto-supplied) are respected instead.
    • Net: a Buildroot/Yocto cross-build gets the toolchain's baseline (e.g. the SDK's -march),
      not x86-64-v2/native; document how an integrator overrides flags if they want.

4. Runtime selection & fallback — build-availability-based, not CPU-capability-based

From autobahn/websocket/__init__.py:

  • HAS_NVX = were the CFFI modules importable (try: import _nvx_utf8validator … except ImportError).
  • USES_NVX = HAS_NVX, unless AUTOBAHN_USE_NVX=0 (force pure Python) or =1 while not built (loud RuntimeError).

Handled gracefully: not built → pure Python; explicitly disabled → pure Python.

⚠️ The one gap to document prominently: a v2 wheel on a sub-v2 CPU. import _nvx_utf8validator succeeds (dlopen loads the object; the SSE4.x code only executes when
validate()/mask() is called), so except ImportError never fires → USES_NVX=True →
SIGILL on the first native call. The probe is a build-capability check, not a CPU-
capability check. Escape: AUTOBAHN_USE_NVX=0 → pure Python. (Cross-link the proposed
runtime ISA-floor-guard issue, which would close this by gating NVX on the CPU meeting the floor.)

5. Build-side hardening (already shipped, #1856 — document it)

AUTOBAHN_USE_NVX=1 requesting NVX while the CFFI extension fails to compile now aborts the
build hard
(non-zero exit) instead of silently degrading to a pure-Python py3-none-any wheel.
A transient native-compile crash (e.g. a gcc SIGSEGV under QEMU ARM64 emulation) therefore fails
CI so it can retry, rather than uploading a structurally valid but unintended artifact.
AUTOBAHN_USE_NVX=0 still produces a pure-Python wheel as before.

6. How to determine a wheel's ISA

  • file / ldd cannot tell you the micro-arch level (only x86-64 + libs).
  • Contract: get_compile_args() (the -march) — the minimum promised ISA.
  • readelf -n <so> | grep -i x86 — GNU ISA property if emitted (often absent for -march=v2 without -fcf-protection).
  • objdump -d <so> | grep -cE '%ymm|%zmm' (AVX/AVX-512 present?) + grep v2/SSE4.2 tells (pcmpistri popcnt crc32 ptest pblendvb round[ps][sd] pextrb pinsrb).
  • elfx86exts — the purpose-built tool. Install via apt (elfx86exts), not cargo: a stable
    measurement tool for a reproducible gate wants a pinned, prebuilt, toolchain-free binary; cargo install floats and needs Rust, buying nothing for ISA-level detection. Worked NVX example:
    $ elfx86exts _nvx_utf8validator.cpython-311-x86_64-linux-gnu.so
    SSE2 (pxor)  SSE41 (pextrb)  ...  CPU Generation: Penryn
    
    → actual highest instruction = SSE4.1 (Penryn, 2007), comfortably inside the declared
    -march=x86-64-v2 contract. Distinguish contract (v2, what you support) from emitted floor
    (SSE4.1, what's in this binary).

7. elfx86exts limits (important)

elfx86exts reports the union of instructions present (static). For NVX this equals the
requirement, because NVX is a fixed -march, no-runtime-dispatch build. For runtime-CPUID-
dispatching
binaries the union is not the requirement (they bundle SSE→AVX-512 paths and pick
one at runtime) — never apply "elfx86exts max = required ISA" to a dispatching third-party binary.
NVX is the easy case precisely because it doesn't dispatch.

8. How to determine a machine's capability

  • grep -m1 '^flags' /proc/cpuinfo — check for the feature (sse4_2, avx2, …). Include the
    elfx86exts→/proc/cpuinfo flag map (SSE41→sse4_1, AVX2→avx2, AVX512→avx512f, AES→aes,
    PCLMUL→pclmulqdq, BMI2→bmi2, ADX→adx).
  • /lib64/ld-linux-x86-64.so.2 --help | grep -i 'x86-64-v' → the CPU's x86-64-vN level.
  • Ground truth: run a real workload — a missing required instruction faults immediately with SIGILL.

9. Vendored FlatBuffers (autobahn's other native/vendoring dimension)

autobahn also vendors FlatBuffers (schema + flatc codegen) and exposes it from Python,
alongside NVX. The page should note the vendoring + version-sync policy — importantly, zlmdb also
vendors FlatBuffers
, and both copies must stay version-synced (see the flatbuffers sync-check work
and the FlatBuffers-pair coupling). This matters downstream because crossbar depends on both
autobahn and zlmdb
(see §12).

10. Consolidate scattered docs — and correct one inaccuracy

Fold in / cross-link: README.md NVX section, docs/wheels-inventory.rst (AUTOBAHN_USE_NVX=0/1),
docs/environments/conda.rst, docs/release-notes.rst, docs/changelog.rst (#1856).

★ Correction: docs/release-notes.rst currently says NVX is "Automatically enabled on x86_64
CPUs with AVX2 support."
This is inaccurate on two counts: (a) the floor is x86-64-v2
(SSE4.2, not AVX2/v3)
; (b) enablement is import-based (HAS_NVX), not AVX2-CPU-gated. The new
page should state the correct floor + fallback semantics and the release note should be fixed.

11. Compatibility table — the asgard fleet (worked example)

Node CPU ISA level pure Python published NVX wheel
asgard1 Xeon D-1587 (Broadwell) x86-64-v3 (AVX2) ✓ accelerated ✓
asgard4 Celeron J3455 (Goldmont) x86-64-v2 (SSE4.2, no AVX) ✓ accelerated ✓ (floor exactly met)
asgard2/3 Atom D525 (Pineview) ~baseline (no SSE4.x) ✓ SIGILL on first NVX call → set AUTOBAHN_USE_NVX=0 → pure Python

Punchline: autobahn has no hard ISA cliff — there is always a working pure-Python path —
unlike a closed native binary with no fallback. The only sharp edge is the sub-v2-wheel case (§4).

12. Stack context (why this matters beyond autobahn)

crossbar depends on both autobahn and zlmdb. The native/vendoring/cross-compile story spans
all three:

  • autobahn vendors FlatBuffers + NVX (own SIMD C, -march floors),
  • zlmdb vendors LMDB (patched) + FlatBuffers (portable C, no ISA floor),
  • crossbar consumes both → the union.
    The FlatBuffers copies in autobahn and zlmdb must stay version-synced. Cross-link the sibling
    zlmdb docs issue.

13. FAQ

  • "SIGILL on an old CPU" → AUTOBAHN_USE_NVX=0 (or install without NVX); see the runtime-guard issue.
  • PyPy: NVX is CFFI (works on CPython and PyPy); pure-Python paths JIT well on PyPy.

Acceptance criteria

  • New RST page under docs/ (e.g. docs/nvx_cpu_isa.rst), linked from the docs index and installation page.
  • Covers: two-layer model; -march floors (v2 / armv8-a / native opt-in); native & cross builds (Gentoo/Arch, Yocto/Buildroot, platform.machine() + native caveats); build-availability-vs-CPU-capability fallback + the sub-v2 SIGILL caveat; the [BUG] Sync .cicd security fix and harden hatch_build.py against silent CFFI build degradation (release-readiness for 26.6.1) #1856 build-hardening; determining wheel ISA (elfx86exts + dispatch limits); determining CPU capability; vendored FlatBuffers + sync note; the asgard table.
  • docs/release-notes.rst "AVX2" claim corrected.

Notes

  • Documentation only — no src/ change, no dependency change; autobahn stays MIT.
  • Related: proposed runtime NVX ISA-floor guard (graceful fallback instead of SIGILL) and the
    check-wheel-isa build gate (assert built wheels don't exceed the declared -march floor via
    elfx86exts, apt, on the outer runner + objdump fallback in-container); sibling zlmdb native/
    vendoring docs issue.
  • The RST page itself is authored and human-reviewed per AI_POLICY.md; this issue captures the substance/outline.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions