On Sat, 21 Nov 2009 06:54:11 +0300, grauzone <[email protected]> wrote:

Denis Koroskin wrote:
type-safe manner anymore (well, one could create a set of trampolines for each of set of types involved in a call, but I don't think it's reasonable or even possible; I'll look into it, too, though). That's why

Yes, it is possible. You'll have to pass the method as alias template parameter. Then you get a tuple of the parameter types. You can foreach over that tuple and serialize/deserialize the actual values and read/write them from the tuple. You also can declare a nested function that does the actual call to the server's function. That delegate can have a type independent from the method, and thus can be stored in the non-template world.

Basically like this (pseudo code):

//client function to invoke a specific RPC
//the parameters can be passed normally thanks to tuples
void makeDynamicCall(Params...)(Stream stream, char[] method, Params p) {
        stream.write(method);

        //serialize the parameters
        foreach (int index, _; p) {
                auto val = p[index];
                stream.write!(typeof(val))(val);
        }
}

alias void delegate(Stream) Stub;

//registry of server functions
Stub[char[]] stubs;

//the server network code calls this on incomming RPC requests
void receiveDynamicCall(Stream stream) {
        auto method = stream.read!(char[])();
        stubs[method].call(stream);
}

//the server calls this on program initialization
//he passes an alias to the server function, and its name
void registerStub(alias Function)(char[] name) {
        //generate code to deserialize a RPC and to call the server
        void stub(Stream stream) {
                //you can get the param tuple of a function
                //Phobos2 and Tango should have something similar
                alias ParamTupleOfFunction!(Function) Params;

                //deserialize the arguments
                Params p;
                foreach (int index, _; p) {
                        alias typeof(p[index]) PT;
                        p[index] = stream.read!(PT)();
                }

                //actually call the function
                Function(p);
        }

        stubs[name] = &stub;
}

It all can be typesafe and non-compiler specific.

The question is; how much template bloat will this emit into the application binary? You have to consider the instantiations of Stream.read and Stream.write, too.


I thought about that, too. And then I thought:
1) you can move deserialization out of this method (my serializer doesn't need type hints to deserialize data). 2) you can reuse the trampoline for those functions that have same type of input argument (less bloat) 3) you can create a trampoline for each type, not for each set of types (N trampolines instead of N! in worst case)

One more step and you don't even need for those trampolines (just carry some type information and translate call to trampoline into a switch).

That's how I ended up with my implementation.

Note that all the asm it uses is just 3 constructs: naked, ret and call! Everything else is implemented with a help of compiler (i.e. pushing arguments, grabbing result, stack alignment etc). My code is most probably not very portable (I didn't test it on anything other that Windows), it doesn't account x64 specifics etc, but it shouldn't be hard to fix.

Wow, I'm surprised that this works. Actually, I'd *like* to do it this way, but the code is completely compiler and platform specific. Maybe it also depends from the compiler code generator's mood if it works. And if you want to deal with user-define types (structs), you're completely out of luck.

Why? It works well with custom structs, too. There is a test case attached, try to run in, it should work.

One of the reasons I posted the code is because I'm not sure it is correct and that it will always work (on a given platform).
I'm not an ASM guru, so comments would be invaluable.

For example, I use the following trick to push arguments to stack:

// A helper function that pushes an argument to stack in a type-safe manner
// extern (System) is used so that argument isn't passed via EAX
// does it work the same way in Linux? Or Linux uses __cdecl?
// There must be other way to pass all the arguments on stack, but this one works well so far
// Beautiful, isn't it?
extern (System) void arg(T)(T arg)
{
        asm {
                naked;
                ret;
        }
}

// A helper function that pushes an argument to stack in a type-safe manner
// Allowed to pass argumet via EAX (that's why it's extern (D))
void lastArg(T)(T arg)
{
        asm {
                naked;
                ret;
        }
}

Explanation: when one of these functions are called, arguments to them are pushed to stack and/or target registers properly (compiler does it for me automatically). By convention, extern (D) and extern (Windows) aka __stdcall function pop their arguments from the stack on their own. Naked function (asm naked) lack prolog and epilog (i.e. they don't pop arguments and restore stack). As a result, these function push their arguments to stack the way compiler wants them while still not being compiler specific.

lastArg uses extern (D) calling convention that allows passing last argument in EAX (this is specified in http://www.digitalmars.com/d/2.0/abi.html)

How safe it is? How portable it is? As far as I know all major compilers support naked functions (Microsoft C++ compiler: _declspec( naked ), GCC: __attribute__(naked))

This is 2 out of 3 uses of asm in my code. The third one is:
void* functptr = ...;
asm {
        call funcptr;
}

I think this is also implementable on other platforms.

I updated code to work with Tango, could anybody please try to run it on Linux x86/x86_64 (ldc)? An output should be:
516.00
13.00

Attachment: DynamicCall.d
Description: Binary data

Attachment: DynamicCall_test.d
Description: Binary data

Reply via email to