Code Instructions
Code instructions are made up of an operator and operands. For example:
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:
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
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
For example:
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:
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:
Call and Return
To support code that you want to repeat it is possible to call a method and return from it.
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:
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.
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:
To access a method in the object:
To access a property it is possible to access the direct method if it known or the general property:
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:
Accessing static classes is also easy with Macro. These do no need to instantiated, but can simply be accessed directly:
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:
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:
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.
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:
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:
In the above example a thread is created and started. To stop a thread before it is completed you can use the kill
instruction.
To wait for a thread to complete you can use the join
instruction.
To paused a thread the wait
instruction can be used, specifying an operand that refers to the number of milliseconds to pause.
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.
Last updated