[Vm-dev] VM Maker: VMMaker.oscog-eem.1522.mcz

Eliot Miranda eliot.miranda at gmail.com
Thu Nov 19 00:32:27 UTC 2015

Hi Ben,

On Wed, Nov 18, 2015 at 3:05 PM, Ben Coman <btc at openinworld.com> wrote:

> On Thu, Nov 19, 2015 at 1:58 AM, Eliot Miranda <eliot.miranda at gmail.com>
> wrote:
> >
> > On Tue, Nov 17, 2015 at 10:23 PM, Ryan Macnak <rmacnak at gmail.com> wrote:
> >>
> >> This commit also broke the stack VMs, so something's probably amiss
> with the Slang changes.
> I'm not sure if a lightbulb just went on or my intuition misleads me.
> How does Slang related to the interpreter?
> i.e. Is it Slang that the interpreter interprets?

The VM is written as a Smalltalk program.  There are three different
versions of the interpreter in it, Interpreter, which is a classical
blue-book interpreter that uses context objects to represent method
activations, the STackInterpreter that avoids creating contexts as much as
possible and uses stack frames to represent method activations, and
CoInterpreter, a subclass of StackInterpreter that can interoperate with
the JIT so that machine code can execute Smalltalk on its own, interleaved
with the CoInterpreter interpreting methods that have yet to be fitted or
are judged too big to be worth jitting.  All of these interpreters
interpret Smalltalk bytecocde methods.  They contain methods like the
following, which is the pushInstVar byte code used to push an instance
variable of the receiver onto the stack:

self pushReceiverVariable: (currentBytecode bitAnd: 16rF).
self fetchNextBytecode

pushReceiverVariable: fieldIndex

self internalPush: (objectMemory fetchPointer: fieldIndex ofObject: self

All three implement their interpreter as a loop that indexes an Array of
selectors for each byte code of methods like the above:

"Just run"
quitBlock := [displayView ifNotNil:
  [displayView containingWindow ifNotNil:
((World submorphs includes: topWindow)
and: [UIManager default confirm: 'close?']) ifTrue:
[topWindow delete]]].
self initStackPages.
self loadInitialContext.
self internalizeIPandSP.
self fetchNextBytecode.
[true] whileTrue:
[self assertValidExecutionPointers.
atEachStepBlock value. "N.B. may be nil"
self dispatchOn: currentBytecode in: BytecodeTable.
self incrementByteCount].
localIP := localIP - 1.
"undo the pre-increment of IP before returning"
self externalizeIPandSP

dispatchOn: anInteger in: selectorArray
"Simulate a case statement via selector table lookup.
The given integer must be between 0 and selectorArray size-1, inclusive.
For speed, no range test is done, since it is done by the at: operation."

self perform: (selectorArray at: (anInteger + 1)).

The BytecodeTable has 256 entries.  Depending on the byte code set the
first 16 elements might be #pushReceiverVariableBytecode, the next 16
#pushTemporaryVariableBytecode and so on.

This is fine for developing the VM but can't yield useful performance in
practice.  We need somehow to translate the Smalltalk code into something a
real machine can run.  We do this in two steps.  First, the Smalltalk code
is converted from Smalltalk to C.  This is "trivial" because in fact the VM
is written in a subset of C that is "trivially" translatable to C.  There
are no Dictionaries or Sets in the bowels of the VM, just whole and for
looks and performs mapped either to inlining of code or to calling through
function pointers.  The thing that does the Smalltalk to C translation is
called Slang.  The pours trees of the methods of the VM are translated into
Slang's parse trees which in turn implement analysis and output code that
allows the entire VM to be written out as a C program.  The last step is to
compile this using a common or garden C compiler.

Here's what the code of the interpreter loop looks like.  You'll be curious
about the <expandCases> pragma above in pushReceiverVariableBytecode.  It
and a couple of other pragmas are used to guide Slang in producing the
interpreter loop's C code:

        /* begin internalizeIPandSP */
        localIP = pointerForOop(GIV(instructionPointer));
        localSP = pointerForOop(GIV(stackPointer));
        localFP = pointerForOop(GIV(framePointer));
        /* begin fetchNextBytecode */
        currentBytecode = byteAtPointer(++localIP);

        /* begin initExtensions */

        while (1) {

                switch (currentBytecode) {
                case 0:
                        /* pushReceiverVariableBytecode */
                                /* begin fetchNextBytecode */
                                currentBytecode = byteAtPointer(++localIP);

                                /* begin pushReceiverVariable: */
                                /* begin internalPush: */
                                longAtPointerput((localSP -= BytesPerOop),
longAt(((longAt(localFP + FoxReceiver)) + BaseHeaderSize)));
                case 1:
                        /* pushReceiverVariableBytecode */
                                /* begin fetchNextBytecode */
                                currentBytecode = byteAtPointer(++localIP);

                                /* begin pushReceiverVariable: */
                                /* begin internalPush: */
                                longAtPointerput((localSP -= BytesPerOop),
longAt(((longAt(localFP + FoxReceiver)) + BaseHeaderSize) + 4 /*
(currentBytecode bitAnd: 15) << self shiftForWord */));

So we get to develop the VM in Smalltalk, but the code is reasonably well
compiled by a C compiler and hence acceptably efficiently executed by a
commodity microprocessor.

And of course the Cogit is another story :-)

best, Eliot
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.squeakfoundation.org/pipermail/vm-dev/attachments/20151118/7789ac7d/attachment.htm

More information about the Vm-dev mailing list