Table of Contents
This document describes phantom virtual machine as implemented at this moment, 23 February 2016.
This article is a bit outdated, please refer to Byte Code section of online documentation for latest information: https://phantomdox.readthedocs.io/en/latest/#byte-code-reference
Phantom virtual machine is a classical stack-based thing. Four stacks exist. See VmStacks for details.
Phantom is a strictly object-oriented environment. It means that no global code or data exist. Any code is a method of some class and is executed in object-related environment.
No “process” thing exists in Phantom, no process globals and process state exist therefore.
There are threads, however, and technically it is possible to associate some data with current thread. No corresponding mechanism exists at this time, though.
Thread can be used to reach outside – it can provide executing objects with links to other system resources. No global way to get them exists, so that one can create a thread which will receive partial access to system resources or will get some special/individual replacements for them.
The only global stuff accessible to any code is classes of the most general things: integer, string, code object, interface object and object of class “class”. The last one is class for objects, representing class. (It can be used to create a new class in runtime if future versions.)
There are two kinds of classes in phantom: general and internal ones. Objects of general classes contain object references only. Internal class objects can hold some other kind of value, and their methods are implemented in kernel.
Here are virtual machine instructions described on assembler (phantasm) language level. If you need more details than I give here, look into the interpreter code, please.
No operation. This operation does completely nothing.
debug; // just print stacks debug “we’re busted”; // show message too debug 33; // no meaning yet debug 33 “we’re busted”; // together
Prints string argument (if any), and top values of integer and object stack. Integer argument (mode) is one byte and upper bit is used internally, so don’t pass anything > 0x7F or < 0 – will be stripped.
- Bit 0 (& 0x01) of mode byte turns on debug instruction printing.
- Bit 1 (& 0x02) turns it off.
jmp label; // code to jump around label:
const 10; loop: // do something 10 times djnz loop;
Decrement top of int stack. If top of int stack is not zero – jump to label.
NB! This operation does not pop integer stack!
repeat: // calculate while condition jz done; // loop body jmp repeat; done:
While-style loop operator. Jump if top of int stack is zero.
NB! This operation does pop integer stack!
switch shift divisor label…
// calculate expression on int stack switch 0 1 case1 case2 case3; // here we’ll come on default (fall through) jmp out; case1: // if expression was 0 we’ll come here jmp out; case2: // if expression was 1 we’ll come here jmp out; case3: // if expression was 2 we’ll come here jmp out; out:
Top of int stack (popped) is diminished by shift (signed), divided by divisor (unsigned) and is used as offset into the jump address table. If result is out of bounds, operator falls through, else jumps to selected address.
Top of object stack is popped and pushed on caller’s stack. If stack is empty null object is pushed to caller. All the exception catchers pushed in this call are discarded.
call methodIndex numberOfArgs
call 0 2;
Pops numberOfArgs arguments from object stack, than pops object to call method for.
Calls selected method passing given number of args. Top of integer stack of called method will have number of arguments passed. After the return exactly one object (possibly null) will be on the object stack.
static_invoke methodIndex numberOfArgs
Pops numberOfArgs arguments from object stack, than pops object to call method for, then pops class to call method in. NB! Class is not checked to be related with this that was passed. This supposed to be used for constructor calls.
Executes this (and only this!) object’s embedded method. NB – it is not possible to execute sys for other object than this because it will render its specialness visible from outside.
This operation is valid for a very limited number of classes.
Nothing can be guaranteed about sys. As for now all of them return something, but it is not enforced by interpreter. Though, for normal operation sys must return some object or throw an exception.
It is possible to pass parameters to sys on int stack and receive some return there as well, which is differs from call, which passes data on object stack only. That is so because sys does not create a call frame and thus all the current stack data is available to its code.
Generally, sys is used as the only content of internal class methods.
Current implementation of sys is restartable: if sys is interrupted (snapshot happens ans OS is rebooted), it will be run once again. Note that for that to be possible, C code implementing sys must be running between vm_unlock_persistent_memory() and vm_lock_persistent_memory() calls.
const «arg val»; const «arg val»; const 2; // 2 args // code to bring this object const «toString»; dynamic_invoke<nowiki>;</nowiki>
Dynamically invoke method by name.
// push object to cast // push tagret type (class reference) cast // object reference now refers to specified class's interface
Pops target type from stack, pops object, checks that it has type is among interfaces or parents, pushes object with new interface.
os dup, is dup
os dup; // now we have one more whatever it was
Duplicate stack top – either object or int.
os drop, is drop
call 0 0; os drop; // discard return value
Delete stack top.
os pull displacement
os pull 2;
Copy object displacement steps down from top of object stack on top of it. Pull 0 is equal to dup.
Load object reference from this object slot (field), push to top of stack.
Pop object reference, store to selected slot (field) of this object.
Load object from given position of object stack, push to top of stack.
Pop object, store to selected position of object stack. These two are for the local variables access.
Create a new object of given (stack top) class. String, in this case. No constructor called automatically. Compiler must generate code to call constructor.
Create a copy of top stack object. Same class, same slot values. A call to method 3?
Not implemented and should not be.
Pop and compare two objects on object stack. If they are the same, push non-zero on integer stack, else push zero.
Pop and compare two objects on object stack. If they are the same, push zero on integer stack, else push non-zero.
Pop object. If it is a null, push non-zero on integer stack, else push zero.
push catcher label
summon “internal.class.string”; push catcher string_thrown; // code ret; string_thrown: // on exception of string type we’ll get here with thrown object on stack top // do cleanup ret;
Top of stack must contain class object. Exceptions of that class will be catched here and control will be passed to a label if such exception is thrown. Thrown object will be on stack top in this case. In general no other assumptions about stack state can be done.
Last pushed catcher will be deleted. Note that all method's catchers will be automatically popped on return.
const “we have a problem here”; throw;
Object on top of stack is thrown down. If stack is empty - will throw special system-wide object (null?), if already on top of call stack - will kill current thread in a bad way.
const “hi there”; // top of '''object''' stack is string object now const 32656; // top of '''integer''' stack has 32656 now
Push constant on stack top. Special shortcuts for 0 and 1 exist. Strings are Unicode UTF-8. MUST BE PLAIN 2-BYTE UNICODE?
Load constant number 22 from constants pool. Constants pool is loaded from class file.
summon this // this object is on stack top now
Puts this object reference on stack.
summon thread // current thread object is on stack top now
Puts current thread object reference on stack.
summon null // stack top has null object
Put null object on stack.
summon “internal.class.class”; summon “internal.class.int”; summon “internal.class.string”; summon “internal.class.interface”; summon “internal.class.code”;
Push corresponding class object on stack.
NB!! Define implementation! Must go through some thread specific class loader?
TODO: must reference constant pool instead. Constant pool object must be resolved at load time and contain class ref.
NB! Integer stack supports long/float/double operations too.
prefix_long, prefix_float, prefix_double
opcode_prefix_double<nowiki>;</nowiki> i2o; // '''object''' stack now has .internal.double object
These prefixes change default integer type for integer stack operations to corresponding type. Next integer stack operation is performed for given type. If type is 64-bit one (long or double), 2 stack slots are used for each operand or result. Note though, that all compare operations return int no matter which prefix you use.
fromi, froml, fromf, fromd
const 10<nowiki>;</nowiki> prefix_float; fromi<nowiki>;</nowiki> // '''int''' stack now has float value on top
Convert int stack value from given type to current (int by default, or as set by prefix).
const 10; i2o; // '''object''' stack now has integer object o2i; // value of integer object moved to '''integer''' stack
These operations can be used to move data between integer and object stacks.
NB! What exception is thrown if it is not int?
(Not implemented!) Load object from this object slot, push to top of integer stack.
(Not implemented!) Pop value from integer stack, store to selected slot of this object.
const 10; const 5; isum; // int stack has 15 on top const 10; imul; // int stack has 150 on top
Addition and multiplication.
isubul, isublu, idivul, idivlu
const 10; const 5;// top is 5 isubul;// upper from lower, 5 – 10 // here we have -5 const 10; const 5;// top is 5 isublu;// lower from upper, 10 - 5 // here we have 5 const 5; const 10;// top is 10 idivul// = 10 / 5 const 10; const 5;// top is 5 idivlu // = 10 / 5
Integer subtraction and division.
ior, iand, ixor, inot
Bit wise operations on integer stack top.
||, &&, !
logical operations on integer stack top.
The 'lock/unlock' opcodes intended to implement JVM like synchronization. Not implemented.
VM Bootstrap - how VM is created on first run of Phantom instance, where no persistent memory state exists.
ObjectOrdinals - .internal.object ordinals