Temporary Objects

One of the final hurdles in designing a BOOL implementation involves temporary objects. Any operation on an object may result in a transitory new object that is consumed by later operations. For example, in the arithmetic expression “5 x (3 + 4)” the addition operation creates a transitory value — 7 — required by the multiply operation.

The question for an implementation is, “Where to store such temporary objects?” In some languages, CPU registers suffice to store transient values (such as seven), but in object-oriented languages, registers aren’t enough. Objects take up some space, and often temporary objects need to be addressable.

Consider the following BOOL example (of the above arithmetic expression):

mul: 5 add: 3 4

The result of sending an add: Message to a “3” object (an *int by default) is a new object: the sum of the target and a parameter from the stack. That new object needs to be on the stack for the mul: Message (which creates its own new object for the stack).

BOOL data is split between the fixed object in the code and the per-thread data that represents the object’s value at the moment. The fixed part is the object’s address. The fixed part receives and dispatches Messages. The question is how to implement that fixed part for temporary objects.

The answer turns out to be: by creating a “phantom” Reference Object (in code) for every Message and Actor. This phantom receives the result object of the underlying Action and provides an address for the object.

That creates output (Python binding) like this:

[0000] (TEMP, 'any', dx-0)
[0001] (MSG, 'add', constant(3), [constant(4)], @0000)
[0002] (TEMP, 'any', dx-1)
[0003] (MSG, 'mul', constant(5), [@0001], @0002)

An important point is that temporary objects are only created by Actions with outputs. While a Message seems to result in a temp object, that object is actually created by the Action invoked by the Model when it dispatches the Message.

An Action Object receives its parameters from the stack (which may include addresses of objects). Upon completion, the Action pushes any return data Object(s). The Actor Object pops them and applies the received Message.

Messages Object first evaluate any parameter(s)  by sending them a Q: Message. Then they send their own Message to the target. That invokes an operation, which presumably pushes a result Object onto the stack.  The Message Object pops that result and applies the received Message.

In both cases, if the received Message is Q: then the pop is skipped, and the Object is left on the stack.  Otherwise the result is popped, but if the received Message is X: it is not applied. After Message application (if any), the object can be discarded.

More on this as the story develops!

For now, here’s an extended example:

@@hypot
>>  *float a
>>  *float b
<<  *float c
.   set:c @sqrt add: mul:a a mul:b b
.

That would compile into:

[0000] (INST, 'float', 0, NULL, ('name','a'))
[0001] (INST, 'float', 1, NULL, ('name','b'))
[0002] (INST, 'float', 2, NULL, ('name','c'))
[0003] (TEMP, 'any', 3)
[0004] (MSG, 'mul', addr('a'), [addr('a')], @0003)
[0005] (TEMP, 'any', 4)
[0006] (MSG, 'mul', addr('b'), [addr('b')], @0005)
[0007] (TEMP, 'any', 5)
[0008] (MSG, 'add', @0004, [@0006], @0007)
[0009] (TEMP, 'float', 6)
[000A] (TOR, 'sqrt', [('sqrt',@0008)], @0009)
[000B] (TEMP, 'any', 7)
[000C] (MSG, 'set', @0002, [@000A], @000B)
[000D]

The (INST, type, dx, init-list) and (TEMP, type, dx) objects contain an index (dx) into the current Call Frame where the actual Object is stored.

Advertisements
%d bloggers like this: