Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// Interface method stub. Needs a custom struct for arguments and return value
// of each interface method.
typedef void
(*cfish_interface_stub_t)(void *self, void *vargs, uint32_t *hash);
// Classes contain a fixed-size hashtable with pointers to interface method
// stubs. This table could also be moved in memory before the class struct
// to avoid overhead for classes that don't implement interfaces.
typedef struct {
...
cfish_interface_stub_t itable[ITABLE_SIZE];
...
} cfish_Class;
// Method names are hashed using a fixed function. The hash is a static value
// known at compile time.
// NOTE: Comparing the symbol address isn't reliable across parcels. We'll need
// a struct containing the hash value and the method name (see below).
uint32_t PREFIX_Method_HASH = 0xCAFEBABE;
// Also a static value known at compile time.
#define PREFIX_Interface_Method_OFFSET \
(offsetof(cfish_Class, itable) \
+ (0xCAFEBABE % ITABLE_SIZE) * sizeof(cfish_interface_stub_t))
// Argument struct for an interface method.
typedef struct {
int arg1;
void *arg2;
int retval;
} PREFIX_Interface_Method_arg_t;
// Interface method wrapper. Similar complexity as normal method wrappers.
// Argument mangling is a bit more expensive on x64-86 since arguments can't
// be passed in registers.
static CFISH_INLINE int
PREFIX_Interface_Method(Interface *self, int arg1, void *arg2) {
// Lookup interface method stub in itable.
cfish_interface_stub_t method = (cfish_interface_stub_t)
cfish_obj_method(self, PREFIX_Interface_Method_OFFSET);
// Set up argument struct.
PREFIX_Interface_Method_arg_t args;
args.arg1 = arg1;
args.arg2 = arg2;
// Call stub.
method(self, &args, &PREFIX_Method_HASH);
return args.retval;
}
// Simple interface method stub in case there are no collisions.
// In the common case, the overhead of an interface method call
// should be about twice the overhead of a class method call.
void
PREFIX_Class_Method_ISTUB(void *self, void *vargs, uint32_t *hash) {
PREFIX_Method_arg_t *args = (PREFIX_Method_arg_t)vargs;
// Methods can be called directly because the call to the stub goes
// through the class's itable, so the exact type of self is known.
// The stub can also be shared with subclasses that don't override
// the method. Alternatively, the method call could be dispatched
// dynamically, and the stub could be shared with all subclasses.
args->retval = PREFIX_Class_Method_IMP((Class*)self, args->arg1,
args->arg2);
}
// Interface method resolution stub in case of collisions.
// The hash values and collisions are known at compile time, so this code can
// be generated.
// Could also be shared with subclasses under certain conditions (probably
// hard to figure out).
void
PREFIX_Class_MRSTUB1(void *self, void *vargs, uint32_t *hash) {
// Switch statement with static values can be optimized by the compiler.
// Branch prediction should keep additional overhead negligible, at
// least in the monomorphic case.
switch (*hash) {
case 0xCAFEBABE: {
PREFIX_Method_arg_t *args = (PREFIX_Method_arg_t)vargs;
// Methods in the same parcel can be called directly.
args->retval = PREFIX_Class_Method_IMP((Class*)self, args->arg1,
args->arg2)
break;
}
case 0xDEADBEEF:
// Extremely rare case where two method names hash to the same
// value. Resolve by comparing address of hash value global.
// NOTE: This doesn't work across with classes and interfaces
// from different parcels. We have to compare the actual
// method names.
if (hash == PREFIX_Method2_HASH) {
PREFIX_Method2_arg_t *args = (PREFIX_Method2_arg_t)vargs;
// Methods in other parcels must go through vtable.
args->retval = PREFIX_Class_Method2((Class*)self, ...)
}
else {
...
}
break;
case ...
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment