Skip to content

v2026.5.3 – BLAKE3 Streaming + Three Emit Closures

v2026.5.3 – BLAKE3 Streaming + Three Emit Closures

Section titled “v2026.5.3 – BLAKE3 Streaming + Three Emit Closures”

Release date: 2026-05-03 Profiles affected: :core (compiler emit), :core stdlib (std.crypto.blake3) Status: Patch release (one stdlib feature + three emit closures)

This release ships a streaming + KDF-mode BLAKE3 surface in std.crypto.blake3 and closes three compiler emit gaps — including the var-decl generic-struct emit MissingOperand that v2026.5.2’s release notes had filed under Known Issues only a day earlier. If you read v2026.5.2 and noticed the var v: T[K, V] = .undefined workaround, you can stop using it.

Three of the four landings (ede7dbd1, f647b85b, 8aaf5a1d) come from the same SPEC-068 byte WAL frame codec landing arc — the codec’s adoption pressure surfaced compiler gaps faster than any other consumer this release cycle. The codec itself is still not landing (a codec-side bug, no longer a compiler one — see “What’s NOT in this release”), but the three compiler advancements are real, named, and self-contained, so they ship.


std.crypto.blake3 — streaming + KDF Hasher

Section titled “std.crypto.blake3 — streaming + KDF Hasher”
use std.crypto.blake3
func main() do
var h = blake3.hasher_init_derive("janus.docs/example/v1")
blake3.hasher_update(&h, "first chunk ")
blake3.hasher_update(&h, "second chunk")
var digest: [32]u8 = .undefined
blake3.hasher_final_into(&h, &digest)
end

Three free functions over a typed Hasher handle:

  • hasher_init_derive(context: []const u8) -> Hasher
  • hasher_update(h: *Hasher, data: []const u8)
  • hasher_final_into(h: *Hasher, out: *[32]u8)

Backed by the existing SIMD primitive in std/hash/blake3.zig. Multi-update calls are equivalent to a single concatenated update — verified by the test suite.

KDF mode only. Per the integrity-primitives doctrine, every BLAKE3 use must be domain-separated by a context string. Bare BLAKE3 (init()), keyed-MAC mode (initKeyed()), and XOF mode are deliberately not exposed by this sprint — they’ll arrive behind a separate RFC.

Single-threaded, non-overlapping use. The Zig side is a single static singleton, so concurrent or interleaved Hashers are not supported. A per-instance heap-backed Hasher is a follow-up RFC.

Free-function API by deliberate workaround. The natural method-style design (Hasher.initDerive(...) / h.update(...) / h.final() -> [32]u8) hits two open compiler gaps: impl-method [N]u8 return mis-lowering and cross-module impl-method dispatch mangling (G7b). When G7b lands the surface migrates to method style without an FFI break.


Var-decl generic-struct annotation lowering

Section titled “Var-decl generic-struct annotation lowering”
struct Pair[A, B] { a: A, b: B }
func main() do
var p: Pair[u32, u32] = .undefined // type-validation passed in 5.2,
p.a = 7 // emit failed with MissingOperand
p.b = 11 // now: emits cleanly
end

The non-generic shape var p: Point = .undefined; p.x = ... worked because lowerVarDecl had an arm for .identifier / .named_type that registers a Struct_Alloca with a field-name table and semantic_type = .named "Point". The generic-instantiation arm did not exist — Pair[u32, u32] fell through to the scalar Alloca path and the subsequent Field_Store had no field-name table to compute offsets from.

The fix extends the same arm to handle .generic_instantiation, resolving the base struct-type name from the GI node’s token range. Bare-name (Pair[u32, u32]) and module-qualified (mymod.Result[u32, u32]) annotation forms now both work.

This closes the Var-decl generic-struct emit MissingOperand Known Issue documented in v2026.5.2’s release notes.

struct_info propagation through emitOptionalUnwrap

Section titled “struct_info propagation through emitOptionalUnwrap”
const m = lookup(key) // returns ?MyStruct
if m != .none do
println(m.?.field) // field-access on optional-unwrap result
end

emitOptionalUnwrap previously stashed only the extracted payload value in self.values[node.id]. Downstream Field_Access on the unwrap result called self.struct_infos.get(node.id) and missed — surfacing as MissingOperand at LLVM emit. Struct_Construct registers struct_info keyed by its own node.id and never propagated through Optional_Some / Call returns into the Optional_Unwrap result.

The fix walks the unwrap node’s semantic_type (collapsed-named first, falling back to .optional[0].named), looks the type name up in named_struct_types and struct_field_names_by_type_name, then re-registers struct_info for the unwrap node. Same-module and cross-module named-struct optionals are both covered.

The prior LSM-A9 alloca-side fix only covered slice payloads (.named = "[]T"); this closure handles user struct payloads accessed without an Alloca round-trip.

Cross-module slice-field-access on direct local

Section titled “Cross-module slice-field-access on direct local”
// fixture.jan declares: pub struct SliceHolder { key: []const u8, n: u32 }
use fixture as p
func main() do
var h = p.SliceHolder { key: "hello", n: 7 }
if h.key.len != 5 do return 1 end // emits cleanly
return 0
end

emitStructAlloca’s “Case 1: count > 0” branch previously only consulted named_struct_types[node.semantic_type.named]. For cross-module struct literals (var h = mod.X { ... }), lowerStructLiteral produces struct_type_name = "mod.X" and semanticTypeForTypeName’s hasStructTypeDefinition check inspects only the local AST — the qualified name misses, the type collapses to .i64, the Struct_Alloca’s effective semantic type at emit-time becomes .ptr (via defaultSemanticTypeForOp), the named-type lookup never finds a match, resolved_field_layout stays empty, and chained .len access on a slice field trips Struct info not found → MissingOperand.

The fix mirrors the pattern already used by Case 2 (parameter Struct_Allocas with no inputs): when the named-type lookup misses, fall back to a field-names-match scan against named_struct_types. The cross-module struct IS registered there via the SPEC-041 export pipeline; the field-names match recovers its layout.

This closes the third compiler gap the SPEC-068 byte WAL frame codec landing arc has surfaced — the cross-module-direct-local face of the slice-field-access family. (See “What’s NOT in this release” below for the codec’s current blocker.)


  • Bare BLAKE3 (init() plain mode) — doctrine forbids bare-BLAKE3 for new integrity surfaces.
  • Keyed-MAC mode (initKeyed()) — reserved for a separate RFC.
  • XOF mode — same.
  • Method-style BLAKE3 API — blocked on G7b cross-module impl-method dispatch mangling.
  • Per-instance heap-backed Hasher — follow-up RFC; current Zig static singleton is intentional for the v0 surface.
  • SPEC-068 byte WAL frame codec — three compiler gaps blocked it across the v2026.5.x arc; all three are now closed (ede7dbd1, f647b85b, 8aaf5a1d). The codec compiles, links, and encodes correctly. Decode fails integrity-tag verification — a separate codec-side bug, likely slice .ptr / .len extraction inside the codec’s helpers when the slice is sourced from a *WalFrameV1 parameter inside its owning module (same family as the gap closed by 8aaf5a1d, different IR shape: slice-PARAM-from-pointer-to-struct instead of cross-module-direct-local-struct-field). Codec lands behind a follow-up debug session — no longer a compiler problem.

Nothing to migrate. The new BLAKE3 surface is additive. Existing :core and :service code compiles unchanged. The two emit closures are pure bug fixes — they remove MissingOperand failures from code that previously didn’t compile.

If you adopted the type-inferred workaround documented in v2026.5.2’s tutorial for var v: T[K, V] = .undefined, you can revert to the annotation form. The constructor-literal form (var v = T[K, V] { field: val }) continues to work.


  • Tutorial: Generics Across Module Boundaries – the var-decl emit closure removes the sharp edge previously documented in Step 3.
  • Source: std/crypto/blake3.jan – the streaming + KDF Hasher surface.
  • Doctrine: Janus/.agents/doctrines/integrity-primitives.md – why KDF mode only.

Binary version: 2026.5.3 Git hash: f647b85 Built on: 2026-05-03