Last active
September 23, 2023 14:02
-
-
Save iK4tsu/5e736365e5066a02ca372fd79ec33ce9 to your computer and use it in GitHub Desktop.
Run-time generics in D
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module app; | |
pure @nogc unittest | |
{ | |
/// API | |
extern(C++) | |
interface IAllocatorNogc | |
{ | |
extern(D): | |
void* alloc(size_t) pure @nogc; | |
void dealloc(void*) pure @nogc; | |
} | |
alias genericAllocatorNogcOf = genericOf!IAllocatorNogc; | |
struct DefaultAllocatorNogc | |
{ | |
void* alloc(size_t sz) pure @nogc { return imported!"core.memory".pureMalloc(sz); } | |
void dealloc(void* ptr) pure @nogc { imported!"core.memory".pureFree(ptr); } | |
} | |
static immutable DefaultAllocatorNogc defaultAllocator; | |
Generic!IAllocatorNogc globalAllocatorNogc = genericAllocatorNogcOf(&defaultAllocator); /// global allocator | |
struct AllocOnceArray(T, I) | |
{ | |
this(T)(size_t length, return T* context) | |
{ | |
this(length, genericOf!I(context)); | |
} | |
this(size_t length, Generic!I allocator) | |
{ | |
this.allocator = allocator; | |
this.ptr = cast(T*) allocator.alloc(length * size_t.sizeof); | |
this.length = length; | |
// just for demonstration purposes | |
this.ptr[0 .. length] = 0; | |
} | |
invariant(ptr !is null); | |
~this() | |
{ | |
allocator.dealloc(ptr); | |
length = 0; | |
ptr = null; | |
} | |
ref opIndex(size_t i, T value) | |
in(i < length) | |
{ | |
return ptr[i] = value; | |
} | |
ref opIndex(size_t i) | |
in(i < length) | |
{ | |
return ptr[i]; | |
} | |
private Generic!I allocator; | |
struct | |
{ | |
private T* ptr; | |
private size_t length; | |
} | |
} | |
alias Array(T) = AllocOnceArray!(T, IAllocatorNogc); | |
{ | |
Array!int array = Array!int(5, globalAllocatorNogc); | |
assert(array.ptr[0 .. array.length] == [0, 0, 0, 0, 0]); | |
array[3] = 15; | |
assert(array.ptr[0 .. array.length] == [0, 0, 0, 15, 0]); | |
} | |
struct TrackingAllocatorNogc | |
{ | |
void* alloc(size_t sz) pure @nogc { allocs++; return imported!"core.memory".pureMalloc(sz); } | |
void dealloc(void* ptr) pure @nogc { deallocs++; imported!"core.memory".pureFree(ptr); } | |
size_t allocs; | |
size_t deallocs; | |
} | |
TrackingAllocatorNogc trackingAllocator; | |
{ | |
Array!int[2] arrays = [ | |
Array!int(5, globalAllocatorNogc), | |
Array!int(5, &trackingAllocator), | |
]; | |
assert(trackingAllocator.allocs == 1); | |
foreach (ref array; arrays) assert(array.length == 5); | |
} | |
assert(trackingAllocator.deallocs == 1); | |
} | |
private static immutable toCtString(ulong n) = n.stringof[0 .. $ - "LU".length]; | |
private struct VTable(I) if (is(I == interface)) | |
{ | |
// given an interface I { void fun(); } | |
// it will generate a property void function() fun_0LU; | |
// each property will be appended with an overload index | |
static foreach (member; __traits(allMembers, I)) | |
static foreach (i, method; __traits(getVirtualMethods, I, member)) | |
mixin(typeof(&method).stringof, " ", member, "_", toCtString!i, ";"); | |
} | |
@safe pure nothrow @nogc unittest | |
{ | |
extern(C++) interface Foo | |
{ | |
extern(D): | |
void attrs() @safe; | |
void overload(); | |
void overload(int); | |
int overload(); | |
int overload(int) @safe; | |
} | |
alias FooT = VTable!Foo; | |
alias getMember(alias member, size_t overload) = __traits(getMember, FooT, member ~ "_" ~ toCtString!overload); | |
static foreach (member; __traits(allMembers, Foo)) | |
static foreach (i, method; __traits(getVirtualMethods, Foo, member)) | |
static assert(is(typeof(getMember!(member, i)) == typeof(&method))); | |
} | |
struct Generic(I) if (is(I == interface)) | |
{ | |
static foreach (member; __traits(allMembers, I)) | |
static foreach (i, method; __traits(getVirtualMethods, I, member)) | |
mixin(dispatchFor!(method, member, i)); | |
private template dispatchFor(alias method, string member, size_t overload) | |
{ | |
static if (is(typeof(&method) Method : ReturnT function(ParamsT), ReturnT, ParamsT...)) | |
{ | |
// TODO: remove this when Phobos' format gets fixed for betterC | |
// https://issues.dlang.org/show_bug.cgi?id=23754 | |
version(D_BetterC) | |
{ | |
static immutable string Attrs = () { | |
string s; | |
static foreach (attr; __traits(getFunctionAttributes, Method)) | |
s ~= " " ~ attr; | |
return s; | |
} (); | |
static immutable dispatchFor = " | |
auto " ~ member ~ ParamsT.stringof ~ " | |
{ | |
static union Faker | |
{ | |
" ~ ReturnT.stringof ~ " delegate" ~ ParamsT.stringof ~ Attrs ~ " dispatch; | |
struct | |
{ | |
void* ctx; | |
void* funcPtr; | |
} | |
alias dispatch this; | |
} | |
// FIXME: make static immutable | |
scope Faker dispatch = { | |
ctx: this.context, | |
funcPtr: __traits(getMember, *this.vtable, \"" ~ member ~ "_" ~ toCtString!overload ~ "\"), | |
}; | |
return dispatch(imported!\"core.lifetime\".forward!(__traits(parameters))); | |
}"; | |
} | |
else | |
{ | |
static immutable dispatchFor = imported!"std.format".format(q{ | |
auto %3$s%2$s | |
{ | |
static union Faker | |
{ | |
%1$s delegate%2$s %5$-(%s %) dispatch; | |
struct | |
{ | |
void* ctx; | |
void* funcPtr; | |
} | |
alias dispatch this; | |
} | |
// FIXME: make static immutable | |
scope Faker dispatch = { | |
ctx: this.context, | |
funcPtr: __traits(getMember, *this.vtable, "%4$s"), | |
}; | |
return dispatch(imported!"core.lifetime".forward!(__traits(parameters))); | |
} | |
}, ReturnT.stringof, ParamsT.stringof, member, member ~ "_" ~ toCtString!overload, [__traits(getFunctionAttributes, Method)]); | |
} | |
} | |
else static assert (0); | |
} | |
private void* context; | |
private VTable!I* vtable; | |
} | |
@safe pure nothrow @nogc unittest | |
{ | |
extern(C++) interface Foo | |
{ | |
extern(D): | |
void attrs() @safe; | |
void overload(int); | |
int overload() @safe; | |
} | |
alias FooT = Generic!Foo; | |
alias OverloadsOf(string str) = __traits(getOverloads, FooT, str); | |
static assert(OverloadsOf!"overload".length == 2); | |
static foreach (member; __traits(allMembers, Foo)) | |
static foreach (i, method; __traits(getVirtualMethods, Foo, member)) | |
static assert(is(typeof(&OverloadsOf!member[i]) == typeof(&method))); | |
} | |
pure nothrow @nogc unittest | |
{ | |
extern(C++) | |
interface Foo { extern(D) int fun() @safe pure nothrow @nogc; } | |
struct S { int fun() @safe pure nothrow @nogc { return 5; } } | |
VTable!Foo vtable = { fun_0: &S.fun }; | |
S s; | |
Generic!Foo gen = { | |
context: &s, | |
vtable: &vtable, | |
}; | |
assert(s.fun() == gen.fun()); | |
assert(gen.fun() == 5); | |
} | |
pure nothrow @nogc unittest | |
{ | |
extern(C++) | |
interface Foo { extern(D) int fun(int) @safe pure nothrow @nogc; } | |
struct S { int fun(int i) @safe pure nothrow @nogc { return i; } } | |
VTable!Foo vtable = { fun_0: &S.fun }; | |
S s; | |
Generic!Foo gen = { | |
context: &s, | |
vtable: &vtable, | |
}; | |
assert(s.fun(5) == gen.fun(5)); | |
assert(gen.fun(5) == 5); | |
} | |
pure nothrow @nogc unittest | |
{ | |
extern(C++) | |
interface Foo | |
{ | |
extern(D): | |
int fun() @safe pure nothrow @nogc; | |
int fun(int) @safe pure nothrow @nogc; | |
} | |
struct S | |
{ | |
int fun() @safe pure nothrow @nogc { return 5; } | |
int fun(int i) @safe pure nothrow @nogc { return i * 2; } | |
} | |
alias Overloads = __traits(getOverloads, S, "fun"); | |
VTable!Foo vtable = { fun_0: &Overloads[0], fun_1: &Overloads[1] }; | |
S s; | |
Generic!Foo gen = { | |
context: &s, | |
vtable: &vtable, | |
}; | |
assert(gen.fun() == 5); | |
assert(gen.fun(5) == 10); | |
} | |
template genericOf(I) if (is(I == interface)) | |
{ | |
Generic!I genericOf(T)(return T* ctx) if (is(T == struct)) | |
{ | |
// workaround to be able to get a static immutable function pointer from a | |
// member method of T. | |
static struct _ | |
{ | |
static foreach (member; __traits(allMembers, I)) | |
mixin(bypassCtOf!member); | |
private static string bypassCtOf(string member)() | |
{ | |
alias Overloads = __traits(getOverloads, T, member); | |
alias Methods = __traits(getVirtualMethods, I, member); | |
static assert(Overloads.length == Methods.length); | |
string ret; | |
static foreach (i, overload; Overloads) | |
{{ | |
static if (is(typeof(&overload) F : Return function(Params), Return, Params...)) | |
{ | |
// overload must be implicitly convertible to the API | |
// not an exact match because the attributes might not match | |
// but still compatible | |
static assert(is(typeof(&overload) : typeof(&Methods[i]))); | |
version(LDC) | |
{ | |
// TODO: remove this when Phobos' format gets fixed for betterC | |
// https://issues.dlang.org/show_bug.cgi?id=23754 | |
version(D_BetterC) | |
{ | |
static immutable string funSignature = () { | |
string s = "static " ~ Return.stringof ~ " " ~ member ~ "_" ~ toCtString!i ~ "(T*"; | |
static foreach (Param; Params) | |
s ~= ", " ~ Param.stringof; | |
s ~= ")"; | |
static foreach (attr; __traits(getFunctionAttributes, F)) | |
s ~= " " ~ attr; | |
return s ~ ";"; | |
} (); | |
// Note: needed due to "requires GC error" | |
if (__ctfe) | |
ret ~= "pragma(mangle, \"" ~ overload.mangleof ~ "\")" ~ funSignature; | |
} | |
else | |
{ | |
enum Stringify(T) = T.stringof; | |
static immutable paramsLDC = ["T*", imported!"std.meta".staticMap!(Stringify, Params)]; | |
// pragma(mangle, fun.mangleof) | |
// static fun(T*, ...) @attributes ...; | |
ret ~= imported!"std.format".format!q{ | |
pragma(mangle, "%s") | |
static %s %s(%-(%s%|, %)) %-(%s %); | |
}(overload.mangleof, Return.stringof, member ~ "_" ~ toCtString!i, paramsLDC, [__traits(getFunctionAttributes, F)]); | |
} | |
} | |
else | |
{ | |
// TODO: remove this when Phobos' format gets fixed for betterC | |
// https://issues.dlang.org/show_bug.cgi?id=23754 | |
version(D_BetterC) | |
{ | |
static immutable string funSignature = () { | |
string s = "static " ~ Return.stringof ~ " " ~ member ~ "_" ~ toCtString!i ~ "("; | |
static foreach (Param; Params) | |
s ~= Param.stringof ~ ", "; | |
s ~= ")"; | |
static foreach (attr; __traits(getFunctionAttributes, F)) | |
s ~= " " ~ attr; | |
return s ~ ";"; | |
} (); | |
// Note: needed due to "requires GC error" | |
if (__ctfe) | |
ret ~= "pragma(mangle, \"" ~ overload.mangleof ~ "\")" ~ funSignature; | |
} | |
else | |
{ | |
ret ~= imported!"std.format".format!q{ | |
pragma(mangle, "%s") | |
static %s %s%s %-(%s %); | |
}(overload.mangleof, Return.stringof, member ~ "_" ~ toCtString!i, Params.stringof, [__traits(getFunctionAttributes, F)]); | |
} | |
} | |
} | |
else static assert(0); | |
}} | |
return ret; | |
} | |
} | |
static immutable VTable!I vtbl = () { | |
VTable!I vtbl; | |
// Note: once again, LDC specific behavior, if there are multiple | |
// methods with the same identifier in our bypasser struct it will | |
// complain with forward referencing and IR issues, to workaround | |
// each method follows has the identifier as the correspondent VTable | |
static foreach (member; __traits(allMembers, I)) | |
static foreach (i, __; __traits(getVirtualMethods, I, member)) | |
version(LDC) | |
mixin("vtbl.", member, "_", toCtString!i, "= cast(typeof(vtbl.", member, "_", toCtString!i, ")) cast(void*) &_.", member ~ "_" ~ toCtString!i, ";"); | |
else | |
mixin("vtbl.", member, "_", toCtString!i, "= &_.", member ~ "_" ~ toCtString!i, ";"); | |
return vtbl; | |
} (); | |
Generic!I generic = { | |
context: () @trusted { return cast(void*) ctx; } (), | |
vtable: () @trusted { return cast(VTable!I*) &vtbl; } (), | |
}; | |
return generic; | |
} | |
} | |
@safe pure nothrow @nogc unittest | |
{ | |
extern(C++) | |
interface Foo | |
{ | |
extern(D): | |
int fun() @safe pure nothrow @nogc; | |
int fun(int) @safe pure nothrow @nogc; | |
} | |
struct MyFooDouble | |
{ | |
int fun() @safe pure nothrow @nogc { return 5; } | |
int fun(int i) @safe pure nothrow @nogc { return i * 2; } | |
} | |
alias genericFoo = genericOf!Foo; | |
MyFooDouble myFooDouble; | |
scope fooDouble = genericFoo(() @trusted { return &myFooDouble; } ()); | |
assert(fooDouble.fun() == 5); | |
assert(fooDouble.fun(fooDouble.fun) == 10); | |
} | |
@safe pure nothrow @nogc unittest | |
{ | |
extern(C++) | |
interface Foo | |
{ | |
extern(D): | |
int fun() @safe pure nothrow @nogc; | |
int fun(int) @safe pure nothrow @nogc; | |
} | |
struct MyFooDouble | |
{ | |
int fun() @safe pure nothrow @nogc { return 5; } | |
int fun(int i) @safe pure nothrow @nogc { return i * 2; } | |
} | |
struct MyFooTriple | |
{ | |
int fun() @safe pure nothrow @nogc { return 5; } | |
int fun(int i) @safe pure nothrow @nogc { return i * 3; } | |
} | |
MyFooDouble myFooDouble; | |
MyFooTriple myFooTriple; | |
alias genericFoo = genericOf!Foo; | |
// foos of different implementations | |
import std.array : staticArray; | |
auto foos = staticArray([ | |
genericFoo(() @trusted { return &myFooDouble; } ()), | |
genericFoo(() @trusted { return &myFooTriple; } ()), | |
]); | |
with(foos[0]) assert(fun(fun) == 10); | |
with(foos[1]) assert(fun(fun) == 15); | |
} | |
@safe pure nothrow @nogc unittest | |
{ | |
extern(C++) | |
interface Foo { extern(D) void fun() @safe; } | |
struct S1 { void fun() @safe; } | |
struct S2 { void fun() @safe pure nothrow @nogc; } | |
struct S3 { void fun() @trusted pure nothrow @nogc; } | |
struct S4 { void fun() pure nothrow @nogc; } | |
struct S5 { void fun(); } | |
alias genericFoo = genericOf!Foo; | |
static assert( __traits(compiles, genericFoo!S1(null))); | |
static assert( __traits(compiles, genericFoo!S2(null))); | |
static assert( __traits(compiles, genericFoo!S3(null))); | |
static assert(!__traits(compiles, genericFoo!S4(null))); | |
static assert(!__traits(compiles, genericFoo!S5(null))); | |
} | |
version(D_BetterC) | |
extern(C) void main() | |
{ | |
static foreach (unit; __traits(getUnitTests, app)) | |
{ | |
unit(); | |
} | |
imported!"core.stdc.stdio".printf("1 modules passed unittests"); | |
} | |
else void main() {} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment