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.
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:
Variables of most common types occupy a single word.
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 UNSPECIFIEDs.
BLOCK: TYPE = ARRAY [0..0) OF UNSPECIFIED;
A BLOCK is used as a placeholder to represent a region of storage of indeterminate size.
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.
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 UNSPECIFIEDs, which are neither signed nor unsigned.
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.
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.
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).
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.
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 INTEGERs 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 |
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.
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.
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.
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).
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];
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 POINTERs appear in memory with the least significant word occupying the lower-numbered memory address (§2.3.1).
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."
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).
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).
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.
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.
[last edited 3/21/1999 10:41AM]