7.4. Message dispatching

This section explains how messages can be dispatched, i.e. how the implementation of a method can be invoked, given the message.

A method is translated by the compiler to a C function with essentially the same arguments as the method, with two mandatory additions and some additions depending on the return type of the method. For the method


(boolean, Any) next

defined for the instance tom.Enumerator, the C function implementing this is


tom_byte
i_tom_Enumerator_or_next (tom_object self,
                          selector cmd,
                          tom_object *ret1)
@end example

As can be seen, the first element of the tuple return type is the type returned by the implementation C function. Such a C function implementing a method always has two `implicit' first arguments: self being the object receiving the message, and cmd being the message sent. Following these two are the `normal' arguments to the method, which are none in this case. Finally, any of the remaining return values are to be returned in the pointer arguments supplied after the normal arguments. In the example, a pointer for returning the second tuple element is provided.

A message is dispatched by invoking the method implementation for the given (self, cmd) pair. The lookup of the implementation, i.e. the pointer to the C function, can be done in three different ways:

lookup

Call the function trt_lookup with the receiver and the selector to be invoked (i.e. the values to be passed for self and cmd). trt_lookup returns a function pointer to the method implementation. Invoke that function with the arguments.

direct

A direct lookup is equivalent to inlining the trt_lookup function. This is a rather unwise way of invoking a method as it considerably increases code size.

send

When using trt_send to dispatch a message, the function trt_send is invoked with all the arguments to be passed to the method implementation. trt_send will perform the lookup and jump directly to the implementation.

These different dispatching mechanisms can be selected by an option to tomc. These options are not yet implemented in tesla, the new TOM compiler. When using tesla, they must be selected when configuring trt. They are described here for explanatory purposes; never implement any of these directly in your C code; the next section explains how to do that portably.

Sending is the preferred way of dispatching messages, though possibly not present on all TOM implementations as it involves an assembly language routine. Also note that there are dependencies of the applicability of some dispatching mechanisms. For example, it is impossible to use sending on dynamically loaded code on hppa-hpux machines. This is not a TOM feature but due to the interspace stubs needed by the hpux shared library interspace calls, added to the fact that a callee stores the return program counter in the stack frame of the caller.

When doing profiling on a TOM program, all code should really use the lookup way of dispatching instead of sending to dispatch. Otherwise, all methods will be reported to only invoke trt_send, and trt_send will be reported as the culprit which invoked every method and thus effectively void the use of the call graph.

7.4.1. Messaging from C

To invoke a method of a TOM object, use the TRT_SEND macro. For example, to retrieve the length of an array a:


tom_object a = ...;

tom_int len = TRT_SEND (, a, SEL (_i_length));

The second argument to TRT_SEND is the receiver of the message. The third argument is the selector to be sent. A selector is an invocation of the SEL macro, with as argument the selector's name with any nasty characters replaced by an underscore.

A more elborate example shows almost all pitfalls when using TRT_SEND. In this example the method (int, float) split float f is invoked, which in TOM would be written as


SomeClass receiver = ...;
float fractional, number = ...;
int integer;

(integer, fractional) = [receiver split number]

is invoked from C as follows:


tom_object receiver = ...;
tom_float fractional, number = ...;
tom_int integer;

integer = TRT_SEND ((tom_int (*) (tom_object, selector,
                                  tom_float, tom_float *)),
                    receiver, SEL (_if__split_f), number, &fractional);

The first argument to TRT_SEND is a cast to the type of the function actually being invoked. This cast is mandatory if the return type of the function (implementing the method) invoked is not a tom_int. The argument prototypes in the cast are mandatory when needed to prevent the C compiler from doing undesirable type conversions due to it not having seen a full prototype of the function being invoked. For instance, usually, tom_float is simply a float, which the compiler will promote to a double when passed as an argument for which the prototype has not been seen.

This example also shows that for a method returning a tuple, the first element of the tuple (or the first element thereof in case it is a tuple too (or...)) is actually returned from the C function implementation, and any remaining elements of the tuple returned are stored in variables the address' of which has been passed as `invisible' trailing arguments.