Merlin's Runtime System

"Abandon all hope, you who enter here!" -- Dante

Welcome to Merlin's underworld! Please don't mind the mess - this area is in constant turmoil so don't be surprised if things differ significantly from what is described here.

The main ideas are objects and message passing. Merlin's process model also comes into play, here. Our objective is to have a reasonable performance with an interpreter-like system while fully supporting optimizing compilation.

User's View

It might seem strange to start a description of the implementation of a system by reviewing the user interface, but the principle of transparency says that the two must be matched.

Some of the concepts that the user learns in Merlin:

objects

This is the first idea that becomes clear by simply looking at the screen. Each object has a 3D on screen presence and casts a shadow on the ground. The persistent store system also guarantees that they have a permanent existence.

messages

Besides moving the objects around, a user can make them do things by pressing buttons on them. Pop up menus offer extra buttons. Some buttons invoke dialog boxes to request extra information. The idea of messages becomes clear later, as the user learns to do the same things by typing text in an evaluator or a shell object.

cloning

The only way to have new objects is to ask for clones of an existing one. The idea that objects can "come from nothing" doesn't have a place in this user interface (besides, Julie Andrews (Sound of Music) wouldn't approve...).

pop-drag-and-push

This is Merlin's version of drag-n-drop. Two problems in most GUIs are that it is too easy to start dragging when you meant to be selecting, and also to drop things without intending to. So in Oz you have to "pop out" an object by pulling it towards you. Only then you can drag it around. You can release it at anytime and go do other things, coming back to continue dragging it later. To embed the object into another, you push it away from you into the destination. Though cumbersome to describe, this operation is almost as lightweight as the drag-n-drop but much safer. The important thing is that this operation is simply a shorthand for message sending ("yank" and "embed:").

slices

The term slice has been used to describe parts of arrays that can be operated on as a unit (the old Sinclair ZX81 Basic had this feature, for example). So a 5 by 5 array X[0..4,0..4] might have all of the elements in a 2 by 3 slice cleared with the expression (in some language) X[1..2,0..2] := 0.

Some operations in the user interface, like carpet morphs and text selections, are very similar to what was described above. So slices are just a generalization of the idea that you can operate on a collection of objects in a single step. Slices can be created in many different ways: dragging the cursor across some text, surrounding a group of objects with a "lasso", selecting an option from a menu, filling out a form or as the result of some other operation. The slice may be transient (disappearing with an operation is done on it, or when the next slice is created) or may persists for as long as the user wishes. The operations that may be selected from the slice's menu is simply the intersection of the operations that may be done on each individual object.

Previous GUI designs for Merlin made operations on multiple objects only possible with a carefully created static structure. The way to change the color of several objects at once was to have them inherit from a single color "wrapper" object. Otherwise, the same operation would only affect one of the objects. Most OOP graphic frameworks are like this. This style is a better reflection of rigid class based system than Self's loose prototype based environment, however, so slices were introduced to cope with the relatively unstructured Morphic world by allowing "on-the-fly" graphic object structures, rather than relying on fixed ones.

Application Programmer's View

One of the main ideas in Merlin is that it should not be possible to tell when a person has become an Application Programmer rather than a User (or Power User). As the person discovers the following ideas, however, a qualitative change in the use of Merlin's powers will be obvious.
objects

Objects present themselves as morphs in the user interface, but it is always possible to request an outliner for an object. As outliners, all Self objects look pretty much alike - a bunch of slots, each with a name and a value. Slots are organized into Categories to make large objects easier to deal with, but that is mostly decorative rather than a fundamental feature of objects.

The user eventually learns about different kinds of slots - constant slots, data slots, argument slots and method slots.

messages

The association between messages and an object's slots is very easy to figure out. The user can now extend the messages an object understands by changing its slots. The fact that methods are essentially the same thing as the message the user is used to typing in a shell means that creating code is no big deal.

parents

The only really new idea the user will meet when creating applications is that objects don't have to implement everything themselves, but can inherit slots from other objects. This includes new concepts such are traits objects, copy-down slots and data parents.

System Programmer's View

As the needs of the application programmer expand, the system may start to seem a bit limiting. The good news is that it is possible to see how the system was built and to change it.
maps/reflectors

The first thing to learn is that each object is not really self-contained, as the language model suggests. Most objects are just clones of others - the value of their data slots may be different, but all other aspects are identical. To save space, Merlin objects are simply sequences of words that contain identifiers of other objects, representing the values of the object's data slots. This identifier is simply the address of that object's first slot or the object's value in the case of small integer's and floating point numbers.

All other information about the object (the names of the slots, the value of constant slots) are stored is a map. Several objects can share a map forming a "clone family", which is the implicit notion of type used by the compiler. This is a little like introducing classes at the implementation level, but Self semantics are fully preserved as you can still change an object individually - if you add a slot, for example, the object first gets a new map and then has the slot added, so no other objects in its original clone family will be affected.

Another role for maps is acting as Apertos-like reflectors for the objects. This means that they define a metaspace for the object by grouping together a set of metaobjects, which forms the execution environment for the object. An object can migrate from one metaspace to another, possibly changing (normally adding to) its set of metaobjects. This allows the objects to change its run-time behavior. An object might be created as a normal Self objecT and then migrate to the real-time meta-space to cope with timing constraints.

metamessages

An object only understands message related to its function: a circleMorph may only be asked to do circleMophy things. How about other things, like copying from one disk to another or getting assigned to a processor? You can't ask the circleMorph to do these things, but you can ask its metaobjects to handle these chores. This is done by sending it metamessages, which are distinguished from normal message by having their names start with an underscore character. This notation is used for primitives in Self 4.0, which is alright since most primitives are reflective in nature. Those that aren't are implemented as metamessages anyway, though this make their implementation somewhat more complex then it should be.

System Hacker's View

When changes to the system cease to be just a way to enhance applications and becomes an end in itself, you know you are a system hacker! You are now ready to receive the last of Merlin's secrets.
Objects

Objects are almost what they seemed before. The only change is that the object doesn't actually point directly to its map/reflector. The reason for this is that switching from the baselevel execution (context) to the metalevel execution (the context of the map) is the most frequent operation, so the system is optimized for that. The current context has a pointer to the metalevel context, which in turn points to the map.

Messages

There are only two very basic operations in the system: starting reflection and ending reflection. Starting reflection means send a message (unlike Apertos, where there are several reflective operations) or return a value (indicated by requesting a message with a null selector to be sent). So messages are actually sent by a metaobject (in theory - it is actually handled by the map/reflector itself to save time).

future/message/context/method clone

When a message is to be sent, a message object should be created to store the value of the receivers, the arguments and the selectors. This is needed in Merlin because messages may have to line up in a queue of a busy object while the sender goes on to do other things. To allow the sender to proceed, a future object must be created to be returned and act as a place holder for the final result. When the message finally becomes gets its turn to execute, a context must be created for it to hold the virtual PC, the stack, and other temporary state. The data from the message object must be copied to the new context to initialize it.

Merlin uses an alternative design to lessen the burden on the memory allocation system and to avoid the copying of information. Future, message and context objects are merged into a single entity, though they can still be distinguished by their use.

After a lookup operation finds the method corresponding to a message, the future/message/context object's map is changed to be the same as the method prototype. This gives the method execution the cloning semantics needed for Self, yet allows the context to be allocated long before the lookup is performed.

pending message queue ( sentinel points to reflector )

The only fixed part in an object's format is that its very first word is a pointer to the start of its message queue. The first element in the queue is the currently executing method context. If the object is idle, then the first word points to the map's context (this acts as a sentinel to mark the end of the queue, or an empty queue in this case).

ready messages queue

Any context/message/future that is not blocked is placed in the ready messages queue. The scheduler simply switches the CPU among these. Normally these messages are also at the head of the queue at their respective objects, but this isn't the case for objects like small integers and floats nor for read-only objects.


see also:
| new3 | | intro | | faq | | history | | runtime | | tutorial | | tiny | | plan |
back to:
| merlin | | LSI | | USP |

please send comments to jecel@lsi.usp.br (Jecel Mattos de Assumpcao Jr), who changed this page on Jun 4, 15:58 .