Skip to content

Code Instructions

Code instructions are made up of an operator and operands. For example:

iload #0

The above shows the operator to load an integer. This particular operator expects only one operand and in this instance is the immediate value '0'.

Instructions usually denote the type of data it is operating upon (integer, boolean, etc.) with a prefix of the type. For example:

iload #0                // An integer to be loaded onto the stack
bload #true             // A boolean value to be loaded onto the stack
sstore localStringValue // The string on the stack to be stored in the
                        // table declaration referenced by localStringValue

Instructions are not type safe. It is up to the programmer to ensure the code is made type safe. Poorly developed code will result in unpredictable results.

Instruction Structure

Instructions are usually structured in the following way:

{type}{action}

Type

The type can be one of the followings:

Type Description
b Boolean
by byte
d date/time
i integer
e set
f float
s string

Action

Depending on the type the action can be one of the following:

Action Description
load Load a value onto the stack
store Store a value in a program location. This could be in a local, or global entry.
return Return a frame off the stack returning to the current program counter location.
add Add a value to another value and store the result.
sub Subtract one value from another and store the result.
div Divide one value by another and store the result.
mul Multiply one value by another and store the result.
cvt Convert one value to another.
return Return a value from a method
or Perform a logical or binary or and store the result.
and Perform a logical or binary and and store the result.

Operands

Operands fall into the following types:

Immediate Operands

Immediate operands refer to constants or values that can be assessed at compile time. They are prefixed with a # unless it is a string which is automatically interpreted as a literal:

sload "This is a string"
byload #23

Registers

Macro has 12 registers that allow provide an alternative to storing temporary data in a table or on the stack and access to processor status values. For example:

loads "This is a string"
store r0
Name Description
r0 - r9 Registers that can hold any object
pc (r10) The program counter of the current instruction. Any changes made to this must be handled with care.
sp (r11) The stack pointer for the current stack. Any changes made to this must be handled with care.
ps (r12) The processor status. Any changes to this must be handles with care.

Processor Status

The process Status is an integer that indicates the status of the current processor thread. Each bit has a certain function:

Position Description
0 Floating Overflow has occurred in the last instruction
1 Divide By Zero has occurred in the last instruction
2 Zero result has occurred with the last instruction.
3 Null result has occurred with the last instruction.
4 Boolean

Referenced

Referenced operands refer to a position in a local table or to an external address. For example:

sload AReallyBigString
store r0

The above will load a string from a local reference (AReallyBigString) and store the value in the register r0. To access an external value the following modification to the reference can be applied:

sload G^AReallyBigString
store r0

In the above the reference to AReallyBigString will be searched for in referenced external libraries referred to in the import directives.

Indexed

Where appropriate it is possible to extend an operand by an index. For example:

byload bytearray[#0]
store r0

This will assume the bytearray is an array and load the element from it referred to by the immediate operand #0. The index can be any type of operand that returns a number for an index relevant to array is is referring to. This means only operands that result ina byte or int value can be used.

Deferred

An operand can sometimes point to the actual operand. For example:

sload (aStringReference)

Combinations

Combining instructions and operands is where the real power of Macro coding resides. Consider a basic instruction to add two integers together and store the result in another value:

iadd #2, #3, result

The basic iadd instruction combines one instruction and three operands together to achieve this process. However, it is possible to use a combination of operands and stack to push the result onto the stack for further use (if it not required to be stored). This would be achieved in the following way:

iadd #2, #3

By missing the third operand off it is assumed that the result should be pushed onto the stack. To complete the initial instruction using this approach:

iadd #2, #3
store result

This can be taken further so that none, some or all of the operands for an add action (or similar type of instruction) can be removed and the stack can be used. Taking the initial add instruction to have no operands the same process could be achieved in the following way:

iload #3
iload #2
iadd
store result

In this example the two values are popped off the stack added and the result is pushed onto the stack. Then the result is popped off the stack and stored in result.

Not all instructions provide this capability.

Fast Instructions

Some more common instructions have been combined into single instructions to save time. These are as follows:

Instruction Description
bload_n Load a boolean value where n can be true or false onto the stack.
byload_n Load a byte value where n can be 0 to 3 onto the stack.
iload_n Load an integer value where n can be 0 to 3 onto the stack

Conversion Instructions

These instructions convert one type to another. They take the form:

{base}cvt{destination}

For example:

icvtb

It is possible to convert types based on the table:

? bool (b) byte(by) int (i) date (d) set (e) float (f) string (s)
bool (b) . x . . . . x
byte (by) x . x . . . x
int (i) x x . x x x x
date (d) . . . . . . x
float (f) x x x . . . x
string (s) x x x x x x .

Conditional Instructions

It is possible to branch from one location to another based on that value on te top of the stack.

Instruction Description
ifeq If equal to zero then branch
ifne If not equal to zero then branch
ifgt If greater than zero then branch
iflt If less than zero then branch
ifge If greater than or equal to zero then branch
ifle If less than or equal to zero then branch
iftrue If true then branch
iffalse If false then branch
ifnull If there is a null value on the stack then branch
ifnotnull If there if not a null value on the stack then branch

Each on of the conditional instructions has a boolean push stack version. this is achieved by adding a b to the instruction. For example:

ifeqb

The above instruction pops the integer value from the stack and converts it to a boolean value (if value equals zero then push true else push false).

Unconditional Branching Instructions

Goto Instruction

To support this there is also the goto instruction that you can use to branch unconditionally to a position in the instructions. For example:

    ...
    load questions
    invokevirtual InitialiseLooping()
a10$:
    load questions
    invokevirtual Next()
    iffalse a20$
    load questions
    invokevirtual GetItem()
    store question
    load question
    invokevirtual Ask()
    goto a10$
a20$:
    ...

Call and Return

To support code that you want to repeat it is possible to call a method and return from it.

    ...
    load question
    call testQuestion(IQuestion)
    ...

    ...
.method testQuestion(IQuestion question)
    load question
    invokevirtual Ask()
    return
    ...

Gosub and Ret

It is possible to run a subroutine of code that is not a specially declared method, but simply referred to by a label in the gosub instruction:

    iload_0
    iload #1
    gosub 10$
    iload #2
    gosub 10$
    ireturn
10$:
    add
    ret

Whilst the example could be better, the benefit of this approach is that no call stack is created for what could be a repetitive task and therefore save time.

Transferring Data

The move instruction is one untyped instruction that allows a reference to be moved from one location to another.

    move #1, r0

The move instruction will take any operands that make sense to transfer a value from one location to another.

Accessing External Libraries

It is possible to access static and dynamic instances of classes and access its methods and properties through a set of instructions.

To create an instance of an object use the new instruction:

    ...
    sload #jsonString
    new IQuestion(string)
    store q1
    ...

To access a method in the object:

    ...
    sload #jsonString
    new IQuestion(string)
    store q1
    load q1
    invokeVirtual Ask()
    ...

To access a property it is possible to access the direct method if it known or the general property:

    ...
    sload #jsonString
    new IQuestion(string)
    store q1
    load q1
    invokeVirtual get_Label()
    ...

Most properties are accessible through a getter which is identified by the get_ prefix. Setting a property is achieved through the setter which is identified by the set_ prefix:

    ...
    sload #jsonString
    new IQuestion(string)
    store q1
    load q1
    load label1
    invokeVirtual set_Label(label)
    ...

Accessing static classes is also easy with Macro. These do no need to instantiated, but can simply be accessed directly:

new InterviewContext()
invokestatic InterviewState.set_Context(InterviewContext)
getstatic InterviewState.get_Context

Stack Management

Just in case you want to mange the stack independently of the rest of the instructions set you can also use the pop instruction to remove unwanted items from the stack.

Error Handling

It is possible to define error management using the pushh, throw and poph instructions. For example:

    ...
    pushh Exception,x30$ NullException,x40$
    load questions
    invokevirtual InitialiseLooping()
a10$:
    load questions
    invokevirtual Next()
    iffalse a20$
    load questions
    invokevirtual GetItem()
    store question
    load question
    invokevirtual Ask()
    goto a10$
a20$:
    ...
x30$:
    // general exception
    ...
x40$:
    // Null exception
    ...

The pushh instruction pushes a handler onto the handler stack. The handler stack operates as a cascading exception handler, so if the error is not picked up by the handler on top of the stack the next handler is used.

The throw instruction will cause an exception to be thrown and the handler stack to be accessed for in search for an exception handler that will support the error. If a handler is available then the stack will have the exception details on it if you wish to use them. For example:

10$:
    store error
    return

The above example assumes (as performed by the runtime error handling) that the error has been pushed onto the stack for use by the error handler.

The poph instruction will remove the handler off the top of the handler stack.

These three instructions allow the Macro Virtual Machine to mimic most language try, catch type error handlers.

Exceptions

It is possible that an instruction itself can cause an exception. If this happens it will trigger access to the handler stack. An exception is based on the following list:

Exception Description
Exception A general exception and a catchall.
NullException A null value was detected.
DivideByZero Division by zero detected.

More exceptions will be made available over time to support the standard libraries.

Process Control

The Macro Virtual Machine includes the ability to pass control back to the calling environment. This can be achieved through the Show and Ask methods of the Interview Models Standard Library. But it is also possible to pass back control using the wait instruction:

wait [ operand ]

The operand can be any operand which is passed back to the calling environment. It is up to the calling environment to expect and process it.

Threads

There are a set of instructions that make it possible to create threads for asynchronous programming. Threads can be created using the follwoing approach:

.method void Test()
.table
    Thread threada
.code
    thread ThreadTest, threada
    start threada
    return

.method void ThreadTest()
.code
    load #0, r0
10$:
    add #1, r0, r0
    sub r0, #10
    bneq 10$
    return

In the above example a thread is created and started. To stop a thread before it is completed you can use the kill instruction.

.method void Test()
.table
    Thread threada
.code
    thread ThreadTest, threada
    start threada
    kill threada
    return

.method void ThreadTest()
.code
    load #0, r0
10$:
    add #1, r0, r0
    sub r0, #10000
    bneq 10$
    return

To wait for a thread to complete you can use the join instruction.

.method void Test()
.table
    Thread threada
.code
    thread ThreadTest, threada
    start threada
    join threada
    return

.method void ThreadTest()
.code
    load #0, r0
10$:
    add #1, r0, r0
    sub r0, #10
    bneq 10$
    return

To paused a thread the wait instruction can be used, specifying an operand that refers to the number of milliseconds to pause.

.method void Test()
.table
    Thread threada
.code
    thread ThreadTest, threada
    start threada
    return

.method void ThreadTest()
.code
    load #0, r0
10$:
    add #1, r0, r0
    sub r0, #10
    wait #30000
    bneq 10$
    return

To support multi threaded code it is possible to queue threads up to run the same code using the lock and unlock instructions.

Locking an object will add it to a thread watchlist and any other threads attemping to lock the same object will placed into a wait state until the object is freed up to be locked again. Unlocking an object will make available for the next thread to lock and continue with the code.