RPC and Dynamic function call

Denis Koroskin 2korden at gmail.com
Sat Nov 21 04:53:00 PST 2009


On Sat, 21 Nov 2009 06:54:11 +0300, grauzone <none at example.net> 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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: DynamicCall.d
Type: application/octet-stream
Size: 5402 bytes
Desc: not available
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20091121/792db5ef/attachment.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: DynamicCall_test.d
Type: application/octet-stream
Size: 1523 bytes
Desc: not available
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20091121/792db5ef/attachment-0001.obj>


More information about the Digitalmars-d mailing list