# Low-level programming

- Data representation
- Sequence parameters
- Calling and naming conventions

## Data representation

The internal representation of values of Modula-2 and Oberon-2 basic types is described in the tables 13. Representation of Modula-2 basic types and 14. Representation of Oberon-2 basic types. In the table 15. Representation of SYSTEM types the representation of system types is described.

Table 13. Representation of Modula-2 basic types

Modula-2 type | Bits | Representation
`SHORTINT` | 8 | signed
`INTEGER` | 16/32 | signed (See Modula-2 INTEGER and CARDINAL types)
`LONGINT` | 32 | signed
`SHORTCARD` | 8 | unsigned
`CARDINAL` | 16/32 | unsigned (See Modula-2 INTEGER and CARDINAL types)
`LONGCARD` | 32 | unsigned
`CHAR` | 8 | unsigned
`BOOLEAN` | 8/32 | unsigned (See Modula-2 BOOLEAN type)
|  | 0 for FALSE, 1 for TRUE
`subranges` |  | according to the base type
`REAL` | 32 | 80x87 single-precision data format
`LONGREAL` | 64 | 80x87 double-precision data format
`LONGLONGREAL` | 80 | 80x87 extended-precision data format

Table 14. Representation of Oberon-2 basic types

Oberon-2 type | Bits | Representation
`SHORTINT` | 8 | signed
`INTEGER` | 16 | signed
`LONGINT` | 32 | signed
`CHAR` | 8 | unsigned
`BOOLEAN` | 8 | unsigned byte
|  | 0 for FALSE, 1 for TRUE
`REAL` | 32 | 80x87 single-precision data format
`LONGREAL` | 64 | 80x87 double-precision data format
`LONGLONGREAL` | 80 | 80x87 extended-precision data format
`SET` | 32 | packed set

Table 15. Representation of SYSTEM types

System type | Bits | Representation
`ADDRESS` | 32 | unsigned
`BOOL8` | 8 | unsigned
`BOOL16` | 16 | unsigned
`BOOL32` | 32 | unsigned
`BYTE` | 8 | unsigned
`CARD8` | 8 | unsigned
`CARD16` | 16 | unsigned
`CARD32` | 32 | unsigned
`INT8` | 8 | signed
`INT16` | 16 | signed
`INT32` | 32 | signed
`LOC` | 8 | unsigned
`WORD` | 32 | ARRAY [0..3] OF LOC

- Modula-2 INTEGER and CARDINAL types
- Modula-2 BOOLEAN type
- Modula-2 enumeration types
- Modula-2 set types
- Pointer, address, and opaque types
- Procedure types
- Record types
- Array types

### Modula-2 INTEGER and CARDINAL types

If the option M2BASE16 is OFF, objects of types `INTEGER` and `CARDINAL` are 4 bytes (32 bits) long, otherwise they are 2 bytes (16 bits) long.

### Modula-2 BOOLEAN type

A value of the type `BOOLEAN` occupies 1 byte of memory.

### Modula-2 enumeration types

Representation of enumeration type values depends on the current ENUMSIZE equation setting. Values of an enumeration type which fits the specified size (1, 2, or 4 bytes) occupy exactly that number of bytes; otherwise the smallest suitable size from that list is taken.

### Modula-2 set types

Sete are represented as bit arrays. The SETSIZE equation specifies the default size for small sets (1, 2, or 4 bytes).

If the option M2BASE16 is OFF, the type `BITSET` is represented by 32 bits, otherwise by 16 bits.

### Pointer, address, and opaque types

The XDS compiler allocates 4 bytes of storage for a value of a pointer, address, or opaque type. Address arithmetic is implemented as 32-bit unsigned arithmetic without overflow checks.

### Procedure types

Procedure types are represented by 4 bytes which hold an address of a procedure entry point in the task code segment.

### Record types

Records are represented by a continuous memory segment containing all record components (fields) in a representation corresponding to their types. The compiler aligns each field according to its size and the current alignment (1,2,4, or 8), which may be set with the ALIGNMENT equation. Fields, which sizes, being rounded to the nearest power of 2, are less or equal to the current alignment, are placed at offsets which are multiple of their (rounded) sizes. Offsets of all other fields are multiples of the current alignment. Variant parts are aligned at the largest alignment of variant fields. Size of a record is rounded so that size of an array of such records is a multiple of the record size and the number of elements in the array, and each record in the array is correctly aligned.

        TYPE
          R1 = RECORD                (* ALIGNMENT   1   2   4  *)
                 f1: CHAR;           (* f1 offset   0   0   0  *)
                 f2: SYSTEM.CARD16;  (* f2 offset   1   2   2  *)
                 f3: SYSTEM.CARD16;  (* f3 offset   3   4   4  *)
                 f4: CARDINAL;       (* f4 offset   5   6   8  *)
                 f5: CHAR;           (* f1 offset   9   10  12 *)
               END;                  (* SIZE(R1)    10  12  16 *)

### Array types

An array is represented by a continuous memory segment containing all array elements in a representation corresponding to their type.

Note that elements within an array can be aligned, so in general for

`TYPE A = ARRAY [0..N-1] OF T;`

`SIZE(A)` may be not equal to `SIZE(T) * N`.

Open arrays, as well as procedure formal parameters of type `ARRAY OF ... ARRAY OF T`, are represented by an open array descriptor. For an N-dimensional open array, the descriptor is an array of 2N 32-bit elements, which are:

- the first element is the address of the array data
- the second is the highest dimension
- for each of N-1 higher dimensions, the descriptor contains the size in bytes of a next dimension array and a number of elements in the dimension

Let `A` be a dynamic 3-dimensional array of `INTEGER` (`SIZE(INTEGER)=2` in Oberon-2) created as

`NEW(A,4,3,6)`

then its descriptor is a 6-element array containing:

         #0:  Address of array itself
         #1:   6
         #2:  12   (6*2)
         #3:   3
         #4:  36   (12*3)
         #5:   4

## Sequence parameters

The array of bytes which is passed to a procedure in place of a formal SEQ-parameter is formed as follows:

- values of all actual parameters forming the sequence are represented as described below and concatenated in the array in their textual order
- integer values are converted to `LONGINT`
- `BOOLEAN`, `CHAR`, cardinal, and enumeration values are converted to `LONGCARD`
- range type values are converted according to their base type
- real values are converted to `LONGREAL`
- pointer, address, opaque, and procedure type values are converted to `ADDRESS`
- a structured value (record or array) is interpreted as a one-dimensional array of bytes and is represented by a 3-element descriptor:

  - the address of the structure
  - a zero 32-bit word (reserved for future extensions)
  - size of the structure (in LOCs) minus one

##### Example

        PROCEDURE write(SEQ args: SYSTEM.BYTE);
        BEGIN
        END write;

        VAR i: INTEGER;
            c: SYSTEM.CARD8;
            r: LONGREAL;
            S: RECORD a: LONGINT; c: CHAR END;
            p: POINTER TO ARRAY OF CHAR;
         .  .  .

        write(i,c,S,r,p^);

For this call the actual byte array passed to `write` will contain:

- 4 bytes of the sign-extended value of `i`
- 4 bytes of the zero-extended value of `c`
- 12 bytes of the array descriptor

  - 4 bytes containing the address of `S`
  - 4 bytes containing 0
  - 4 bytes containing 4 (`SIZE(S)-1`)
- 8 bytes value of `r` in the double-precision 80387 format
- 12 bytes of the array descriptor

  - 4 bytes containing the address of the `P` data
  - 4 bytes containing the value 0
  - 4 bytes containing `SIZE(p^)-1`

## Calling and naming conventions

The calling and naming conventions for Modula-2, Oberon-2, and foreign procedures are described in this section.

- General considerations
- Open arrays
- Oberon-2 records
- Result parameter
- Nested procedures
- Oberon-2 receivers
- Sequence parameters
- Order of parameters
- Stack cleanup
- Register usage
- Naming conventions

### General considerations

All parameters are always passed on the stack. The number of bytes occupied by a parameter is a multiple of 4. High-order bytes of parameters which are of shorter types (e.g. `CHAR`, `SYSTEM.CARD16`) are undefined.

Value parameters of scalar types (boolean, character, enumeration, whole, range, real, pointer, opaque, and procedure) and sets of size not greater than 32 bit are placed onto the stack. A complex type value parameter is passed as a pair of real.

Value parameters of all other types (even an array of a single `CHAR`) are passed by reference. A procedure is responsible for copying its non-scalar value parameter onto the stack, unless it is marked as read-only.

Warning: In C, a caller should copy value parameters of structure type onto the stack. You should provide a wrapper C function which receives these parameters by reference. Fortunately, this is a very rare case.

Note: The number of 4-byte words pushed onto the stack is passed in the AL register to a `"SysCall"` foreign procedure.

### Open arrays

For an N-dimensional open array parameter N+1 parameters are actually passed --- the address of the array and its sizes in all dimensions from left to right. This is true for Modula-2 and Oberon-2 procedures only. In case of a foreign procedure, only the address is passed.

### Oberon-2 records

To a formal VAR-parameter which type is an Oberon-2 record type, the address of the actual parameter and the address of its dynamic type descriptor are passed.

### Result parameter

If a function procedure result type is not scalar, it receives one extra parameter --- the address of a temporary variable in which the procedure should store the reslut. Note: This may be incompatible with C.

A complex result is returned as a record with two real fields.

### Nested procedures

A nested Modula-2 or Oberon-2 procedure, which access scopes of outer procedures, receives their bases as extra parameters. More precisely, the procedure P receives bases of all outer procedures which scopes are accessed by P or any procedure nested in P.

The base of a procedure is the address at which the procedure's return address resides on the stack.

### Oberon-2 receivers

An extra parameter --- receiver --- is passed to an Oberon-2 type bound procedure. A reference to its dynamic type descriptor is also passed if the receiver is declared as a VAR-parameter.

### Sequence parameters

Sequence parameters for a Modula-2/Oberon-2 procedure are collected into a temporary variable, which is then passed as an `ARRAY OF BYTE` (i.e. its address and size are passed). For foreign procedures, a C-compatible approach is used --- parameters are pushed onto the stack. In either case, all ordinal type parameters are extended to 4 bytes, `REAL`s to `LONGREAL`s, non-scalar type parameters are passed by reference.

### Order of parameters

The abstract order of parameters (all categories are optional):

- address of a temporary variable to store the result
- bases of outer procedures
- receiver (Oberon-2 procedures only)
- regular parameters
- sequence parameter

Actual order, in all cases except `"Pascal"` foreign procedures, is from-right-to-left, i.e. the last sequence parameter is pushed onto the stack first, the result parameter is pushed last.

### Stack cleanup

The stack space allocated for parameters has to be freed upon return from a procedure. Depending on the language of the procedure, it is performed by the caller (`"C"` and `"SysCall"`) or the procedure itself (Modula-2, Oberon-2, `"StdCall"`, `"Pascal"`).

### Register usage

A procedure must preserve reisters `ESI`, `EDI`, `EBP`, and `EBX` registers, keep `ES=DS`, and clear the `D` flag.

The FPU stack must be empty before a call to a procedure and upon return from it. Exceptions are procedures which return `REAL` or `LONGREAL`. In this case, the result is placed in `ST(0)`.

Note: If the CC equation is set to either `"WATCOM"` or `"SYMANTEC"`, foreign procedures declared as `"C"` are considered to return `REAL` results in `EAX`, and `LONGREAL` results in `EAX` (low order bytes) and `EDX` (high order bytes).

### Naming conventions

External names of exported procedures in object modules are built accroding to the following rules:

Convention | Name is | As in
`"Modula"` | prepended with the module name and "_" | `Module_Proc`
`"Oberon"` | ditto | `Module_Proc`
`"C"` | prepended with "_" (see note) | `_Proc`
`"Pascal"` | capitalized | `PROC`
`"StdCall"` | unchanged | `Proc`
`"SysCall"` | unchanged | `Proc`

Note: If the CC equation is set to `"WATCOM"`, external names of `"C"` foreign procedures are not prepended with an underscore character.