From 458868e2e36b1e9247b7a599b6dc0de68270b7d4 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 27 May 2026 22:01:09 +0300 Subject: [PATCH] =?UTF-8?q?ffi=20checkpoint:=20step=205=20done=20=E2=80=94?= =?UTF-8?q?=20generic=20Into(Block)=20impl=20+=203=20unblocking=20bug=20fi?= =?UTF-8?q?xes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- current/CHECKPOINT-FFI.md | 105 +++++++++++++++++++++++++++++++++----- 1 file changed, 92 insertions(+), 13 deletions(-) diff --git a/current/CHECKPOINT-FFI.md b/current/CHECKPOINT-FFI.md index 786df2f..b13dba0 100644 --- a/current/CHECKPOINT-FFI.md +++ b/current/CHECKPOINT-FFI.md @@ -6,6 +6,82 @@ add a test and make it pass — that's two commits). ## Last completed step +**M5.A.next.5 — generic `Into(Block) for Closure(..$args) -> $R`** +(commits `3bd6f26` → `2eaf932`, plus 3 unblocking compiler-bug +fixes — issues 0048 / 0049 / 0050). + +The visible end-user payoff for the whole variadic-heterogeneous- +type-packs feature. One stdlib impl replaces every per-signature +hand-rolled `Into(Block)` pair; the compiler monomorphises the +impl body per call shape and emits a dedicated `__invoke` +`callconv(.c)` trampoline + Block literal via a single `#insert +build_block_convert($args, $R)`. + +| Slice | Commit | What | +|---|---|---| +| 5.0 probe | (no commit) | Confirmed nested `() -> Ret callconv(.c) { body }` parses + `@inner` address-of binds + indirect call works. The trampoline emission pattern is unblocked at the language-surface level. | +| issue-0048 xfail | `8fcf352` | Bare `$args` slice loses `.len` across a function-call boundary — pin via `examples/173-pack-bare-args-cross-call.sx`. | +| issue-0048 fix | `0ede097` | `lazyLowerFunction` saves/nulls `pack_arg_nodes` / `pack_param_count` / `pack_arg_types` / `inline_return_target` before lowering the callee body. Without it, a lazily lowered regular fn called from inside a pack-fn mono inherited the outer pack maps and `lowerFieldAccess`'s `.len` intercept folded the callee's same-named param to the outer mono's arity. | +| issue-0049 xfail | `64dcbca` | New-form variadic `..parts: []string` defined in stdlib + called from another module crashes LLVM emit (`LLVMBuildExtractValue` inside `emitStrCmp`). Pinned via the path_join migration. | +| issue-0049 fix | `b5301c4` | `resolveParamType` + `packVariadicCallArgs` now detect when a variadic param's declared type is already a slice (`..name: []T`) and use it as the element-shape container rather than wrapping `[]T` to `[][]T`. | +| variadic migration | `5b3d864` | Stdlib (`format` / `print` / `open`) and example fixtures (`19` / `20` / `50` / `120` / `ffi-foreign-cvariadic`) move to new `..name: []T` syntax. | +| variadic cutover | `952dc0e` | Parser hard-rejects the legacy `name: ..T` form. `specs.md` documents `..name: []T` as the surface syntax. | +| issue-0050 xfail | `ec2a99a` | Generic-mono path (`monomorphizeFunction`) leaks the outer pack-fn's `pack_arg_types` into the generic's body lowering — `args.len` constant-folds to the wrong arity per `examples/175-generic-fn-pack-state-leak.sx`. | +| issue-0050 fix | `5316bf7` | Same isolation pattern as 0048 applied to `monomorphizeFunction`. | +| 5.1.A xfail | `3bd6f26` | `build_block_convert(args: []Type, $ret: Type) -> string` undefined — pin output format via `examples/176-build-block-convert.sx` across 3 void shapes + 1 non-void shape. | +| 5.1.B fix | `aeb950b` | Builder added to `library/modules/std/objc_block.sx`. Emits nested `callconv(.c)` trampoline + Block literal source. | +| 5.2.A xfail | `f5342e9` | Generic `Into(Block)` impl absent — `Closure(s64, s64) -> void` (uncovered by hand-rolled impls) emits the "no Into(Block) for cl_s64_s64__void" diagnostic per `examples/177-generic-into-block.sx`. | +| 5.2.B fix | `165b621` | Generic impl `Closure(..$args) -> $R` added with `#insert build_block_convert($args, $R)`. `lowerExpr`'s `.comptime_pack_ref` + `resolveTypeArg` + `type_bridge.isTypeShapedAstNode` extended so impl-mono `$args` (pack_bindings) and `$R` (type_bindings) resolve in both expr and type positions. | +| 5.3 | `2eaf932` | Delete hand-rolled `__block_invoke_void` + `__block_invoke_bool` + the two per-shape impls. The generic impl covers both at runtime. | + +What's now possible end-to-end (from +`examples/177-generic-into-block.sx`): + +```sx +#import "modules/std/objc_block.sx"; + +main :: () -> s32 { + cl := (a: s64, b: s64) => { g_a = a; g_b = b; }; + blk : Block = xx cl; // generic impl mono'd for + // Closure(s64, s64) -> void + invoke_fn : (*Block, s64, s64) -> void callconv(.c) = xx blk.invoke; + invoke_fn(@blk, 10, 20); + 0; +} +``` + +The `xx cl : Block` site monomorphises the generic +`Into(Block) for Closure(..$args) -> $R` impl. Inside the impl +mono, `#insert build_block_convert($args, $R)` evaluates the +builder at comptime with `$args = [s64, s64]` and `$R = void`, +and substitutes the resulting source — a nested +`__invoke :: (block_self: *Block, arg0: s64, arg1: s64) -> void +callconv(.c) { ... }` trampoline plus the Block literal that +points its `invoke` slot at `@__invoke`. Stack-local block layout +matches Apple's published spec; UIKit / Foundation consumers can +take this directly. + +Adding a new closure shape to stdlib used to mean writing a +per-signature `__block_invoke_` trampoline + a focused +`Into(Block) for Closure()` impl. Now: no stdlib edit +needed. The generic impl emits per-call-shape on demand. + +217/217 example tests + `zig build test` green. + +Known follow-ups (out of scope for step 5): +- `string`-typed arg in a generic block trampoline segfaults at + runtime — the 16-byte `{ptr, len}` slice doesn't round-trip + through the `callconv(.c)` ABI cleanly in the generated + trampoline. Hand-rolled impls didn't hit this because they + pre-dated string-arg shapes. Real closures of shape `Closure( + string, ...) -> ...` are uncommon in Apple block APIs; revisit + when a UIKit caller needs it. +- Step 6 of the pack feature (rewriting `print` / `format` to + use `..$args: []$T` for compile-time arity + type checking + instead of `..args: []Any` runtime boxing). + +--- + **M5.A.next.4A.bare — bare `$args` + dynamic reflection intrinsics** (commits `c792642` → `2162662`, 5 slices in order). @@ -852,19 +928,22 @@ plus 2 codegen fixes surfaced along the way.** ## Current state -- 213/213 example tests pass; `zig build test` green. -- issue-0048 (bare `$args` slice loses `.len` across function- - call boundary — root cause: `lazyLowerFunction` did not save/ - restore `pack_arg_nodes` / `pack_param_count` / `pack_arg_types` - / `inline_return_target`, so a lazily lowered callee inside a - pack-fn mono inherited the outer pack maps and `lowerFieldAccess`'s - `.len` intercept folded the callee's same-named param - to the outer mono's arity). FIXED — defer-restore block added to - `lazyLowerFunction`; regression locked in at - `examples/173-pack-bare-args-cross-call.sx`. -- Step 5.0 probe done — nested `() -> Ret callconv(.c) { body }` - parses, `@inner` address-of binds to a fn-pointer, indirect call - works. The trampoline emission pattern is unblocked. +- 217/217 example tests pass; `zig build test` green. +- **Step 5 of the variadic heterogeneous type packs feature done + end-to-end.** Generic `Into(Block) for Closure(..$args) -> $R` + impl in stdlib emits per-call-shape `__invoke` trampoline + + Block literal via `#insert build_block_convert($args, $R)`. + Hand-rolled `__block_invoke_void` / `__block_invoke_bool` + deleted; `examples/95` / `96` route through the generic + unchanged. +- Three compiler-bug fixes landed alongside step 5 — issues + 0048 (`lazyLowerFunction` pack-state leak), 0049 (new-form + variadic `..name: []T` cross-module emit crash), 0050 + (`monomorphizeFunction` pack-state leak). Each is captured by + a focused regression test (`examples/173` / `174` / `175`). +- Legacy variadic syntax `name: ..T` rejected at parse time; + stdlib (`path_join` / `format` / `print` / `open`) and example + fixtures migrated to `..name: []T`. `specs.md` updated. - Phase 3.0/3.1/3.2 + M1.0–M1.3 + M2.1–M2.3 + M3 + M4.0 + M4.A all landed. - Pack feature step 1 done (1c.A → 1d.B; commits bb6eca6 → 08feb60). - Pack feature step 2 done — typed `args[$i]` at literal indices