5.1 KiB
In a picture, or when taking a picture, the frame is very important. It sets whatever is in the picture into context.
So it is a bit strange that having a frame had the same sort of effect for me in programming. I made the frame explicit, as an object, with functions and data, and immediately the whole message sending became a whole lot clearer.
You read about frames in calling conventions, or otherwise when talking about the machine stack. It is the area a function uses for storing data, be it arguments, locals or temporary data. Often a frame pointer will be used to establish a frames dynamic size and things like that. But since it's all so implicit and handled by code very few programmers ever see it was all a bit muddled for me.
My frame has: return and exceptional return address, self, arguments, locals, temps
and methods to: create a frame, get a value to or from a slot or args/locals/tmps , return or raise
The divide, compile and runtime
I saw Tom's video on free compilers and read the underlying book on Partial Evaluation a bit, and it helped to make the distinctions clearer. As did the Layers and Passes post. And the explicit Frame.
The explicit frame established the vm explicitly too, or much better. All actions of the vm happen in terms of the frame. Sending is creating a new one, loading it, finding the method and branching there. Getting and setting variables is just indexing into the frame at the right index and so on. Instance variables are a send to self, and on it goes.
The great distinction is at the end quite simple, it is compile-time or run-time. And the passes idea helps in that i start with most simple implementation against my vm. Then i have a data structure and can keep expanding it to "implement" more detail. Or i can analyse it to save redundancies, ie optimize. But the point is in both cases i can just think about data structures and what to do with them.
And what i can do with my data (which is off course partially instruction sequences, but that's beside the point) really always depends on the great question: compile time vs run-time. What is constant, can i do immediately. Otherwise leave for later. Simple.
An example, attribute accessor: a simple send. I build a frame, set the self. Now a fully dynamic implementation would leave it at that. But i can check if i know the type, if it's not reference (ie integer) we can raise immediately. Also the a reference tags the class for when that is known at compile time. If so i can determine the layout at compile time and inline the get's implementation. If not i could cache, but that's for later.
As a further example on this, when one function has two calls on the same object, the layout must only be retrieved once. ie in the sequences getType, determine method, call, the first step can be omitted for the second call as a layout is constant.
And as a final bonus of all this clarity, i immediately spotted the inconsistency in my own design: The frame i designed holds local variables, but the caller needs to create it. The caller can not possibly know the number of local variables as that is decided by the invoked method, which is only known at run-time. So we clearly need a two level thing here, one that the caller creates, and one that the receiver creates.
Messaging and slots
It is interesting to relate what emerges to concepts learned over the years:
There is this idea of message passing, as opposed to function calling. Everyone i know has learned an imperative language as the first language and so message passing is a bit like vegetarian food, all right for some. But off course there is a distinct difference in dynamic languages as one does not know the actual method invoked beforehand. Also exceptions make the return trickier and default values even the argument passing which then have to be augmented by the receiver.
One main difficulty i had in with the message passing idea has always been what the message is. But now i have the frame, i know exactly what it is: it is the frame, nothing more nothing less. (Postscript: Later introduced the Message object which gets created by the caller, and the Frame is what is created by the callee)
Another interesting observation is the (hopefully) golden path this design goes between smalltalk and self. In smalltalk (like ruby and...) all objects have a class. But some of the smalltalk researchers went on to do Self, which has no classes only objects. This was supposed to make things easier and faster. Slots were a bit like instance variables, but there were no classes to rule them.
Now in ruby, any object can have any variables anyway, but they incur a dynamic lookup. Types on the other hand are like slots, and keeping each Type constant (while an object can change layouts) makes it possible to have completely dynamic behaviour (smalltalk/ruby) and use a slot-like (self) system with constant lookup speed. Admittedly the constancy only affects cache hits, but as most systems are not dynamic most of the time, that is almost always.