PIOS Virtual Machine and Code

Mike Prince mprince@crl.com
Thu, 27 Oct 1994 19:05:34 -0700 (PDT)

This is our first big test; agreeing on the virtual machine=20
architecture.  I have been piecing this virtual machine=20
together for a while now, and every part interplay=92s with=20
every other part.  Unfortunately the justification for this=20
model exists on millions of scraps of papers and in my head. =20
This text file was put together in only a few hours and lacks=20
most of my justification for this model, so please give do not=20
be too harsh for the glaring omissions.  In time, through=20
discussion, they will all be resolved.

As I have said before, nothing is cast in stone, and I am very=20
versatile.  Please critique any and every part of this=20
proposal.  I would be happy to be proved completely wrong if=20
we end up finding a better way to do this.  We can not=20
compromise the project due to individual pride.  I am looking=20
forward to you opinions.

My date for beginning to discuss the virtual machine/language=20
issue was the first of November, so we are running a little=20
ahead of schedule.  You can still look forward to a more=20
detailed agenda of November on the 1st though.

Now on to the work...

For this project we will need to define at least two=20

First we must define a basic model for our computing world. =20
That model will be represented by a virtual machine.  Our=20
tools will be distributed in the programming language of this=20
virtual machine.  For now we will call this language vmcode.

We could program all our tools in this low level vmcode.  But=20
there are two immediate disadvantages.  First, we may decide=20
to change the vmcode.  If we did, all prior coding would have=20
to be re-written.  Secondly, there are many good programming=20
languages already out there that we could choose from.  We=20
might even decide to create a hybrid language of our own=20
better suited to programming our virtual machine.

I would like to model vmcode and the machine loosely on Forth.

Virtual Machine
The largest object is the tool box.  A tool box contains=20
tools, stacks, and agents.

Tools are executable code and are accessible to all agents=20
within the tool box. The tool box stacks are data global to=20
the tool box.

Agents are the execution primitives.  Agents have their own=20
stacks which are only accessible to the agent to which they=20
belong.  Agents reside in one tool box at a time.  Agents can=20
move between tools and tool boxes at the tools discretion. =20
Agents carry their own stacks with them as they move between=20
tools or tool boxes.

An agent has two active stacks at any time A and P.  A is the=20
stack used for generic operations.  P is the path stack and is=20
used as a return stack for program flow control.  Each stack=20
also has a stack pointer.  Neither A nor P may be a tool box=20
stack (global) when an agent transfers control to another tool=20

NOTE: Should we have more active stacks?  A floating point=20
stack, secondary stack, loop counter stack, etc?=20

Agents are synchronized through the use of blocking=20
semaphores.  All semaphores are stored in tool box stack 2. =20
Semaphores are 32 bits and are referenced as an array.  Vmcode=20
has up, down, and setsemaphore primitives.  Up increases the=20
value of the semaphore by one. If an agent is blocked on that=20
semaphore, then only one agent will be unblocked and the=20
semaphore will automatically be reduced by one again.  A down=20
attempts to reduce the semaphore by one.  If the count is=20
already zero the agent blocks until the count can be reduced. =20
A set forces to semaphore to a particular value.

Our virtual machine is broken up into domains.  Domains are=20
further broken up into work spaces.  Each work space can=20
contain one or more tool boxes.  A work space may or may not=20
be accessible at any given time (i.e. has been disconnected=20
from the net, turned off, etc).  Domains are used to=20
encapsulate groups of work spaces to ensure security or the=20
containment of computational resources.

Each tool box provides a name for itself up to 2^7 bytes long. =20
Each tool box has up to 2^15 tools and/or stacks.  Each agent=20
can have up to 2^15 private stacks.  Each stack is up to 2^31=20
bytes long.

NOTE: All the numbers above has lost one bit.  In the case of=20
the stacks, agents use positive numbers to reference their=20
stacks, and negative numbers to reference global stacks,=20
resulting in a full 2^16 worth of stacks.

Each stacks data is accessible randomly or like a traditional=20
stack. All numbers are stored little endian.  Stacks grow=20
upwards.  The stack pointer points to the first free byte. =20
Data is not necessarily aligned on word boundaries.  Data=20
types are consistently represented on all machines.

Stacks are referenced by their number.  All positive numbered=20
stacks belong to the current agent.  All negative numbered=20
stacks are global and belong to the tool box the agent is=20
currently in.

When a stack underflow occurs, error information is pushed=20
onto the agents stack, and a call is issued to mark 0 of the=20
current tool; the error handling routine.  That routine may=20
elect to kill the agent, continue on as if nothing happened,=20
or take corrective action.

When a stack overflow occurs, the kernel attempts to adjust=20
the size of the stack to accommodate the new data.  If the=20
stack cannot be made larger then the tool box is moved to a=20
workspace that can handle a larger stack.

There are seven basic data types; 8 , 16, 32, and 64 bit=20
signed numbers, 80 bit floating point numbers(?), strings and=20
blocks.  Strings are length delimited arrays of bytes up to=20
2^7 bytes long.  Blocks are length delimited arrays of bytes=20
2^31 bytes long.

8bit,16bit,32bit,64bit: all represent signed numbers
F: is an 80 bit floating point number
S: is a string of 8bit length plus n bytes of data.
B: is a block of 32bit length plus n bytes of data
#: is a number of expected type
$: stands for any one of 8, 16, 32, 64 (bit=20
numbers), F, S, or B.
xx: stands for the byte code for the operation.
op: stands for one of the previously mentioned=20
(): surround equations which are resolved by the=20
[]: surrounds optional arguments
stack: a 16bit stack number, positive for agent,=20
negative for group.
size: a 32bit number.
flag: a 16bit number, non-zero if true.

Format of Instruction Descriptions
The first line is what the vmcode assembler would expect, or=20
what a disassembler would generate.  The second line is the=20
resultant vmcode. The third line is the affect of the=20
instruction on the stack, to the left of the =3D> is before the=20
operation, to the right is after the operation. The rightmost=20
item on either side of the =3D> is the topmost item on the=20
stack.  Items on either side of the =3D> are assumed to be on=20
the agents current stack.  The last line is a description of=20
the operation.

xx #
-- =3D> #
Pushes the number onto the stack.  If a type is not defined=20
then 16bit is assumed.

xx 8bit data
-- =3D> data 8bit
Pushes the string plus the string length onto the stack.

{ ... }
xx 8bit data
-- =3D> data 8bit
Creates a string containing all data between the braces and=20
places it on the stack along with the resultant length.

[ ... ]
xx 32bit data
-- =3D> data 32bit
Creates a block containing all data between the braces and=20
places it on the stack along with the resultant length.

# =3D> --
Pops the number, string, or block off the stack.
NOTE: Should this be replaced by a simple #of bytes GetSP -=20
SetSP macro?

add.$ sub.$ mul.$ div.$
a b =3D> (a op b)
Takes the two top numbers off of the stack, and applies the=20
operation leaving the result on the stack.

a =3D> b
Converts types.  Does not apply to strings or blocks.

S =3D> --
Notifies kernel tool box has been renamed.

-- =3D> --
Notifies kernel that tool box cannot be migrated from this=20

-- =3D> --
Notifies kernel that tool box can be migrated to different=20

-- =3D> --
Notifies kernel that this tool box may not be duplicated.

-- =3D> --
Notifies kernel that this tool box may be duplicated.

stack =3D> flag
Creates a code segment from a data segment.  The code segment=20
is expected to be intermediate byte code.  The segment is=20
scanned for validity, and compiled to binary if deemed=20

a =3D> --
Sets the primary stack to use stack a.

a =3D> --
Sets the path stack to use stack a.  Stack a must be an agent=20

-- =3D> a
Gives the current primary stack number.

-- =3D> a
Gives the current path stack number.

a =3D> --
Sets the stack pointer for the primary stack.

-- =3D> a=20
Returns the value of the stack pointer for the primary stack=20
before the instruction.

a =3D> --
Sets the stack pointer for the path stack.

-- =3D> a
Returns the value of the stack pointer for the path stack.

stack size =3D> flag
Creates a new stack of size, returns true if successful.

stack =3D> flag
Deletes stack and returns true if successful.

size =3D> flag
Resizes stack and returns true if successful.

NewAgent (Fork?)
size =3D> flag
Creates a new agent in current tool box.  Returns false if=20
cannot complete.  Size bytes are pulled off the primary stack=20
of the creating agent and placed in agents primary stack. =20
Execution for both continues at the next instruction.

-- =3D> --
Terminates execution of current agent.  Cleans up stacks.

>[.$] <[.$] =3D[.$] >=3D[.$] <=3D[.$]=20
# # =3D> flag
Compares two values leaving a flag of 0 if false, non-zero if=20

-- =3D> --
Locks kernel from compiling any more machine dependent code.

32bit =3D> #
Has the CPU do an In from the port.  Can only be compiled=20
before a LockUp command is executed.  Code in which PortIn are=20
automatically restricted from migrating from their workspace.

32bit xx =3D> --
Has the CPU do an Out to the indicated port. Can only be=20
compiled before a LockUp command is executed.  Code in which=20
PortIn are automatically restricted from migrating from their=20

a =3D> --
Does an up on semaphore indexed by a.  If agents are blocked=20
on that semaphore then one will be unblocked.  All semaphores=20
are in stack -2.

a =3D> --
Does a down on a semaphore.  If the semaphore is smaller than=20
1 then the agent will block until another agent does an up. =20
All semaphores are in stack -2.

32bit a =3D> --
Initializes a semaphore.

-- =3D> --
Parses path stack for a path to vector execution to.

-- =3D> --
Indicates to compiler or interpreter a tool entry point.

{complex} =3D> --
Parses primary stack for path to vector execution to.

S flag =3D> --
If flag is true vector execution to path in string S.

{complex} =3D> --
Parses primary stack to path to vector execution to.  Saves=20
return path in path stack.

size offset source_stack =3D> data
Copies size data starting from offset in source_stack onto top=20
of current stack.

data size offset dest_stack =3D> --
Moves size bytes off current stack into dest_stack staring at=20

# =3D> # #
Duplicates top item on stack, including strings and blocks.

Rot, Pick?
How to implement these, do we need these?

I know I=92m missing instructions, the previous ones should give=20
you a flavor for the kind of virtual machine and instruction=20
set I would like.  Please let me know how big the instruction=20
set should get (# of instructions), what instructions should=20
be added, what we can reduce/get rid of, etc.

Program Flow Control
Herein lays a major obstacle to migrating agents.  Because all=20
the stacks have consistent representation between machines,=20
stacks can simply be bundled up and sent.  Because our return=20
path is a stack, and exhibits this same behavior, it to can be=20

Security is our first issue.  How can we maintain secure=20
communication between tool boxes.  First off, we can limit=20
migration of tool boxes to certain domains to minimize their=20
exposure.  Secondly, we allow tool boxes to control the gates=20
for agents entering and exiting.

All agents entering a tool box must first execute tool 1, mark=20
0.  This tool is the incoming gatekeeper code.  All agents=20
leaving a tool box are vectored likewise through tool 1, mark=20
2.  If no security is desired the code here can simply pass=20
the agent along.  However at this point code can encrypt all=20
outgoing agents and their data, and verify all incoming agents=20
validity.  The level of security implemented is up to the=20

Return information is stored on the path stack in the=20
following format.  Tool box names are stored as strings; data,=20
8 bit length, and the vmcode byte code for push string.  Tool=20
numbers are stored as 32 bit tool numbers and the byte code=20
for push 32 bit.  Mark numbers are stored as 16 bit numbers=20
and the byte code for push 16 bit.

When a return is executed, the path is parsed to determine how=20
to vector the agent.

When a call is executed the active stack is parsed to=20
determine how to vector the agent.  Immediately the return=20
mark is placed on the path stack.  If an agent moves between=20
tools, then the calling tool number is also placed in the=20
path.  If the agent moves between tool boxes then the agent is=20
first directed to the gate for the tool box.  The gate will=20
then issue a goto to vector the agent to the remote tool box. =20
Before the agent is relocated the tool box name is placed in=20
the path.

When a goto is issued that causes the agent to move between=20
tools, the path is evaluated to determine if the top entry on=20
path is tool number or tool box name.  If not then the sending=20
tool number is saved in the path.

When a goto is issued that causes the agent to move between=20
tool boxes, the path is evaluated to determine if the top=20
entry is a tool box name.  If not then the sending tool box=20
name is saved in the path.

This is only a partial explanation.  Because I would like to=20
encourage more discussion early on I am sending this out=20
incomplete.  By next week the full text should be ready.

Here are some of my questions
How many tools/stacks per tool box should we have as a limit?=20
2^15 or 2^31?  The smaller lines up nicely with current=20
segmentation schemes of the 486.  The larger allows us to=20
manage much larger numbers of objects per tool box.

How many more/what kind access instructions should we have for=20
moving data to/from stacks?

Should we have more fundamental types of stacks (a floating=20
point one, a loop counter one, alternate one, etc).

Should the stack pointer be global or local?  If it=92s local=20
then everyone has to have their own copy of SP=92s to every=20
global stack.  In addition if we implement automatic stack=20
contraction/expansion then we would have to reconcile the=20
differences between all the agents global SP=92s.  If it=92s=20
global than how do we handle the case of two agents accessing=20
the same stack simultaneously (i.e. a two CPU system with=20
shared memory).  How do we manipulate the SP then?

How do we limit access to global stacks?  Should we make the=20
tools manage that themselves using semaphores, or provide=20
primitives for locking the stacks themselves?  Any other=20

How do we represent the agents vmcode IP when moving between=20
workspaces?  The crude get it done way is to count the number=20
of vmcode instructions up till the point execution was=20
stopped.  Is there a faster way to determine this?  A better=20
way, maybe using predefined stall points indicated by marks? =20

In what order should we push arrays onto the stack?

What language should we program the kernel in?

What language should we select for our high level language? =20
Or should we create our own?

It is a big leap of faith to jump from a simple RISC virtual=20
machine (TAOS) to a Forth like machine.  What are arguments=20
for and against this?

What should the format of floating point numbers be, 32, 64,=20
80 bit?

Longer Term Plans
Here is a little glimpse into how I would like for things to=20
turn out.  One camp of developers will have full blown=20
development systems to write code in.  If our system becomes=20
accepted these will be developed in time, at great expense. =20
For now though, we do not have the time or money.

The other camp will write interactively like Forth.  This is=20
the one that can be implemented relatively easily and quickly. =20
This also is closer to my idea of organic computing.

I would like for the programmer to =93exist=94 within a tool box. =20
The programmer would build from the inside out, testing code=20
as he/she went along.  While the programmer was writing code=20
agents could enter into the tool box and exit, or resident=20
agents could continue executing the code they were working on. =20

I believe we could write a simple debug tool that would sit=20
inside the tool box and facilitate this; keeping track of tool=20
names and mark names, sending and receiving information to an=20
assembler, interacting with the programmers interface be it a=20
GUI or simple command line, etc.

How about a simple tool that sits in your tool box while you=20
are developing it that just provides a command line interface. =20
The tool could be called the basement, because it lets you=20
work in the lowest part of the OS, giving you an unadulterated=20
view and control of virtual machine.

A little further down the line I hope to alleviate device=20
drivers.  Each device would come with it=92s open tool box, that=20
would be found by the kernel when starting up and executed. =20
This is very similar to work being done with Open Boot (is=20
that the right name?) that embeds Forth like code that=20
initializes and provides an interface to devices that plug=20
into the computer.

Just a few ideas for now.

I wrote this to get the ball rolling.  I expect for there to=20
be MANY changes to what I have just proposed.  Please voice=20
any and all questions and comments.  An open dialog is what we=20
need to hash out these ideas.

Hope to hear from you soon,