FMF:The HVM, From the Bottom Up

From OHRRPGCE-Wiki
Jump to navigation Jump to search
Mobile-phone.png
This article is about the OHRRPGCE FMF project, which is an alternate implementation of the OHRRPGCE for Java mobile phones. Technical implementation details discussed here should not be confused with those of the RPG format


This page describes the workings of the HVM in relation to the bytecodes it processes. For your reference, here is a copy of the data structures diagram:


Fmf hvm data structures.png


We will now list some critical operations performed by the HVM and how it uses the data structures to track these operations. Before reading this section, you should probably read the syntax dictionary, so that you have an idea of Henceforth's intended functionality. Otherwise, this section might not make a lot of sense.

For these charts, the green background is default. If the background is red, that means the chart only applies to a full-blown user script. A subroutine will fail, since it doesn't have the requisite data structures allocated. We check if a subroutine is being run by seeing if the PC Stack's size is greater than 1. Checking another stack's size to determine this is non-standard.

For brevity, when we say "advance the PC by one" (or, equivalently, PC += 1), we are referring to a "smart increment". For multi-word bytecodes, this means we have to increment by the length of the bytecode, not simply by 1.


Lifecycle
Trigger

After the HVM initializes, and before it starts running scripts.

Response
  • Create a new entry in the script cache
    • New Name = "root"
    • ID = -1
    • Pinned = true
    • In Use = 1
    • Bytecode = new Array[]
    • DoTable = new Array()
    • Optionally, load into "Bytecode" the contents of "global.HF", or read directly from user input.
    • Note: We do not create an entry in the script name multiplexer; root is effectively read-only.
  • Make a new Script UDT:
    • Local Stack = new Stack()
    • Program Counter Stack = new Stack(0)
    • Script Cache Pointer Stack = new Stack(Script Cache Reference)
    • Do/If Stack Stack = new Stack(new Stack())
    • Local Variable Hash Stack = new Stack(new Hash())
    • ID = -1
    • Parent Script Pointer = -1
    • Local Variable Array = new Array()
    • String Table = new Array()
    • Local Function Start Indices = new Array()
    • Return Address Stack = new Stack()
    • Note: If the root script attempts to use script-only data structures (e.g., local parameters), the behavior of the HVM is undefined.
  • Make a new entry in the Running Scripts table:
    • Script = our new Script UDT
    • Wait = false
    • Wait On = 0
  • Insert this new Running Scripts entry at index zero (or as the head node, if it's a linked list). This spot is reserved for the root script.


Functional Primitives
Trigger

dup
swap
drop
over
rot
add
sub
mult
div
random
b_xor
b_and
eq
lt
not
and
xor
b_not
b_or
or

Response
  • Pop a pre-defined number of arguments from the local stack.
  • Perform the desired math/logic
  • Push the resultant, if applicable, to the local stack.


Numbers
Trigger

The HVM encounters a "short integer" or a "full-width integer"

Response

Push that number to the "local stack" of the currently-running script.


Script-Local Subroutines
Trigger

The HVM encounters a "define local script" with id x.

Response
  • In the "local function start indices" array, at index x, insert (PC+1)
  • Advance the PC without executing commands until you reach an "end define".
  • Advance the PC by one.
Trigger

The HVM encounters a "call local script" with id x.

Response
  • Push (PC+1) to the "return address stack"
  • Set the PC to the "local function start indices" array value at index x.
Trigger

The HVM encounters an "end define" and the "return address stack" is not empty.

Response
  • Pop the "return address stack" and set the PC to this value.


Global Variables
Trigger

The HVM encounters a "retrieve global variable" with id x.

Response
  • If x is equal to the magic number (0x3FF for 10-bit ids), then pop the stack and store that value in x.
  • Push the value of that variable from the Global Variable Table.
Trigger

The HVM encounters a "set global variable" with id x.

Response
  • Pop the stack.
  • If x is equal to the magic number (0x3FF for 10-bit ids), then pop the stack and store that value in x.
  • Store the popped value at location x in the Global Variable Table.


Call Subroutines
Trigger

The HVM, while running a script with id y in the Running Scripts Table, encounters a "call user-defined function by id" with id x.

Response
  • Retrieve a new entry in the script cache
    • Get entry x from the script lookup table; if "Cache" == -1, then load the source from the appropriate "Chunk" and add a new entry to the script_cache:
      • New Name = null
      • ID = x
      • Pinned = false
      • In Use = 0
      • Bytecode = loadFromChunk()
      • Do Table = new Array[do_udt]
    • For the appropriate script cache entry, set InUse = InUse + 1
  • Make a new Script UDT:
    • Local Stack = new Stack[]
    • Program Counter Stack = new Stack(0)
    • Script Cache Pointer = new Stack(id of the script cache entry we retrieved)
    • Do/If Stack Stack = new Stack(new Stack())
    • Local Variable Hash Stack = new Stack(new Hash{})
    • *Note: The HVM can leave "optional" components like this un-initialized until they are actually used.
    • ID = x
    • Parent Script Pointer = y
    • Local Variable Array = new Array[max+1]
    • String Table = new Array[]*
    • Local Function Start Indices = new Array[]*
    • Return Address Stack = new Stack[]
    • Set Local Variable Array to -1 at index 0
  • Make a new entry in the Running Scripts table:
    • Script = our new Script UDT
    • Wait = false
    • Wait On = 0
  • Insert this new Running Scripts entry one step ahead of script Y; this ensures it will be run next.
  • Set script Y's "Wait" flag to "true".
Trigger

The HVM, while running a script with id y in the Running Scripts Table, encounters a "call named subroutine" with name exp.

Response
  • Retrieve a new entry in the script cache
    • Get entry exp from the script name multiplexer
    • Use exp to get the appropriate script cache entry; set its InUse = InUse + 1
  • In the current script UDT:
    • Program Counter Stack.push(0)
    • Script Cache Pointer.push(id of the script cache entry we retrieved)
    • Do/If Stack Stack.push(new Stack())
    • Local Variable Hash Stack.push(new Hash{})
    • *Note: The HVM can leave "optional" components like this un-initialized until they are actually used.
Trigger

The HVM encounters a "hamsterspeak api call" with id x.

Response
  • Pop the agreed-upon number of arguments
  • Call API function x with said arguments
  • Push the return variable, if applicable


Local Variables
Trigger

The HVM encounters a "push local variable by id" with id x.

Response
  • Pop the stack
  • If x is equal to the magic number (0x1FF for 9-bit ids), then pop the stack and store that value in x. Subtract 1 from x.
  • Store the popped value in script_udt.local_variable_array[x+1]
Trigger

The HVM encounters a "pop local variable by id" with id x.

Response
  • If x is equal to the magic number (0x1FF for 9-bit ids), then pop the stack and store that value in x. Subtract 1 from x.
  • Retrieve the value in script_udt.local_variable_array[x+1]
  • Push the retrieved value to the stack
Trigger

The HVM encounters a "pop local variable by name" with name test.

Response
  • Pop the stack
  • Create a key in script_udt.local_variable_hash_stack.top() for "test"
  • Store the popped value in script_udt.local_variable_hash_stack.top(){"test"}
Trigger

The HVM encounters a "push local variable by name" with name test.

Response
  • If the local variable hash stack's top entry has no key at "test", push -1 and return
  • Retrieve the value in script_udt.local_variable_hash_stack.top(){"test"}
  • Push the retrieved value to the stack


If Statements
Trigger

The HVM encounters an "if_start"

Response
  • Push a new do_udt with startIndex = PC to the do/if stack stack.top()
  • Retrieve the script cache entry associated with script_cache_pointer_stack.top(). If this contains a do_udt with the same startIndex, set the elseIndex and endIndex of this do_udt to the values in the cached entry. Else, set them to -1. Do the same for the endIndex.
  • Pop the stack. If this value is not 0, increment the PC. If 0, then set the PC to the elseIndex. (If the elseIndex is -1, scan & dispose bytecodes until the else_start command is found, and then update the script_cache.) Increment the PC.
Trigger

The HVM encounters an "if_end"

Response
  • Pop the do/if stack stack.top(). If that entry's endIndex is -1, set endIndex = PC and update the script_cache.
Trigger

The HVM encounters an "else_start" (This will only happen if the else branch is not taken)

Response
  • Pop the do/if stack stack.top(). If that entry's elseIndex is -1, set elseIndex = PC and update the script_cache.
  • Set the PC = endIndex. (If endIndex is -1, scan & dispose bytecodes until the if_end command is found, and then update the script cache.) Increment the PC


Loops & Flow Control
Trigger

The HVM encounters a "do_start"

Response
  • Push a new do_udt with startIndex = PC to the do/if stack stack.top()
  • If the script_cache entry for this script contains a do_udt with the same startIndex, set the endIndex of this do_udt to that value. Else, set it to -1. Set elseIndex = -2.
  • Increment the PC
Trigger

The HVM encounters a "do_end"

Response
  • Pop the do/if stack stack.top(). If that entry's endIndex is -1, set endIndex = PC and update the script_cache.
Trigger

The HVM encounters a "break" or "break_x"

Response
  • Set x = 1 if this is a "break" command, or x = stack.pop() if this is "break_x"
  • while x > 0, do the following:
  • Pop the do/if stack stack.top() until you encounter a do_udt with elseIndex == -2. Pop that as well.
  • Set PC = endIndex of this do_udt. (If endIndex is -1, scan & discard bytecodes until a do_end is found. Then update the script_cache.) Increment the PC.
  • Decrement x
Trigger

The HVM encounters a "continue" or "continue_x"

Response
  • Set x = 1 if this is a "continue" command, or x = stack.pop() if this is "continue_x"
  • Pop the do/if stack stack.top() until you have removed x do_udts with elseIndex == -2. Push the last entry back to the do/if stack stack.top().
  • Set PC = do/if_stack_stack.top().top().startIndex + 1


Subroutines & Strings
Trigger

The HVM encounters a "string define" with string test string.

Response
  • Push test string to the end of the string table
Trigger

The HVM encounters a "begin define" with string my_function.

Response
  • Look for my_function in the script name multiplexer. If it's not there, add it and set definitions = new Array[int]
  • Create a new entry in the script_cache:
    • New Name = "my_function"
    • ID = -1
    • Pinned = true
    • In Use = 0
    • Do Table = new Array[do_UDT]
  • Scan all bytecodes up to and including an end_define into the "Bytecode" field for this new script cache entry.
  • Push into script_name_multiplexer{"my_function"} a reference to the script cache entry you just created.
Trigger

The HVM encounters an "un-define named subroutine" with string my_function.

Response
  • Pop the last definition from the script_name_multiplexer{"my_function"} into variable x.
  • If script_name_multiplexer{"my_function"} has no more definitions, remove it.
  • Set script_cache[x] to be a null row; remove it from the array if you can do so without introducing inconsistencies. At the very least, de-allocate all its data.
Trigger

The HVM encounters an "end_define", or a "return" command in a user-defined script.

Response
  • If there is a return value in script_udt.local_variable_array[0], push it to the parent script's stack. If not, push the last calculated response ($_)
  • Set this script's parent's entry in the running scripts table to Wait = false
  • Remove this script from the running scripts table.
  • Decrement the in_use field for the script_cache entry for this script_udt
  • Delete this script_udt
Trigger

The HVM encounters an "end_define" in a subroutine.

Response
  • Pop the script cache pointer stack, set the the corresponding script cache's entry's In Use to In Use -1
  • Pop the Program Counter Stack
  • Pop the Do/If Stack
  • Pop the Local Variable Hash Stack