hashsig-glue: replace Box allocs with Zig-owned placement-init pattern - #935
hashsig-glue: replace Box allocs with Zig-owned placement-init pattern#935zclawz wants to merge 3 commits into
Conversation
PublicKey gained a _buf field in the placement-init refactor (PR #935) but the PublicKeyCache::deinit in lib.zig still used the old single-field struct init. Match the already-corrected pattern from hashsig.zig: reconstruct both _buf and handle from the stored atomic integer (they both point to the start of the same C-heap allocation). Fixes build/test CI failures on macOS.
|
CI fix pushed — commit Root cause: const raw: [*]u8 = @ptrFromInt(ptr_int);
var pk = PublicKey{ ._buf = raw, .handle = @ptrCast(raw) };But var pk = PublicKey{ .handle = @ptrFromInt(ptr_int) }; // missing _bufThis caused Fix: port the same corrected pattern to 🤖 zclawz |
Rust no longer heap-allocates the outer KeyPair / Signature / PublicKey wrapper structs. Instead, each init function accepts a caller-supplied buffer and writes into it with std::ptr::write; the matching _deinit runs std::ptr::drop_in_place (Rust Drop) without freeing the buffer. Zig side allocates the buffers via C malloc (always malloc-aligned, safe for all Rust #[repr(C)] types) and frees them after calling _deinit. New Rust exports hashsig_sizeof_keypair / _signature / _public_key — allocation size hashsig_alignof_keypair / _signature / _public_key — required alignment hashsig_keypair_generate_into (was: hashsig_keypair_generate → *mut Box) hashsig_keypair_from_ssz_into (was: hashsig_keypair_from_ssz → *mut Box) hashsig_keypair_deinit (was: hashsig_keypair_free → Box::from_raw) hashsig_sign_into (was: hashsig_sign → *mut Box) hashsig_signature_deinit (was: hashsig_signature_free) hashsig_signature_from_ssz_into hashsig_public_key_from_ssz_into hashsig_public_key_deinit Removed Rust exports (were Box-allocating): hashsig_keypair_generate, hashsig_keypair_from_ssz, hashsig_keypair_free hashsig_sign, hashsig_signature_free, hashsig_signature_from_ssz hashsig_public_key_from_ssz, hashsig_public_key_free Note: the inner XMSS types (leansig XmssPublicKey / XmssSignature etc.) still carry their own heap-allocated data (Vec<u8> inside leansig); those are inherent to the leansig API and are freed correctly when _deinit runs Drop in-place. The intermediate Vec<u8> in to_bytes() serialisation helpers is similarly a leansig API constraint; those are short-lived temporaries that do not escape the FFI call. Addresses: #918 (comment)
PublicKey gained a _buf field in the placement-init refactor (PR #935) but the PublicKeyCache::deinit in lib.zig still used the old single-field struct init. Match the already-corrected pattern from hashsig.zig: reconstruct both _buf and handle from the stored atomic integer (they both point to the start of the same C-heap allocation). Fixes build/test CI failures on macOS.
cb8401e to
2efa790
Compare
|
CI fix: Rebased onto The CI was failing because Fix: clean rebase onto |
Addresses the review comment #918 (comment) (ref PR #918) requesting that key-generation and signing be refactored to avoid additional Rust heap allocations, consistent with the caller-supplies-buffer pattern already used in multisig-glue.
Problem
hashsig-gluewas Box-allocating everyKeyPair,Signature, andPublicKeyon the Rust heap and handing raw pointers to Zig. Zig then had to call back into Rust (hashsig_keypair_freeetc.) to free them. This created a split-ownership model where allocation and deallocation crossed the FFI boundary in opposite directions, complicating lifetime tracking and adding unnecessary alloc/free pairs.Solution: placement-init pattern
Rust exports layout queries so Zig can pre-allocate correctly-sized storage, then passes that storage to Rust for in-place initialisation. Cleanup runs Rust Drop in-place, then Zig frees its own buffer. The Rust heap is never involved for the outer struct.
rust/hashsig-glue/src/lib.rsNew layout queries:
hashsig_sizeof_keypair / _signature / _public_key— Zig queries at runtime to know how much to allocatehashsig_alignof_keypair / _signature / _public_key— required alignment (Cmallocalways satisfies this for#[repr(C)]types)New placement-init (replaces Box-returning functions):
hashsig_keypair_generate_into(out: *mut KeyPair, ...)— usesstd::ptr::write, returnsi32(0 / -1)hashsig_keypair_from_ssz_into,hashsig_sign_into,hashsig_signature_from_ssz_into,hashsig_public_key_from_ssz_into— same patternNew drop-in-place (replaces
Box::from_rawfrees):hashsig_keypair_deinit/hashsig_signature_deinit/hashsig_public_key_deinit— callstd::ptr::drop_in_place, do not free the buffer (caller owns it)Removed (all were Box-allocating):
hashsig_keypair_generate,hashsig_keypair_from_ssz,hashsig_keypair_free,hashsig_sign,hashsig_signature_free,hashsig_signature_from_ssz,hashsig_public_key_from_ssz,hashsig_public_key_freepkgs/xmss/src/hashsig.zigKeyPair,Signature,PublicKeywrappers now own their storage viacAlloc/cFree(thin wrappers overstd.c.malloc/std.c.free)mallocalways returns memory aligned tomax_align_t(>=8 bytes on LP64), satisfying the alignment of any Rust#[repr(C)]type used heregenerate,fromSsz,sign,verify,toBytes,deinitmethods; no changes needed inaggregation.zigor any other callerPublicKeyCacheslot semantics unchanged;deinitcorrectly reconstructs_buf+handlefrom the stored integer pointerWhat this does NOT eliminate
Vec<u8>data inside the XMSS key/signature types — these are owned by the leansig library and freed correctly whendrop_in_placeruns DropVec<u8>inside theto_bytes()serialisation helpers — a leansig API constraint; short-lived temporaries that never escape the FFI callVerification
cargo check -p hashsig-gluecleanzig ast-checkonhashsig.zig,aggregation.zig,lib.zigall pass