-
Notifications
You must be signed in to change notification settings - Fork 160
Script execution
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.
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. |
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.
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) |
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:
- Save program counter to the callstack array
- Set program counter to the entry of the called function
- Restore program counter
- Get instance id of callee script
- Save program counter and stack pointer
- Call function
- Restore program counter and stack pointer
- 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
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)
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
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)