lib/sys/src/apple/objc.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const builtin = @import("builtin");
  3 
  4 pub const Class = *opaque {};
  5 pub const Ivar = *opaque {};
  6 pub const SEL = *opaque {};
  7 pub const Id = *anyopaque;
  8 pub const BOOL = boolType(builtin.cpu.arch);
  9 
 10 pub const nil: ?Id = null;
 11 
 12 extern fn objc_getClass(name: [*:0]const u8) ?Class;
 13 extern fn objc_allocateClassPair(
 14     superclass: Class,
 15     name: [*:0]const u8,
 16     extra_bytes: usize,
 17 ) ?Class;
 18 extern fn objc_registerClassPair(class_value: Class) void;
 19 extern fn objc_disposeClassPair(class_value: Class) void;
 20 extern fn class_addIvar(
 21     class_value: Class,
 22     name: [*:0]const u8,
 23     size: usize,
 24     alignment: u8,
 25     types: ?[*:0]const u8,
 26 ) BOOL;
 27 extern fn class_addMethod(
 28     class_value: Class,
 29     operation: SEL,
 30     implementation: *const anyopaque,
 31     types: [*:0]const u8,
 32 ) BOOL;
 33 extern fn object_setInstanceVariable(
 34     object: Id,
 35     name: [*:0]const u8,
 36     value: ?*anyopaque,
 37 ) ?Ivar;
 38 extern fn object_getInstanceVariable(
 39     object: Id,
 40     name: [*:0]const u8,
 41     value: *?*anyopaque,
 42 ) ?Ivar;
 43 extern fn sel_registerName(name: [*:0]const u8) SEL;
 44 extern fn objc_msgSend() void;
 45 extern fn objc_msgSend_stret() void;
 46 extern fn objc_autoreleasePoolPush() *anyopaque;
 47 extern fn objc_autoreleasePoolPop(context: *anyopaque) void;
 48 
 49 pub const AutoreleasePool = struct {
 50     context: *anyopaque,
 51 
 52     pub fn init() AutoreleasePool {
 53         return .{ .context = objc_autoreleasePoolPush() };
 54     }
 55 
 56     pub fn deinit(self: *AutoreleasePool) void {
 57         objc_autoreleasePoolPop(self.context);
 58         self.* = undefined;
 59     }
 60 };
 61 
 62 pub fn lookupClass(name: [*:0]const u8) ?Class {
 63     return objc_getClass(name);
 64 }
 65 
 66 pub fn class(name: [*:0]const u8) ?Id {
 67     return @ptrCast(lookupClass(name) orelse return null);
 68 }
 69 
 70 pub fn allocateClassPair(superclass: Class, name: [*:0]const u8) ?Class {
 71     return objc_allocateClassPair(superclass, name, 0);
 72 }
 73 
 74 pub fn registerClassPair(class_value: Class) void {
 75     objc_registerClassPair(class_value);
 76 }
 77 
 78 pub fn disposeClassPair(class_value: Class) void {
 79     objc_disposeClassPair(class_value);
 80 }
 81 
 82 pub fn addPointerIvar(class_value: Class, name: [*:0]const u8) bool {
 83     const pointer_alignment: u8 = @intCast(@ctz(@as(usize, @alignOf(*anyopaque))));
 84     return isTrue(class_addIvar(
 85         class_value,
 86         name,
 87         @sizeOf(*anyopaque),
 88         pointer_alignment,
 89         "^v",
 90     ));
 91 }
 92 
 93 pub fn addMethod(
 94     class_value: Class,
 95     operation: SEL,
 96     implementation: *const anyopaque,
 97     types: [*:0]const u8,
 98 ) bool {
 99     return isTrue(class_addMethod(class_value, operation, implementation, types));
100 }
101 
102 pub fn setInstancePointer(
103     object: Id,
104     name: [*:0]const u8,
105     value: ?*anyopaque,
106 ) bool {
107     return object_setInstanceVariable(object, name, value) != null;
108 }
109 
110 pub fn instancePointer(object: Id, name: [*:0]const u8) ?*anyopaque {
111     var value: ?*anyopaque = null;
112     _ = object_getInstanceVariable(object, name, &value) orelse return null;
113     return value;
114 }
115 
116 pub fn selector(name: [*:0]const u8) SEL {
117     return sel_registerName(name);
118 }
119 
120 pub fn boolean(value: bool) BOOL {
121     return booleanFor(builtin.cpu.arch, value);
122 }
123 
124 pub fn isTrue(value: BOOL) bool {
125     return isTrueFor(builtin.cpu.arch, value);
126 }
127 
128 pub fn retain(target: anytype) @TypeOf(target) {
129     const retained = send(Id, @ptrCast(target), selector("retain"), .{});
130     return @ptrCast(retained);
131 }
132 
133 pub fn release(target: anytype) void {
134     send(void, @ptrCast(target), selector("release"), .{});
135 }
136 
137 /// Whether `target` implements `operation`, so that optional API can be probed before it is sent.
138 pub fn respondsTo(target: Id, operation: SEL) bool {
139     return isTrue(send(BOOL, target, selector("respondsToSelector:"), .{operation}));
140 }
141 
142 pub fn send(comptime Return: type, target: Id, operation: SEL, arguments: anytype) Return {
143     return dispatch(Return, entryForReturn(Return, builtin.cpu.arch), target, operation, arguments);
144 }
145 
146 const Entry = enum {
147     message,
148     structure,
149 };
150 
151 fn dispatch(
152     comptime Return: type,
153     comptime entry_kind: Entry,
154     target: Id,
155     operation: SEL,
156     arguments: anytype,
157 ) Return {
158     const entry = if (comptime entry_kind == .structure)
159         &objc_msgSend_stret
160     else
161         &objc_msgSend;
162     const implementation: *const message(Return, @TypeOf(arguments)) = @ptrCast(entry);
163     return @call(.auto, implementation, .{ target, operation } ++ arguments);
164 }
165 
166 fn boolType(comptime arch: std.Target.Cpu.Arch) type {
167     return switch (arch) {
168         .aarch64 => bool,
169         else => i8,
170     };
171 }
172 
173 fn booleanFor(comptime arch: std.Target.Cpu.Arch, value: bool) boolType(arch) {
174     return switch (arch) {
175         .aarch64 => value,
176         else => @intFromBool(value),
177     };
178 }
179 
180 fn isTrueFor(comptime arch: std.Target.Cpu.Arch, value: boolType(arch)) bool {
181     return switch (arch) {
182         .aarch64 => value,
183         else => value != 0,
184     };
185 }
186 
187 fn entryForReturn(comptime Return: type, comptime arch: std.Target.Cpu.Arch) Entry {
188     return switch (@typeInfo(Return)) {
189         .@"struct" => if (comptime supportedAggregateReturn(Return))
190             if (arch == .x86_64 and @sizeOf(Return) > 16) .structure else .message
191         else
192             @compileError("Objective-C aggregate returns support only non-empty extern f64 structures"),
193         else => .message,
194     };
195 }
196 
197 fn supportedAggregateReturn(comptime Aggregate: type) bool {
198     const aggregate = switch (@typeInfo(Aggregate)) {
199         .@"struct" => |info| info,
200         else => return false,
201     };
202     if (aggregate.layout != .@"extern" or aggregate.field_types.len == 0) return false;
203     inline for (aggregate.field_types) |field_type| {
204         switch (@typeInfo(field_type)) {
205             .float => if (field_type != f64) return false,
206             .@"struct" => if (!supportedAggregateReturn(field_type)) return false,
207             else => return false,
208         }
209     }
210     return true;
211 }
212 
213 /// The most explicit arguments one message takes. Metal's longest selectors take nine.
214 const max_message_arguments = 12;
215 
216 /// The C signature of a message that takes `Arguments`, a tuple of explicit argument types, after
217 /// the receiver and selector.
218 fn message(comptime Return: type, comptime Arguments: type) type {
219     const argument_types = @typeInfo(Arguments).@"struct".field_types;
220     if (argument_types.len > max_message_arguments) {
221         @compileError("Objective-C messages support at most twelve explicit arguments");
222     }
223     var parameter_types: [argument_types.len + 2]type = undefined;
224     parameter_types[0] = Id;
225     parameter_types[1] = SEL;
226     for (argument_types, parameter_types[2..]) |argument_type, *parameter_type| {
227         parameter_type.* = argument_type;
228     }
229     return @Fn(&parameter_types, &@splat(.{}), Return, .{ .@"callconv" = .c });
230 }
231 
232 test "Objective-C ABI scalar and pointer layouts" {
233     try std.testing.expectEqual(@as(usize, 1), @sizeOf(BOOL));
234     try std.testing.expect(boolType(.aarch64) == bool);
235     try std.testing.expect(boolType(.x86_64) == i8);
236     try std.testing.expectEqual(@sizeOf(usize), @sizeOf(Class));
237     try std.testing.expectEqual(@sizeOf(usize), @sizeOf(Ivar));
238     try std.testing.expectEqual(@sizeOf(usize), @sizeOf(SEL));
239     try std.testing.expectEqual(@sizeOf(usize), @sizeOf(Id));
240     try std.testing.expectEqual(true, booleanFor(.aarch64, true));
241     try std.testing.expectEqual(false, booleanFor(.aarch64, false));
242     try std.testing.expectEqual(@as(i8, 1), booleanFor(.x86_64, true));
243     try std.testing.expectEqual(@as(i8, 0), booleanFor(.x86_64, false));
244     try std.testing.expect(isTrueFor(.aarch64, true));
245     try std.testing.expect(isTrueFor(.x86_64, -1));
246     try std.testing.expect(isTrue(boolean(true)));
247     try std.testing.expect(!isTrue(boolean(false)));
248 }
249 
250 test "Objective-C message signatures preserve exact C ABI types" {
251     const Signature = message(?Id, struct { u64, BOOL });
252     const info = @typeInfo(Signature).@"fn";
253     try std.testing.expectEqual(std.builtin.CallingConvention.c, info.attrs.@"callconv");
254     try std.testing.expect(info.return_type.? == ?Id);
255     try std.testing.expect(info.param_types[0].? == Id);
256     try std.testing.expect(info.param_types[1].? == SEL);
257     try std.testing.expect(info.param_types[2].? == u64);
258     try std.testing.expect(info.param_types[3].? == BOOL);
259 }
260 
261 test "Objective-C dispatch exposes one message entry API" {
262     try std.testing.expect(!@hasDecl(@This(), "sendStret"));
263 }
264 
265 test "Objective-C autorelease pool has an explicit poll lifetime" {
266     if (comptime builtin.os.tag != .macos) return error.SkipZigTest;
267     var pool = AutoreleasePool.init();
268     pool.deinit();
269 }
270 
271 const ObjectiveCPointFixture = extern struct {
272     x: f64,
273     y: f64,
274 };
275 
276 const ObjectiveCRectFixture = extern struct {
277     origin: ObjectiveCPointFixture,
278     size: ObjectiveCPointFixture,
279 };
280 
281 const ObjectiveCIntegerPairFixture = extern struct {
282     x: u64,
283     y: u64,
284 };
285 
286 const ObjectiveCNativePointFixture = struct {
287     x: f64,
288     y: f64,
289 };
290 
291 test "Objective-C aggregate entry follows the macOS architecture ABI" {
292     try std.testing.expect(supportedAggregateReturn(ObjectiveCPointFixture));
293     try std.testing.expect(supportedAggregateReturn(ObjectiveCRectFixture));
294     try std.testing.expect(!supportedAggregateReturn(ObjectiveCIntegerPairFixture));
295     try std.testing.expect(!supportedAggregateReturn(ObjectiveCNativePointFixture));
296     try std.testing.expectEqual(Entry.message, entryForReturn(ObjectiveCPointFixture, .aarch64));
297     try std.testing.expectEqual(Entry.message, entryForReturn(ObjectiveCRectFixture, .aarch64));
298     try std.testing.expectEqual(Entry.message, entryForReturn(ObjectiveCPointFixture, .x86_64));
299     try std.testing.expectEqual(Entry.structure, entryForReturn(ObjectiveCRectFixture, .x86_64));
300 }