1
0
Fork 0
zar/deps/interfaces/interface.zig

494 lines
18 KiB
Zig
Raw Permalink Normal View History

2020-07-09 01:16:31 +01:00
const std = @import("std");
const mem = std.mem;
const trait = std.meta.trait;
const assert = std.debug.assert;
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
2021-03-31 14:43:35 +01:00
pub const SelfType = opaque{};
2020-07-09 01:16:31 +01:00
fn makeSelfPtr(ptr: anytype) *SelfType {
2020-07-09 01:16:31 +01:00
if (comptime !trait.isSingleItemPtr(@TypeOf(ptr))) {
@compileError("SelfType pointer initialization expects pointer parameter.");
}
const T = std.meta.Child(@TypeOf(ptr));
if (@sizeOf(T) > 0) {
return @ptrCast(*SelfType, ptr);
} else {
return undefined;
}
}
fn selfPtrAs(self: *SelfType, comptime T: type) *T {
if (@sizeOf(T) > 0) {
return @alignCast(@alignOf(T), @ptrCast(*align(1) T, self));
} else {
return undefined;
}
}
fn constSelfPtrAs(self: *const SelfType, comptime T: type) *const T {
if (@sizeOf(T) > 0) {
return @alignCast(@alignOf(T), @ptrCast(*align(1) const T, self));
} else {
return undefined;
}
}
pub const Storage = struct {
pub const Comptime = struct {
erased_ptr: *SelfType,
ImplType: type,
2020-09-17 18:21:24 +01:00
fn makeInit(comptime TInterface: type) type {
return struct {
fn init(obj: anytype) !TInterface {
const ImplType = PtrChildOrSelf(@TypeOf(obj));
2020-07-09 01:16:31 +01:00
2020-09-17 18:21:24 +01:00
comptime var obj_holder = obj;
2020-07-09 01:16:31 +01:00
2020-09-17 18:21:24 +01:00
return TInterface{
.vtable_ptr = &comptime makeVTable(TInterface.VTable, ImplType),
.storage = Comptime{
.erased_ptr = makeSelfPtr(&obj_holder),
.ImplType = @TypeOf(obj),
},
};
}
2020-07-09 01:16:31 +01:00
};
}
pub fn getSelfPtr(comptime self: *Comptime) *SelfType {
return self.erased_ptr;
}
pub fn deinit(comptime self: Comptime) void {}
};
pub const NonOwning = struct {
erased_ptr: *SelfType,
2020-09-17 18:21:24 +01:00
fn makeInit(comptime TInterface: type) type {
return struct {
fn init(ptr: anytype) !TInterface {
return TInterface{
.vtable_ptr = &comptime makeVTable(TInterface.VTable, PtrChildOrSelf(@TypeOf(ptr))),
.storage = NonOwning{
.erased_ptr = makeSelfPtr(ptr),
},
};
}
2020-07-09 01:16:31 +01:00
};
}
pub fn getSelfPtr(self: NonOwning) *SelfType {
return self.erased_ptr;
}
pub fn deinit(self: NonOwning) void {}
};
pub const Owning = struct {
allocator: *mem.Allocator,
mem: []u8,
2020-09-17 18:21:24 +01:00
fn makeInit(comptime TInterface: type) type {
return struct {
fn init(obj: anytype, allocator: *std.mem.Allocator) !TInterface {
const AllocT = @TypeOf(obj);
2020-07-09 01:16:31 +01:00
2020-09-17 18:21:24 +01:00
var ptr = try allocator.create(AllocT);
ptr.* = obj;
2020-07-09 01:16:31 +01:00
2020-09-17 18:21:24 +01:00
return TInterface{
.vtable_ptr = &comptime makeVTable(TInterface.VTable, PtrChildOrSelf(AllocT)),
.storage = Owning{
.allocator = allocator,
.mem = std.mem.asBytes(ptr)[0..],
},
};
}
2020-07-09 01:16:31 +01:00
};
}
pub fn getSelfPtr(self: Owning) *SelfType {
return makeSelfPtr(&self.mem[0]);
}
pub fn deinit(self: Owning) void {
2020-09-17 18:21:24 +01:00
const result = self.allocator.shrinkBytes(self.mem, 0, 0, 0, 0);
2020-07-09 01:16:31 +01:00
assert(result == 0);
}
};
pub fn Inline(comptime size: usize) type {
return struct {
const Self = @This();
mem: [size]u8,
2020-09-17 18:21:24 +01:00
fn makeInit(comptime TInterface: type) type {
return struct {
fn init(value: anytype) !TInterface {
const ImplSize = @sizeOf(@TypeOf(value));
if (ImplSize > size) {
@compileError("Type does not fit in inline storage.");
}
var self = Self{
.mem = undefined,
};
if (ImplSize > 0) {
std.mem.copy(u8, self.mem[0..], @ptrCast([*]const u8, &args[0])[0..ImplSize]);
}
return TInterface{
.vtable_ptr = &comptime makeVTable(TInterface.VTable, PtrChildOrSelf(@TypeOf(value))),
.storage = self,
};
}
};
2020-07-09 01:16:31 +01:00
}
pub fn getSelfPtr(self: *Self) *SelfType {
return makeSelfPtr(&self.mem[0]);
}
pub fn deinit(self: Self) void {}
};
}
pub fn InlineOrOwning(comptime size: usize) type {
return struct {
const Self = @This();
data: union(enum) {
Inline: Inline(size),
Owning: Owning,
},
pub fn init(args: anytype) !Self {
2020-07-09 01:16:31 +01:00
if (args.len != 2) {
@compileError("InlineOrOwning storage expected a 2-tuple in initialization.");
}
const ImplSize = @sizeOf(@TypeOf(args[0]));
if (ImplSize > size) {
return Self{
.data = .{
.Owning = try Owning.init(args),
},
};
} else {
return Self{
.data = .{
.Inline = try Inline(size).init(.{args[0]}),
},
};
}
}
pub fn getSelfPtr(self: *Self) *SelfType {
return switch (self.data) {
.Inline => |*i| i.getSelfPtr(),
.Owning => |*o| o.getSelfPtr(),
};
}
pub fn deinit(self: Self) void {
switch (self.data) {
.Inline => |i| i.deinit(),
.Owning => |o| o.deinit(),
}
}
};
}
};
fn PtrChildOrSelf(comptime T: type) type {
if (comptime trait.isSingleItemPtr(T)) {
return std.meta.Child(T);
}
return T;
}
const GenCallType = enum {
BothAsync,
BothBlocking,
AsyncCallsBlocking,
BlockingCallsAsync,
};
fn makeCall(
comptime name: []const u8,
comptime CurrSelfType: type,
comptime Return: type,
comptime ImplT: type,
comptime call_type: GenCallType,
self_ptr: CurrSelfType,
args: anytype,
2020-07-09 01:16:31 +01:00
) Return {
const is_const = CurrSelfType == *const SelfType;
const self = if (is_const) constSelfPtrAs(self_ptr, ImplT) else selfPtrAs(self_ptr, ImplT);
const fptr = @field(ImplT, name);
const first_arg_ptr = comptime std.meta.trait.is(.Pointer)(@typeInfo(@TypeOf(fptr)).Fn.args[0].arg_type.?);
const self_arg = if (first_arg_ptr) .{self} else .{self.*};
return switch (call_type) {
.BothBlocking => @call(.{ .modifier = .always_inline }, fptr, self_arg ++ args),
.AsyncCallsBlocking, .BothAsync => await @call(.{ .modifier = .async_kw }, fptr, self_arg ++ args),
.BlockingCallsAsync => @compileError("Trying to implement blocking virtual function " ++ name ++ " with async implementation."),
};
}
fn getFunctionFromImpl(comptime name: []const u8, comptime FnT: type, comptime ImplT: type) ?FnT {
const our_cc = @typeInfo(FnT).Fn.calling_convention;
// Find the candidate in the implementation type.
for (std.meta.declarations(ImplT)) |decl| {
if (std.mem.eql(u8, name, decl.name)) {
switch (decl.data) {
.Fn => |fn_decl| {
const args = @typeInfo(fn_decl.fn_type).Fn.args;
if (args.len == 0) {
2020-09-17 18:21:24 +01:00
return @field(ImplT, name);
2020-07-09 01:16:31 +01:00
}
2020-09-17 18:21:24 +01:00
if (args.len > 0) {
const arg0_type = args[0].arg_type.?;
const is_method = arg0_type == ImplT or arg0_type == *ImplT or arg0_type == *const ImplT;
const candidate_cc = @typeInfo(fn_decl.fn_type).Fn.calling_convention;
switch (candidate_cc) {
.Async, .Unspecified => {},
else => return null,
}
const Return = @typeInfo(FnT).Fn.return_type orelse noreturn;
const CurrSelfType = @typeInfo(FnT).Fn.args[0].arg_type.?;
const call_type: GenCallType = switch (our_cc) {
.Async => if (candidate_cc == .Async) .BothAsync else .AsyncCallsBlocking,
.Unspecified => if (candidate_cc == .Unspecified) .BothBlocking else .BlockingCallsAsync,
else => unreachable,
};
if (!is_method) {
return @field(ImplT, name);
}
// TODO: Make this less hacky somehow?
// We need some new feature to do so unfortunately.
return switch (args.len) {
1 => struct {
fn impl(self_ptr: CurrSelfType) callconv(our_cc) Return {
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{} });
}
}.impl,
2 => struct {
fn impl(self_ptr: CurrSelfType, arg: args[1].arg_type.?) callconv(our_cc) Return {
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{arg} });
}
}.impl,
3 => struct {
fn impl(self_ptr: CurrSelfType, arg1: args[1].arg_type.?, arg2: args[2].arg_type.?) callconv(our_cc) Return {
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{ arg1, arg2 } });
}
}.impl,
4 => struct {
fn impl(self_ptr: CurrSelfType, arg1: args[1].arg_type.?, arg2: args[2].arg_type.?, arg3: args[3].arg_type.?) callconv(our_cc) Return {
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{ arg1, arg2, arg3 } });
}
}.impl,
5 => struct {
fn impl(self_ptr: CurrSelfType, arg1: args[1].arg_type.?, arg2: args[2].arg_type.?, arg3: args[3].arg_type.?, arg4: args[4].arg_type.?) callconv(our_cc) Return {
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{ arg1, arg2, arg3, arg4 } });
}
}.impl,
6 => struct {
fn impl(self_ptr: CurrSelfType, arg1: args[1].arg_type.?, arg2: args[2].arg_type.?, arg3: args[3].arg_type.?, arg4: args[4].arg_type.?, arg5: args[5].arg_type.?) callconv(our_cc) Return {
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{ arg1, arg2, arg3, arg4, arg5 } });
}
}.impl,
else => @compileError("Unsupported number of arguments, please provide a manually written vtable."),
};
2020-07-09 01:16:31 +01:00
}
},
else => return null,
}
}
}
return null;
}
fn makeVTable(comptime VTableT: type, comptime ImplT: type) VTableT {
if (comptime !trait.isContainer(ImplT)) {
@compileError("Type '" ++ @typeName(ImplT) ++ "' must be a container to implement interface.");
}
var vtable: VTableT = undefined;
for (std.meta.fields(VTableT)) |field| {
var fn_type = field.field_type;
const is_optional = trait.is(.Optional)(fn_type);
if (is_optional) {
fn_type = std.meta.Child(fn_type);
}
const candidate = comptime getFunctionFromImpl(field.name, fn_type, ImplT);
if (candidate == null and !is_optional) {
@compileError("Type '" ++ @typeName(ImplT) ++ "' does not implement non optional function '" ++ field.name ++ "'.");
} else if (!is_optional) {
@field(vtable, field.name) = candidate.?;
} else {
@field(vtable, field.name) = candidate;
}
}
return vtable;
}
fn checkVtableType(comptime VTableT: type) void {
if (comptime !trait.is(.Struct)(VTableT)) {
@compileError("VTable type " ++ @typeName(VTableT) ++ " must be a struct.");
}
for (std.meta.declarations(VTableT)) |decl| {
switch (decl.data) {
.Fn => @compileError("VTable type defines method '" ++ decl.name ++ "'."),
.Type, .Var => {},
}
}
for (std.meta.fields(VTableT)) |field| {
var field_type = field.field_type;
if (trait.is(.Optional)(field_type)) {
field_type = std.meta.Child(field_type);
}
if (!trait.is(.Fn)(field_type)) {
@compileError("VTable type defines non function field '" ++ field.name ++ "'.");
}
const type_info = @typeInfo(field_type);
if (type_info.Fn.is_generic) {
@compileError("Virtual function '" ++ field.name ++ "' cannot be generic.");
}
switch (type_info.Fn.calling_convention) {
.Unspecified, .Async => {},
else => @compileError("Virtual function's '" ++ field.name ++ "' calling convention is not default or async."),
}
}
}
2020-09-17 18:21:24 +01:00
fn vtableHasMethod(comptime VTableT: type, comptime name: []const u8, is_optional: *bool, is_async: *bool, is_method: *bool) bool {
2020-07-09 01:16:31 +01:00
for (std.meta.fields(VTableT)) |field| {
if (std.mem.eql(u8, name, field.name)) {
is_optional.* = trait.is(.Optional)(field.field_type);
2020-09-17 18:21:24 +01:00
const fn_typeinfo = @typeInfo(if (is_optional.*) std.meta.Child(field.field_type) else field.field_type).Fn;
is_async.* = fn_typeinfo.calling_convention == .Async;
is_method.* = fn_typeinfo.args.len > 0 and blk: {
const first_arg_type = fn_typeinfo.args[0].arg_type.?;
break :blk first_arg_type == *SelfType or first_arg_type == *const SelfType;
};
2020-07-09 01:16:31 +01:00
return true;
}
}
return false;
}
fn VTableReturnType(comptime VTableT: type, comptime name: []const u8) type {
for (std.meta.fields(VTableT)) |field| {
if (std.mem.eql(u8, name, field.name)) {
const is_optional = trait.is(.Optional)(field.field_type);
var fn_ret_type = (if (is_optional)
@typeInfo(std.meta.Child(field.field_type)).Fn.return_type
else
@typeInfo(field.field_type).Fn.return_type) orelse noreturn;
if (is_optional) {
return ?fn_ret_type;
}
return fn_ret_type;
}
}
@compileError("VTable type '" ++ @typeName(VTableT) ++ "' has no virtual function '" ++ name ++ "'.");
}
pub fn Interface(comptime VTableT: type, comptime StorageT: type) type {
comptime checkVtableType(VTableT);
const stack_size: usize = if (@hasDecl(VTableT, "async_call_stack_size"))
VTableT.async_call_stack_size
else
1 * 1024 * 1024;
return struct {
vtable_ptr: *const VTableT,
storage: StorageT,
const Self = @This();
2020-09-17 18:21:24 +01:00
const VTable = VTableT;
const Storage = StorageT;
2020-07-09 01:16:31 +01:00
2020-09-17 18:21:24 +01:00
pub const init = StorageT.makeInit(Self).init;
2020-07-09 01:16:31 +01:00
pub fn initWithVTable(vtable_ptr: *const VTableT, args: anytype) !Self {
2020-07-09 01:16:31 +01:00
return .{
.vtable_ptr = vtable_ptr,
2020-09-17 18:21:24 +01:00
.storage = try init(args),
2020-07-09 01:16:31 +01:00
};
}
pub fn call(self: anytype, comptime name: []const u8, args: anytype) VTableReturnType(VTableT, name) {
2020-07-09 01:16:31 +01:00
comptime var is_optional = true;
comptime var is_async = true;
2020-09-17 18:21:24 +01:00
comptime var is_method = true;
comptime assert(vtableHasMethod(VTableT, name, &is_optional, &is_async, &is_method));
2020-07-09 01:16:31 +01:00
const fn_ptr = if (is_optional) blk: {
const val = @field(self.vtable_ptr, name);
if (val) |v| break :blk v;
return null;
} else @field(self.vtable_ptr, name);
2020-09-17 18:21:24 +01:00
if (is_method) {
const self_ptr = self.storage.getSelfPtr();
const new_args = .{self_ptr};
2020-07-09 01:16:31 +01:00
2020-09-17 18:21:24 +01:00
if (!is_async) {
return @call(.{}, fn_ptr, new_args ++ args);
} else {
var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
return await @asyncCall(&stack_frame, {}, fn_ptr, new_args ++ args);
}
2020-07-09 01:16:31 +01:00
} else {
2020-09-17 18:21:24 +01:00
if (!is_async) {
return @call(.{}, fn_ptr, args);
} else {
var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
return await @asyncCall(&stack_frame, {}, fn_ptr, args);
}
2020-07-09 01:16:31 +01:00
}
}
pub fn deinit(self: Self) void {
self.storage.deinit();
}
};
}