-
Notifications
You must be signed in to change notification settings - Fork 0
Bytecode
The GameMaker: Studio virtual machine interprets bytecode which represents the user defined portion of game logic. This page is dedicated to formally documenting the exact format of this bytecode.
The easiest way to read GM:S bytecode is to run a game using the newer debugger (post-Early Access program commencing). In the Source pane, right click a tab and select Display VMASM to swap to bytecode view.
There are a few things to note about this display;
- The view is divided into three columns. From left to right these are; the instruction addresses, the byte representation of instructions (what the machine can see) and the textual representation of instructions (easier for a human to read).
- The byte representation of instructions are in little-endian 32-bit blocks of little-endian byte order. That is; when reading blocks, read them from left to right, however induvidual bytes in blocks should be read from right to left. This leads to a degree of confusion when identifying opcodes.
There is an implicit ret inserted by the runtime after the last instruction of a script. This is not shown in the GM:S debugger. As a result, some jumps may appear to jump outside the disassembled area, when they are actually jumping to this implicit instruction.
An analysis of the GM:S Bytecode reveals that contrary to what people would believe, a number of passes touch the bytecode before it is finalized. These can be responsible for VMASM which differs slightly to the original code. The passes discovered are as follows;
-
Constant propagation: operations on constants are folded into single constants, ie
3 + 3
becomes6
at compile time.
Most instructions will encode one or more types for their operation. The following table shows all the types GM:S will correctly decode from instructions and their corresponding byte representation.
Byte | Internal | Symbol | Type |
---|---|---|---|
0x00 | eVMT_Double | d | 64-bit double |
0x01 | eVMT_Float | f | 32-bit float |
0x02 | eVMT_Int | i | 32-bit signed integer |
0x03 | eVMT_Long | l | 64-bit signed long |
0x04 | eVMT_Bool | b | bool |
0x05 | eVMT_Variable | v | variable reference |
0x06 | eVMT_String | s | string |
0x07 | eVMT_Instance | n/a | instance |
0x0f | eVMT_Error | e | 16-bit signed integer |
Some instructions encode object identifiers. This is usually encoded as a 16-bit field.
Value | Equivalence | Description |
---|---|---|
0 | n/a | The instance with ID specified by the stack head as an integer |
-1 | self | The current instance |
-2 | other | The other instance |
-3 | all | All instances in the room |
-4 | noone | Nobody |
n/a | n/a | A specific instance ID |
The GM:S calling convention is as follows;
- Arguments are converted to type variable and placed on the stack from right to left, that is, the first argument is at the top of the stack
- When a function returns, it places its return value at the top of the stack where the calling scope can access it via pop or discard it via popz.
Arrays are not considered as a concrete type. Arrays are all handled as 1-dimensional; 2-dimensional arrays are first converted to 1-dimensional arrays by multiplying the first index by 32000
.
As such, the upper bound of arrays are 32000
. The runner does not automatically check array indices to be positive; these checks are inserted as break instructions and can be disabled in the compile options for higher performance.
Instructions for GameMaker: Studio are formatted in 32-bit blocks. The first byte of each instruction is an opcode and following bytes are instruction specific. Some instructions span more than a single 32-bit block.
To decipher an opcode using the following table, lookup the byte nibbles horizontally and then vertically.
0x | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | push | |||||||||||||||
1 | slt | pop | ||||||||||||||
2 | dup | |||||||||||||||
3 | conv | seq | ||||||||||||||
4 | mul | sne | ||||||||||||||
5 | div | |||||||||||||||
6 | rem | sgt | ||||||||||||||
7 | mod | b | ||||||||||||||
8 | add | bt | ||||||||||||||
9 | sub | bf | ||||||||||||||
a | call | |||||||||||||||
b | ||||||||||||||||
c | ||||||||||||||||
d | ret | |||||||||||||||
e | exit | |||||||||||||||
f | popz | break |
The conv
instruction converts the type on the top of the stack into another type. The structure of this instruction is as follows;
Bits | Description |
---|---|
4 | Type to be converted into |
4 | Type of stack head |
The mul
instruction multiplies the two values on the top of the stack together. The result is pushed onto the top of the stack as a variable and the two slots on the top of the stack are removed. The structure of this instruction is as follows;
Bits | Description |
---|---|
4 | Type of first stack slot (top) |
4 | Type of second stack slot |
Since this instruction is analogous to the *
operator, it's also used to repeat strings.
The div
instruction divides the second value on the stack by the value at the top of the stack. The result is pushed onto the top of the stack as a variable and the two slots are removed. The structure of this instruction is as follows;
Bits | Description |
---|---|
4 | Type of first stack slot (top) |
4 | Type of second stack slot |
This instruction is analogous to the /
operator.
The rem
instruction calculates the remainder of the second value on the stack when divided by the value at the top of the stack. The result is pushed onto the top of the stack as a variable and the two slots are removed. The structure of this instruction is as follows;
Bits | Description |
---|---|
4 | Type of first stack slot (top) |
4 | Type of second stack slot |
This instruction is analogous to the rem
operator.
The mod
instruction performs the modulo of the second value on the stack by the value at the top of the stack. The result is pushed onto the top of the stack as a variable and the two slots are removed. The structure of this instruction is as follows;
Bits | Description |
---|---|
4 | Type of first stack slot (top) |
4 | Type of second stack slot |
This instruction is analogous to the %
operator.
The add
instruction adds the two values on the top of the stack together. The result is pushed onto the top of the stack as a variable and the two slots on the stack are removed. The structure of this instruction is as follows;
Bits | Description |
---|---|
4 | Type of first stack slot (top) |
4 | Type of second stack slot |
Since this instruction is analogous to the +
operator, it's also used to concatenate strings.
The sub
instruction subtracts the second value on the stack by the value on the top of the stack. The result is pushed onto the top of the stack as a variable and the two slots on the stack are removed. The structure of this instruction is as follows;
Bits | Description |
---|---|
4 | Type of first stack slot (top) |
4 | Type of second stack slot |
This instruction is analogous to the -
operator.
The slt
instruction checks whether the second value on the stack is less than the top of the stack, with the property that both are signed. The result is pushed onto the top of the stack as a boolean and the two slots on the stack are removed. The structure of this instruction is as follows;
Bits | Description |
---|---|
4 | Type of first stack slot (top) |
4 | Type of second stack slot |
Since this instruction is analogous to the <
operator, it's also used in lexicographical string comparison, i.e str < "foo"
.
The seq
instruction checks whether the two values on the top of the stack are equal, with the property that both are signed. The result is pushed onto the top of the stack as a boolean and the two slots on the stack are removed. The structure of this instruction is as follows;
Bits | Description |
---|---|
4 | Type of first stack slot (top) |
4 | Type of second stack slot |
Since this instruction is analogous to the ==
operator, it's also used in lexicographical string comparison, i.e str == "foo"
.
The sne
instruction checks whether the two values on the top of the stack are not equal, with the property that both are signed. The result is pushed onto the top of the stack as a boolean and the two slots on the stack are removed. The structure of this instruction is as follows;
Bits | Description |
---|---|
4 | Type of first stack slot (top) |
4 | Type of second stack slot |
Since this instruction is analogous to the !=
operator, it's also used in lexicographical string comparison, i.e str != "foo"
.
The sgt
instruction checks whether the second value on the stack is greater than the top of the stack, with the property that both are signed. The result is pushed onto the top of the stack as a boolean and the two slots on the stack are removed. The structure of this instruction is as follows;
Bits | Description |
---|---|
4 | Type of first stack slot (top) |
4 | Type of second stack slot |
Since this instruction is analogous to the >
operator, it's also used in lexicographical string comparison, i.e str > "foo"
.
The pop
instruction moves a value from the stack into the destination specified. The structure of this instruction is as follows;
Bits | Description |
---|---|
4 | Type of destination |
4 | Type of source |
16 | Instance |
16 | Unknown |
16 | Metadata; usually a variable identifier |
The dup
instruction duplicates the value at the top of the stack, consuming another stack slot. The structure of this instruction is as follows;
Bits | Description |
---|---|
8 | Type of top of stack |
The ret
instruction returns from the current scope with the value on the top of the stack.
Bits | Description |
---|---|
8 | Type of top of stack |
Note that GM:S will only emit ret.v
instructions.
The exit
instruction exits the current scope. If the current scope is a script, it will return 0. The structure of this instruction is as follows;
Bits | Description |
---|---|
8 | Type to return |
Note that GM:S will only emit exit.i
instructions.
The popz
instruction discards the value on the top of the stack. This is used to i.e. discard the return value of a function. The structure of this instruction is as follows;
Bits | Description |
---|---|
8 | Type of top of stack |
The b
instruction is an uncondition branch. The structure of this instruction is as follows;
Bits | Description |
---|---|
8 | Alignment padding (ignored) |
16 | 16-bit signed integer number of standard size instructions (32-bits each) to jump |
If the second parameter of this instruction is zero, an infinite loop is produced.
The bt
instruction branches if the boolean at the top of the stack is true
. The structure of this instruction is as follows;
Bits | Description |
---|---|
8 | Alignment padding (ignored) |
16 | 16-bit signed integer number of standard size instructions (32-bits each) to jump |
The bf
instruction branches if the boolean on the top of the stack is false
. The structure of this instruction is as follows;
Bits | Description |
---|---|
8 | Alignment padding (ignored) |
16 | 16-bit signed integer number of standard size instructions (32-bits each) to jump |
The push
instruction moves a value onto the stack. The next byte indicates the kind of push as a VM Type, and the proceeding bytes are type-specific;
Push a floating point double onto the stack.
Bits | Description |
---|---|
16 | Alignment padding (ignored) |
64 | 64-bit IEEE754 floating point double |
Push an integer onto the stack.
Bits | Description |
---|---|
16 | Alignment padding (ignored) |
32 | 32-bit signed integer |
Push a long onto the stack.
Bits | Description |
---|---|
16 | Alignment padding (ignored) |
32 | 64-bit signed long |
Push a variable onto the stack. The variable can be scoped to another object.
Bits | Description |
---|---|
16 | Instance |
16 | Variable load type |
16 | Variable identifier |
If the variable load type is 0x1
, then the target is treated as an array and the array index is loaded from the top of the stack (see Arrays). In other cases this field is often 0xa001
and the variable is treated as a regular variable.
Push a string onto the stack.
Bits | Description |
---|---|
16 | Alignment padding (ignored) |
32 | String identifier |
Push a 16-bit error identifier onto the stack. GameMaker: Studio uses this instruction to push small values onto the stack.
Bits | Description |
---|---|
16 | 16-bit signed integer |
The call
instruction dispatches control to a function or script. The structure of this instruction is as follows;
Bits | Description |
---|---|
8 |
Type of return (always i ) |
16 | 16-bit unsigned number of arguments supplied on the stack |
32 | A unique function identifier |
See the Calling Convention for more information.
The break
instruction will break if the top of the stack contains the specified value. The structure of this instruction is as follows;
Bits | Description |
---|---|
8 |
Type of top of stack (always e ) |
16 | 16-bit signed integer to break upon |
When enabled, GM:S will emit this instruction to guard on invalid array access.