Documentation

QCode

Note

My understanding only, based on reading the opl-dev source code.
—Tomsci

The OPL bytecode format is called QCode (due to the intermediary parsed code format being called PCode). It is a simple stack machine with variable length commands. Each command consists of an 8-bit opcode followed by variable length parameters. A command like “AddInt” is a single 8-bit opcode, which pops 2 values from the stack and pushes 1 result. The OPO file format defines a collection of procedures with metadata (such as number of arguments, required local variable stack frame size, etc) for each plus the QCode itself.

An “application” is an OPO file called “X.app” alongside a file “X.aif” (Application Info Format) describing the app’s icons and localised name (or X.OPA on SIBO).

There are something like 280 defined opcodes, plus another 128 or so “functions” invoked with the CallFunction opcode. The distinction between dedicated opcode and function code appears entirely arbitrary as there are some extremely complex “opcodes” and some fairly basic functions codes. Opcodes whose numerical value doesn’t fit in a single byte are expressed as opcode 255 (“NextOpcodeTable”) followed by a second byte being the real code minus 256. There are no opcodes requiring more than 2 bytes to express.

Strings are limited to a maximum length of 255 bytes using a leading length byte (this was increased in Quartz, as well as making strings support UCS-2). Various other internal data structures were increased from 1 byte to 2 at the same time. Strings not part of a string array have a maxlength byte preceding the length byte - string addresses always point to the length byte. String arrays have a single maxlength byte common to all elements, immediately preceding the first string element. For this reason opcodes that operate on strings take an explicit max length parameter on the stack, since it is not possible to know a string’s max length based solely on its address (you need to know whether it’s in an array or not, and if so where the start of the array is).

Arrays are limited to 32767 elements (signed 16-bit) although the overall local variable size of a function is also limited to something like 16KB. The array length is stored in the 2 bytes immediately preceding the first element (in the case of number arrays) or preceding the max length byte (in the case of string arrays). Array size is statically fixed at the point of declaration and cannot be changed at runtime. Array addresses always point to the start of the first element. Arrays are not first-class values (you cannot pass an array to a proc, or assign one array to another) but some commands do accept array parameters.