EffeTune DSP native ABI audit
Status: Phase 0 audit conclusion, 2026-07-28
The existing C ABI v1 is a WASM host ABI, not a public native ABI. Phase 0
does not change it or promise native binary compatibility. Python uses a
private adapter statically linked to the required C++ kernel, while the
candidate public boundary is the semantic high-level API described in
api-design.md.
64-bit asset handoff
Evidence
dsp/include/effetune/abi.hdeclareset_instance_asset_begin()with auint32_treturn value.dsp/core/abi.cppobtains a staginguint8_t *, casts it throughuintptr_t, and narrows it touint32_t. That represents a wasm32 linear-memory offset but can truncate a normal 64-bit native pointer.- Asset byte size, footprint, channel count, frame count, and related capacity
queries use
uint32_t.et_engine_memory_required()also returnsuint32_t. These are coherent wasm32 limits, not a reviewed native large-buffer or large-asset contract. et_engineandet_instanceareuint32_ttable handles. Engine instances are resolved through private process-global slots indsp/core/abi.cpp; their representation and lifetime are current host implementation details.- Other ABI functions return raw arena and telemetry pointers. Their native pointer width is not narrowed, but their ownership and lifetime depend on the current engine and arena implementation and are unsuitable for an accidental stable ABI.
Phase 0 decision
C ABI v1 remains unchanged and WASM-internal. It must not be documented, linked, or versioned as a public native ABI. The Phase 0 nanobind module uses a private C++ adapter and a private static kernel target; it does not pass assets through C ABI v1 or export that ABI from the Python package.
Deferred action
If a native C ABI becomes a concrete requirement, introduce a separately
versioned ABI rather than extending v1 in place. Use opaque native handles,
status-returning calls, and either an out pointer with an explicit ownership
contract or caller-to-library copy functions. Express externally supplied byte
and frame lengths with checked 64-bit types, convert to size_t only after
bounds checks, and never encode a native pointer in a 32-bit integer.
Export specification
Evidence
ET_EXPORTindsp/include/effetune/abi.hexpands to__attribute__((used, visibility("default")))for GCC and Clang. It expands to nothing for other compilers, so there is no MSVC__declspec(dllexport)/__declspec(dllimport)contract.dsp/CMakeLists.txtbuildseffetune_dsp_coreasSTATIC. It defines no native shared-library target, default-hidden symbol policy, import/export build definition, installable ABI library, or native symbol audit.- The Emscripten executable passes
-sEXPORTED_FUNCTIONS=@dsp/exports.txt. That file is an explicit WASM allowlist for C ABI v1 functions plus_free; it is not a native library export map. - The Phase 0 Python CMake file builds only the nanobind module and privately links a static Compressor kernel. The module initializer is the only dynamic entry point intended for Python loading.
Phase 0 decision
The current attributes and dsp/exports.txt are sufficient evidence for the
existing Emscripten host boundary only. They do not establish portable native
exports. Phase 0 adds no export macro or shared target because doing so would
suggest a public native ABI that this audit explicitly rejects.
Deferred action
A future public native shared library must define build and consume modes:
__declspec(dllexport)/__declspec(dllimport) on MSVC and explicit default
visibility for public GCC/Clang symbols while the target uses hidden visibility
by default. Each platform must use an allowlist or export map and a symbol-table
test proving that only the documented, separately versioned native ABI is
visible.
Public and private scope
Evidence
- C ABI v1 exposes kernel enumeration, parameter hashes and packed buffers, engine/instance handles, asset staging, arena/scratch pointers, binary telemetry, and binary pipeline descriptors.
- C++ registry entries return
KernelDescriptorobjects that contain construction and destruction functions and object layout information. params.jsonis code-generated into packed C++ structs and JavaScript packers. Compressor currently has six packed floats and a generated layout hash. These values protect a wrapper/artifact pairing; they are not semantic API versioning.plugins/audio-processor.jstranslates application objects into C ABI calls and exchanges private AudioWorklet messages. The Phase 0 worklet wrapper deliberately reuses that host implementation.- The Phase 0 nanobind adapter reaches
PluginKerneland the generated Compressor parameter struct directly through private static linkage.
Phase 0 decision
The candidate public surface consists of semantic Effect, Compressor, and
ordered Chain objects; long parameter names; planar float32 audio; explicit
sample rate; deterministic uint32 seed behavior; the semantic chain schema;
typed wrapper errors; an asset-resolver boundary; and decoded wrapper-owned
telemetry.
The following remain private implementation details:
- C ABI v1 and all engine, instance, pointer, and WASM handles;
- generated parameter structs, packed field order, short keys, layout hashes, and parameter packers;
PluginKernel, kernel descriptors, registry contents, and construction functions;- arenas, scratch storage, pipeline descriptors, asset staging, and asset footprint internals;
- binary telemetry layouts, rings, taps, and drop counters;
- nanobind adapter types and AudioWorklet message formats.
No private detail above is covered by semantic schema version 1.
Deferred action
Phase 1 may implement the semantic wrappers while retaining all current binary details behind them. A public native ABI is considered only after a concrete native consumer requires it and after its separate versioning, ownership, 64-bit size, error, lifecycle, visibility, and symbol-allowlist contracts are implemented and tested. A schema or wrapper release must not implicitly promote the current C ABI to public status.
Audit conclusion
The Phase 0 native ABI gate is complete as a design decision: use private static linkage for Python, keep C ABI v1 as the WASM host ABI, and expose only semantic high-level concepts to future library users. No C++ or build-system change is required for this conclusion.