Nat! bio photo

Nat!

Senior Mull

Twitter Github Twitch

mulle-objc: a meta calling convention

Continued from mulle-objc: a new Objective-C


So the mulle-objc runtime is written in pure C. How is that supposed to work ?

Currently a method is just like a C-function. You declare the parameters and they are passed by using the current ABI scheme. Sometimes on the stack, sometimes in registers, sometimes in a mix of both.

That makes things very complicated on the runtime side, and you need special libraries like FFI or write your own assembler code to access the arguments.

I try to keep the language consistent with respect to parameter-vs-argument. So as we are inside the function, it is parameter. In that respect, main( argc, argv) is wrong :(, confusing.

A method in mulle-objc has two faces

On the syntax side, you still write code like this:

- (int) fooWithA:(int) a b:(int) b
{
	return( a + b);
}

but internally the compiler creates a method like this:

struct whatever
{
	int  a;
	int  b;
};

- (int) fooWithA:b:(struct whatever *) _param
{
	return( _param->a + _param->b);
}

mulle-objc uses a modified clang compiler that creates the new meta-calling convention. I call it meta calling convention, because it does not conflict with the actual calling convention used by the compiler like cdecl or fastcall, but rather exists “on top” of it.

All parameters outside of self and _cmd are passed via a single parameter _param, that references a struct. This struct is created in the default C struct manner, and not in the “hidden” stack/register ABI layout, so sizeof() and offsetof() can be used.

And the message send routine itself looks something like this now:

void  *mulle_objc_object_call( void *self,
                               SEL _cmd,
                               void *_param)

The fixed number of only three parameters makes everything much easier for the runtime implementation.

A more formal definition of the mulle-objc meta call convention

  1. a method which expects no arguments does not get a _param parameter (or _param may be any bogus value, if that is more convenient).
  2. a method with one parameter that fits inside (void *) may get that parameter directly. In this case _param will not exist, but just the regular parameter name.
  3. all other methods get a struct address passed in as _param.
  4. a method returning a struct/union type or a type that doesn’t cast transparently into void * always gets a _param parameter with a size large enough to hold the return value
  5. the struct passed in is an even multiple of (5 * sizeof( void *)) bytes
  6. the struct passed in is “destroyed” by the callee and the caller can make no assumptions about its contents after the call regardless on how many parameters it actually passed. All bytes must be considered garbage after the callee returns, except when the returned value is placed in the _param block
  7. as the method is free to change the struct. It can use it in tail calls asparameter storage.

MetaABI to param or not to param

Food for thought

  1. How much slower is this, when ?
  2. How fast and simple is it now to forward a message ?
  3. What happens with variable style arguments ?
  4. How could tail calls reuse and expand existing parameter blocks ?

Continue to mulle-objc: removing superflous ifs


Post a comment

All comments are held for moderation; basic HTML formatting accepted.

Name:
E-mail: (not published)
Website: