abi: pass >16B aggregates by ptr-in-next-reg (Apple ARM64 ABI) + Path B for fn-ptr casts
Three stacked compiler bugs were causing iOS-sim chess to crash inside [MTLTexture replaceRegion:...]. Fixing them lets every replaceRegion call site succeed (1×1 RGBA8, 1MB R8 atlas, 440×440 chess pieces). Path B for callconv(.c) fn-pointer casts: - FunctionInfo now carries call_conv: CallConv (TypeInfo.CallConv) so function-type interning distinguishes sx-CC from C-CC. Inst.zig's Function.CallingConvention aliases the same enum. - Parser accepts an optional `callconv(.c)` suffix on fn-pointer type spellings (factored into parseOptionalCallConv() shared with parseFnDecl and parseLambda). - resolveFunctionType passes the parsed CC through functionTypeCC(). - .call_indirect reads fp.call_conv == .c and applies the C-ABI alloca+materialize for >16B aggregate args (Path A's behaviour at .call). Apple ARM64 ABI (drop LLVM byval): - Side-by-side asm diff vs clang's emission for the equivalent C call site showed LLVM's `byval` attribute lowers Apple-arm64 byval on the stack, while clang passes the struct via a pointer in the next int register (x2 for replaceRegion:). The runtime objc_msgSend dispatch path expects clang's convention. - Dropped the byval attribute from the function-signature emission and from both call sites (.call and .call_indirect). The materialize-into- alloca + pass-plain-ptr pattern stays — the call site now matches clang's `mov x2, sp` exactly. - Path A's sx-to-sx case continues to work since both ends use plain ptr (caller does alloca+store+pass, callee loads from the ptr in prologue). Protocol dispatch (emitProtocolDispatch): - Untargeted `null` lowers as const_null with type .void (per target_type orelse .void). The "wrap-value-in-alloca-pass-pointer" branch alloca'd a void slot, which LLVM's IRBuilder asserts on — EXC_BREAKPOINT in getTypeSizeInBits, manifesting as exit 133 / SIGTRAP when building the chess game. Fixed by re-emitting as constNull(void_ptr) when arg_ty == .void && expected_ty == void_ptr. - is_pointer_ty only recognized .pointer, so [*]T (many_pointer) was alloca-wrapped — the heap pixels pointer from stbi_load was stored into a stack slot and the slot's address was passed as the *void arg. Fixed by extending the check to `.pointer or .many_pointer`. metal.sx call sites + lifecycle guards: - msg_replace (replaceRegion:, MTLRegion = 48B) and the two setScissorRect: sites (MTLScissorRect = 32B) now spell their fn-pointer types with by-value params + callconv(.c) — the *MTLRegion/@local workaround is gone. - metal_begin_frame_ios bails before nextDrawable when pixel_w/h are 0 (drawableSize 0×0 makes nextDrawable abort via XPC). - metal_init_ios only sets drawableSize when dims are positive. - begin_frame's encoder/cmd_buffer failure paths now clear self.drawable so a partial failure doesn't leak a drawable back into the pool. Examples + tests: - examples/86-callconv-c-fnptr-large-aggregate.sx — new, covers Path B with C-CC fn-ptr cast. - examples/87-fnptr-cast-large-aggregate.sx — renamed from issue-0025.sx, covers Path B with default sx-CC (the negative case). - examples/85-cc-c-large-aggregate.sx — from Session 60, covers Path A. - examples/issue-0014.sx, issue-0024.sx, issue-0025.sx — removed (resolved earlier this work). 71 regression tests pass, 0 failed. Chess game builds clean for iOS sim and reaches its frame loop without aborting. Runtime: chess UI still doesn't render — remaining issue is in the UIKit lifecycle / CAMetalLayer setup (legacy-app vs scene-API hybrid), not a compiler bug. See current/CHECKPOINT.md "Next step" for the diagnosis + options.
This commit is contained in:
@@ -54,11 +54,10 @@ MTLClearColor :: struct {
|
||||
}
|
||||
|
||||
// MTLOrigin / MTLSize / MTLRegion / MTLScissorRect — integer aggregates.
|
||||
// MTLRegion is 48 bytes and MTLScissorRect is 32 bytes; arm64 Apple ABI
|
||||
// passes >16-byte composites by reference (address in the next register).
|
||||
// We declare the call shapes with `*MTLRegion` etc., construct a local on
|
||||
// the stack, and pass `@local` — the machine state matches what the Obj-C
|
||||
// method expects.
|
||||
// MTLRegion is 48 bytes and MTLScissorRect is 32 bytes; both are passed
|
||||
// by value to the Obj-C runtime, which the compiler marshals as
|
||||
// `ptr byval(<T>)` via the C-ABI byval coercion. The fn-pointer cast
|
||||
// must spell `callconv(.c)` so the indirect call applies that coercion.
|
||||
MTLOrigin :: struct { x: u64; y: u64; z: u64; }
|
||||
MTLSize :: struct { width: u64; height: u64; depth: u64; }
|
||||
MTLRegion :: struct { origin: MTLOrigin; size: MTLSize; }
|
||||
@@ -243,8 +242,14 @@ metal_init_ios :: (self: *MetalGPU) -> bool {
|
||||
msg_ou(self.layer, sel_registerName("setPixelFormat:".ptr), MTL_PIXEL_FORMAT_BGRA8_UNORM);
|
||||
msg_ob(self.layer, sel_registerName("setFramebufferOnly:".ptr), 1);
|
||||
|
||||
size := CGSize.{ width = xx self.pixel_w, height = xx self.pixel_h };
|
||||
msg_osize(self.layer, sel_registerName("setDrawableSize:".ptr), size);
|
||||
// setDrawableSize:(0,0) makes nextDrawable abort via XPC. Skip the
|
||||
// size set when dims are not yet known — the layer's drawableSize
|
||||
// defaults to its bounds×contentsScale until we override it, which
|
||||
// also lets the first frame render at the natural backing size.
|
||||
if self.pixel_w > 0 and self.pixel_h > 0 {
|
||||
size := CGSize.{ width = xx self.pixel_w, height = xx self.pixel_h };
|
||||
msg_osize(self.layer, sel_registerName("setDrawableSize:".ptr), size);
|
||||
}
|
||||
}
|
||||
|
||||
true;
|
||||
@@ -263,6 +268,7 @@ metal_begin_frame_ios :: (self: *MetalGPU, clear: ClearColor) -> bool {
|
||||
inline if OS != .ios { return false; }
|
||||
if self.layer == null { return false; }
|
||||
if self.queue == null { return false; }
|
||||
if self.pixel_w <= 0 or self.pixel_h <= 0 { return false; }
|
||||
|
||||
msg_o : (*void, *void) -> *void = xx objc_msgSend;
|
||||
msg_oo : (*void, *void, *void) -> void = xx objc_msgSend;
|
||||
@@ -300,12 +306,12 @@ metal_begin_frame_ios :: (self: *MetalGPU, clear: ClearColor) -> bool {
|
||||
|
||||
// cmd = [queue commandBuffer] (autoreleased)
|
||||
self.cmd_buffer = msg_o(self.queue, sel_registerName("commandBuffer".ptr));
|
||||
if self.cmd_buffer == null { return false; }
|
||||
if self.cmd_buffer == null { self.drawable = null; return false; }
|
||||
|
||||
// encoder = [cmd renderCommandEncoderWithDescriptor:pass] (autoreleased)
|
||||
self.encoder = msg_oo_ret(self.cmd_buffer,
|
||||
sel_registerName("renderCommandEncoderWithDescriptor:".ptr), pass);
|
||||
if self.encoder == null { return false; }
|
||||
if self.encoder == null { self.cmd_buffer = null; self.drawable = null; return false; }
|
||||
|
||||
true;
|
||||
}
|
||||
@@ -520,10 +526,10 @@ metal_update_texture_region_ios :: (self: *MetalGPU, handle: u32, x: s32, y: s32
|
||||
bytes_per_row : u64 = xx (slot.bytes_per_pixel * cast(u32) w);
|
||||
|
||||
// [tex replaceRegion:region mipmapLevel:0 withBytes:pixels bytesPerRow:bytes_per_row]
|
||||
msg_replace : (*void, *void, *MTLRegion, u64, *void, u64) -> void = xx objc_msgSend;
|
||||
msg_replace : (*void, *void, MTLRegion, u64, *void, u64) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg_replace(slot.tex,
|
||||
sel_registerName("replaceRegion:mipmapLevel:withBytes:bytesPerRow:".ptr),
|
||||
@region, 0, pixels, bytes_per_row);
|
||||
region, 0, pixels, bytes_per_row);
|
||||
}
|
||||
|
||||
// ── Per-draw state ───────────────────────────────────────────────────────
|
||||
@@ -575,9 +581,9 @@ metal_set_scissor_ios :: (self: *MetalGPU, x: s32, y: s32, w: s32, h: s32) {
|
||||
inline if OS != .ios { return; }
|
||||
if self.encoder == null { return; }
|
||||
rect : MTLScissorRect = .{ x = xx x, y = xx y, width = xx w, height = xx h };
|
||||
// [encoder setScissorRect:rect] (MTLScissorRect is 32 bytes → indirect)
|
||||
msg : (*void, *void, *MTLScissorRect) -> void = xx objc_msgSend;
|
||||
msg(self.encoder, sel_registerName("setScissorRect:".ptr), @rect);
|
||||
// [encoder setScissorRect:rect] (MTLScissorRect is 32 bytes → ptr byval)
|
||||
msg : (*void, *void, MTLScissorRect) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg(self.encoder, sel_registerName("setScissorRect:".ptr), rect);
|
||||
}
|
||||
|
||||
metal_disable_scissor_ios :: (self: *MetalGPU) {
|
||||
@@ -586,8 +592,8 @@ metal_disable_scissor_ios :: (self: *MetalGPU) {
|
||||
// Metal has no "disable scissor" — set the rect to cover the full
|
||||
// drawable so subsequent draws aren't clipped.
|
||||
rect : MTLScissorRect = .{ x = 0, y = 0, width = xx self.pixel_w, height = xx self.pixel_h };
|
||||
msg : (*void, *void, *MTLScissorRect) -> void = xx objc_msgSend;
|
||||
msg(self.encoder, sel_registerName("setScissorRect:".ptr), @rect);
|
||||
msg : (*void, *void, MTLScissorRect) -> void callconv(.c) = xx objc_msgSend;
|
||||
msg(self.encoder, sel_registerName("setScissorRect:".ptr), rect);
|
||||
}
|
||||
|
||||
metal_draw_triangles_ios :: (self: *MetalGPU, vertex_offset: s32, vertex_count: s32) {
|
||||
|
||||
Reference in New Issue
Block a user