Skip to content
Ivan Mogilko edited this page Mar 20, 2017 · 4 revisions

General

The script execution logic is very much based on the x86 architecture, or more generally said a register-memory-architecture.

The original script text is being compiled into byte code, which is then executed by interpreter. Interpreter uses a number of registers (simply an array of integers with predefined meaning). Each instruction in a byte-code defines an atomic operation of copying data between registers, performing arithmetic, calling an engine API function, and so on.

Calculations are performed as 32 bit signed integer for fixed point and 32 bit float for floating point values.

Registers

Before discussing instructions, we must mention registers, for instructions often refer to these. There are 7 register total, each having very specific purpose.

ID Name Purpose
1 SREG_SP Holds the stack pointer. It points to the next free stack address
2 SREG_MAR Holds a memory address for reading from and writing to memory. Direct memory reads and writes go through this register. An exception are reads to values on the stack, which can be accessed relative to the stack pointer.
3 SREG_OP When calling a member function of an AGS script object, this register holds the memory address of the object (this address).
4-7 SREG_AX, SREG_BX, SREG_CX, SREG_DX General purpose registers. They can hold both int and float values since there are no dedicated floating point registers. CX and DX are currently reserved.

Instructions

Each instruction in the byte code has following format:

0xIICCCCCC [0xP1P1P1P1 [0xP2P2P2P2 [0xP3P3P3P3]]]

Where I = script instance id, C = operation code (aka opcode), P1 - P3 = optional parameters.

Instructions have variable length depending on how much parameters they take. The opcode itself takes 4 byte, with instance id packed in the highest byte, and each parameter additional 4 byte. There can be 0 to 3 parameters, which results in total 4 to 16 bytes per instruction.

Opcodes

Following is the list of supported opcodes and their arguments (click to reveal).

(The list is conforming to AGS v3.4.1)

For parameters:

  • R - mean register's number
  • A - means literal integer
ID Opcode PP Hint Meaning
1 SCMD_ADD RA reg1 += arg2 Add integer to register's value (result saved in register)
2 SCMD_SUB RA reg1 -= arg2 Subtract integer from register's value (result saved in register)
3 SCMD_REGTOREG RR reg2 = reg1 Copy value from one register to another
4 SCMD_WRITELIT AA m[MAR] <- arg2 Interpret arg2 and reg[MAR] as memory addresses and copy arg1 bytes from arg2 to reg[MAR]
5 SCMD_RET - ret from sub Return from a function withing same script: jump to byte-code offset saved on stack
6 SCMD_LITTOREG RA reg1 = arg2 Copy arg2 value to reg1
7 SCMD_MEMREAD R- reg1 <- m[MAR] Interpret reg[MAR] and reg1 as memory addresses and copy int32 from reg[MAR] to reg1
8 SCMD_MEMWRITE R- m[MAR] <- reg1 Interpret reg1 and reg[MAR] as memory addresses and copy int32 from reg1 to reg[MAR]
9 SCMD_MULREG RR reg1 *= reg2 Multiply reg1's value by reg2's value (save in reg1)
10 SCMD_DIVREG RR reg1 /= reg2 Divide reg1's value by reg2's value (save in reg1)
11 SCMD_ADDREG RR reg1 += reg2 Sum reg1's value with reg2's value (save in reg1)
12 SCMD_SUBREG RR reg1 -= reg2 Subtract reg2's value from reg1's value (save in reg1)
13 SCMD_BITAND RR reg1 &= reg2 Bitwise ADD reg1 and reg2 values (save in reg1)
14 SCMD_BITOR RR reg1 I= reg2 Bitwise OR reg1 and reg2 values (save in reg1)
15 SCMD_ISEQUAL RR reg1 = (reg1 == reg2) Compare two register values and save boolean result in reg1
16 SCMD_NOTEQUAL RR reg1 = (reg1 != reg2) Compare two register values and save inverse boolean result in reg1
17 SCMD_GREATER RR reg1 = (reg1 > reg2) Test if reg1 is greater than reg2 and save boolean result in reg1
18 SCMD_LESSTHAN RR reg1 = (reg1 < reg2) Test if reg1 is less than reg2 and save boolean result in reg1
19 SCMD_GTE RR reg1 = (reg1 >= reg2) Test if reg1 is greater or equal to reg2 and save boolean result in reg1
20 SCMD_LTE RR reg1 = (reg1 <= reg2) Test if reg1 is less or equal to reg2 and save boolean result in reg1
21 SCMD_AND RR reg1 = (reg1 && reg2) Boolean AND operation, save result in reg1
22 SCMD_OR RR reg1 = (reg1 OR reg2) Boolean OR operation, save result in reg1
23 SCMD_CALL R- jump to sub at reg1 Call a function within same script: jump to byte code offset stored in reg1
24 SCMD_MEMREADB R- reg1 <-- m[MAR] (1) Interpret reg[MAR] and as memory addresses and copy int8 from reg[MAR] to reg1
25 SCMD_MEMREADW R- reg1 <-- m[MAR] (2) Interpret reg[MAR] and as memory addresses and copy int16 from reg[MAR] to reg1
26 SCMD_MEMWRITEB R- m[MAR] <-- reg1 (1) Interpret reg1 and reg[MAR] as memory addresses and copy int8 from reg1 to reg[MAR]
27 SCMD_MEMWRITEW R- m[MAR] <-- reg1 (2) Interpret reg1 and reg[MAR] as memory addresses and copy int16 from reg1 to reg[MAR]
28 SCMD_JZ A- jump if ax==0 to arg1 If reg[AX] is zero, then jump to byte-code offset arg1
29 SCMD_PUSHREG R- m[sp]=reg1; sp++ Push value from reg1 onto stack
30 SCMD_POPREG R- sp--; reg1=m[sp] Pop value from stack, copy it into reg1
31 SCMD_JMP A- jump to arg1 Jump to byte-code offset arg1
32 SCMD_MUL RA reg1 *= arg2 Multiply reg1's value by arg2 (save in reg1)
33 SCMD_CALLEXT R1- call API from reg1 Calls real C++ function, taking its address from reg1; saves return value in reg[AX]
34 SCMD_PUSHREAL R- reg1 -> fargs Pushes reg1 onto function arguments stack
35 SCMD_SUBREALSTACK A- <- fargs Pops arg1 arguments from function argument stack
36 SCMD_LINENUM -- linenum Identifies beginning of a line of the source script
37 SCMD_CALLAS R- call ext from reg1 Jumps to the byte-code offset reg1 in another script
38 SCMD_THISBASE -- pc offset Identifies current byte-code offset
39 SCMD_NUMFUNCARGS -- fargs num Saves number of arguments for the following function call
40 SCMD_MODREG RR reg1 %= reg2 Remainder of dividing reg1 by reg2 (save in reg1)
41 SCMD_XORREG RR reg1 ^= reg2 Bitwise XOR reg1 and reg2 values (save in reg1)
42 SCMD_NOTREG R- reg1 = !reg1 Boolean invert value in reg1
43 SCMD_SHIFTLEFT RR reg1 = (reg1 << reg2) Bitwise shift left reg1 by reg2 (save in reg1)
44 SCMD_SHIFTRIGHT RR reg1 = (reg1 >> reg2) Bitwise shift right reg1 by reg2 (save in reg1)
45 SCMD_CALLOBJ R- m[OP] = reg1 Test if reg1 contains valid object pointer and save it in reg[OP]
46 SCMD_CHECKBOUNDS RA reg1: [0..arg2] Test if reg1 is between 0 and arg2
47 SCMD_MEMWRITEPTR R- m[MAR] = reg1 (ref) Find managed handle of object address from reg1 and save in reg[MAR]; increment ref counter for new object and decrement for previously referenced by reg[MAR]
48 SCMD_MEMREADPTR R- reg1 = m[MAR] (ref) Find managed object by the handle from reg[MAR] and save its address in reg1
49 SCMD_MEMZEROPTR -- m[MAR] = 0 Find managed object by the handle from reg[MAR] and decrease its ref count
50 SCMD_MEMINITPTR R- m[MAR] = reg1 Same as SCMD_MEMWRITEPTR, but ignore another object previously referenced by reg[MAR]
51 SCMD_LOADSPOFFS A- m[MAR] = m[SP] - arg1 Get memory address of stack variable at arg1 offset and save it in reg[MAR]
52 SCMD_CHECKNULL -- error if m[MAR]==0 Test if reg[MAR] contains null pointer
53 SCMD_FADD RR reg1(f) += arg2 Add integer to float from reg1 (save in reg1)
54 SCMD_FSUB RR reg1(f) -= arg2 Subtract integer from float in reg1 (save in reg1)
55 SCMD_FMULREG RR reg1 *= reg2 (float) Multiply reg1's float by reg2's float (save in reg1)
56 SCMD_FDIVREG RR reg1 /= reg2 (float) Divide reg1's float by reg2's float (save in reg1)
57 SCMD_FADDREG RR reg1 += reg2 (float) Sum reg1's float with reg2's float (save in reg1)
58 SCMD_FSUBREG RR reg1 -= reg2 (float) Subtract reg2's float from reg1's float (save in reg1)
59 SCMD_FGREATER RR reg1 = (reg1 > reg2) (float) Test if reg1's float is greater than reg2 and save boolean result in reg1
60 SCMD_FLESSTHAN RR reg1 = (reg1 < reg2 (float)) Test if reg1's float is less than reg2 and save boolean result in reg1
61 SCMD_FGTE RR reg1 = (reg1 >= reg2) (float) Test if reg1's float is greater or equal to reg2 and save boolean result in reg1
62 SCMD_FLTE RR reg1 = (reg1 <= reg2) (float) Test if reg1's float is less or equal to reg2 and save boolean result in reg1
63 SCMD_ZEROMEMORY A- m[MAR]..m[MAR+(arg1-1)] = 0 Clear arg1 bytes starting from memory address found in reg[MAR]
64 SCMD_CREATESTRING R- reg1 = new String(reg1) Create managed string from a non-managed const char* pointer (save in reg1)
65 SCMD_STRINGSEQUAL RR reg1 = ((char*)reg1 == (char*)reg2) Compare two strings from reg1 and reg2 and save result in reg1
66 SCMD_STRINGSNOTEQ RR reg1 = ((char*)reg1 != (char*)reg2) Compare two strings from reg1 and reg2 and save inverse result in reg1
67 SCMD_CHECKNULLREG R- error if reg1 == NULL Test if reg1 contains null pointer
68 SCMD_LOOPCHECKOFF -- noloopcheck Disables loop counter safecheck for the duration of the function
69 SCMD_MEMZEROPTRND -- m[MAR] = 0 (no dispose if = ax) Temporarily disables disposing of managed object referenced by reg[AX], decrement ref counter for object referenced by reg[MAR], and clear reg[MAR]
70 SCMD_JNZ A- jump to arg1 if ax!=0 Jump to byte-code offset arg1, but only if reg[AX] is not 0
71 SCMD_DYNAMICBOUNDS R- reg1: [0..m[MAR]] Test if reg1 is in the range between 0 and size of dynamic array, which address is stored in reg[MAR]
72 SCMD_NEWARRAY RAA reg1 = new arg2[reg1] Create dynamic array of reg1 elements, where each element is arg2 bytes in size; arg3 tells if elements are managed references (save in reg1)
73 SCMD_NEWUSEROBJECT RA reg1 = new userobj(arg2) Creates new managed object of user type, which is arg2 bytes in size (save in reg1)

Function calls

Parameters for functions are always passed through the stack. The return value is stored in SREG_AX.

The machine distinguishes three kinds of function calls:

Calls to functions within the same script (SCMD_CALL)

  • Save program counter to the callstack array
  • Set program counter to the entry of the called function
  • Restore program counter

Calls to functions in another script (SCMD_CALLAS)

  • Get instance id of callee script
  • Save program counter and stack pointer
  • Call function
  • Restore program counter and stack pointer

Calls to C functions (SCMD_CALLEXT)

  • Parameters are pushed onto the callstack array via SCMD_PUSHREAL
  • If the function is a script object member function, set the object with SCMD_CALLOBJ
  • SCMD_CALLEXT is called with the address of the C function as first parameter
  • Call function, return value is in SREG_AX
  • The C function can have between 0 and 9 parameters

Accessing static variables

Static variables are accessed through C pointer values directly in the code.

The opcodes for accessing an imported variable are:

  • Write variable address (SCMD_LITTOREG) into SREG_MAR register
  • Note, that this address is subsituted by actual object address during runtime script initialization
  • For structs - add (SCMD_ADD) offset to SREG_MAR (only of offset is greater than zero)

The opcodes for accessing a global variable are:

  • Write global data offset (SCMD_LITTOREG) into SREG_MAR register
  • Note, that this address is subsituted by actual global data address during runtime script initialization

For either type of variable, if that variable is a static array:

  • Add (SCMD_ADDREG) element offset to SREG_MAR, copying it from SREG_CX (where it was previously stored)

Then, it is one of the following:

  • Writing to variable (SCMD_MEMWRITEPTR, value copied from SREG_AX),
  • Reading variable (SCMD_MEMREADPTR, value copied to SREG_AX),
  • Store the variable address (SCMD_REGTOREG, address copied to SREG_AX)

Example

Store address of Nth element of global game's array (e.g. 'objects' or 'characters') to the AX:

  • SCMD_LITTOREG | SREG_MAR | < ARRAY_PTR >
  • SCMD_ADDREG | SREG_MAR | SREG_CX
  • SCMD_REGTOREG | SREG_MAR | SREG_AX

Accessing dynamic script objects

Dynamic script objects are represented through "handles". A handle is assigned when an object is created and destroyed when the reference count for the object reaches zero (garbage collection). Opcodes for dynamic objects are somewhat unintuitively named. The "pointer" part of their name has to be understand as an AGS object pointer in the scope of the scripting language and not a C/C++ pointer.

  • SCMD_MEMWRITEPTR 47 // m[MAR] = reg1 (adjust ptr addr)
  • SCMD_MEMREADPTR 48 // reg1 = m[MAR] (adjust ptr addr)
  • SCMD_MEMZEROPTR 49 // m[MAR] = 0 (blank ptr)
  • SCMD_MEMINITPTR 50 // m[MAR] = reg1 (but don't free old one)
  • SCMD_MEMZEROPTRND 69 // m[MAR] = 0 (blank ptr, no dispose if = ax)