Skip to content

Instantly share code, notes, and snippets.

@iK4tsu
Last active September 23, 2023 14:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save iK4tsu/5e736365e5066a02ca372fd79ec33ce9 to your computer and use it in GitHub Desktop.
Save iK4tsu/5e736365e5066a02ca372fd79ec33ce9 to your computer and use it in GitHub Desktop.
Run-time generics in D
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