Question

I am developing a VM and I would like to make it possible to call compiled functions. However, because every function may end up having a different signature, my plan is to generalize all calls to 2 possible scenarios - a call for function with no return and no parameters, and a call to a function which takes one void * parameter.

The plan is to use it similarly to thiscall - all parameters are properly aligned at the location of the passed pointer and parameters are retrieved through indirection. Shouldn't be slower than reading them from the stack, at least IMO.

So instead of:

int foo(int a, int b) { return a+b; }

I can have something like:

void foo2(void *p) {
   *(int*)p = *(int*)(p + 4) + *(int*)(p + 8);
}

So my question is what could potentially go wrong using this approach? What I can tell right away is it works "in the dark" so it would be crucial to calculate the offsets correctly. It is also a little inconvenient since all the temporaries need to be provided by the user. Assuming my VM compiler will deal with those two issues, I am mostly concerned about performance - I don't want to create a normal function and for each normal function a void * wrapper - I would like to directly use that convention for all functions, so I can't help but wonder how good of a job will the compiler do of inlining the functions when used in compiled code? Are there going to be any other possible performance implications I am overlooking (excluding __fastcall which will use one more register and one less indirection)?

Was it helpful?

Solution 2

After running a few benchmarks I'd say the compiler does a pretty good job of optimizing similar pointer functions. The void * function is just as fast as the add function and the regular + operator.

It seems that this convention will be useful to provide the necessary calling abstraction without hurting optimizations and overall performance. The only sacrifice is safety, which may or may not be a primary concern depending on the application context.

OTHER TIPS

Performance wise (and ease of use) you will probably be best off with cdecl - everything goes onto the stack. The C standard allows you to specify function prototypes with arbitrary arguments

typedef void (__cdecl * function_with_any_parameters)();

You will have to make sure to define all functions that you wish to invoke as:

void __cdecl f(type1 arg1, type2 arg2, type3 arg3); // any amount of arguments

And just invoke them with the right amount of arguments:

f(arg1, arg2, arg3, arg4);

If you wish to go through a single pointer then you do have extra overhead: the one pointer. The easiest way would be to define all functions as accepting a pointer to an anonymous struct:

void f(struct {type1 a; type2 b;} * args);

Then you can invoke the function with a pointer to the appropriate struct to avoid any misalignments.

struct {type1 a; type2 b;} args = {arg1, arg2};
f(&args);

You are effectively implementing cdecl on your own.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top