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.