The Activation Record
Until all of the (WHY???)'s or "(??"'s are gone, this document
isn't done.
Ok, as seen from an intstruction in a code segment, here
is the stack..
(after the code executed a 'setupFrame n')
x Arg x
. ...
8 Arg 0
7 Mesg
6 Target
5 Argument Count
4 PC (of instruction after
Call's operand) [Pushed by Call instruction]
3 Parent Local (what the parent
is) [Pushed by ActivateSlot]
2 Code-Proto Pointer (the
proto that holds _code_ and the locals,
This is used by
pushSelf (possibly what block context would use as a parent)
[Pushed by ActivateSlot]
1 Preserved FP
FP-> 0 locals
[The FP is what the SP was, SP preservation]
. ...
n locals
SP->
First bits of code within a method:
Name: [Ignore This Slot] Count: [86]
"0" <- {
"op" -> (Get:0) -> "slotName"
"operand" -> (Get:1) -> "Start"
}
"1" <- {
"op" -> (Get:0) -> "setupFrame"
"type" -> (Get:1) -> "self"
}
Keep the previous info in mind.
Problems:
1 - will a context reference have enough resolution to refer to a context
or be equivalent to a context's FP? (It uses the bottom 3 bits)
(Need to finish implementing, and use
'stackframe.doc' as the working template)
This section describes the most complicated
portion of this system. Understanding it is necessary for working on the
runtime.
In the following paragraphs, I will state
that code can be activated, this is the same as saying 'run'.
Code vs. Blocks
Code can be activated in several ways:
a slot activates via a passthru, or a block activates via a message send,
or the VM starts and calls the start (it simulates a passthru). Blocks
are just like Smaltalk/Self blocks, however, I tried to keep the underlying
code for either code system the same. (In Smalltalk the block activations
have different compiled code.) I didn't like the idea that the code for
a method was different than the code for a block (Self went along this
path as well).
When a proto has code defined for the slot,
the slot is initialized with a reference to a code proto. The named slot
is now what is called a 'PassThru' to the code proto. This is what is the
default case when you compile code. The code proto has a slot called 'value'
or 'value:' or 'value:with:', etc.
That slot refers to a blob of code.
<Diagram Here>
When a slot refers to a code proto and
it wasn't setup as a passThru, then it is a normal reference. The only
way to activate it is to use the 'value', or 'value:", etc., message. (Which
is what the PassThru refers to)
Activation
As mentioned before, only methods or block
invocations actually cause new activation contexts.
Ok, so there are several steps to an activation.
We will go through those and then cover the various special cases in each
step.
Lets assume that we are already in a method
and we are in some code which is about to activate a slot.
The first thing the compiler will do is
push the arguments onto the stack. They will be pushed in reverse order
(like C). The first argument will be pushed last (which should be the target
of the slot activation).
Special. If any of the arguments
are references to blocks, then the VM must clone the proto and set the
parent to refer to this context during these push statements. It does that
by making the parent of the newly cloned block proto a Context Reference.
This reference should be the FP of the context on the stack of this activation...
the one doing the push.
(Can this refer to a FP??)
After the cloning and setting of the parent,
the push instruction will push a reference to this new code proto onto
the stack. We will cover this in more detail further on.
After the arguments, an argument count
should be pushed on the stack. This is useful for simple error checking
and/or for getting information about the activation later on. For
example, later on we may have the ability to treat the entire argument
as an array and access it or manipulate it.
After that, the call to activateSlot is
done. The VM will do several things automatically. The first thing that
it will do is increment the program counter to be behind the operand of
the call (so that when the return happens, the code executes from there).
Next the VM pushes the program counter onto the stack. Finally, the function
is called.
For the purposes of this discussion, the
call is always to 'activateSlot'.
activateSlot is a very, very, very powerful
function (it is also slightly complex). It embodies the core principles
of the prototype inheritance system. The first thing it will do is locate
the slot in the target. If the slot isn't there, then it will lookup the
parent slot of the target. If it finds the parent, then it will follow
the parent slot and start the search again at that proto. If that slot
doesn't exist, the algorithm will lookup the information in the DefaultBehaviours
or the Global namespaces. If it does find the slot, then it will go to
the next step. If it finds it and it was a GETFRAME or Frame referenced
parent variable, it will use that value. Finally, if a method isn't found
or it goes up too many parent levels (32 currently), then it declares the
activateSlot a 'method not found' error. This should then be considered
an 'exception' (which will be handled by an exception handling system in
the near future.)
Special. Code protos will usually
have their parent slot set as a GETFRAME type. This is because code protos
refer to their local variables (which have a higher priority than the target
object). So, the code-proto's parent will refer to the actual target that
was pushed onto the stack. Usually this code-proto is the 'secret self'.
Example: if you do 'car run.' The run
method will be the self since it will have local variables. However, the
real self in this instance would be the object messaged ('car').
Special. Another Related Important
Thing is to consider the case were the code-proto is pointing to a block
proto. Block protos could have variables on the stack local to the FP.
However, when the parent slot is accessed, the activateSlot will use a
different FP internally for frame relative lookups (without really changing
the FP). Lets cover this later...we need to understand more before covering
that.
OK, once it finds the slot, it peforms
the specific action. The actions are GET, SET, GETFRAME, SETFRAME, PASSTHRU,
CODE, or INTRINSIC.
GET is easy, it just returns the slot's
data. SET will set the slot's data with the argument.
GETFRAME will use the data in the data
slot of the proto as an FP or frame relative number and get that location.
SETFRAME does the same for a set operation. GETFRAME and SETFRAME exist
only to handle the parent slot for a code proto or local variables for
any code activations.
PASSTHRU will use the data as a reference
to a code proto and activate the method (via slot '_code_').
Note: code
protos only have one '_code_' slot... a code proto exists for each method.
The result of the PASSTHRU is a new context... more on that later. CODE
activations are the results of 'do or value' or 'value:' slots. If you
are referencing a code proto directly (as a block), then this can happen.
The result of activating this method will cause a new context. INTRINSIC
slots will cause the referenced C code to be activated. The slot values
are set to pointers to routines that are written in C or assembly.
For all of those actions, only two are
important. The PASSTHRU and CODE slots. They take us to the next parts
of the activation (because all of the others will do their work and leave
the result on the stack frame... ie. these cause new activations)
Next Part of the Activation
These two action handlers will do
the next interesting thing.
Generally, the target of a call will be
some proto or the code-proto for the current context (secret self). The
first activateSlot in a method will always have the code-proto as the first
call. After that, a new target proto may be acquired and it will be the
target for subsequent calls to activateSlot. So, to reiterate, the target
of a call could be the code-proto or it could be some other object. The
reason that this is important is as follows:
When a new activation is started, and it
does the 'frame mapped parent local', and it just used the target, it would
be using the previous context's code proto. This is not what is intended.
So, they will look at that target and judge
by it's type (which is encoded in the reference) to see if it is a code-proto
or otherwise (A relatively inexpensive check.). If it is a code-proto,
it will take the code-proto's parent from the secret self as the parent-local
from the existing context (since the FP didn't change). If it isn't a code-proto,
it will just take the target. Once it has this value it will push it onto
the stack.
Special: What if the target is
a block to activate? It should just push the parent local for the context
pointed to by the parent context reference. The only reason that a value
is pushed is for a code-proto, we could just push nil.
What if the target is a method from a
previous activation? (Example, a method is activated that was located through
the current block-proto's parent). What gets pushed for the code-proto?
This is probably the most complicated example. The answer is this, the
block-proto should have it's parent followed and the parent local should
be copied from it (unless it was a block activation as well, then it should
follow that too.)
We are now ready to proceed to the next
step.
Next Step...
Now we have located the new method. The
passThru will set the PC to the begining of the _code_ blob. Both of the
previous 'action handlers' will push onto the stack the pointer
to this code proto. This is necessary since the pushSelf opcode will need
to push the 'secret self'.
WHY? Since block-proto's will be cloned,
their code will not know what the 'secret-self' is when it is assembled.
Therefore, the pushSelf opcode must dynamically get this value.
Finally, the handler will return. This
will cause the VM to continue executing code.
Code Execution
The code in the method should be the same.
The first instruction should be a 'setupFrame' opCode and operand. It will
preserve the FP on the stack. Then it will set the new FP to the current
stack pointer. It should then move the stack pointer forward for the number
of local variables that it has.
At this point, the code can now execute.
Everything that needed to be done is done. The first set of code should
be for local variable initialization.
Returning within the Method
The last thing a method can do is perform
a return. If a method has a value to return, which it always should, it
will leave that as the last thing pushed onto the stack. The next instruction
will be a restoreFrame. This will take the value and store it temporarily.
Then it will restore the stack pointer to what the fp is. Then it will
restore the previous method's fp. Finally, it will push the new return
value back onto the stack. The next instruction will be the 'return' opcode.
It will get it's operand and hold that temporarily. Next it will get the
return value off of the stack and store that temporarily as well. Now the
stack will be unwound back using the operand as a counter. It is used to
unwind the arguments from the call off of the stack. Then it leaves the
return value there. At this point the previous context is now ready to
resume.
Special. Lets review and add to
the handling of the 'parent' slot for a block activation.
When a block is acitvated, it will set
it's parent to be a context reference. When a new activateSlot call occurs
and the variable cannot be found in the block proto, it will follow the
parent slot. This slot will contain a context reference. If that reference
is below the current frame pointer, then the system should throw an exception.
We cannot allow contexts to be referenced that no longer exist.
If the context is valid, then the activateSlot
call will set the FP internal to itself to be the same as the context reference.
If the parent of the new context is a block, it would do the same procedure
again.
Once it finds the slot, the typical handlers
will take care of the call (using this hidden FP). However, if it is a
passThru or code it will work. Just as it does normally (the target and
the new method proto will be mapped, and a new context is started. They
will not need the hidden FP). The moral of this story is that the FP doesn't
get changed in a visible way.
Worst Case Example
A worst case scenario would be a method
activation after a block was activated, and that was just described in
the previous paragraph.
6/27/99 - Changed the activation documentation,
it is too hazy...
7/10/99 - Worked it out a little more,
not done, need to implement then document.
6/3/00 - Another attempt to
document one of the most complex portions of the system
7/2/00 - A really good attempt at getting
this stuff documented.
8/14/00 - Day(s) after the big re-write.
8/21/00 - A few tidbits clarified and
added on.
|