Skip to content

ByteCode

Dmitry Zavalishin edited this page Nov 4, 2019 · 14 revisions

Table of Contents

Phantom bytecode and virtual machine definition

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

Structure

Phantom virtual machine is a classical stack-based thing. Four stacks exist. See VmStacks for details.

Object model

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.

Operations

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.

Strange stuff

nop

No operation. This operation does completely nothing.

debug

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.

Flow control

jmp

jmp label;
// code to jump around
label:

Unconditional jump.

djnz

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!

jz

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.

ret

ret;

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.

sys syscall_number

sys 0;

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.

See alsoBlockingSyscalls

dynamic_invoke

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.

Cast

// 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.

Stack manipulations

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 slot_num

load 3;

Load object reference from this object slot (field), push to top of stack.

save slot_num

save 3;

Pop object reference, store to selected slot (field) of this object.

get absolute_stack_position

get 0;

Load object from given position of object stack, push to top of stack.

set absolute_stack_position

set 0;

Pop object, store to selected position of object stack. These two are for the local variables access.

new

summon_string_class
new;

Create a new object of given (stack top) class. String, in this case. No constructor called automatically. Compiler must generate code to call constructor.

copy

copy;

Create a copy of top stack object. Same class, same slot values. A call to method 3?

Not implemented and should not be.

os eq

os eq;

Pop and compare two objects on object stack. If they are the same, push non-zero on integer stack, else push zero.

os neq

os neq;

Pop and compare two objects on object stack. If they are the same, push zero on integer stack, else push non-zero.

os isnull

os isnull;

Pop object. If it is a null, push non-zero on integer stack, else push zero.

Exceptions

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.

pop catcher

pop catcher;

Last pushed catcher will be deleted. Note that all method's catchers will be automatically popped on return.

throw

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.

Constants

const

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?

const_pool

const_pool 22;

Load constant number 22 from constants pool. Constants pool is loaded from class file.

Summoning

summon this

summon this
// this object is on stack top now

Puts this object reference on stack.

summon thread

summon thread
// current thread object is on stack top now

Puts current thread object reference on stack.

summon null

summon null
// stack top has null object

Put null object on stack.

summon class_name

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.

Integer stack operations

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).

i2o, o2i

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?

iload slot_num

iload 3;

(Not implemented!) Load object from this object slot, push to top of integer stack.

isave slot_num

isave 3;

(Not implemented!) Pop value from integer stack, store to selected slot of this object.

isum, imul

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.

Not yet described codes

The 'lock/unlock' opcodes intended to implement JVM like synchronization. Not implemented.

See also

VM Bootstrap - how VM is created on first run of Phantom instance, where no persistent memory state exists.

ObjectOrdinals - .internal.object ordinals

You can’t perform that action at this time.