ffi M4.0a: prepend __sx_allocator to sx-defined-class state struct
State struct for an sx-defined `#objc_class` now leads with an Allocator field at index 0 — captured at +alloc time, read by -dealloc to free the state through the same allocator. User fields shift to index 1+; the existing by-name lookups in emitObjcDefinedClassPropertyImps + lookupObjcDefinedStateFieldOnPointer naturally resolve them at the new indices. This step is the layout change only; the +alloc IMP still mallocs (M4.0b will rewrite it to thread context.allocator through), and -dealloc still uses free() (M4.0c). The field is allocated but uninitialised; nobody reads it yet. Storage type comes from `Context.fields[0].ty` via the new `objcStateAllocatorType` helper — same Allocator value-shape the implicit context machinery has used all along. If Context isn't registered (early-init paths), the helper falls back to omitting the field rather than synthesising a half-broken layout. IR snapshot for 142-objc-class-method-lowering updated to reflect the new struct shape and the +24-byte state allocation. Chess on iOS-sim green; 184/184 example tests pass.
This commit is contained in:
@@ -4795,6 +4795,17 @@ pub const Lowering = struct {
|
||||
if (self.module.types.findByName(name_id)) |existing| return existing;
|
||||
|
||||
var fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty;
|
||||
// M4.0: prepend __sx_allocator at field index 0 — captured at +alloc
|
||||
// time, read at -dealloc time to free the state struct through the
|
||||
// same allocator. Lookup by name (the existing by-name resolution in
|
||||
// emitObjcDefinedClassPropertyImps + lookupObjcDefinedStateFieldOnPointer)
|
||||
// naturally finds user fields at their post-shift indices.
|
||||
if (self.objcStateAllocatorType()) |allocator_ty| {
|
||||
fields.append(self.alloc, .{
|
||||
.name = self.module.types.internString("__sx_allocator"),
|
||||
.ty = allocator_ty,
|
||||
}) catch unreachable;
|
||||
}
|
||||
for (fcd.members) |m| {
|
||||
switch (m) {
|
||||
.field => |f| {
|
||||
@@ -4811,6 +4822,17 @@ pub const Lowering = struct {
|
||||
} });
|
||||
}
|
||||
|
||||
/// Return the `Allocator` protocol TypeId (the value-shape used in
|
||||
/// Context.allocator). Falls back to null if Context isn't registered
|
||||
/// yet (early-init paths); callers omit the field in that case.
|
||||
fn objcStateAllocatorType(self: *Lowering) ?TypeId {
|
||||
const ctx_name = self.module.types.internString("Context");
|
||||
const ctx_ty = self.module.types.findByName(ctx_name) orelse return null;
|
||||
const ctx_info = self.module.types.get(ctx_ty);
|
||||
if (ctx_info != .@"struct" or ctx_info.@"struct".fields.len < 1) return null;
|
||||
return ctx_info.@"struct".fields[0].ty;
|
||||
}
|
||||
|
||||
/// Lower `inst.method(args)` on an `#objc_class` / `#objc_protocol`
|
||||
/// receiver. The selector is derived by `deriveObjcSelector`; arity
|
||||
/// is validated against the keyword count produced by the mangling
|
||||
|
||||
Reference in New Issue
Block a user