ffi checkpoint: step 5 done — generic Into(Block) impl + 3 unblocking bug fixes

This commit is contained in:
agra
2026-05-27 22:01:09 +03:00
parent 2eaf932fcf
commit 458868e2e3

View File

@@ -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 `<pack_name>.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_<sig>` trampoline + a focused
`Into(Block) for Closure(<sig>)` 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
`<pack_name>.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.0M1.3 + M2.1M2.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