I implemented delegates in D

maik klein via Digitalmars-d digitalmars-d at puremagic.com
Sat Jun 11 04:29:52 PDT 2016


On Thursday, 9 June 2016 at 23:53:33 UTC, BBasile wrote:
> On Thursday, 9 June 2016 at 21:02:26 UTC, maik klein wrote:
>> I am currently writing a task system and I have this case 
>> where I want to send a delegate to a different thread but this 
>> delegate also captures a variable. I use this to implement a 
>> "future".
>>
>> Now as far as I know this delegate will allocate GC memory and 
>> I just wanted to avoid that, just for fun.
>>
>> Here is the code https://dpaste.dzfl.pl/cd77fce99a5b
>>
>> I have only worked on it a couple of hours and I am sure there 
>> are many problems with it.
>>
>> Basically the idea is that you can use normal lambda syntax. 
>> If you want a function that returns and int and takes an int, 
>> you can write it like this:
>>
>> (int i) => i
>>
>> If you want a function that returns an int, takes an int, but 
>> also captures and int you would write it like this
>>
>> (int i, int captured) => i + captured
>>
>> But you also have to declare the base function type without 
>> the captured variables beforehand.
>>
>> Fn!(FnType!(int, int), (int i, int captured) => i + 
>> captured)(42);
>>
>> That is how I know what the captured variables are.
>>
>> The only part that is currently missing are polymorphic 
>> delegates. They are not too useful if I can't pack them into 
>> the same array.
>>
>> I guess I have to do this with classes/interfaces.
>>
>> Thoughts?
>>
>> Has this been done before?
>
> What you have here is more functor, to the extent that you can 
> memorize the parameters. The problem is that it's not 
> compatible with the delegates as defined in the language.
>
> Actual D delegates get collected when we take the address and 
> the context of a member function with "&". To allocate the 
> delegate itself in the non GC heap is easy (with a struct that 
> contains two pointers).
>
> Your message has motivated me to make this, thanks to a union 
> true nogc delegates are possible:
>
> =========================================
> module runnable;
>
> union Delegate(FT)
> {
>     // delegate layout as defined in the D ABI
>     struct DgMembers
>     {
>         void* ptr;
>         FT funcptr;
>     }
>     DgMembers members;
>
>     // the 2nd member is "true" D delegate type
>     import std.array: replace;
>     mixin("alias DT = " ~ FT.stringof.replace("function", 
> "delegate") ~ ";");
>     DT dg;
> }
>
> void main() @nogc
> {
>     static struct Foo
>     {
>         @nogc string delegate() test;
>         @nogc string source() {return "test";}
>     }
>
>     import std.experimental.allocator.mallocator;
>     import std.experimental.allocator;
>
>     Foo foo;
>     alias DelegateT = Delegate!(typeof(&Foo.source));
>
>     // &Foo.source takes the static address, i.e in the data 
> segment
>     DelegateT* dg = make!DelegateT(Mallocator.instance);
>     dg.members.ptr = &foo;
>     dg.members.funcptr = &Foo.source;
>
>     foo.test = dg.dg;
>     assert(foo.test() == "test");
> }
> =========================================
>
> Here you have a true D delegate that's conform with the 
> language.

The problem is I currently do this

struct LocalTaskQueue{
...
     Array!(Box!Fiber) work;
     Array!(Box!Fiber) queuedWork;
...
}

This is where I submit my delegates to, but before that I also 
wrap them inside a fiber. Btw "Box" is my version of Unique.

I execute a fiber inside work and if it is done I want to delete 
it, because it is not needed anymore. If it is on hold it 
transfer it from "work" to queuedWork.

That means I need be able to put my delegates inside a fiber, 
this is the constructor for a Fiber currently.


     this( void delegate() dg, size_t sz = PAGESIZE*4 ) nothrow
     in
     {
         assert( dg );
     }
     body
     {
         allocStack( sz );
         reset( dg );
     }

I mean I can easily convert my "Fn" version to delegate with 
&foo.opCall, but then I would not know how long my "Fn" should 
stay alive.

I think what I can do is switch my struct Fn to a class and 
implement my own function polymorphism.

I would then do

Array!(Box!Fiber) work;
Array!(Box!PolymorphicFn!(void, void)) rawFunction;

and when I delete a fiber from work I can delete it from 
rawFunction.

But that seems to be so much trouble just to avoid a bit of GC.







More information about the Digitalmars-d mailing list