grouping (was: Version Numbers)
Jecel Mattos de Assumpcao Jr.
jecel@lsi.usp.br
Tue, 9 May 1995 20:39:11 -0300
Rainer,
> The (application-) extractor, the module system and the type inferencers
> (inferrers? ;-) (static and run-time) seem to have a lot in common. I
> guess one of the reasons why they didn't exploit that (yet?) are the
> limited reflective capabilities of Self.
Exactly. Programming at the VM level in C++ and programming at the
Self level are not mixable, so they must duplicate code sometimes. Note
that the current implementations of the Self compilers depend on
runtime type feedback and no longer do the sophisticated type
analysis described in so many of the Self papers. So the Self level
type inferencer could have shared code with the Self 2.0 compiler, but
would have to do type inference itself from Self 3.0 on.
> A lot of the information (all those constraints, esp.) would not have to be
> computed every time from scratch if Self featured reifications of notions
> like types (which are sets of clone families), send sites, arguments and
> (return) values. Reification of every(?) concept that programmers use
> should be a requirement for Tunes.
You can reify some concept statically or on demand. Examples of the
latter in Self and Smalltalk implementations are contexts ( or activations )
and messages. Most modern implementations use a simple stack for
efficiency, but will create the "traditional" context objects on the fly
whenever the programmer tries to use them. Message objects are also not
created for every message sends, but only for the very rare cases
where the message object itself is accessed.
If you reify something on demand, it doesn't make sense to keep
the results around for a possible reuse as you want to save time
and memory.
> Merlin is supposed to have a reflective architechture. What did you plan
> to include?
I am writting a paper called "Bootstrapping the Object Oriented Operating
System Merlin: Just Add Reflection" that I plan to send to the Meta95
workshop on reflection of ECOOP'95. I will make this paper available
in the net as soon as it is finished ( which must be before the 15th ).
It is important to contrast my goals with those of the Self group. They
allow the implementation to "cheat" on the programming model only as
long as it is impossible to get caught. They reject the common tail
recursion elimination technique, for example, because it is possible
to see that some stack frames are missing. They allow you to use maps
to save space and time in the VM, as long as it is impossible for the
Self programmer to find out that this has been done. This is what is
limiting their use of reflection and makes them duplicate code to
group objects. The advantage, of course, is that they can radically
change the implementation ( which they did ) without the danger of
anything breaking.
My goal, on the other hand, was that there should be no "black boxes".
I did not want to have one group of programmers that worked in Self
while another used C++ with an entirely different development
environment. I wanted the whole system to be defined in terms of
itself, so I allow reflection even though I try to keep it under
control in order not limit the evolution of the system. I also
allow the implementation to "bend" the programming model with things
like tail recursion elimination, though I try to limit this too.
The most important objects in my reflection model are:
map/reflector - this object "knows" all about the format and other
operations of one ( or more ) "base level" Self
objects. It knows which metaobjects are used to
manage that object's memory, which metaobjects
can send messages from/to it, etc...
context/message/future - this should be three different objects, but
have been lumped together to reduce the strain on
the garbage collector. As a context, it knows about
the executing method's program counter, its stack,
interrupt flags, execution mode, etc. As a message
it knows the receiver, the selector and the arguments.
As a future it knows whether the answer has been
returned yet and has a list of pending messages.
CPU - this object isolates the hardware and allows switching between
normal level and meta level execution. It knows how to do DMA,
how to setup interrupt handlers, how to flush caches, how to
initialize system state, etc. It is a lot like the Alpha PALcode.
I know this is too brief to be clear, but it is enough to get an idea
of how message sending works. This is the most crucial part, as in
Self we have "message sending at the bottom". Note that this is a
case where things are reified on demand - most of the time the
compiler will optimize the sequence below into nothing!
Sending the message:
1. switch to reflector/map ( actually, the mailer object )
2. if receiver is future
block and call scheduler
3. else
make future/message/context object
4. if receiver in other session
trap to "kernel mode"
5. else
put future/message/context in receiver's queue
6. switch back to calling context
Receiving the message:
7. object returns from previous message: move next future/message/context
to ready queue
8. start dispatcher object ( inside the receiver's reflector/map )
9. if meta-message
patch context and switch to the right metaobject
10. else
lookup the message selector
11. start slot "reflector" apply code
Anyone who has taken a look at the Moostrap language will see where most
of this came from. Note that steps 1, 4 and 6 involve the CPU object, as
do steps 9 and 11. All of the other steps should be replacable with
user supplied code in the final, fully reflective version. This, of
course, might be *very* dangerous. That is why "inter session" messages
go through a system trap - any damage you do can't affect the other
users in the system.
Slots, in my system, as pseudo objects that have map/reflectors and
can be sent messages ( like the "apply" message in step 11 ). They
might be reified into full objects on demand.
I hope it doesn't seem as confusing to you as it does to me ;-)
-- Jecel