mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
178 lines
7.0 KiB
Zig
178 lines
7.0 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const assert = std.debug.assert;
|
|
const c = @import("c.zig");
|
|
const objc = @import("main.zig");
|
|
|
|
/// Returns a struct that implements the msgSend function for type T.
|
|
/// This is meant to be used with `usingnamespace` to add dispatch
|
|
/// capability to a type that supports it.
|
|
pub fn MsgSend(comptime T: type) type {
|
|
// 1. T should be a struct
|
|
// 2. T should have a field "value" that can be an "id" (same size)
|
|
|
|
return struct {
|
|
/// Invoke a selector on the target, i.e. an instance method on an
|
|
/// object or a class method on a class. The args should be a tuple.
|
|
pub fn msgSend(
|
|
target: T,
|
|
comptime Return: type,
|
|
sel: objc.Sel,
|
|
args: anytype,
|
|
) Return {
|
|
// Our one special-case: If the return type is our own Object
|
|
// type then we wrap it.
|
|
const is_object = Return == objc.Object;
|
|
|
|
// Our actual return value is an "id" if we are using one of
|
|
// our built-in types (see above). Otherwise, we trust the caller.
|
|
const RealReturn = if (is_object) c.id else Return;
|
|
|
|
// See objc/message.h. The high-level is that depending on the
|
|
// target architecture and return type, we must use a different
|
|
// objc_msgSend function.
|
|
const msg_send_fn = switch (builtin.target.cpu.arch) {
|
|
// Aarch64 uses objc_msgSend for everything. Hurray!
|
|
.aarch64 => &c.objc_msgSend,
|
|
|
|
// x86_64 depends on the return type...
|
|
.x86_64 => switch (@typeInfo(RealReturn)) {
|
|
// Most types use objc_msgSend
|
|
inline .Int, .Bool, .Pointer, .Void => &c.objc_msgSend,
|
|
.Optional => |opt| opt: {
|
|
assert(@typeInfo(opt.child) == .Pointer);
|
|
break :opt &c.objc_msgSend;
|
|
},
|
|
|
|
// Structs must use objc_msgSend_stret.
|
|
// NOTE: This is probably WAY more complicated... we only
|
|
// call this if the struct is NOT returned as a register.
|
|
// And that depends on the size of the struct. But I don't
|
|
// know what the breakpoint actually is for that. This SO
|
|
// answer says 16 bytes so I'm going to use that but I have
|
|
// no idea...
|
|
.Struct => if (@sizeOf(Return) > 16)
|
|
&c.objc_msgSend_stret
|
|
else
|
|
&c.objc_msgSend,
|
|
|
|
// Floats use objc_msgSend_fpret for f64 on x86_64,
|
|
// but normal msgSend for other bit sizes. i386 has
|
|
// more complex rules but we don't support i386 at the time
|
|
// of this comment and probably never will since all i386
|
|
// Apple models are discontinued at this point.
|
|
.Float => |float| switch (float.bits) {
|
|
64 => &c.objc_msgSend_fpret,
|
|
else => &c.objc_msgSend,
|
|
},
|
|
|
|
// Otherwise we log in case we need to add a new case above
|
|
else => {
|
|
@compileLog(@typeInfo(RealReturn));
|
|
@compileError("unsupported return type for objc runtime on x86_64");
|
|
},
|
|
},
|
|
else => @compileError("unsupported objc architecture"),
|
|
};
|
|
|
|
// Build our function type and call it
|
|
const Fn = MsgSendFn(RealReturn, @TypeOf(target.value), @TypeOf(args));
|
|
// Due to this stage2 Zig issue[1], this must be var for now.
|
|
// [1]: https://github.com/ziglang/zig/issues/13598
|
|
var msg_send_ptr = @ptrCast(*const Fn, msg_send_fn);
|
|
const result = @call(.auto, msg_send_ptr, .{ target.value, sel.value } ++ args);
|
|
|
|
if (!is_object) return result;
|
|
return .{ .value = result };
|
|
}
|
|
};
|
|
}
|
|
|
|
/// This returns a function body type for `obj_msgSend` that matches
|
|
/// the given return type, target type, and arguments tuple type.
|
|
///
|
|
/// obj_msgSend is a really interesting function, because it doesn't act
|
|
/// like a typical function. You have to call it with the C ABI as if you're
|
|
/// calling the true target function, not as a varargs C function. Therefore
|
|
/// you have to cast obj_msgSend to a function pointer type of the final
|
|
/// destination function, then call that.
|
|
///
|
|
/// Example: you have an ObjC function like this:
|
|
///
|
|
/// @implementation Foo
|
|
/// - (void)log: (float)x { /* stuff */ }
|
|
///
|
|
/// If you call it like this, it won't work (you'll get garbage):
|
|
///
|
|
/// objc_msgSend(obj, @selector(log:), (float)PI);
|
|
///
|
|
/// You have to call it like this:
|
|
///
|
|
/// ((void (*)(id, SEL, float))objc_msgSend)(obj, @selector(log:), M_PI);
|
|
///
|
|
/// This comptime function returns the function body type that can be used
|
|
/// to cast and call for the proper C ABI behavior.
|
|
fn MsgSendFn(
|
|
comptime Return: type,
|
|
comptime Target: type,
|
|
comptime Args: type,
|
|
) type {
|
|
const argsInfo = @typeInfo(Args).Struct;
|
|
assert(argsInfo.is_tuple);
|
|
|
|
// Target must always be an "id". Lots of types (Class, Object, etc.)
|
|
// are an "id" so we just make sure the sizes match for ABI reasons.
|
|
assert(@sizeOf(Target) == @sizeOf(c.id));
|
|
|
|
// Build up our argument types.
|
|
const Fn = std.builtin.Type.Fn;
|
|
const args: []Fn.Param = args: {
|
|
var acc: [argsInfo.fields.len + 2]Fn.Param = undefined;
|
|
|
|
// First argument is always the target and selector.
|
|
acc[0] = .{ .arg_type = Target, .is_generic = false, .is_noalias = false };
|
|
acc[1] = .{ .arg_type = c.SEL, .is_generic = false, .is_noalias = false };
|
|
|
|
// Remaining arguments depend on the args given, in the order given
|
|
for (argsInfo.fields) |field, i| {
|
|
acc[i + 2] = .{
|
|
.arg_type = field.field_type,
|
|
.is_generic = false,
|
|
.is_noalias = false,
|
|
};
|
|
}
|
|
|
|
break :args &acc;
|
|
};
|
|
|
|
// Copy the alignment of a normal function type so equality works
|
|
// (mainly for tests, I don't think this has any consequence otherwise)
|
|
const alignment = @typeInfo(fn () callconv(.C) void).Fn.alignment;
|
|
|
|
return @Type(.{
|
|
.Fn = .{
|
|
.calling_convention = .C,
|
|
.alignment = alignment,
|
|
.is_generic = false,
|
|
.is_var_args = false,
|
|
.return_type = Return,
|
|
.args = args,
|
|
},
|
|
});
|
|
}
|
|
|
|
test {
|
|
// https://github.com/ziglang/zig/issues/12360
|
|
if (true) return error.SkipZigTest;
|
|
|
|
const testing = std.testing;
|
|
try testing.expectEqual(fn (
|
|
u8,
|
|
objc.Sel,
|
|
) callconv(.C) u64, MsgSendFn(u64, u8, @TypeOf(.{})));
|
|
try testing.expectEqual(fn (u8, objc.Sel, u16, u32) callconv(.C) u64, MsgSendFn(u64, u8, @TypeOf(.{
|
|
@as(u16, 0),
|
|
@as(u32, 0),
|
|
})));
|
|
}
|