Skip to main content
Engineering LibreTexts

13.6: Message Execution

  • Page ID
    43741
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    The Virtual Machine represents execution state as context objects, one per method or block currently executed (the word activated is also used). In Pharo, method and block executions are represented by MethodContext instances. In the rest of this chapter we survey contexts, method execution, and block closure execution.

    Sending a message

    To send a message to a receiver, the VM has to:

    1. Find the class of the receiver using the receiver object’s header.
    2. Lookup the method in the class method dictionary. If the method is not found, repeat this lookup in each superclass. When no class in the superclass chain can understand the message, the VM sends the message doesNotUnderstand: to the receiver so that the error can be handled in a manner appropriate to that object.
    3. When an appropriate method is found:
      1. check for a primitive associated with the method by reading the method header;
      2. if there is a primitive, execute it;
      3. if the primitive completes successfully, return the result object to the message sender;
      4. when there is no primitive or the primitive fails, continue to the next step.
    4. Create a new context. Set up the program counter, stack pointer, home contexts, then copy the arguments and receiver from the message sending context’s stack to the new stack.
    5. Activate that new context and start executing the instructions in the new method.

    The execution state before the message send must be remembered because the instructions after the message send must be executed when the message returns. State is saved using contexts. There will be many contexts in the system at any time. The context that represents the current state of execution is called the active context.

    When a message send happens in the active context, the active context is suspended and a new context is created and activated. The suspended context retains the state associated with the original compiled method until that context becomes active again. A context must remember the context that it suspended so that the suspended context can be resumed when a result is returned. The suspended context is called the new context’s sender. Figure \(\PageIndex{1}\) represents the relations between compiled methods and context. The method points to the currently executed method. The program counter points to the last instruction of the compiled method. Sender points to the context that was previously active.

    Contexts and compiled methods relationship diagram.
    Figure \(\PageIndex{1}\): Relations between contexts and compiled methods.

    Sketch of implementation

    Temporaries and arguments for blocks are handled the same way as in methods. Arguments are passed on the stack and temporaries are held in the corresponding context. Nevertheless, a block can access more variables than a method: a block can refer to arguments and temporaries from the enclosing method. As we have seen before, blocks can be passed around freely and activated at any time. In all cases, the block can access and modify the variables from the method it was defined in.

    Let us consider the example shown in Figure \(\PageIndex{2}\). The temp variable used in the block of the exampleReadInBlock method is non-local or remote variable. temp is initialized and changed in the method body and later on read in the block. The actual value of the variable is not stored in the block context but in the defining method context, also known as home context. In a typical implementation the home context of a block is accessed through its closure. This approach works well if all objects are first-class objects, including the method and block context. Blocks can be evaluated outside their home context and still refer to remote variables. Hence all home contexts might outlive the method activation.

    First closures diagram.
    Figure \(\PageIndex{2}\): A first understanding of closures.

    Implementation. The previously mentioned approach for block contexts has disadvantages from a low-level point of view. If method and block contexts are normal objects that means they have to be garbage collected at some point. Combined with the typical coding practice of using small methods that call many other objects, Pharo can generate a lot of contexts.

    The most efficient way to deal with method contexts is to not create them at all. At the VM level, this is done by using real stack frames. Method contexts can be easily mapped to stack frames: whenever we call a method we create a new frame, whenever we return from a method we delete the current frame. In that matter Pharo is not very different from C. This means whenever we return from a method the method context (stack frame) is immediately removed. Hence no high-level garbage collection is needed. Nevertheless, using the stack gets much more complicated when we have to support blocks.

    As mentioned before, method contexts that are used as home contexts might outlive their activation. If method contexts work as we explained up to now we would have to check each time for home contexts if a stack frame is removed. This comes with a big performance penalty. Hence the next step in using a stack for contexts is to make sure method contexts can be safely removed when we return from a method.

    The Figure \(\PageIndex{3}\) shows how non-local variables are no longer directly stored in the home context, but in a separate remote array which is heap allocated.

    VM variable storage.
    Figure \(\PageIndex{3}\): How the VM stores remote variables so that they continue to leave when a method returns.

    This page titled 13.6: Message Execution is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by Alexandre Bergel, Damien Cassou, Stéphane Ducasse, Jannik Laval (Square Bracket Associates) via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.