Previous [TOC] Next

| 2 Data Types

This chapter describes the characteristics of the basic data types used by the Mesa processor. The descriptions contained here give only the essential properties of the types. The legal operations on each type are explicitly enumerated elsewhere (and are more restrictive than allowed by the Mesa language).

Note: The routines appearing in this chapter are solely defined for use by the code contained in subsequent chapters. These routines need not be implemented by the processor (although several will be), since they are not available directly to the programmer.

For example, the following routine is useful in describing the processor but is not provided as an instruction:

    Log: PROCEDURE [count: CARDINAL] RETURNS [CARDINAL];

This routine returns the number of bits required to store the number of values given by its argument, assuming an unsigned, unbiased binary representation.

### 2.1 Basic Data Types

The primary unit of storage is the sixteen-bit word.

    WordSize: CARDINAL = 16;

The most significant bit of a word is numbered zero; the least significant bit is numbered fifteen:

Figure 2.1 Sixteen Bit Word

Variables of most common types occupy a single word.

### 2.1.1. Unspecified

Unless they have other specific properties (described in the following sections), words are declared as type `UNSPECIFIED`, a type that is essentially just a bit string. Only a subset of the basic operators (§2.1.3) apply to this type; in particular, arithmetic operators are not used with `UNSPECIFIED`s.

    BLOCK: TYPE = ARRAY [0..0) OF UNSPECIFIED;

A `BLOCK` is used as a placeholder to represent a region of storage of indeterminate size.

### 2.1.2 Bit, Nibble, Byte

The following types, which occupy one, four, and eight bits respectively, are used to represent the substructure of a word:

    BIT: TYPE = [O..2);
    NIBBLE: TYPE = [0..16);
    BYTE: TYPE = [0..256);

A byte is often interpreted as two adjacent nibbles; likewise, a word can be interpreted as two adjacent bytes. The structures NibblePair and BytePair reflect these interpretations:

    NibblePair: TYPE = MACHINE DEPENDENT RECORD[
        left (0: 0..3), right (0: 4..7): NIBBLE];

    BytePair: TYPE = MACHINE DEPENDENT RECORD[
        left (0: 0..7), right (0: 8..15): BYTE];

Two routines are used to extract the bytes of a word:

    HighByte: PROCEDURE [u: UNSPECIFIED] RETURNS [BYTE] =
        BEGIN
        pair: BytePair = u;
        RETURN[pair.left];
        END;

    LowByte: PROCEDURE [u: UNSPECIFIED] RETURNS [BYTE] =
        BEGIN
        pair: BytePair = u;
        RETURN[pair.right];
        END;

The architecture also defines several double word (thirty-two bit) types; see §2.3.

### 2.1.3 Basic Operators

Fundamental operators are defined for all types: they are assignment () and comparison for equality (`=`) and inequality (`#`). In addition, the processor implements the primitive operations found in most ALUs; these include the logical operations Not, And, Or, Xor, and Shift, as well as the arithmetic operators negation (`-`), addition (`+`), subtraction (`-`), and ArithShift.

Note that comparison (for other than equality) is not considered a basic operator, since its result depends on whether the operands are signed or unsigned (§2.2). Such operations can not be performed on `UNSPECIFIED`s, which are neither signed nor unsigned.

### 2.1.3.1 Basic Logical Operators

The following standard logical operations on bit strings are primitive:

    Not: PROCEDURE [UNSPECIFIED] RETURNS [UNSPECIFIED];
    Odd: PROCEDURE [UNSPECIFIED] RETURNS [BOOLEAN];

Odd returns `TRUE` if the least significant bit of its argument is one, and `FALSE` otherwise.

    And: PROCEDURE [UNSPECIFIED, UNSPECIFIED] RETURNS [UNSPECIFIED];
    Or: PROCEDURE [UNSPECIFIED, UNSPECIFIED] RETURNS [UNSPECIFIED];
    Xor: PROCEDURE [UNSPECIFIED, UNSPECIFIED] RETURNS [UNSPECIFIED];
    Shift: PROCEDURE [data: UNSPECIFIED, count: INTEGER] RETURNS [UNSPECIFIED];
    Rotate: PROCEDURE [data: UNSPECIFIED, count: INTEGER] RETURNS [UNSPECIFIED];

In Shift, data is shifted the number of bits specified by count; the shift is to the left if count is positive and to the right if it is negative. If count is zero, the result is the value of data unchanged; if the absolute value of count is greater than fifteen, the result of the operation is zero. In all cases, zeros are supplied to vacated bit positions. In Rotate, data is rotated the number of bits given by count; it is left-rotated if count is positive and right rotated if negative. If count is zero, data is returned unchanged.

### 2.1.3.2 Basic Arithmetic Operators

The basic arithmetic operators, negation, addition, and subtraction, assume a two's complement binary representation. If overflow is ignored, the result can be considered either signed or unsigned (see also the section on arithmetic types below).

The following shift routine is also used in the code, but is not provided as an instruction:

    ArithShift: PROCEDURE [data: INTEGER, count: INTEGER] RETURNS [INTEGER];

This operation is similar to logical shift, except that when shifting right, a copy of bit zero (the sign bit) is shifted into the left of `data`; when shifting left, bit zero is undisturbed.

### 2.2 Numeric Types

The numeric types include signed and unsigned fixed point numbers. There is also a provision for a floating point representation of real numbers (see §2.2.3). The operations on numeric types include the fundamental operators (§2.1.3), the basic arithmetic operators (§2.1.3.2), and the comparison operators (`<`, `<=`, `>`, and `>=`), plus multiplication (`*`), division (`/`), and remainder (`MOD`).

### 2.2.1 Cardinal

Unsigned numbers are of type `CARDINAL` and occupy a single word. The values zero through 65,535 are represented using true binary notation. All operations performed on cardinals produce unsigned results in the range given above.

### 2.2.2 Integer

Signed numbers are of type `INTEGER` and occupy a single word. The values -32,768 through 32,767 are represented using two's-complement binary notation. All operations performed on `INTEGER`s produce signed results according to the rules of algebra. For multiplication, the product is negative if exactly one of the multiplicand or the multiplier is negative and the other operand is not zero.

Multiplicand | Multiplier | Product
positive | positive | positive
positive | negative | negative
negative | positive | negative
negative | negative | positive

For division and remainder, the dividend and the remainder have the same sign; that is the results satisfy the following equation: dividend = quotient * divisor + remainder.

Dividend | Divisor | Quotient | Remainder
positive | positive | positive | positive
positive | negative | negative | positive
negative | positive | negative | negative
negative | negative | positive | negative

### 2.2.3 Real

Except that they occupy two and four words respectively, the formats of `REAL` and `LONG REAL` types are not defined by the architecture.

Design Note: Adoption of the proposed IEEE floating point standard [2] is currently in progress.

### 2.3 Long and Pointer Types

The processor implements several long (double-word) types, as well as both short and long pointer types. The representations of these types are defined as extensions of the types described above.

### 2.3.1 Long Types

The architecture supports double-word configurations of the types `UNSPECIFIED`, `CARDINAL`, `INTEGER`, and `POINTER` (see below). These types occupy thirty-two bits, wherein the most significant bit of a double word is numbered zero, and the least significant bit is numbered thirty-one.

Figure 2.2 Thirty-two Bit word

When these types are stored in memory, the low-order (least significant) sixteen bits occupy the first memory word (at the lower numbered address), and the high-order (most significant) sixteen bits occupy the second memory word (at the higher memory address).

Figure 2.3 Double Word in Memory

Design Note: This inconsistent convention is solely for the convenience and efficiency of operations that use the evaluation stack (§3.3.2).

The following constructs are used to extract the subcomponents of an arbitrary long type:

    Long: TYPE = MACHINE DEPENDENT RECORD [
        low(0), high(1): UNSPECIFIED];

    HighHalf: PROCEDURE [u: LONG UNSPECIFIED] RETURNS [UNSPECIFIED] =
        BEGIN
        long: Long = LOOPHOLE[u];
        RETURN[long.high];
        END;

    LowHalf: PROCEDURE [u: LONG UNSPECIFIED] RETURNS [UNSPECIFIED] =
        BEGIN
        long: Long = LOOPHOLE[u];
        RETURN[long.low];
        END;

All of the operations applicable to `UNSPECIFIED`, `CARDINAL`, and `INTEGER` types are also valid for their long counterparts, with the same semantics and restrictions, except for the range of the results. In addition, shifting operations are defined for long types:

    LongShift: PROCEDURE [data: LONG UNSPECIFIED, count: INTEGER]
        RETURNS [LONG UNSPECIFIED];

    LongArithShift: PROCEDURE [data: LONG INTEGER, count: INTEGER]
        RETURNS [LONG INTEGER];

### 2.3.2 Pointer Types

Values of type `POINTER` and `LONG POINTER` are memory addresses occupying single and double words, respectively. In addition to the fundamental operations, all of the basic logical operators and the basic arithmetic operators, addition and subtraction, can be applied to pointers. For arithmetic purposes, pointers are always unsigned.

Design Note: Like all long types, the components of `LONG POINTER`s appear in memory with the least significant word occupying the lower-numbered memory address (§2.3.1).

### 2.4 Type Conversion

This section defines the conversions performed between the types defined above. Except for the operators already defined (HighByte, LowByte, etc.), and for the cases involving the numeric and pointer types described below, conversions between operands are performed by the standard assignment operator ( ), which means "copy the bits."

### 2.4.l Assignment

In the statement `left right`, if either operand is more than sixteen bits wide, both must be the same width. Otherwise, if `right` is shorter than `left`, sufficient high-order zeros are supplied. In general, when operands smaller than a word appear in an expression, they are considered to be embedded in a word by zero extending (not sign extending), just as in the expression `"int + 6"`, the constant is assumed to be extended as necessary. If more than sixteen bits are required, the lengthening of an operand is always made explicit (using `LONG`, defined below). Likewise, if bits other than zeros are required, a built-in operation is used. For example:

    SignExtend: PROCEDURE [z: BYTE] RETURNS [INTEGER] =
        BEGIN
        RETURN[IF z IN [0..177B] THEN z ELSE z - 400B];
        END;

SignExtend defines the conversion of a signed byte to a sixteen-bit integer. The shortening of an operand is always indicated explicitly by using LowByte, LowHalf, or some other explicitly coded function (see, for example, the ReadField and WriteField routines defined in §7.5).

### 2.4.2 Signed/Unsigned Conversions

Conversions between signed and unsigned numbers of the same length are performed using the operators `CARDINAL` and `INTEGER`. Given `i: INTEGER` and `c: CARDINAL`, the following examples illustrate their usage:

    i  INTEGER[c];     -- check c <= LAST[INTEGER]
    c  CARDINAL[i];    -- check i >= FIRST[CARDINAL]

The `INTEGER` conversion implies a check that the cardinal is less than 32,3768 (yielding an `ERROR` if it fails); the `CARDINAL` conversion implies a check that the integer is non-negative. With appropriate change in range, the same conversions also apply to `LONG CARDINAL` and `LONG INTEGER`).

### 2.4.3 Short/Long Conversions

Conversions from short to long are performed using the `LONG` operator. Given `i: INTEGER, li: LONG INTEGER, c: CARDINAL`, and `lc: LONG CARDINAL`, the following table defines the conversion rules:

    li  LONG[i];      -- sign extend
    li  LONG[c];      -- supply high order zeros
    k  LONG[i];       -- check non-negative
    k  LONG[c];       -- supply high order zeros

The `LONG` operator applied to an `UNSPECIFIED` always produces a `LONG UNSPECIFIED` by prefixing high-order zeros. Conversions from long to short values are performed using the built-in routines defined above (for example, LowByte or LowHalf). These operations do not check for loss of significant bits. If a check is required, it appears explicitly in the code.

### 2.4.4 Pointer Conversions

Conversions of constants to pointers are performed using a `LOOPHOLE`. The mapping of virtual to real memory addresses is the subject of §3.1.1; conversions from short to long pointers involve special addressing considerations described in §3.2.1.

Previous [TOC] Next

[last edited 3/21/1999 10:41AM]