(c) 1977 by Wadsworth Publishing Company, Inc., Belmont, California 94002. All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transcribed, in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior written permission of the publisher.
Printed in the United States of America
1 2 3 4 5 6 7 8 9 10---81 80 79 78 77
Portions of this text are reprinted from "PL360 Reference Manual" with the permission of the publishers, SCIP Academic Computing Services. Copyright (c) 1975 by the Board of Trustees of The Leland Stanford Junior University.
*********************************************************** * Library of Congress Cataloging in Publication Data * * * * Guertin, Richard L * * Introduction to PL360 programming. * * * * Bibliography: p. * * Includes index. * * 1. PL360 (computer program language) I. Title. * * QA76.73.P267G83 001.6'424 76-51752 * * ISBN 0-534-00524-1 * ***********************************************************
A few years ago, while I was teaching a short course on PL360 at Stanford University, a student asked: "Is there a textbook on PL360?" I found there wasn't, but recognized the need for one. So I set out to write this textbook with two principle goals in mind. First, describe the PL360 programming language in such a way that each section of the text builds upon material covered in earlier sections. Second, write in a style that would appeal to assembly language programmers as well as PL/1 and FORTRAN programmers.
PL360, by its very nature, is an intermediate level programming language dealing with high level language concepts such as 'block structuring', and assembly language concepts such as 'registers'. This textbook is designed to be used with an intermediate level programming course, a transitional course between higher level languages and pure assembly language programming. Of course, assembly language programmers who wish to 'move up' to a higher style of programming would also find this textbook useful, first as a learning tool, and later as a reference document. In all fairness though, I must point out that PL360 is NOT an assembly language. Most versions of the language do not have 'macro' capability, although expanded macros can be programmed directly in PL360.
The PL360 programming language was originally designed and implemented by Nicklus Wirth and Joe Wells at Stanford University for the IBM System/360 computers. However, the FUNCTION capability of PL360 makes the language extensible to IBM System/370 and Amdahl computers. In fact, PL360 can be used on any computer that supports the basic System/360 instruction set. The other instructions can be programmed through FUNCTION declarations and statements.
I would like to thank the many students and friends who helped me in preparing this textbook by reviewing the preliminary material. The final drafts were reviewed by John Lindsay at Queen's University, and by Steven M. Dreyer at City University of New York. Both of these gentlemen contributed significantly; however, any and all errors or omissions are solely my responsibility. Also, I would like to thank Mike Snell, Jenny Sill, and Anne Kelly at Wadsworth Publishing Company for all their editorial help. Finally, special thanks to Stanford University for providing me with the environment that made writing this text possible.
The PL360 compiler is a proprietary program licensed by Stanford University and is available to universities and other non-profit organizations for a small fee by writing to:
Program Librarian Information Technology Systems and Services Polya Hall Stanford University Stanford, California 94305
In recent years, many new languages have been introduced to aid in programming digital computers. Most of these languages have been somewhat 'transportable'; that is, the programs written in these languages could be taken from one computer to another with minor changes. They are machine independent languages. FORTRAN, ALGOL, and COBOL are just a few examples.
However, these languages, to be machine independent, are usually restrictive in nature. They seldom use the full instruction power of a computer. As a result, they are sometimes inefficient, wasteful of computer memory space, or incapable of handling some special problems. FORTRAN, for example, is not well suited to character manipulations.
These 'high level' languages are generally not suitable for 'systems programming'. Compilers, programs that translate a language such as FORTRAN into the machine dependent instructions representing the source program, are usually written in a lower level machine dependent language: an assembly language. Assembly language is much more difficult to program, but provides the programmer with access to every machine instruction available on the computer. One difficulty in assembly language programming is that each instruction must be specified on a separate source card by means of some cryptic mnemonic, and usually in varying formats depending on the mnemonic. The programmer must learn a myriad of mnemonics and formats just to get started. This clerical work is not only a burden, but also a rich source of pitfalls.
Clearly, it would be advantageous to be able to write assembly level programs in a high level language style, such as ALGOL. This is what the PL360 programming language provides for the IBM System/360 (and now 370) computers: an ALGOL style machine level language. The benefits of the ALGOL style are so overwhelming that those programmers familiar with ALGOL will hardly recognize the machine level aspects of the language. And those programmers familiar with System/360 assembly language will greatly appreciate the ease of programming in PL360. PL360, therefore, is usually a suitable language choice whenever assembly language is considered. The language contains all the facilities commonly needed to express compiler and supervisor programs; and the programmer is able to determine almost every detailed machine operation. Interestingly enough, the PL360 compiler is an efficient one pass compiler written in its own language!
We shall cover both the IBM System/360 architecture and the ALGOL style of programming in this text. The first chapter provides a basic overview of the System/360 architecture. The second chapter introduces the basic symbols used in PL360 programming and discusses how constants are specified. The remaining chapters describe PL360 programming and language structure with an aim toward defining a complete PL360 program. Some of the Exercise problems given will be complete programs, but only knowledge gained in preceding chapters will be needed to answer such problems.
The basic structure of a System/360 digital computer consists of a central processing unit (CPU) and a main storage or memory unit. Input/output operations are usually handled by an operating system (OS), the environment in which a PL360 program is normally run. Direct input/output operations can be performed by a PL360 program, but only indirect input/output operations will be covered in this text.
The central processing unit of a System/360 is directed by instructions stored in memory. Each instruction performs some basic action, such as ADD one operand to another. Instructions are normally taken in sequence, but branching instructions provide for alterations in the flow of control. Most instructions operate on data held either in main storage or in the CPU. The basic unit of addressable information is a byte.
A byte consists of eight bits (binary digits) and is the basic building block of all information. Bytes may be handled separately or grouped together in fields. A halfword is a group of two consecutive bytes and is the basic building block of instructions. A fullword is a group of four consecutive bytes; a double-word is a field consisting of two consecutive fullwords. The first byte of a set of consecutive bytes is sometimes referred to as the left-most, highest, or top byte. Similarly, the last byte is sometimes referred to as the right-most, lowest, or bottom byte.
The location of any field, operand, or group of bytes in main storage is specified by the address of the left-most byte of the field. Byte locations are consecutively numbered, left to right, starting with 0; each number is considered the address of the corresponding byte. Thus, a fullword specified at location 1000 consists of bytes 1000, 1001, 1002, and 1003. The addressing arrangement of most System/360 computers uses a 24-bit binary address to accommodate a maximum of 16,777,216 byte addresses. This textbook deals mainly with 24-bit addressing, but System/370 and newer models can accommodate 31-bit addresses (2GB), and PL360 can compile programs that are 31-bit clean.
The length of any field in main storage is either implied by the operation to be performed or is stated explicitly as part of the instruction. When the length is implied, the information is said to have a fixed length, which can be either one, two, four, or eight bytes. When the length of a field is not implied by the operation, but is stated explicitly, the information is said to have a variable length which can vary in 1-byte increments from a minimum of one byte to a maximum of 256 bytes (more for certain System/370 instructions).
Fixed-length fields, such as halfwords, fullwords, and double-words, must be aligned in main storage on an integral boundary commensurate with their length. For example, fullwords (four bytes) must be located in main storage so that the address of the left-most byte is a multiple of 4. A halfword must have an address that is a multiple of 2, and a double-word must have an address that is a multiple of 8. Variable-length fields are not limited to integral boundaries, and may start on any byte location. System/370 and newer models do not impose alignment restrictions, but unaligned fullwords and halfwords cause degradation in speed.
Data fields in main storage are commonly referred to as 'cells'; whereas data fields in the CPU are kept in what are called 'registers'.
There are 16 general-purpose 32-bit registers for fixed-point operations, and four floating-point 64-bit registers for floating-point operations. Additions, subtractions, multiplications, divisions, and comparisons can be performed upon one operand in a register and another operand either in a register or in main storage. For some purposes, a pair of general-purpose registers are treated as a single register of 64 bits. In PL360, the general registers are called INTEGER registers, and the floating-point registers are called REAL or LONG REAL registers depending on how they are used.
The general-purpose 32-bit registers serve as accumulators in fixed-point arithmetic and logical operations. All fixed-point arithmetic is done in the general registers using signed operands. The sign bit is the high order bit of the operand, and is 0 for positive operands and 1 for negative operands. A negative operand is maintained as the two's complement of a positive operand. (See Appendix A for a full discussion of two's complement.)
Since binary (base 2) is inconvenient for expressing operands, or doing arithmetic, the hexadecimal (base 16) system is used instead. A group of four bits represents one hexadecimal digit according to the following table:
BINARY HEX BINARY HEX BINARY HEX BINARY HEX 0000 0 0100 4 1000 8 1100 C 0001 1 0101 5 1001 9 1101 D 0010 2 0110 6 1010 A 1110 E 0011 3 0111 7 1011 B 1111 F
Thus, positive 3 would be represented by 00000003 as a 32-bit hexadecimal value, and negative 3 would be represented by FFFFFFFD. Throughout this text, positive values will be shown with no leading sign, and negative values will be shown with a leading underscore character ( 3 and _3). This is done so as not to confuse the sign of a value with the operations of addition and subtraction, represented by the symbols + and -. Thus, + _3 indicates addition of a negative 3. The basic fixed-point operand is the 32-bit word. Halfword storage operands may be used for fixed-point operations of addition, subtraction, and multiplication, and for fetching and storing with general registers. Fullword multiplications and divisions require two adjacent general registers coupled together to provide a two-word capacity for products and dividends. Fixed-point operations will be covered in greater detail in section 5.1.1, Integer Register Assignments.
In PL360, the fixed-point 32-bit fullword operands are called INTEGER operands; and the 16-bit halfword storage operands are called SHORT INTEGER operands.
Because the 32-bit word size of the general registers readily accommodates a 24-bit storage address, the general registers are also used for address arithmetic. To fully understand the addressing usage of the general registers, we must first examine instruction formats.
The length of an instruction can be one, two, or three halfwords. All instructions must be located in storage on halfword boundaries. Figure 1 shows the four basic instruction formats.
________________________________________________________ | FIRST HALFWORD | SECOND HALFWORD | THIRD HALFWORD | ------------------ | OP CODE | | ------------------ I or RR or MR ------------------------------------- | OP CODE | | X | B | D | ------------------------------------- R or M ------------------------------------- | OP CODE | | B | D | ------------------------------------- I or RR -------------------------------------------------------- | OP CODE | | B | D | B | D | -------------------------------------------------------- L or L1L2 FIGURE 1. BASIC INSTRUCTION FORMATS
In each format, the first instruction halfword consists of two parts. The first byte contains the operation code (op code). The second byte is used either as a single 8-bit field designating a value (I) or length (L), or as two 4-bit fields designating two registers (RR or RX), a mask and a register (MR or MX), or two lengths (L1L2).
The second and third halfwords always have the same format: a 4-bit base register designator (B), followed by a 12-bit displacement value (D). These halfwords provide what is called 'Base+Displacement' addressing in that a storage address is computed as follows.
To the 12-bit displacement value specified by the D-field of the instruction is added the lower 24-bit value contained in the general register specified by the associated B-field of the instruction (except when B is 0). Those instructions with an X-field provide additional addressing capability, called 'indexing'. To the 'Base+Displacement' address is added the lower 24-bit value contained in the general register specified by the X-field of such instructions (except when X is 0). The final storage address is the 24-bit result of either a Base+Displacement or Index+Base+Displacement computation. This computation is done assuming all values are positive (12-bit and 24-bit), extending each value to 32 bits with leading zeros, performing 32-bit additions (ignoring overflow), and extracting the final address from the lower 24 bits of the result. If zero is specified in a B-field or X-field, then positive zero is used for that component of the address calculation rather than the contents of general register 0. Thus, general register 0 never participates in the address calculation. Figure 2 shows a sample address calculation.
CODE X B D ---------------------- ----------------- | | | 4 | 9 | 008 | ------------> | 0 0 0 0 0 0 0 8 | ---------------------- ----------------- General Register 9 + ----------------- ----------------- | 4 A 0 0 6 5 0 0 | ---> | 0 0 0 0 6 5 0 0 | ----------------- ----------------- General Register 4 + ----------------- ----------------- | F F F F F F F C | ---> | 0 0 F F F F F C | ----------------- ----------------- (Note: General Register 4 contains _4) ----------------- Final Result: | x x 0 0 6 5 0 4 | ----------------- FIGURE 2. SAMPLE ADDRESS CALCULATION
The B and D fields or X, B, and D fields of an instruction constitute the addressing field of an instruction. The displacement value in an instruction address field provides a relative addressing mechanism, that is, for any given storage address contained in the associated base register, up to 4096 consecutive bytes of storage may be referenced beginning at that base address. Storage can be thought of as being segmented into sections of up to 4096 bytes. These segments may be program instruction or data areas having some specific structure, and may start anywhere in main storage providing alignment rules are not violated.
For example, consider a data area consisting of the following fixed-length fields in the order specified:
a fullword, two halfwords, a double-word, and three fullwords.
Let's call these seven fixed-length fields XX1 to XX7, draw a diagram of the area (Figure 3), and number the bytes starting at 0.
----------- Base Address 0 | XX1 | fullword |-----------| 4 | XX2 | XX3 | two halfwords |-----------| 8 | XX4 | double-word 12 | | |-----------| 16 | XX5 | fullword |-----------| 20 | XX6 | fullword |-----------| 24 | XX7 | fullword ----------- 4 bytes wide FIGURE 3.
Notice that XX5 begins at relative address 16. If this data area were located in main storage beginning at location 1000, and a base register associated with the area, say general register 4, contained the address 1000, then XX5 could be referenced by the address field of an instruction which specifies register 4 in its B-field and 16 in its D-field. It is important to note that the instruction would not be changed if the area were located instead at location 2000 in main storage, and general register 4 contained that address. This makes it easy to move the data storage area without having to change every instruction that refers to it.
The logical operations of AND, OR, and XOR (Exclusive-OR), and logical or un-signed addition, subtraction, and comparison can be performed upon one operand in a general register using a fullword operand either in a general register or in main storage. In such operations, all operands are considered un-signed, and all 32 bits of the operands participate in the operations. AND, OR, and XOR logic tables are given below with binary operands and results.
AND OR XOR Operand 1 1 1 0 0 1 1 0 0 1 1 0 0 Operand 2 1 0 1 0 1 0 1 0 1 0 1 0 Result: 1 0 0 0 1 1 1 0 0 1 1 0 LOGIC TABLES
There are also storage-to-storage operations which allow processing of variable-length data starting at any byte address and continuing left to right for up to 256 bytes. Such operations as translation, comparison, editing, and storage-to-storage copy are possible. Usually the data consists of alphabetic or numeric character codes in Extended Binary-Coded-Decimal Interchange Code (EBCDIC). These operations will be covered in more detail later. An EBCDIC table is given in Appendix C.
The general registers may also participate in an operation known as shifting. The contents of a general register (or pair of adjacent general registers) may be shifted to the right or left some number of bit positions.
When shifted to the right, bits are dropped from the right end of the register and either zero bits are inserted from the left end (logical right shift), or the sign bit is propagated (arithmetic right shift).
When shifted to the left, bits are dropped from the left end, and zero bits are inserted from the right end. No error is possible in a logical left shift; but in an arithmetic left shift, an error condition is flagged if a bit differing from the original sign bit is dropped or occupies the sign bit position.
Shifting (usually arithmetic) provides a convenient means of multiplying or dividing by powers of 2. A shift of 1 to the left is equivalent to multiplication by 2; a shift of 1 to the right is equivalent to division by 2 (dropping the remainder).
Floating-point values occur in either of two fixed-length formats: 32-bit fullwords called REAL values, and 64-bit double-words called LONG REAL values. These formats differ only in the length of the fractional portions (see Figure 4).
REAL value (fullword) -------------------------------- |S| Exponent | Fraction | -------------------------------- 0 1 7 8 31
LONG REAL value (double-word) -------------------------------------------------------- |S| Exponent | Fraction | -------------------------------------------------------- 0 1 7 8 63 FIGURE 4.
A REAL value, equivalent to about seven decimal places of precision, permits a maximum of operands to be placed in storage and gives the shortest execution times. The LONG REAL values, used when higher precision is desired, give up to 17 decimal places of precision.
The fraction of a floating-point value is expressed as six hexadecimal digits occupying bits 8-31 for REAL values, and 14 hexadecimal digits occupying bits 8-63 for LONG REAL values.
The radix point of the fraction is assumed to be immediately to the left of the high order fraction digit (immediately before bit 8). To provide the proper magnitude for the floating-point value, the fraction is considered to be multiplied by some power of 16. The 'Exponent' portion of the floating-point value (bits 1-7) is used to indicate this power. The exponent ranges from 0 through 127 representing true (base 16) exponents from _64 through 63, and permits representations of decimal values with magnitudes ranging approximately from 10 to the _78th through 10 to the 75th.
This method of specifying exponents within the computer's representation of a value is known as 'excess 64 notation', in that 64 is added to the true exponent to arrive at the computer's exponent.
Bit position 0 in either format is the sign (S) of the fraction. The fraction (and exponent) of negative values is the same as that of positive values. Only the sign differs. Negative values are not maintained in two's complement as they are for fixed-point values.
Four 64-bit floating-point registers are provided in the hardware, and the arithmetic operations of addition, subtraction, multiplication, division, and comparison can be performed with one operand in a register and another in a register or in storage. The result, developed in a register, is generally of the same length as the operands, either 32-bit or 64-bit.
Most of the floating-point operations produce what is known as a 'normalized' result; that is, the result is adjusted so that the first hexadecimal digit of the fraction is non-zero. For example, 0.001 would become 0.1 with appropriate exponent adjustment. Normalized results retain the greatest precision possible. However, unnormalized addition and subtraction operations are provided to allow for easy transformation of floating-point values to fixed-point format or other similar purposes. (See Appendix B for further discussion of floating- point values.)
A two-word quantity called the program status word (PSW), contains the information required for proper instruction execution. The PSW includes, among other things, the address of the next instruction to be executed and a 2-bit quantity called the condition code. The condition code provides decision-making capability. Most of the arithmetic and logical operations can set the code to one of four states (0, 1, 2, and 3). Conditional branch instructions can specify any selection of these four states as the criterion for branching. Specifying all four states results in an unconditional branch; specifying none of the four states results in a do-nothing operation.
The condition code reflects such conditions as: zero, negative, or positive result; overflow; and comparison is equal, low, or high. Once set, the condition code remains unchanged until modified by another instruction that reflects a different status. For example, branch instructions do not change the condition code, but additions and subtractions do change the code.
This chapter has presented a brief overview of the System/360 architecture with an emphasis on those items of particular importance to the PL360 programming language. The reader is directed to IBM's Principles of Operation manuals for details concerning these and other System/360 computer operations, such as input/output operations. Later in this text, specific computer instructions will be covered in more detail.
What is the hexadecimal result (32-bit) of a logical right shift by 3 of the value _8 ?
Compute the address in decimal by performing the Index+Base+Displacement calculations indicated below given the following general register contents:
General Register: 0 1 2 3 4 5 Contents (decimal): 7160 _16 510000 16000 72568 _100 X B D (in decimal) (a) 0 2 30 (b) 1 4 0 (c) 3 2 200 (d) 5 2 50 (e) 0 0 100
Determine the result in hexadecimal of logical AND, OR, and XOR operations for each value below with all values below. The values are given as 1-byte hexadecimal values.
0F F0 18
The REAL values shown below are given in decimal notation on the left and hexadecimal (machine) notation on the right. Supply the missing decimal or hexadecimal values in the table.
Decimal Hexadecimal 0.5 40800000 8.0 41800000 9.0 41900000 (a) 8.5 -------- (b) --- 42180000 (c) --- 42800000
PL360 programs are input to the PL360 compiler on 80-column cards or card images. The compiler processes only columns 1 through 72 of each card; columns 73 through 80 are ignored by the compiler, and are usually used for card sequencing information by the programmer.
The input consists of (one or more) PL360 programs and compiler control cards. A compiler control card is distinguished by the character $ in column 1 of the card. The PL360 program input is considered to be a stream of characters with column 1 of each non-control card immediately following column 72 of the preceding non-control card.
A program contains declarations and statements composed of identifiers, basic symbols, constants, strings, and comments. Declarations serve to identify cells of storage, registers, procedures, and other quantities which are involved in the algorithm or problem to be solved by the program. Statements specify the operations to be performed on these quantities.
The remainder of this chapter is devoted to defining identifiers, basic symbols, constants, strings, and comments. In subsequent chapters, we will proceed to construct PL360 programs with declarations and statements.
Identifiers are used to give a name to some quantity, such as a register or storage cell. Identifiers are composed of the mixed case alphabetic characters and decimal digits, where the first character of the identifier must be alphabetic. They may be of any length, but only the first 10 characters are retained by the PL360 compiler. The following are examples of legal identifiers, the last two of which are considered identical because the first 10 characters match:
ABLE HELP SOMELIKEITCOLD BASE16 JackRabbit SOMELIKEITHOT FLAG K27P5 FLAG3 X
Case doesn't matter, and identifiers are considered identical regardless of the case in which they are specified. The following are all considered identical identifiers:
POT POt Pot pot poT pOT
Some identifiers are reserved for exclusive use by the PL360 language. These identifiers have a specific meaning in the language and therefore may not be chosen as user-defined identifiers. The following is a complete list of the reserved identifiers in alphabetic order:
AND COMMENT EXTERNAL LONG SEGMENT THEN ARRAY COMMON FOR NULL SHLA UNTIL BASE DATA FUNCTION OF SHLL WHILE BEGIN DO GLOBAL OR SHORT XOR BYTE DUMMY GOTO PROCEDURE SHRA CASE ELSE IF REAL SHRL CHARACTER END INTEGER REGISTER STEP CLOSE EQUATE LOGICAL REPEAT SYN
There is also a set of pre-declared identifiers that may be redefined by the programmer. However, it is recommended that these identifiers NOT be redefined. A complete list of these 'standard identifiers' follows:
INTEGER REGISTERS R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15
FLOATING-POINT REGISTERS F0 F2 F4 F6 (REAL REGISTERS) F01 F23 F45 F67 (LONG REAL REGISTERS)
CELL NAMES B1 B2 ... B15 MEM (INTEGER CELLS) H1 H2 ... H15 (SHORT INTEGER CELLS) C1 C2 ... C15 (BYTE CELLS) L1 L2 ... L15 (LONG REAL CELLS) DSTAR, PSTAR (DATA & PROGRAM BASE-DISP)
MONADIC OPERATORS (SPECIAL SYMBOLS) ABS DEC GT LE NEG
EQUATE VALUES (INTEGER VALUES) TRUE = _1 FALSE = 0 OVERFLOW = 1 CARRY = 3 MIXED = 4 ON = 1 OFF = 8 STRING = length of last string compiled DATAFILL = variable array size
FUNCTIONS BALR EX MVI RESET STCM TS CLC IC MVN SET STH UNPK CLI ICM MVZ SLDA STM XC CLM LA NC SLDL SVC XI CVB LH NI SPM TEST CVD LM OC SRDA TM ED LTR OI SRDL TR EDMK MVC PACK STC TRT
EXTERNAL PROCEDURES BCDTOVAL KLOSE PRINT READ CANCEL OPEN PUNCH WRITE GET PAGE PUT VALTOBCD
There are a number of special symbols and delimiters used in writing PL360 programs. These special symbols serve as separators between identifiers and constants, and are listed below with a brief description of their use:
Symbol Description + plus sign, add - minus sign, subtract ++ double plus, add logical or unnormalized -- double minus, subtract logical or unnormalized * star, multiply / slash, divide, separator @ address symbol @@ absolute address initialization ( ) left and right parens, subscripts, brackets := assignment operator =: reverse assignment operator : colon, label indicator _ underscore, negative value , comma, separator ; semicolon, terminator . period, decimal point # hexadecimal number indicator ' apostrophe, exponent indicator " quote, character string delimiters = equal sign, compare equal < compare less than > compare greater than <= compare less than or equal >= compare greater than or equal ~= compare not equal ~ not indicator | comment delimiter blank, space
Numbers or numerical constants written in PL360 programs are divided into three classes, namely:
(a) fixed-point (b) floating-point (c) variable-length strings
There are two types of fixed-point constants: (32-bit) fullword constants called INTEGER values, and (16-bit) halfword constants called SHORT INTEGER values.
A positive integer value may be written in decimal notation using the digits 0 to 9. A negative value is written as a positive value preceded by an underscore character (_). An integer value may also be written in hexadecimal notation using the character # followed by the hexadecimal digits 0 to 9 and A to F. Negative hexadecimal values must be written in computer form (see Appendix A).
A short integer value is written as an integer value followed by the letter S.
The following are examples of (32-bit) integer values starting with the largest positive value, and ending with the largest negative value that can be written in PL360 language.
Decimal Hexadecimal 2147483647 #7FFFFFFF 65536 #00010000 or #10000 1 #00000001 or #1 0 #00000000 or #0 _1 #FFFFFFFF _2 #FFFFFFFE _65536 #FFFF0000 _2147483647 #80000001
The largest possible negative value is #80000000, but this value can only be written in hexadecimal notation, not decimal notation.
The following are examples of (16-bit) short integer values starting with the largest positive value, and ending with the largest negative value.
Decimal Hexadecimal 32767S #7FFFS 1S #0001S or #1S 0S #0000S or #0S _1S #FFFFS _32768S #8000S
There are two types of floating-point constants: (32-bit) full- word constants called REAL values, and (64-bit) double-word constants called LONG REAL values. Floating-point constants are generally used in connection with the REAL and LONG REAL floating-point registers.
A floating-point constant is written in decimal notation as:
The portion of the number preceding the decimal point in (a) or (b), or the number preceding the exponent in (c), may not exceed 2147483647.
An exponent is written as the apostrophe character ('), which signifies 'times 10 to the power of', followed by a positive or negative decimal integer value indicating the power of 10 desired. The final magnitude of the number must range from 10'_75 through 10'75.
When written as specified above, the values are REAL values. A LONG REAL value is written as a REAL value followed by the letter L. A negative value is written as a positive value preceded by an underscore character (_).
A floating-point constant may also be written as a positive or negative decimal integer value followed by the letter R for REAL values, or the letter L for LONG REAL values.
All the above decimal forms of writing floating-point constants yield normalized values. Normalized (or unnormalized) values are a characteristic of how the System/360 computer represents the value internally. Floating-point constants may be written in this internal form, either normalized or unnormalized, in hexadecimal notation. Hexadecimal floating-point constants are written as: the character # followed by (up to) eight hexadecimal digits and the letter R, for REAL values, or (up to) 16 hexadecimal digits and the letter L for LONG REAL values.
The following are examples of REAL (normalized) values, the first column showing a variety of ways of writing 'pi', and the last two values showing the largest and smallest positive non-zero decimal values.
3.14159 1.75 _15.0 314159'_5 1R 175.'_2 0.314159'1 0. (zero) 7.237'75 314.159'_2 _7.6 1'_75
The following are samples of real values in hexadecimal notation, starting with the largest and smallest possible positive non-zero normalized values, and ending with an unnormalized long real value which will be discussed later in section 5.1.2.
Value Hexadecimal 7.237'75 #7FFFFFFFR 5.4 x 10-79 #00100000R 1.56256'_2 #3F400000R 0.5 #40800000R 1.0 #41100000R 0.0 (zero) #00000000R or #0R (special) #4E00000000000000L
Note: the first three values are only approximately equal to the hexadecimal values given, and the second value is too small to be written in decimal notation. The reason for approximations is that exact values in one base system may not convert to exact values in another base system, in this case, decimal to hexadecimal. For example, decimal 0.5 (5/10) is hexadecimal 0.8 (8/16), but decimal 0.3 (3/10) is a repeating hexadecimal value: 0.4CCCCCCC.... Therefore, floating-point values are only accurate to six or seven decimal digits for real values, and 16 or 17 decimal digits for long real values.
(a) There are four types of fixed-length values: INTEGER, SHORT INTEGER, REAL, and LONG REAL. (b) REAL and LONG REAL values may be written in decimal digits with decimal point, exponent, or both; and LONG REAL values must end in the letter L. (c) Any value may be written in hexadecimal (leading #), or in decimal digits (without decimal point or exponent). SHORT INTEGER values end in S, REAL values end in R, and LONG REAL values end in L. (d) The length of a hexadecimal value is usually twice the byte length of the fixed-length value being represented (two hexadecimal digits per byte). (e) Negative decimal values are indicated by a leading underscore character (_).
A string is a sequence of characters, each character taking one byte (two hexadecimal digits). The set of all possible characters is given in Appendix C. The set includes the English alphabet in both upper and lower case, decimal digits, a selection of special symbols, blank (or space), and other characters (non-graphic).
A string is normally written as a sequence of characters enclosed in quotation marks, called a quote string, and may contain a sequence of from one through 255 characters. If a quote mark (") is to be a character of the sequence, it is represented by a pair of consecutive quotes. All characters are significant within quote strings, including blank characters. An "accent" character (`) followed by a normal character is used to represent certain special (non-graphic) characters. If a true accent character is to be entered in the string, it must be doubled (similar to what happens for quote).
Examples: "A""z" denotes the sequence A"z """X""" denotes the sequence "X" """" denotes the single character " "This is an example of a quoted string" "This string has an accent (``) character"
If a quote string cannot be contained on a single input card because of its length, then note that column 72 of one card is followed by column 1 of the next card (excluding control cards). The character $ cannot occur in column 1 of a card (since that signifies a compiler control card).
There are instances where a quote string is not a convenient means of specifying a variable-length string, such as when non-graphic characters are to be used for some special purpose. In such instances, another form of variable-length string may be used, namely a hexadecimal string. A hexadecimal string is written as the character # followed by from one to 16 hexadecimal digits followed by the character X. Each pair of hexadecimal digits represent one character. If the number of hexadecimal digits specified is odd, then a hexadecimal zero is prefixed to the first specified hexadecimal digit to make the total even. A hexadecimal string then may be used to specify from one to eight characters (bytes).
Examples: #2X denotes the single character 02 #C3C1C2X denotes a 3-byte string equivalent to "CAB" #4096FX denotes a 3-byte packed-decimal value of +4096 #20202021204B2020X denotes an 8-byte special editing value
Certain non-graphic characters may be included in a quote string using the accent escape character followed by a graphic character. Here is a table showing the character which follows the accent to obtain the non-graphic character, shown in hexadecimal below the character.
Table of Accent (`) Characters. . . A B C D E F G H I J K L M N O . #01 #02 #03 #37 #2D #2E #2F #16 #05 #25 #0B #0C #0D #0E #0F . . P Q R S T U V W X Y Z % [ > ? . #10 #11 #12 #13 #3C #3D #32 #26 #18 #19 #3F #17 #27 #28 #09 . . ! $ * ) ; ~ 0 1 \ / ` : # @ ] . #04 #14 #15 #2B #55 #FA #20 #21 #22 #07 #79 #08 #09 #00 #1D . . = ^ . < ( + & " . #2C #35 #1E #1F #1B #1C #0A #7F
#20202021204B2020X could be given as: "`0`0`0`1`0.`0`0".
Comments may appear freely throughout a PL360 program. A comment is written as the reserved identifier COMMENT followed by a delimiter character and any number of characters except a semicolon, and is terminated by a semicolon (;). A comment may also be written as any number of characters excluding the vertical bar (|) between vertical bars. As with quote strings, the character $ may not appear in column 1 of a card; otherwise a non-quoted $ signifies end-of-card.
|This is a comment| $ rest of card is comment COMMENT This is the end of Chapter 2.;
Which of the following are not valid identifiers, and why?
(a) IDENTIFIER (b) 2X (c) A.B (d) Samson (e) EASY2READ (f) ICU2 (g) #F
Which of the following are not valid constants, and why?
(a) #AX (b) 2,124,587 (c) "This must be right!" (d) .2L (e) _5'_6L (f) #8A S (g) PI (h) #FACES
What is the 'type' of the valid constants in Problem 2 above? Specify INTEGER, SHORT INTEGER, LONG REAL, STRING, etc.
The addressing mechanism of the System/360 computers is such that instructions can indicate the absolute address of stored information only relative to a base address contained in a register. The difference D between the desired absolute address and the available base address must satisfy: 0 <= D < 4096. To accomplish this, a PL360 program may be subdivided into individually identified parts, called segments. Every quantity requiring storage space and defined within a program is known by the segment in which it occurs and by its displacement relative to the origin of that segment. The problem then consists of subdividing the program and choosing base registers so that proper instructions can be generated. Of course, the number of times base addresses must be reloaded into base registers should be kept reasonably small; and therefore, the programmer is encouraged to organize the program in such a way as to minimize the number of cross-references between segments by explicitly stating which parts of the program constitute segments.
One natural division is between the instructions of a program and the data operated upon by the instructions. Therefore, in PL360, the program is divided into program segments (instructions) and data segments (data areas). There are two benefits in doing this. First, there are many instances where it is desirable that program and data areas be kept apart as separate entities, such as in reentrant programs. Second, the programmer's knowledge about segment sizes and occurrences of cross-references differs for program and data areas. In the program area case, his knowledge about the eventual size of the compiled program segment is only vague; whereas in the data area case, he knows exactly the amount of storage space needed for declared quantities, and he knows precisely in which places in the program these quantities are referenced.
For the purposes of illustration, we shall assume a main program consists of only one program segment and one data segment. We shall assign the program segment base register R15, and the data segment base register R13. These are the base register assignments assumed by the PL360 compiler unless the compiler is explicitly directed to assign other base registers.
Every segment generated by the PL360 compiler must be given a name. This name either is explicitly specified by the programmer, or is generated by the compiler. All generated names are seven characters long, the first three of which are usually SEG and the last four are always the letter N and three decimal digits. Generally the programmer is not concerned about what names are generated, as long as the result is valid and acceptable. But it is important to note that every segment is named.
In our forthcoming examples, neither the program segment nor the data segment will be explicitly named; and therefore, compiler generated names will be used to name each segment. Thus, SEGN001 and SEGN000 will be assigned to the program and data segments respectively.
The PL360 compiler is a program which, like most computer programs, performs essentially three functions: input, processing, and output.
The input consists of an ordered set of card images upon which is inscribed, in PL360 language, the declarations and statements of a problem program.
The processing is that of compiling or translating the input PL360 program into System/360 machine language or computer instructions.
The output consists of a set of card images, called object decks, each deck describing one segment of the compiled program. The name of the segment, the amount of space the segment would occupy in main storage, and the System/360 machine instructions or other data which compose the segment are all part of this description. Also, information relating to cross-references between segments is included.
The compiler also outputs a listing or printout of the original program along with other information relating to the compilation, such as any error diagnostics which may have occurred.
The object decks are later processed by another program, called a loader. It is the loader's task to load or place into main storage all the segments of the compiled program, resolving cross-references between segments. The loader then transfers control to the starting address of the main program segment, initializing the base register (R15) to that starting address. The program then proceeds to execute.
A block begins with the reserved identifier BEGIN and ends with the reserved identifier END. A block contains declarations, statements, and labels, composed of identifiers, basic symbols, constants, and strings. All declarations must come first in a block, each declaration being terminated by a semicolon (;). Then, statements and labels may occur, each statement terminated by a semicolon (;), and each label terminated by a colon (:). The general form of a block is:
BEGIN D; D; D; ... S; S; S; END
where the D's represent declarations and the S's represent statements. A label (L:) may occur anywhere a statement (S;) may occur. Labels serve to designate branch points within a block, and GOTO statements refer to such points. The two main purposes of a block are:
The following list includes all possible PL360 declarations: ------------------------------- | 1. data segment declarations | | 2. cell declarations | | 3. cell synonyms | | 4. register synonyms | | 5. integer value synonyms | | 6. function declarations | | 7. procedure declarations | -------------------------------
Declarations serve to define data segments and identify storage cells and other quantities. The following is a sample cell declaration which defines alpha, beta, and gamma as integer cells.
INTEGER ALPHA, BETA, GAMMA;
Note: although the compiler allows identifiers in mixed case, reserved and pre-declared identifiers will be shown in upper case, and some user-defined identifiers will be shown in lower case to indicate that no specific identifier is intended.
The following list includes all possible PL360 statements:
------------------------------------ | 1. block | | 2. register assignment statement | | 3. cell assignment statement | | 4. GOTO statement | | 5. IF statement | | 6. WHILE statement | | 7. REPEAT/UNTIL statement | | 8. FOR statement | | 9. CASE statement | | 10. function statement | | 11. procedure statement | | 12. NULL statement | ------------------------------------
Notice that a block is an allowed statement; that is, blocks may be nested one inside another. The following two lines serve to illustrate the concept:
BEGIN D; D; D; ... S; S; block; S; S; END (outer block) BEGIN D; D; ... S; S; END (inner block)
The second line is called the 'inner block' in relation to the first line, and it replaces the word 'block' of the first line. The first line is called the 'outer block' in relation to the second line. Therefore, the inner block is contained within the outer block.
Since other declarations may occur within inner blocks, the question of the 'scope' of a declaration presents itself. (The scope of a declaration is that portion of the program where the declared quantities of a block may legitimately be referenced.)
The restriction is that the scope of any declared quantity is limited to the block in which the declaration occurs and any inner blocks, unless re-declared within an inner block. The declared quantity is known from the point where the declaration occurs within a block through to the END of that block. It is not known outside that block.
Every declared quantity must also be unique to a block. That is, the same identifier may not be declared more than once within a block. However, an inner block may re-declare an identifier, in which case the new meaning for that identifier holds from the point of re-declaration through to the END of that inner block, and then the old or original meaning resumes within the outer block.
The following 5-block example should clarify most of the points covered thus far. The BEGIN's and END's of each block are joined together by a numbered line solely for the purposes of illustration, and only cell declarations are included in the example. Other declarations and statements are left out to keep the example simple, but '....;' shows where they might occur.
| BEGIN INTEGER ALPHA, SAVE; | ....; | | BEGIN REAL BETA, SAVE; | | ....; | | | BEGIN INTEGER GAMMA; 1 | 2 | 3 | ....; | | | END; | | ....; | | | BEGIN INTEGER SAVE; | | 4 | ....; | | | END; | | ....; | | END; | ....; | | BEGIN REAL DELTA; | 5 | ....; | | END; | ....; | END. ALPHA (declared in block 1) is known throughout all blocks. BETA (declared in block 2) is known only in blocks 2, 3, and 4. GAMMA (declared in block 3) is known only in block 3. DELTA (declared in block 5) is known only in block 5. SAVE is declared 3 times; once in block 1, and again (re-declared) in block 2, and again (re-declared) in block 4. The scope of each is: SAVE of block 1 is known in blocks 1 and 5 only. SAVE of block 2 is known in blocks 2 and 3 only. SAVE of block 4 is known in block 4 only. Note that although SAVE is declared INTEGER in both blocks 1 and 4, they are NOT the same SAVE. A reference to SAVE in any block is a reference to the only SAVE known to that block and no other.
A main program is defined as a block followed by a period (.). The preceding 5-block example represents a main program since it terminates with a period. Comments may occur anywhere before the terminating period.
Assuming no other declarations in the 5-block example just given, all the cell declarations would be collected together by the compiler to form a single data segment which could be diagrammed as:
------------------- | ALPHA | |-------------------| | SAVE (of block 1) | |-------------------| | BETA | |-------------------| | SAVE (of block 2) | |-------------------| | GAMMA | |-------------------| | SAVE (of block 4) | |-------------------| | DELTA | -------------------
This sample data segment would be assigned some base register which, as stated earlier, we shall assume is register R13. Each cell declared within this data segment then would have some relative address depending upon the number of bytes of storage space required by all cells which precede it. (Reread section 1.5, Relative Addressing, if necessary.)
All of the statements in our 5-block example generate System/360 computer instructions that compose our main program segment. This segment also requires a base register which, as stated earlier, we shall assume is register R15.
Constants and strings referenced by PL360 statements, and which cannot be contained within generated instructions, are placed at the end of the program segment following all generated instructions. These constants and strings are called 'literals', and the collection of all literals at the end of a program segment is called the 'literal pool'. The base register associated with these literals is the program segment's base register, not a data segment's base register as might be assumed.
Under certain circumstances, which will be covered more completely in Chapter 8, a block may define a program segment separate from the main program segment. Usually this further subdividing of the program into more than one program segment is done when the amount of code generated by the problem program would exceed 4096 bytes. Every such program segment is a logically self-contained entity having its own literal pool. Only those literals referenced by the statements of a particular program segment are contained in that segment's literal pool.
The following is a complete main program. Although a majority of this program involves material not yet covered, it is being presented at this point to give you some idea of what PL360 programs look like. The problems in this exercise use this program, but only material covered thus far is used in these problems.
|01| BEGIN COMMENT -- THIS IS A SAMPLE PROGRAM WHICH |02| * READS IN THE DIMENSIONS OF A BOX, COMPUTES THE |03| * VOLUME, AND WRITES THE RESULT. --; |04| |05| COMMENT -- DECLARE FUNCTIONS AND VARIABLES --; |06| FUNCTION REDUCE(6,#0600); |-- SUBTRACT 1 FROM REGISTER --| |07| |08| ARRAY 133 BYTE INPUT = (" INPUT DIMENSIONS ARE:",111(" ")); |09| BYTE CARD SYN INPUT(23); |-- CARD PART OF INPUT --| |10| ARRAY 133 BYTE OUTPUT = (" VOLUME =",124(" ")); |11| BYTE ANSWER SYN OUTPUT(10); |-- ANSWER PART OF OUTPUT --| |12| |13| COMMENT -- MAIN PROGRAM CODE --; |14| WHILE R0 := @CARD; READ; = DO |-- PROCESS CARD --| |15| BEGIN R0 := @INPUT; WRITE; |-- SHOW INPUT DATA --| |16| R1 := @CARD; R2 := 1 =: R5; R3 := 3; |17| WHILE R3 > 0 DO |-- COMPUTE VOLUME --| |18| BEGIN BCDTOVAL; R5 := R5 * R0; REDUCE(R3); |19| END; COMMENT -- NOW GENERATE ANSWER --; |20| R1 := @ANSWER; R3 := 7; R0 := R5; VALTOBCD; |21| R0 := @OUTPUT; WRITE; |-- OUTPUT THE ANSWER --| |22| END; |23| END.
Specify the line numbers of the BEGIN and END of all blocks. Line numbers are shown on the left side of the program as |nn| comments.
Integer and string constants are used in this program. Which lines contain string constants? (See section 2.5.4.)
Using section 2.3,
This chapter covers all PL360 declarations except function and procedure declarations which will be covered later in separate chapters. As for the others, data segment declarations define data segments, and cell declarations define the structure and content of data segments. Cell synonyms provide an additional cell naming capability, such as declaring that X and Y are the same cell. Register synonyms allow alternate names for the standard registers, such as stating that ZERO and R0 refer to the same register. And integer value synonyms provide the programmer with the capability of giving a name to an integer value, an integer constant expression.
As program segments are composed of computer instructions generated by statements, so also data segments are composed of cells defined by cell declarations. And as statements are bound into program segments by blocks (BEGIN through END), so also cell declarations are bound into data segments by data segment declarations.
Let us first examine data segment declarations. There are six declarations that may begin or initiate a data segment, and one declaration that may end or terminate any data segment. Of the six declarations that begin data segments, five will be discussed here, and the last, called a DUMMY segment, will be discussed separately in the section on DUMMY BASE declarations.
Each of the first five data segment declarations is given below in general syntactic form, where 'name' is an identifier naming the segment, and Rn indicates the selected base register, R0 through R15. (Note: only the first eight characters of a 'name' longer than eight characters are significant in naming a segment.) Remember that each of these declarations is terminated by a semicolon.
--------------------------------- | 1. COMMON DATA name BASE Rn; | | 2. COMMON BASE Rn; | | 3. SEGMENT BASE Rn; | | 4. GLOBAL DATA name BASE Rn; | | 5. EXTERNAL DATA name BASE Rn; | ---------------------------------
Each of the above declarations specifies three things:
(a) the type of data segment (b) the name of the data segment (possibly compiler defined) (c) the base register to be assigned to the data segment
Every data segment is assigned a base register, and that register must be an integer register, R0 through R15, other than the current program segment's base register. At the point in the block where these data segment declarations occur, an instruction is generated in the corresponding program segment to load the specified base register with the base address of the declared data segment. In assembly language, the instruction generated would correspond very closely to:
Of course, R0 is not allowed to be a base register by the hardware, so no instruction is generated when R0 is specified. We shall cover such usage at the end of DUMMY BASE declarations.
It is the programmer's responsibility to insure the integrity of this data segment base register; that is, the register must contain the base address of the data segment whenever cells declared within that segment are referenced by subsequent statements. Of course, the contents of the register could be saved, the register used for some other purpose, and then the contents restored. But if cells are referenced that depend on a base address and the base register does not contain that address, then either the program will not execute properly or it will abnormally terminate.
The COMMON declarations define what is called a 'common area'. This type of area is normally used in conjunction with FORTRAN programs. The first declaration specifies labeled or named common; and the second declaration specifies unlabeled or blank common (so called because the data segment name is all blank). The same (named) common area may be declared more than once, usually by separately compiled programs, such as a PL360 program and a FORTRAN program. All would refer to the same storage area identified by their common name.
The SEGMENT BASE and GLOBAL DATA declarations both create data segments that must be unique. The only difference between these two declarations is that for the SEGMENT BASE declaration, the compiler generates the data segment name; whereas for the GLOBAL DATA declaration, the programmer explicitly specifies the data segment name.
The EXTERNAL DATA declaration allows reference to GLOBAL DATA segments either created in separately compiled programs to be run with this program, or created elsewhere in this same program. EXTERNAL DATA declarations do not create an actual data segment, but only refer to a data segment created by a GLOBAL DATA declaration somewhere else. As such, the cell declarations that compose an EXTERNAL DATA segment serve only to define the structure of the segment and not the content.
Normally, data segments are declared early in the program so that the cells which compose them may be referenced over the widest range of statements. Thus, for example, if a GLOBAL DATA segment were declared early in a program (in the outermost block), then the need to refer to that same data segment by an EXTERNAL DATA declaration is greatly reduced. However, an EXTERNAL DATA declaration could serve to re-establish the base address of a previously defined GLOBAL DATA segment by writing a statement of the form:
BEGIN EXTERNAL DATA name BASE Rn; END
All of the above data segment declarations either create or refer to data segments that occupy specific areas of main storage. The instructions generated in the program segments load the absolute address of the start or base of these data segments into their associated base registers. The cross-references from program segments to these data segments are resolved by a loader program as described in section 3.3, Compilation and Execution.
One other declaration may be used to define a data segment. It is the DUMMY BASE declaration, and has the general syntactic form:
This declaration is discussed separately because it differs substantially from the preceding declarations. For example, dummy segments do not have a name, not even a compiler generated name. Also, no instruction is generated in the program segment to load the base register (Rn) specified. In fact, dummy segments neither create nor refer to any specific area of main storage!
A dummy segment acts as a template or overlay for any desired section of main storage. The cell declarations composing such a segment define only the structure of the template, never the contents of storage.
To use a dummy segment, the program computes, or is given as a parameter during execution, the address of some section of main storage which is assumed to have the same structure as that defined by the dummy segment. Statements of the program then place this address into the base register (Rn) of the dummy segment before other statements reference the cells of that segment.
If the address is changed or replaced by a new address, then the dummy segment or template is moved to overlay a different section of storage beginning at the new address.
For example, let us assume that some section of storage contains telephone book entries, each composed of a 32-character name, a 32-character address, and an 8-character telephone number, in that order. Then the following dummy segment could be used to reference the component parts of any individual entry once the dummy segment's base register (in this case, R7) has been set to the origin address of an entry.
DUMMY BASE R7; ARRAY 32 CHARACTER NAME, ADDRESS; ARRAY 8 CHARACTER TELEPHONE; CLOSE BASE; (We shall cover CLOSE BASE and cell declarations shortly.)
There is one special case of base register specification for any data segment, which is especially useful in DUMMY BASE declarations. That is when R0 is specified as the base register, which really implies no base register at all, since R0 can never be used as a base or index register (see section 1.4, Instructions and Addressing). The cell declarations of such a segment supply only relative displacement values. The base registers for these cells are supplied explicitly by the programmer when these cells are referenced. Therefore, the programmer may choose one or more base registers (R1 through R15) with which to make references to these cells. For instance, if the dummy segment in our preceding telephone book example was declared DUMMY BASE R0 instead of DUMMY BASE R7, then an 8-character telephone number (or other component of the segment) could be referenced using any base register containing the address of an entry. In fact, two entirely separate entries could be referenced simultaneously (such as when comparing two telephone numbers) using two registers, each containing the address of one entry.
All cell declarations are associated with data segments; that is, once a data segment has been initiated by one of the preceding declarations, the data segment is open to accepting cell declarations. The first cell declared in a data segment is assigned a displacement value of 0. Succeeding cells are assigned higher displacement values.
While a data segment is open, a new data segment may be initiated; that is, data segments may be nested one inside another. Cell declarations would then be associated with the newly opened data segment.
A data segment is closed to accepting further cell declarations upon encountering the END of the block in which the data segment was initiated; but the CLOSE BASE declaration may be used to close a data segment before the END. The CLOSE BASE declaration serves only to prevent subsequent cell declarations (if any) from being associated with the closed data segment. The scope of the cell declarations (composing the data segment) is not affected. The CLOSE BASE declaration is simply the reserved identifiers:
When the main program is defined as a block terminated by a period (BEGIN ... END.), the compiler initiates a data segment which could be described:
SEGMENT BASE R13; ARRAY 18 INTEGER B13;
The cell declaration (for B13) beginning the data segment provides a save area for linkage to other programs and subroutines. Other types of main programs will be discussed in Chapter 8, Procedures.
The structure of a data segment is defined by the cell declarations that compose it. There are five basic types of cell declarations, each type defining the number of bytes of storage to be reserved for the declared cell as specified in the table below.
TYPE BYTES RESERVED ---- -------------- BYTE (or CHARACTER) 1 SHORT INTEGER 2 INTEGER (or LOGICAL) 4 REAL 4 LONG REAL 8
The general form of a cell declaration is:
where 'Type' is one of the basic types specified in the table above, and the id's are cell identifiers. For example, the following data segment and cell declarations would define the structure of some storage area as diagrammed below. (We saw this diagram in Figure 3 in Chapter 1.)
SEGMENT BASE R7; INTEGER XX1; SHORT INTEGER XX2, XX3; LONG REAL XX4; INTEGER XX5, XX6, XX7; CLOSE BASE;
----------- R7 ------> 0 | XX1 | |-----------| 4 | XX2 | XX3 | |-----------| 8 | XX4 | 12 | | |-----------| 16 | XX5 | |-----------| 20 | XX6 | |-----------| 24 | XX7 | ----------- 4 bytes wide FIGURE 5.
The data segment declaration specifies register R7 as the base address register of this area. That is, it defines the value of the B-field of any instructions that refer to the declared cells. The cell declarations in turn specify the D-field of such instructions. (See Instructions and Addressing in Chapter 1, if necessary.)
The displacement value associated with any declared cell is determined by the total number of bytes of storage space reserved by preceding cell declarations of the data segment, and by the alignment rule associated with the type of the cell being defined.
The alignment rule simply states that the displacement value assigned for a cell must be an integral multiple of the basic cell length: 1, 2, 4, or 8 bytes depending on the type. The alignment rule is always applied before the compiler reserves the space associated with the basic cell type. The following example should demonstrate the concept.
DUMMY BASE R2; SHORT INTEGER S; INTEGER X; BYTE B; INTEGER Y; CLOSE BASE;
------- R2 ------> 0 | S |///| |-------| 4 | X | |-------| 8 |B|/////| |-------| 12 | Y | ------- FIGURE 6.
The slashed areas (/) of the diagram are not exactly waste space, because they can be referenced as we shall see later. However, a tighter packing of the area would result by ordering the declarations as follows:
DUMMY BASE R2; INTEGER X, Y; SHORT INTEGER S; BYTE B; CLOSE BASE;
The area would then appear as:
------- R2 ------> 0 | X | |-------| 4 | Y | |-------| 8 | S |B|/| ------- FIGURE 7.
The various types not only define the number of bytes of storage to be reserved, but also the manner in which the cells are likely to be used. We shall cover this concept in detail in the next chapter.
Each basic cell declaration may be preceded by the reserved word ARRAY followed by a non-negative integer value. An array declaration applies to every cell identifier of the declaration, and it causes the amount of space reserved for each cell to be n-times the basic cell length, where 'n' is the non-negative integer value. For example:
ARRAY 4 BYTE NAME, ADDRESS;
indicates that four bytes of space must be reserved for both NAME and ADDRESS. The compiler does not 'remember' array declarations, but simply reserves the requested space. Of course, alignment is always done before space is reserved. An ARRAY 0 type of declaration reserves no space of its own; it only aligns the declared cell to the appropriate boundary. Thus:
ARRAY 0 LONG REAL Y; ARRAY 2 INTEGER X;
causes X and Y both to begin at the same double-word boundary. The declaration of Y causes the alignment, and the declaration of X reserves the actual space.
Changing the cell declarations given with Figure 6 by including arrays, we can create exactly the same area as shown below.
DUMMY BASE R2; ARRAY 2 SHORT INTEGER S; INTEGER X; ARRAY 4 BYTE B; INTEGER Y; CLOSE BASE; ------- R2 ------> 0 | S | | ------- 4 | X | ------- 8 |B| | | | ------- 12 | Y | ------- FIGURE 8.
Cells of an array are normally referenced by displacements which are a multiple of the basic cell length, starting with 0. Thus, the four bytes of B would be referenced as B(0), B(1), B(2), and B(3). The two short integers of S would be referenced as S(0) and S(2). Note that a reference to S alone is NOT a reference to the entire array! It is only a reference to S(0). Also, S(4) would be a reference to the first halfword of integer X, and S(6) would be a reference to the second halfword of X. Similarly, X(_4) is a reference to all of the declared S array (two short integers = one integer). We shall cover cell references in more detail in section 4.4.
Cell declarations of SEGMENT BASE and GLOBAL DATA areas may be initialized at compile time to specific values. Then, when these data segments are loaded into core, the initialized cells will contain the specified values. Non-initialized cells could contain unpredictable values unless the operating system initializes core.
Cell initialization is specified by following a cell identifier with an equal sign (=) and a predefined value or list of values. For example:
INTEGER X = 27, SIX = 6;
Cell identifiers declared to be an array of 'n' cells may be initialized with a list of up to 'n' values. The values must be separated by commas and the entire list must be enclosed in parentheses. For example:
ARRAY 12 INTEGER TERMS = 10, TABLE = (1,4,4,4,5,6,1,4,4,4,5,6);
In the above example, only the first reserved integer of TERMS is initialized and the remaining 11 reserved integers of TERMS are undefined. All of TABLE is initialized by a parenthesized list of values (12 integers). Any parenthesized initialization list may be preceded by a positive (non-zero) integer replication factor, and any individual value of a list may be replaced by a parenthesized list of values optionally preceded by such a replication factor. Therefore, the preceding example could be rewritten as follows:
ARRAY 12 INTEGER TERMS = 10, TABLE = 2(1,3(4),5,6);
In all the examples given thus far, the type of the declared cell has been the same as the type of the initializing values. If the initializing values are fixed-length values, then the cell type determines the amount of space initialized by each value. This is made possible by the fact that all fixed-length values are maintained within the PL360 compiler as fullword (fixed-point) or double-word (floating-point) values regardless of their specific type. Therefore, an array of byte cells may be initialized by a list of integer values, and only the lower byte of each value is used in doing the initialization. Similarly, an array of integer cells may be initialized by a list of short integer values with each short integer value sign-extended to fill the integer cell. For example:
ARRAY 4 BYTE MARKS = (1,5,0,7); INTEGER RATION = 9S; ----------- MARKS |01|05|00|07| |-----------| RATION |00 00 00 09| -----------
Since the PL360 compiler does not check the type of an initializing fixed-length value against the type of the cell being initialized, it is possible to initialize INTEGER cells with floating-point values or REAL cells with fixed-point values. In general however, cells should be initialized with values of compatible type.
If the initializing value is a variable-length string, then the string determines the amount of space initialized regardless of cell type. Strings are never truncated or expanded. Each character occupies one byte starting with the left-most byte. For example, an 8-byte string would completely initialize any LONG REAL, ARRAY 2 INTEGER, ARRAY 4 SHORT INTEGER, or ARRAY 8 BYTE cell declaration.
SHORT INTEGER and INTEGER cells may be initialized to the Base-Displacement field of a previously declared cell as follows:
SEGMENT BASE R3; INTEGER SOMECELL; SHORT INTEGER POINT = @SOMECELL; CLOSE BASE;
The value initializing POINT would be equivalent to #3000S since 3 is the Base field value and 000 is the Displacement field value for SOMECELL.
INTEGER cells may also be initialized to the absolute address of a previously declared cell. The initialization is done by the Loader. Absolute address initialization is indicated by using the @@-symbol. The general form of absolute address initialization is:
INTEGER cellname = @@prevcell(disp/flags)
where "disp" is an additional displacement added to the absolute address of prevcell, and "flags" are a byte-sized value that will be placed in the upper byte of the integer along with the 3-byte absolute address. Such usage is restricted to 24-bit addressing. For example:
SEGMENT BASE R4; INTEGER SOMECELL; INTEGER PARMPTR = @@SOMECELL(0/#80); CLOSE BASE;
PARMPTR will contain the absolute address of SOMECELL when loading is completed, with hex-80 in the upper byte.
The @@-symbol may also be used to initialize INTEGER cells to the absolute entry point address of previously declared procedures. Also, the @-symbol may be used to initialize SHORT INTEGER and INTEGER cells to the program segment Base and relative Displacement of previously declared procedures. We shall cover procedures in depth in Chapter 8.
DUMMY BASE and EXTERNAL DATA areas may also show cell initialization, but such areas are not actually initialized. Any initialization shown for cells of such areas only serves as documentation of what is expected, not of what may actually exist.
Arrays may also be declared such that the amount of space they take is determined by the amount of initialization given.
ARRAY DATAFILL celltype cellname = (fill); or ARRAY DATAFILL celltype = (fill); |-- No cellname --|
The amount of "fill" determines the amount of space. For example:
ARRAY DATAFILL INTEGER MARKERS = (1,3,5,9); and ARRAY 4 INTEGER MARKERS = (1,3,5,9);
would produce the same storage results. If there is no fill, then ARRAY DATAFILL acts like ARRAY 0. DATAFILL is a compiler pre-declared EQUATE whose value is not likely to be given as an ARRAY size.
DSTAR is a pre-declared data cell address definition of base-displacement form corresponding to the current Bddd location in the data segement. DSTAR can be used to compute the length of data regions. For example:
ARRAY DATAFILL INTEGER MARKERS = (1,3,5,9); EQUATE MARKLEN SYN DSTAR - MARKERS; |-- length in bytes --|
Since both DATAFILL and DSTAR are pre-declared, you may redeclare them, but then you would lose the fill-defined array capability or relative data location capability. The compiler is not checking the names, just the special values associated with them.
DATAFILL is very handy when defining string arrays:
ARRAY DATAFILL BYTE MESSAGE = ("This is a message"); EQUATE MSGSIZE SYN STRING; |-- Length of array --|
The following examples demonstrate the points covered by this section on cell initialization.
ARRAY 120 BYTE ITEMS = ( 5, "Items", 8, "Examples", 4, "Help"); ARRAY 132 BYTE BLANKS = 132(" "), BUFF = 33(" ",2("*")," "); INTEGER WORD = "word"; INTEGER WORDLOC = @@WORD; ARRAY 3 SHORT INTEGER INSTRUCTION = (#D200,@BUFF,@BLANKS);
Once cells have been declared, they may be referenced in subsequent declarations and statements. For each cell identifier, the compiler remembers the cell's Index-Base-Displacement, i.e., its X-B-D fields. Normally, the X-field is 0, the B-field is Rn of the Data Segment Declaration containing the declared cell, and the D-field is the relative displacement of the start of the declared cell within the data segment.
A cell reference allows the programmer to modify the X-B-D field and directs the PL360 compiler to use the resultant X-B-D field in satisfying the declaration or statement containing the reference.
Several forms of cell reference are shown below, the last of which indicates a non-subscripted (unmodified) cell reference.
identifier(Rb+Rx+k) or identifier(Rb+Rx-k) identifier(Rb+k) or identifier(Rb-k) identifier(Rb) or identifier(Rx) identifier(Rx+k) or identifier(Rx-k) identifier(k) identifier
Rb is allowed if the referenced cell has 0 for its B-field. In such a case, Rb replaces the B-field. DUMMY BASE R0 cells are examples of such cases. Rb defines the BASE register and should not be R0.
Rx is allowed if the referenced cell has 0 for its X-field. In such a case, Rx replaces the X-field. Rx defines the INDEX register and should not be R0.
'k' is either a single integer value or an expression of added and/or subtracted integer values. Each integer value is either added to or subtracted from the D-field to create a new D-field which must satisfy 0 <= D < 4096.
It is important to note that the D-field is always a byte displacement and that 'k' is an adjustment in byte amounts. Therefore, given the following integer array:
ARRAY 5 INTEGER SPACE;
each element of the array takes four bytes and would be referenced as follows:
SPACE(0) or SPACE SPACE(4) SPACE(8) SPACE(12) SPACE(16)
Throughout the remainder of this text we shall use the term 'cell' to indicate a cell reference regardless of cell type. Also, whenever the X-field of a cell is non-zero, the cell is 'indexed'.
Cell synonym declarations provide the programmer with the ability to associate another identifier with a previously declared cell. The general form of a cell synonym declaration is:
where 'Type' is one of the basic cell types as given in section 4.3, i.e., BYTE, SHORT INTEGER, INTEGER, REAL, and LONG REAL.
The 'Type' of the newly declared identifier need not match the type associated with the referenced cell. In fact, the PL360 compiler does not check either type or alignment. Therefore, the programmer may define different types of cells having the same address. For example:
DUMMY BASE R5; ARRAY 3 INTEGER Y; CLOSE BASE; INTEGER YINDEX SYN Y(R3); SHORT INTEGER YHI SYN Y, YLO SYN Y(2);
The Index-Base-Displacement fields for the above are:
Y 05000 YINDEX 35000 YHI 05000 YLO 05002
'Type' may be preceded by 'ARRAY n', but the compiler will ignore the array portion of the declaration because cell synonym declarations never reserve any actual space. Using ARRAY in such a case is more for documentation purposes than anything else. However, if the SYN portion of the declaration were removed, the declaration would become a standard cell declaration which does reserve space. For example:
ARRAY 132 BYTE OUTPUT; ARRAY 80 BYTE CARD SYN OUTPUT(1); |-- CARD is 80 bytes of OUTPUT beginning at 2nd byte --|
Cell synonym declarations also allow direct definition of the X-B-D field of a cell identifier by using an integer value in place of a cell reference. For example, the definition of Y above could have been done as follows:
INTEGER Y SYN #5000;
There is a set of pre-declared cell identifiers that may be used in defining other identifiers. They are:
MEM, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15
These identifiers are given below using declarations that would yield equivalent definitions to those pre-declared by the PL360 compiler.
DUMMY BASE R0; INTEGER MEM; CLOSE BASE; DUMMY BASE R1; INTEGER B1; CLOSE BASE; DUMMY BASE R2; INTEGER B2; CLOSE BASE; DUMMY BASE R3; INTEGER B3; CLOSE BASE; DUMMY BASE R4; INTEGER B4; CLOSE BASE; DUMMY BASE R5; INTEGER B5; CLOSE BASE; DUMMY BASE R6; INTEGER B6; CLOSE BASE; DUMMY BASE R7; INTEGER B7; CLOSE BASE; DUMMY BASE R8; INTEGER B8; CLOSE BASE; DUMMY BASE R9; INTEGER B9; CLOSE BASE; DUMMY BASE R10; INTEGER B10; CLOSE BASE; DUMMY BASE R11; INTEGER B11; CLOSE BASE; DUMMY BASE R12; INTEGER B12; CLOSE BASE; DUMMY BASE R13; INTEGER B13; CLOSE BASE; DUMMY BASE R14; INTEGER B14; CLOSE BASE; DUMMY BASE R15; INTEGER B15; CLOSE BASE;
or INTEGER MEM SYN 0, |-- cell defined by integer value --| B1 SYN MEM(R1), B2 SYN MEM(R2), B3 SYN MEM(R3), B4 SYN MEM(R4), B5 SYN MEM(R5), B6 SYN MEM(R6), B7 SYN MEM(R7), B8 SYN MEM(R8), B9 SYN MEM(R9), B10 SYN MEM(R10), B11 SYN MEM(R11), B12 SYN MEM(R12), B13 SYN MEM(R13), B14 SYN MEM(R14), B15 SYN MEM(R15);
All of these pre-declared cell identifiers may be re-declared to be anything desired by the programmer; however, this is not recommended. The ARRAY 18 INTEGER B13; declaration shown in section 4.2 actually re-declares B13 to be the same as its pre-declared definition. If the declaration had been ARRAY 9 LONG REAL B13; then B13 would have been redefined.
Register synonyms serve to associate identifiers with registers, i.e., to define other identifiers for registers besides the standard register identifiers listed in section 2.3.
There are three types of registers: INTEGER, REAL, and LONG REAL. The general form of a register synonym declaration is:
where 'Type' is either INTEGER, REAL, or LONG REAL and 'reg' is a pre-declared or previously declared register identifier of matching type. For example:
INTEGER REGISTER ZERO SYN R0, Z0 SYN ZERO; REAL REGISTER FLOAT SYN F2;
A register synonym identifier may be used anywhere a standard register identifier may be used, throughout the scope of the register synonym declaration. For example:
INTEGER REGISTER RECORD SYN R1; DUMMY BASE RECORD;
The DUMMY BASE declaration is equivalent to:
DUMMY BASE R1;
Frequently, registers are used in place of storage cells, and therefore register synonym identifiers serve better to define the nature of these 'pseudo cells' than do the standard register identifiers. Also, registers that serve a specific purpose in the program, such as the base address register of some data, are often given register synonym identifiers. But regardless of what the intended purpose may be, the programmer should not be led to believe that a different name implies a different physical entity; IT DOES NOT. One of the most common programming errors is the failure to recognize that fact.
Although the pre-declared register identifiers may be re-declared as anything desired by the programmer, imagine the confusion that could cause! It is therefore strongly recommended that the standard register identifiers be treated as though they were basic symbols.
Throughout this text, any references to the pre-declared register identifiers apply to register synonyms as well.
Integer value synonyms are commonly referred to as 'equates' or 'equate declarations'. These declarations serve to associate identifiers with integer values or integer value expressions which are computed by the PL360 compiler at the time the declaration is processed. These integer value identifiers may be used anywhere integer values are used, and their definition remains in effect throughout the scope of the declaration. (See section 2.5.1 for the definition of integer values.)
The general form of an integer value synonym declaration is:
where each 'exp' may be of the form
start operator iv operator iv ... operator iv or start
where 'iv' represents any pre-declared, previously declared, or self-defining integer value; and 'start' represents one of the following eight ways to start an equate's expression:
iv ABS iv DEC iv NEG iv NEG ABS iv cella - cellb "string" regname
The pre-declared words ABS, DEC, NEG and NEG ABS are called monadic operators. They indicate taking the absolute value, decrement by 1, sign inversion, or sign inversion of the absolute value of the integer value which follows.
The cella-cellb form represents a value which is the difference between the D-fields of two pre-declared or previously declared cells. The only restriction is that the X and B-fields must be identical for the two referenced cells.
The "string" form is limited to a character string of from one through four characters. The value defined by the "string" is the EBCDIC representation of the characters right-justified and zero-filled to the left if less than four characters. Thus,
EQUATE ACTSTR SYN "A123", SEPCHAR SYN ".";
is equivalent to defining the following:
EQUATE ACTSTR SYN #C1F1F2F3, SEPCHAR SYN #0000004B;
The regname form specifies a register identifier and the value is taken to be the number of the specified register. Thus,
EQUATE X SYN R1, Y SYN R5;
is equivalent to defining the following:
EQUATE X SYN 1, Y SYN 5;
Register synonyms are typically used in the regname form since the actual register number may vary depending on the synonym.
Once the expression has been started, it may be continued by as many occurrences of 'operator iv' as desired. The allowed operators are:
(a) Arithmetic (see section 1.3) + addition - subtraction * multiplication / division ++ logical addition (unsigned) -- logical subtraction (unsigned)
(b) Logical (see section 1.6) AND conjunction OR inclusive injunction XOR exclusive injunction
(c) Shifting (see section 1.7) SHLL shift left (multiply by 2 raised to iv power) SHLA (same as SHLL) SHRL shift right (divide by 2 raised to iv power) SHRA (same as SHRL)
There is no operator precedence! The expression is evaluated in a strictly left-to-right sequence.
There are a number of pre-declared equates which we shall find useful in the next few chapters. They are shown below using declarations equivalent to the compiler's default declarations.
EQUATE OVERFLOW SYN 1, ON SYN 1, MIXED SYN 4, OFF SYN 8, CARRY SYN 3, FALSE SYN 0, TRUE SYN _1; EQUATE DATAFILL SYN #87654321; EQUATE STRING SYN 0; |-- varies during compilation --|
The last definition, for STRING, cannot be truly duplicated because the value of STRING varies during compilation. Its initial value is 0, but every time the compiler processes a "string" the value of STRING becomes the length (in bytes) of that "string". DATAFILL is a special value used to assign array size by content.
There is also a SHORT EQUATE declaration that provides SHORT INTEGER equivalents. The values defined will be treated like integers ending in S, such as: #40S, _3S, etc.
SHORT EQUATE LOMASK SYN #FFF8;
Examples: EQUATE A SYN 200, B SYN A+8, C SYN 4, D SYN A/C AND _4; INTEGER S = A; ARRAY B BYTE X, Y; EQUATE E SYN Y(B)-S, F SYN E-C SHLL 2; INTEGER REGISTER XR SYN R1, YR SYN R5; EQUATE G SYN XR, H SYN YR - G + 1; ARRAY H INTEGER SPACE; EQUATE I SYN "AB", J SYN STRING; A=200 B=208 C=4 D=48 E=420 F=1664 G=1 H=5 I=#C1C2 J=2
The sample declarations given in this section will be used throughout the remainder of this text, particularly in Chapters 5 and 6.
BEGIN INTEGER REGISTER ZERO SYN R0, TEMP SYN R1, I SYN R7, J SYN R8, K SYN R9; BYTE C1 SYN B1, C2 SYN B2, C3 SYN B3; SHORT INTEGER H1 SYN B1, H2 SYN B2, H3 SYN B3; LONG REAL LRA, LRB = #4E00000000000000L; INTEGER IA SYN LRA(4), IB SYN LRB(4); REAL RA SYN LRA, RB SYN RA(4); INTEGER X, Y, Z; SHORT INTEGER S1, S2; INTEGER BLANKS = " "; ARRAY 132 BYTE LINE = 132(" "); ARRAY 16 BYTE TAB = "0123456789ABCDEF"; ARRAY 256 BYTE TRTAB SYN TAB(_240); EQUATE SQUARES SYN 64, STAKL SYN SQUARES * 3, SPACE SYN SQUARES * 5; ARRAY STAKL INTEGER STACK; ARRAY SPACE BYTE TABLES; BYTE FLAGS SYN TABLES(_1), TABLE1 SYN FLAGS(SQUARES), TABLE2 SYN TABLE1(SQUARES), TABLE3 SYN TABLE2(SQUARES), TABLE4 SYN TABLE3(SQUARES);
The tables below list the identifiers given in the declarations on the preceding page into two groups. The first group is arranged by the sort order of the identifiers. The second group is arranged by the order in which the identifiers were declared. For each group, the value the compiler associates with the identifier is also given.
Sorted order Declared order ------ ----- -------- ----- Identifier Value Identifier Value BLANKS D068 ZERO 0000 C1 1000 TEMP 0001 C2 2000 I 0007 C3 3000 J 0008 FLAGS D3FF K 0009 H1 1000 C1 1000 H2 2000 C2 2000 H3 3000 C3 3000 I 0007 H1 1000 IA D04C H2 2000 IB D054 H3 3000 J 0008 LRA D048 K 0009 LRB D050 LINE D06C IA D04C LRA D048 IB D054 LRB D050 RA D048 RA D048 RB D04C RB D04C X D058 SPACE 00000140 Y D05C SQUARES 00000040 Z D060 STACK D100 S1 D064 STAKL 000000C0 S2 D066 S1 D064 BLANKS D068 S2 D066 LINE D06C TAB D0F0 TAB D0F0 TABLES D400 TRTAB D000 TABLE1 D43F SQUARES 00000040 TABLE2 D47F STAKL 000000C0 TABLE3 D4BF SPACE 00000140 TABLE4 D4FF STACK D100 TEMP 0001 TABLES D400 TRTAB D000 FLAGS D3FF X D058 TABLE1 D43F Y D05C TABLE2 D47F Z D060 TABLE3 D4BF ZERO 0000 TABLE4 D4FF
The following block contains: two other blocks, three data segments (ALPHA, BETA, DELTA), and six cells (TEST, EXAM, FLAGS, TOPS, EXTRA, LAST).
|01| BEGIN GLOBAL DATA ALPHA BASE R6; |02| INTEGER TEST; |03| GLOBAL DATA BETA BASE R7; |04| INTEGER EXAM; |05| |.... (statement group 1) ....| |06| BEGIN INTEGER FLAGS; |07| CLOSE BASE; |08| INTEGER TOPS; |09| |.... (statement group 2) ....| |10| END; |11| |.... (statement group 3) ....| |12| BEGIN INTEGER EXTRA; |13| GLOBAL DATA DELTA BASE R8; |14| INTEGER LAST; |15| |.... (statement group 4) ....| |16| END; |17| |.... (statement group 5) ....| |18| END
On the following page is a complete main program which computes and prints odd-sized magic squares from a 15 by 15 down to a 3 by 3.
|01| BEGIN ARRAY 133 BYTE LINE = 133(" "); |-- OUTPUT LINE AREA --| |02| ARRAY 256 INTEGER XX; |-- SPACE FOR UP TO 16 BY 16 SQUARE --| |03| INTEGER SIZE, LIMIT; |-- SQUARE SIZE AND SUB-SQUARE LIMIT --| |04| PROCEDURE MAGIC (R6); |-- MAGIC SQUARE GENERATOR --| |05| BEGIN SHORT INTEGER NSQR; |-- CURRENT SQUARE SIZE --| |06| INTEGER REGISTER N SYN R0, I SYN R1, J SYN R2, |07| X SYN R3, IJ SYN R4, K SYN R5; |08| NSQR := N; I := N * NSQR; NSQR := I; |09| I := 1 + N SHRL 1; J := N; |10| FOR K := 1 STEP 1 UNTIL NSQR DO |11| BEGIN IJ := I SHLL 4 + J SHLL 2; |12| X := XX(IJ-68); IF X ~= 0 THEN |13| BEGIN I := I - 1; J := J - 2; |14| IF I <= 0 THEN I := I + N; |15| IF J <= 0 THEN J := J + N; |16| IJ := I SHLL 4 + J SHLL 2; |17| END; XX(IJ-68) := K; I := I + 1; |18| IF I > N THEN I := I - N; |19| J := J + 1; IF J > N THEN J := J - N; |20| END; |21| END; |22| |23| |-- MAIN PROGRAM CODE STARTS HERE --| |24| R0 := 15 =: SIZE; |-- ESTABLISH INITIAL 15 BY 15 --| |25| WHILE R0 > 1 DO |-- MAIN LOOP FOR SQUARE GENERATION --| |26| BEGIN R1 := 16 - SIZE SHLA 2 =: LIMIT; |27| XX := 0; XX(4/256) := XX; |-- ZERO OUT --| |28| XX(260/256) := XX; |-- THE WORK --| |29| XX(516/256) := XX; |-- SPACE. --| |30| XX(772/252) := XX; |31| R0 := SIZE; MAGIC; |-- FORM MAGIC SQUARE --| |32| R2 := 1; R4 := R4-R4; R7 := R4; R3 := 5; |33| R6 := SIZE; WHILE R4 < R6 DO |-- OUTPUT LINES --| |34| BEGIN R1 := @LINE; R5 := R5-R5; WHILE R5 < R6 DO |35| BEGIN R0 := XX(R7); VALTOBCD; R5 := @B5(1); |36| R7 := @B7(4); R1 := @B1(R3); |37| END; R0 := @LINE; WRITE; R7 := R7 + LIMIT; |38| R4 := @B4(1); |-- BUMP LINE COUNTER --| |39| END; LINE := " "; LINE(1/132) := LINE; |40| R0 := @LINE; WRITE; |-- WRITE BLANK LINE --| |41| R0 := SIZE - 2 =: SIZE; |-- REDUCE SIZE BY 2 --| |42| END; |-- OF MAIN WHILE LOOP --| |43| END.
This chapter and the next cover the majority of all statements used in a PL360 program. Each statement is normally terminated by a semicolon; however, a semicolon is not part of the formal definition of a statement, and therefore many examples of statements will not include the terminating semicolon. The reason for this will become clear when we examine the IF statement in the next chapter.
This chapter covers all PL360 assignment statements. An assignment statement is recognizable by its characteristic form:
where 'item' is either a register identifier or a cell reference, and ':=' is the assignment operator that may occur only once in any assignment statement.
An assignment statement is read as follows: 'item' is assigned the value of the 'expression'; that is, the contents of 'item' becomes the value of the 'expression'. The permissible forms of 'expression' depend on the type of 'item'; but it is important to note that every 'expression' is evaluated in a strictly left-to-right sequence and that the contents of 'item' changes at each step in the evaluation. This is quite different from languages such as FORTRAN where the 'expression' is completely evaluated before its value is assigned to the 'item'.
For example, the FORTRAN statement I = J + I is done as follows:
"Take the current value of J and add to it the current value of I; now place the result of the computation into I."
The PL360 statement I := J + I is done as follows:
"Take the value of J and place it into I; now take the value in I and add it to the value in I, i.e., double the value in I."
As you can see the two methods may lead to entirely different results for the final value left in I. The correct PL360 statement that accomplishes the same result as the FORTRAN statement would be:
I := I + J
In section 4.8, I and J were declared as integer register synonyms for R7 and R8 respectively; therefore, the above statement is equivalent to:
R7 := R7 + R8
The PL360 statement R7 := R8 + R7 is equivalent to the two statements
R7 := R8; R7 := R7 + R7;
There are three types of register assignment statements corresponding to the three types of registers: INTEGER, REAL, and LONG REAL. In general, the 'expression' following the assignment operator (:=) consists of one or more terms separated from each other by operators other than the assignment operator; that is,
Each term may either be a constant value or specify the contents of a designated register or cell. Terms must be of compatible type to the assigned register; that is, INTEGER registers with INTEGER and SHORT INTEGER terms, REAL registers with REAL terms, and LONG REAL registers with REAL and LONG REAL terms.
The first term immediately following the assignment operator may be preceded by one of the monadic operators ABS, NEG, and NEG ABS which indicate taking the absolute value, sign inversion, or sign inversion of the absolute value of the first term, then placing the resulting value into the assigned register. If the first term is not preceded by a monadic operator and is not the same as the assigned register, then the value or contents of the designated register or cell is placed into the assigned register. Thus,
R1 := R2 place contents of register R2 into register R1 J := X place contents of cell X into register J R3 := 50 place the value 50 into register R3 F0 := 1R place the value 1.0 into register F0 F23 := NEG LRB place the complement of the contents of cell LRB into register F23
The assignment operator causes the generation of zero, one, or two instructions depending on the term which follows the assignment operator and the use or non-use of monadic operators. The following examples illustrate the cases:
R1 := R1 no instruction because same register on both sides R1 := R2 one instruction, load R1 from R2 R1 := X one instruction, load R1 from cell X R1 := NEG R1 one instruction, complement contents of R1 R1 := NEG R2 one instruction, load R1 with complement from R2 R1 := 5000 one instruction, load R1 from a cell containing the integer constant 5000 R1 := NEG X two instructions; first instruction corresponding to R1 := X; second instruction corresponding to R1 := NEG R1
As a shorthand for 'register := register op term2 ...' where register is the same on both sides of the assignment operator, PL360 allows 'register op term2 ...' as the definition of a register assignment statement; that is, R1 := R1 can be shortened to R1 only. However, this shorthand form is not recommended since it lacks the clarity of the long form.
When the assigned register is an integer register, then all terms must be integer or short integer values or cells, or integer registers. The first term immediately following the assignment operator may be preceded by a monadic operator, ABS, DEC, NEG, or NEG ABS. ABS results in the absolute value of the term (zero or positive). DEC results in the value of the term reduced by 1. NEG results in the negation of the term, with positives becoming negative, negatives becoming positive (except for the largest negative), and zero remaining zero. NEG ABS results in the negation of the absolute value of the term (zero or negative).
The first term immediately following the assignment operator may also be one of the following, but they may not be preceded by the monadic operators:
(a) "string" (string of 4 or fewer characters) (b) @ Procname (Procname is a known PROCEDURE name) (c) @ cell (cell may be of any type: BYTE, REAL, etc.)
R1 := "WORD" is equivalent to R1 := #E6D6D9C4 and R1 := "2" is equivalent to R1 := #000000F2 or R1 := #F2
R4 := NEG 4; |-- R4 = _4 --| R9 := #6500; |-- R9 = some address --| then R1 := @B9(R4+8); |-- R1 = #6504 --|
The result in R1 is the same as the address calculation given in section 1.4. If the statement had been R1 := B9(R4+8) then R1 would be assigned the 4-byte contents of the cell whose address was #6504.
Since B1, B2, etc. are cell names corresponding to a D-field of 0, an X-field of 0, and a B-field specifying R1, R2, etc., then the use of the address operator (@) before these cell names simply implies doing address arithmetic with the B-field integer register. Modification of such cell names by a displacement or index register results in nothing more than additive arithmetic. Generally, it is more economical to increment integer register contents by using address arithmetic rather than by using straight addition. For example:
R1 := @B1(4) adds 4 to the contents of R1 R2 := @B2(R1+8) adds R1+8 to the contents of R2 R3 := @B2(R1+8) adds R1+8 to R2 and places result in R3
The examples above could just as well have been done as follows:
R1 := R1 + 4 R2 := R2 + R1 + 8 R3 := R2 + R1 + 8
The major difference between the two methods is that the address arithmetic involves only a single 'load address' instruction and no memory references for constants in the literal pool. The other method takes a constant in the literal pool, and therefore a memory reference, and in the last two cases it takes more instructions. So @-operator arithmetic is more economical providing the following conditions are accepted:
(1) The result of the address computation is always greater than or equal to 0 and less than 2'24. This means that the top byte of the result will always be 00 hexadecimal. If a negative result is possible, @-operator arithmetic is not appropriate. (2) Addition or subtraction of integer constants must not violate: 0 <= ddd < 4096. Addition or subtraction of constants outside that range is not allowed by @-operator arithmetic. (3) Not more than two integer registers may participate in @-operator arithmetic, and only integer registers are allowed. Addition is the only allowed operation. Subtraction cannot be done using @-operator arithmetic. (4) Address arithmetic never affects the machine 'condition code'.
Now at first glance, these restrictions seem to eliminate a lot of cases; but in actual fact, much of the arithmetic involving integer registers can be done with @-operator arithmetic. Consider a simple loop through an array of integer cells where an index register is incremented by 4 each time in the loop; such as, R1 := @B1(4). This is just one of many possible examples.
Interestingly, integer register assignments of constants from 0 through 4095 are done with a 'load address' instruction in which the X and B-fields are 0 and the D-field is the constant. Since neither a 'load address' nor a 'load' instruction affects the machine's condition code, then using a 'load address' instruction is more efficient than using a straight 'load' instruction which refers to the constant stored in the literal pool. A memory reference is saved as well as the space needed for the constant!
Once the assignment statement has been started, it may be continued by as many occurrences of 'op term' as desired. The allowed operators are essentially the same as for the integer value synonym declarations described in section 4.7. In fact, you will notice a close similarity between the integer register assignment statement and the integer value synonym declaration.
(a) Arithmetic (see sections 1.3 and 1.8) + addition - subtraction * multiplication / division ++ logical addition (unsigned) -- logical subtraction (unsigned) (b) Logical (see section 1.6) AND conjunction OR inclusive injunction XOR exclusive injunction (c) Shifting (see section 1.7) SHLL shift left logically SHLA shift left arithmetically SHRL shift right logically SHRA shift right arithmetically (d) Reverse Assignment =: reverse assignment
Each operator specifies an action to be taken with two operands. One of these operands is ALWAYS the assigned register and the other is the 'term' following the operator. With the assignment (:=) and reverse assignment (=:) operators, the action is that of moving a quantity from one operand to the other. Reverse assignment is the only operator which acts upon the 'term' using the assigned register. For any other operator, the 'term' which follows the operator acts upon the assigned register. Here are some examples.
(1) R1 := S1 =: X
Load register R1 with the value contained in the short integer cell S1, extending the sign of the value. Then, store the value in R1 into the integer cell X. Assuming S1 contained _3 to start with, the results in hexadecimal would look like this:
---- S1 = |FFFD| ---- -------- R1 = |FFFFFFFD| -------- -------- X = |FFFFFFFD| --------
The assembly language equivalents would be:
LH 1,S1 load register 1 from S1 ST 1,X store register 1 in X
The assembly language equivalents have been introduced for two main reasons. First, the machine instructions described in IBM's Principles of Operation manuals are shown in assembly language form, so the assembly language equivalents here act as a link into those manuals. Second, the equivalences aid assembly language programmers in making the transition to PL360 by pointing out what instructions in assembly language correspond to the machine instructions generated by the PL360 compiler. However, knowledge of assembly language is not required to learn PL360. Appendix D of this text contains the relations between machine instructions, assembly language, and PL360 constructs.
(2) R3 := NEG 7 =: S2
Load register R3 with the integer constant 7; negate the contents of R3 thus obtaining negative 7; store the lower half of the contents of R3 into the short integer cell S2. The results would look like this:
-------- R3 = |FFFFFFF9| -------- ---- S2 = |FFF9| ----
The assembly language equivalents would be:
LA 3,7 place 7 in register 3 LCR 3,3 complement register 3 STH 3,S2 store register 3 in S2
(3) R4 := R4 + X =: X =: R2
Add to R4 the current value in X, store the result back into X, and also place the result in R2. When we are finished, R4, R2, and X will all contain the same value.
The assembly language equivalents would be:
A 4,X add X to register 4 ST 4,X store register 4 back into X LR 2,4 load register R2 from R4
We've already looked at the possible terms which may follow the assignment operator, including the use of monadic operators. The term which follows the reverse assignment operator may be only an integer register, integer cell, or short integer cell. Constants are not allowed to follow reverse assignment operators because the contents of a register shouldn't be stored on top of a constant: it wouldn't be constant any more!
Now let's look at the other operators and the restrictions regarding their use. First, it is important to understand that each 'op term' generates only one machine instruction and that the order in which the instructions are generated is the same as the order in which the 'op terms' appear reading the assignment statement from left to right. There is no operator precedence! Second, each operator acts upon the assigned register using the term following the operator to produce a result which replaces the contents of the assigned register. Third, most operators are restricted to using only certain terms. Consider the following example:
R2 := NEG 7 * S1 + X SHRA 4 OR R3
This one statement could be broken down into a series of statements each of which represents a single instruction. Each PL360 statement below is followed by a comment which indicates the assembly language equivalent. Remember, R1 := R1 generates no instruction; it only satisfies the requirement of being an assignment statement.
R2 := 7; | LA 2,7 | R2 := NEG R2; | LCR 2,2 | R2 := R2 * S1; | MH 2,S1 | R2 := R2 + X; | A 2,X | R2 := R2 SHRA 4; | SRA 2,4 | R2 := R2 OR R3; | OR 2,3 |
The shift operators allow only an integer constant or an integer register term. When a register is specified, the lower byte of the register contains the shift amount; the remainder of the register contents is ignored. A logical shift moves the entire contents of the assigned register to the left or right the shift amount. Binary zeros are inserted into the register at the left end for right shifts, into the right end for left shifts, and the bits at the opposite end are discarded.
32-bit value -------- ..000000 |FDB97531| 000000.. --------
SHLL 4 (shift value left 4 bits, 1 hex digit) -------- ..000000F |DB975310| 00000.. -------- Result
The picture above represents what happens. The value is shifted to the left by moving the box which surrounds the value to the right. The box would move left for values which are right-shifted.
Arithmetic shifts operate in essentially the same way as logical shifts, except that on left shifts the computer continuously checks that the sign bit position always contains the original sign bit. If a bit that is different from the original sign bit occupies the sign bit position, an OVERFLOW condition occurs. This means that the resulting value has lost significant bits of the original value. On an arithmetic right shift, the original sign bit is propagated into the left end as bits are dropped from the right end. Therefore, the resulting value retains the sign of the original value. Considering the shift amount as specifying a power of 2, then left shifting is equivalent to multiplying by a power of 2, and right shifting is equivalent to dividing by a power of 2 dropping any remainder. However, right shifting a negative value can never result in zero, so in that sense, it's not quite the same as integer division.
32-bit value -------- ..FFFFFF |FDB97531| 000000.. --------
SHRA 8 (shift value right eight bits, two hex digits) -------- ..FFFF |FFFDB975| 31000000.. -------- Result
Arithmetic shifts, and almost all of the other operators to be discussed, except multiplication and division, affect what is called the machine's 'condition code'. There are four possible conditions for any of these operations. One is the OVERFLOW condition, a case where the resulting value has changed sign improperly. Such a condition is possible with arithmetic left shift (SHLA), addition (+), and subtraction (-). The four possible conditions are:
Arithmetic Comparison Logical result ---------- ---------- -------------- (0) result = 0 operands are equal zero, no carry (1) result < 0 first operand is low non-zero, no carry (2) result > 0 first operand is high zero, carry (3) OVERFLOW non-zero, carry
One of the four arithmetic conditions will occur with the monadic operators, but OVERFLOW is only possible when the original value is the largest possible negative number (#80000000) and either NEG or ABS is used in an attempt to make it positive. (See Appendix A for a discussion of two's complement arithmetic.)
These conditions are covered in greater detail in Chapter 6, Branching, Testing, and Loops. For now it is only important to note that almost every operation affects the 'condition code'.
Let's get back to the integer register assignment statement operators again and look at the rest of them. All the logical operations and division (AND, OR, XOR, ++, --, and / ) are restricted to integer terms: registers, cells, and constants. Short integer cells and short integer constants are not allowed with these operators. Addition, subtraction, and multiplication (+, -, and * ) may use any of the five possible terms: integer registers, cells, or constants, and short integer cells and constants.
Logical addition and subtraction produce the same result as do normal addition and subtraction, but the 'condition code' has a different set of meanings for logical arithmetic. They indicate whether the result is zero or non-zero, and whether or not a CARRY has occurred.
The logical operations of AND, OR, and XOR result only in conditions 0 (meaning zero result), and 1 (meaning non-zero result). Logical OR is typically used to force bits in the assigned register to ones when they may be either zeros or ones originally. Logical AND is typically used to force bits in the assigned register to zeros when they may be either zeros or ones originally. ANDing with a negative power of 2 (such as _2, _4, _8, etc.) will round down to the nearest whole multiple of that power of 2. Thus, the statement R1 := R1 AND _4; is equivalent to R1 := R1 SHRL 2 SHLL 2; leaving R1 with a value that is a whole multiple of 4. The logical XOR operation inverts bits in the assigned register wherever there are bits that are ones in the 'term'. Thus R1 := R1 XOR _1 would invert all bits in R1 effecting a one's complement operation (see Appendix A). Logic tables are given in section 1.6 describing these bit operations.
Multiplication and division have a special set of rules regarding their use. Multiplication of two numbers can create a product which has a total number of digits equal to the sums of the digits in the multiplier and multiplicand. Therefore, when we multiply the contents of an integer register (32 bits) by a short integer term (16 bits), the product (48 bits) would not fit in the integer register. But if the upper 17 bits of the product are all sign bits, then the top 16 bits can be discarded leaving a 32-bit result with proper sign. For example,
R2 := R2 * 10S | MH 2,=H'10' |
Assuming R2 contains the value 100 decimal, then
Multiplicand = R2 = 00000064 (100 in hexadecimal) Multiplier = 10S = 000A (10 in hexadecimal) Product = 0000000003E8 (1000 in hexadecimal) Result in R2 = 000003E8 (1000 in hexadecimal)
The computer assumes that for short integer multiplication the upper 16 bits of the product can always be discarded, even if this assumption is incorrect! But typically short integer multiplication is done with values that will not yield a product greater than 31 significant bits. Notice in the example that the product has only three significant digits, the sum of the number of significant digits in the multiplier and multiplicand.
Multiplication of two integer quantities would result in a 64-bit product. In this case, the designers decided not to discard anything. Instead, the product is placed into two registers as though they were just one big register. These two registers are called an even-odd pair because the upper digits of the product (and sign bit) are placed in an even-numbered register (R0, R2, R4, etc.) and the lower digits are placed in the odd-numbered register next in sequence to the even-numbered register (R1, R3, R5, etc.). Also, and this is very important, the multiplicand must be in the odd-numbered register at the start of the operation, and that register determines the even-odd pair. Thus,
R1 := R1 * R4 | MR 0,4 | Multiplicand = R1 Multiplier = R4 Product = R0,R1 (sign at top of R0)
Of course, the product could be such that the even-numbered register contains only sign bits (no significant digits) and the odd-numbered register contains the significant product with proper sign. Still, both registers of the even-odd pair are affected by integer multiplication. You will notice that the assembly language programmer must specify the even-numbered register (0) while the PL360 programmer specifies the odd-numbered register (R1). Specifying the odd-numbered register is more meaningful since the multiplicand must be in that register at the start of an integer multiplication. Also, the significant portion of the product will be in the odd-numbered register after the multiplication if the product is not excessively large.
Division operates in a manner just the opposite of multiplication. The dividend must be in an even-odd register pair specified by an odd-numbered assignment register. The divisor is 'term' which must be an integer term. When the division is completed, the computer places an integer quotient in the odd-numbered register, and an integer remainder in the even-numbered register. Now that may seem simple enough, but there is a problem with integer division which is quite unique. It is possible that the divisor could be sufficiently small, and the dividend sufficiently large, such that the quotient would exceed the capacity of the odd-numbered register meant to receive it. Take for example the division of 50 billion by 10. The result should be 5 billion, but a single integer register cannot contain a value greater than 2,147,483,647 (slightly more than 2 billion). Under such circumstances, a FIXED DIVIDE interrupt occurs and the dividend is left unchanged. Of course, zero is the smallest possible divisor, so division by zero always results in a FIXED DIVIDE interrupt. As a general rule, using the absolute values of both dividend and divisor, a FIXED DIVIDE interrupt will occur whenever the divisor is less than or equal to twice the portion of the dividend which is in the even-numbered register.
Now let's look at a normal division example. Assume that X contains the decimal value 'negative eleven' (_11).
R7 := 3 * X / 5 =: X | replace X by three-fifths of X |
The assembly language equivalents would be:
LA 7,3 place 3 in register 7 M 6,X multiply register 7 by X (product in 6,7) D 6,=F'5' divide product by 5 ST 7,X store quotient in register 7 back into X
This example points out a number of things and poses some questions. First, notice that the assembly language programmer must specify the even-numbered register (as in multiplication), while in PL360, the assignment register must be odd-numbered. Second, you can see that we obtained the dividend in the even-odd register pair by using integer multiplication before the division. If the multiplication had not been necessary, we could have obtained the proper dividend as follows:
R6 := X =: R7 SHRA 31; R7 := R7 / 5 =: X; | one-fifth X |
In the first statement, R6 and R7 are both assigned the value in X, and then the assigned register, R6, is arithmetically right-shifted till it contains only sign bits of X. The second statement then specifies division of R6,R7 by the integer value 5 with the quotient to be stored back into X. If X contained only positive values, the following statements would accomplish the division:
R6 := R6-R6; R7 := X / 5 =: X; | one-fifth X (positive) |
Let's look at the original example (three-fifths X) and consider the results. What are the quotient and the remainder? First of all, the product of positive three times negative eleven is negative thirty-three (3 * _11 = _33). The familiar rules of sign prevail: multiplication of a positive and a negative results in a negative product, multiplication of two positives or two negatives results in a positive product. For the division, we have a negative dividend and positive divisor. Again, the standard rules of sign prevail for the quotient, which turns out to be negative six (_6). The quotient is the nearest whole integer which when multiplied by the divisor would come closest to the original dividend. The remainder is what's left over, in this case, negative three (_3). The sign of the remainder is always the same as the sign of the original dividend, except when the remainder is zero. There is no 'negative zero', so any zero result is simply zero, regardless of sign convention. That's true of products and quotients as well as remainders. Thus, when the (absolute) value of the dividend is smaller than the (absolute) value of the divisor, integer division will yield a zero quotient with a remainder equal to the original dividend (_1/2 = 0 with _1 remainder).
What we've covered so far in this section could be called the 'mechanics' of integer register assignment statements; i.e., each operation and what it does. What we shall cover now is 'how' the integer register assignment statements are typically used and how the operations may be combined to obtain a desired result.
There are three basic uses of integer registers:
(1) as base registers of data areas (2) as index registers to subscript through arrays (3) as computational scratch pads
When integer registers are used as base registers, their content is rarely changed. Once a data area has been established, it's not likely to move. Of course, a DUMMY BASE area may be considered as an overlay to data in different locations, and as such the base address may be computationally moved about with integer register assignment statements. Take the telephone book entries example in section 4.1.1 as a sample case. Each entry is 72 bytes long (32+32+8) based on R7. If entries were laid out one after the other in core, then R7 := @B7(72) would increment the base register ahead from one entry to the next.
When integer registers are used as index registers, computations are done to move the index from one entry to another while keeping the base register unchanged. Let's say we wish to do in PL360 what would be required to satisfy the following FORTRAN declaration and statement:
INTEGER X, STACK(64,3) STACK(I,J) = X
Assume that I and J are integers which, in PL360, can be maintained in integer registers called I and J. Also assume that I contains any integer value from 1 through 64, and that J contains any integer value from 1 through 3.
The ARRAY STAKL INTEGER STACK declaration in section 4.8 satisfies the FORTRAN STACK(64,3) declaration; and X is also satisfactorily declared. Let's assume that the array is to be laid out by columns; that is, the 64 row elements of column 1, followed by the 64 row elements of column 2, and so forth. In PL360, the general formula for computing the 'index' of a 2-dimensional array is:
index = ((J-1)*rows+(I-1))*size
where 'rows' is the number of row elements in each column, which is 64 in this case; and 'size' is the number of bytes taken by each element, which is 4 in this case because we are dealing with integer cells each of which takes four bytes. The general formula becomes:
index = ((J-1)*64+(I-1))*4 or index = (J*64+I-65)*4 or index = (J*64+I)*4-260
We can now satisfy the FORTRAN statement by writing the following PL360 integer register assignment statements:
TEMP := J SHLA 6 + I SHLA 2 - 260S; K := X =: STACK(TEMP);
We have used integer registers TEMP and K as scratch pads. The index computation in TEMP can be broken down into a series of assignments to make it easier to understand:
TEMP := J; | TEMP = J | | LR TEMP,J | TEMP := TEMP SHLA 6; | times 64 | | SLA TEMP,6 | TEMP := TEMP + I; | plus I | | AR TEMP,I | TEMP := TEMP SHLA 2; | times 4 | | SLA TEMP,2 | TEMP := TEMP - 260S; | minus 260 | | SH TEMP,=H'260' |
Now if the array had been declared as STACK(3,64) and we still planned to reference STACK(I,J) with I ranging from 1 through 3 and J ranging from 1 through 64, then the formula would become:
index = ((J-1)*3+(I-1))*4 or index = (J*3+I-4)*4 or index = (J*3+I)*4-16 and we could write: TEMP := J * 3S + I SHLA 2 - 16S; K := X =: STACK(TEMP);
But wait! There are two changes we can make to simplify matters. First of all, we can move the subtraction of 16 from the TEMP computation into the subscripted reference to STACK. Since the original displacement of STACK is large enough to permit a reduction by 16 and still satisfy 0 <= ddd < 4096, we could write:
TEMP := J * 3S + I SHLA 2; K := X =: STACK(TEMP-16);
We had to subtract 260 in the TEMP computation of our first example because STACK's original displacement is only 256 (#100).
The second improvement we can make is the elimination of the 'multiply by 3S' (short integer multiply). We can write:
TEMP := J + TEMP + J + I SHLA 2; K := X =: STACK(TEMP-16);
We've substituted two add operations for a single multiply operation taking the same number of instruction bytes; but we've eliminated a constant in core (3S), a core reference for that constant, and we've gained execution speed because two adds are faster than one multiply!
Here are some examples of where multiply operations can be eliminated by using other instructions:
TEMP := J + TEMP; | TEMP := J * 2S; | TEMP := J + TEMP + J; | TEMP := J * 3S; | TEMP := J SHLA 2; | TEMP := J * 4S; | TEMP := J SHLA 2 + J; | TEMP := J * 5S; | TEMP := J SHLA 3 - J; | TEMP := J * 7S; | TEMP := J SHLA 3; | TEMP := J * 8S; | TEMP := J SHLA 3 + J; | TEMP := J * 9S; |
The last three examples above can be generalized to handle multiply operations by one less than a power of 2, by a power of 2, and by one more than a power of 2, such as 63, 64, and 65.
In both STACK(I,J) examples, we assumed that I and J each started with a low bound value of 1; but if we started both I and J at 0, then we could have eliminated the subtracts of 260 and 16 from our computations. In other words, we could assume that the FORTRAN reference to STACK(0,0) is equivalent to the PL360 reference to STACK(0). It is a general practice when referencing arrays in PL360 to start at subscript (0). Also, as you can see from our examples, a multi-subscripted array must be referenced by a single index register in PL360. There is no reason, of course, why a multi-dimensional array has to be handled by multiple subscripts. In our STACK(3,64) case, if we were interested in doing something like:
STACK(2,J) = STACK(0,J) + STACK(1,J)
for all J beginning at 0, we could write in PL360 something like:
TEMP := TEMP - TEMP; |-- initialize TEMP to 0 --| LOOP: |-- branch point for doing all J's --| K := STACK(TEMP) + STACK(TEMP+4) =: STACK(TEMP+8); TEMP := @MEM(TEMP+12); |-- bump TEMP ahead 12 bytes --|
At this point we would test TEMP for still being in range. If TEMP still represents a valid reference, we would branch back to LOOP to repeat the computations for the next group. We shall cover testing and branching in Chapter 6, Branching, Testing, and Loops.
Here are some other examples of integer register assignment statements that point out concepts related to this section:
(1) Rn := Rn XOR _1; |-- one's complement of Rn --|
(2) R1 := Ry + Rz SHRA 1 AND _n; |-- n = power of 2 --| R0 := Ry + Rz =: R1 SHRA 31; |-- n ~= power of 2 --| R1 := R1 / n SHRA 1 * n;
These assignment statements determine the point halfway between the two points defined by Ry and Rz such that this new point is always a multiple of a fixed interval n. This technique is commonly used in 'binary' search processes against a list of fixed-length items.
(3) R1 := Rn * p/q; |-- where 2p < q --|
If the original value in Rn were positive and we were not interested in the remainder resulting from the division by q, we could accomplish the same result as p/q by just doing an integer multiply! Since integer multiplication results in a double-length answer, we could assume that the multiplier is a fraction with its radix point assumed before the value rather than following the value. Thus, we could write:
R1 := Rn * m; |-- answer in R0 --|
where m is an integer value assumed to be a fraction. For example, if p=1 and q=3 then m=#55555556, an assumed fraction. We could compute any such assumed fraction once by statements such as:
R0 := p; R1 := R1-R1 / q; |-- R1 contains m --|
The above computation of m is exact if the remainder in R0 following the division is zero; otherwise the value in R1 must be increased by one (R1 := R1 + 1) to obtain a more precise value of m.
Once we have obtained the value m by a computation such as the one shown above, we can use m rather than p/q remembering to take the answer from the even-numbered register rather than from the odd-numbered register. Thus, we could rewrite the statement shown in example 2 above for the case where n ~= power of 2:
R1 := Ry + Rz * m; R1 := R0 SHRA 1 * n;
(4) Mixed numbers can be handled with integer register assignment statements providing a fixed number of fractional digits is to be maintained. The best example of this is the processing of dollars and cents values. A quantity such as $1.55 could be processed as though it were 155 cents. Additions and subtractions of such numbers, as well as multiplications and divisions by pure integers, always result in cents values. The location of the decimal point is assumed in all calculations. The final presentation of any results would include a leading dollar sign and decimal point in proper positions, but that's nothing more than a pictorial problem.
Floating-point register assignment statements are much more restrictive than integer register assignment statements. Shifting or logical operations are not allowed and we can't use @-operators or "string" constants either. The only operations possible are assignment, reverse assignment, arithmetic, and the monadic operators, NEG, ABS, and NEG ABS.
(a) Arithmetic (see section 1.8) + addition - subtraction * multiplication / division ++ addition (unnormalized) -- subtraction (unnormalized)
(b) Reverse Assignment =: reverse assignment
The form of a floating-point register assignment statement is the same as for integer register assignments, i.e.,
where 'register' is either a REAL or LONG REAL register identifier, and the expression consists of one or more 'terms' separated from each other by the operators shown above. The 'terms' are restricted to floating-point registers, cells, or constants of compatible type to the assigned register. (LONG REAL registers may use both REAL and LONG REAL terms in an analogous way to INTEGER registers using both INTEGER and SHORT INTEGER terms.)
There are, physically, only four floating-point registers available in the hardware. They are the LONG REAL registers designated by the standard identifiers: F01, F23, F45, and F67. The REAL registers, designated by F0, F2, F4, and F6, are simply the top half of the corresponding LONG REAL registers. Therefore, any operations which affect F01 also affect F0, and any operations which affect F2 also affect the top half of F23, just to cite two examples. The only differences between using the LONG REAL registers as opposed to the REAL registers is the speed with which operations can be done (REAL's are faster than LONG REAL's), the accuracy of the results (LONG REAL's are more accurate than REAL's), and the amount of cell space needed to store floating-point registers (LONG REAL registers require LONG REAL cells of eight bytes in length, and REAL registers require REAL cells of four bytes in length). (See Appendix B.)
REAL and LONG REAL cells must always be referenced using integer registers, never floating-point registers (see section 4.4). BASE and/or INDEX registers in any subscripted cell reference are always limited to integer registers regardless of cell type.
The advantages of floating-point arithmetic are:
(a) Mixed numbers (integers and/or fractions) can be operated upon without having to worry about decimal points. We can add 0.125 to 10.5 and get 10.625 without any problems. (b) Division doesn't have a remainder, only a quotient. So if we divide 1.0 by 2.0 we would get 0.5 as expected. (c) Very large and very small values can be processed.
Any work with geometric or trigonometric problems usually requires floating-point arithmetic. Imagine trying to compute the area of a circle knowing its radius using only integer arithmetic!
There are some disadvantages to using floating-point arithmetic. For one thing, floating-point is not always accurate. Integers are integers, pure and simple; but floating-point values may be inaccurate. One-third (1.0/3.0) is not exact in floating-point form; 0.333333 is not one-third, it's 333333/1000000. Since the computer's representation of a floating-point value is limited to only a specific number of digits, repeating fractions can't be exactly represented. Even one-tenth isn't exact when represented in hexadecimal!
Another disadvantage is the limited number of registers available and the limited number of instructions that can operate upon them. Therefore, floating-point operations are seldom encountered in systems programming, information retrieval, or similar programming.
One of the common problems encountered when working with floating- point is the conversion between floating-point and integer values. For example, given a positive integer value in R1, how do we get the corresponding floating-point value in F01? To demonstrate the method, let's use the cells LRB and IB defined in section 4.8 and write the following assignment statements:
R1 := R1 =: IB; |-- R1 >= 0 --| F01 := F01 - F01 + LRB; Let's see what happened. First, we'll draw a diagram of LRB and IB and look at the relationship between them.
------------------- |4E000000 00000000| ------------------- | |<- IB ->| IB overlays the last |<----- LRB ----->| four bytes of LRB
Notice that LRB is initialized to a value which in floating-point is a zero fraction with an exponent equivalent to 16'14 (0E). The exponent indicates that the radix point of the fraction is to be placed 14 hexadecimal digits from the start of the fraction. That would place the radix point at the far right end of the fraction. So instead of 0.00000000000000 x 16'14 we would have 00000000000000. x 16'0, the integer value of zero! Storing R1 into IB simply replaces this zero with the integer value in R1. Then, zeroing F01 and adding LRB forces the computer to normalize the answer. We end up with a normalized floating-point value in F01 which is equivalent to the integer value in R1. Try it with R1 = 3 and you'll get F01 = #4130000000000000L which is equivalent to F01 = 3L.
We can reverse the process provided we don't mind losing any fractional portion of the floating-point value (we only want the integer portion), and the floating-point value is positive and less than the largest possible positive integer that will fit in an integer register (less than 2,147,483,648). Using LRA and IA, we write the statements:
F01 := F01 ++ #4E00000000000000L =: LRA; R1 := IA;
The ++ operator indicates unnormalized addition which could be described in the following way: "The computer compares the exponents of the two values being added. The fraction of the value with the smaller exponent is right-shifted as many hexadecimal digits as the difference between the the exponents. This lines up the radix points of the two fractions according to the larger exponent. Now the fractions can be added together and the result given the larger exponent." That's it! Normalized addition works the same way, but with one more step: "The resulting fraction is left- or right-shifted until the most significant hexadecimal digit occupies the first fractional digit position. Then the exponent is reduced (or increased) accordingly."
Multiplication and division also produce a normalized result. The process is similar to our own paper-and-pencil method for exponential notation. The fractions are multiplied together or properly divided, one into the other. Then the true exponents are added or subtracted to yield a new exponent. The resulting fraction and exponent are reassembled and normalized to create the final result. Thus, 0.5'4 * 0.7'2 yields 0.35'6.
Multiplication and division do not affect the machine's condition code. Assignment (:=) and reverse assignment (=:) also have no effect on the condition code. Addition, subtraction, and comparison (next chapter), and most other operations affect condition codes.
Since all of the arithmetic operations involve shifting to align or normalize the fraction, it is possible for the final exponent to fall outside the range of allowable exponents. This is especially true of multiplication and division since the exponents are being added or subtracted independent of normalization. Whenever the exponent becomes too large or too small, an EXPONENT OVERFLOW or UNDERFLOW occurs usually resulting in a program interrupt. We shall not cover the details of such circumstances in this text; instead, see IBM's Principles of Operation manuals.
Cell assignment statements provide the means for modifying cell content. As with register assignment statements, the form of a cell assignment statement is recognized by:
where 'cell' is a cell reference; the 'terms' are a mixture of constants, "strings", or other cell references; and the 'op's' are logical operators. The first 'term' may also be a register identifier.
We have already encountered cell assignment with registers! The reverse assignment operator (=:) introduced in section 5.1 actually provided cell assignment. Thus,
R1 := R1 =: IB;
could be written as the cell assignment statement:
IB := R1;
Exactly the same machine instruction is generated in both cases, equivalent to ST 1,IB in assembly language. The only difference is that now the cell is the assigned quantity. In the register assignment statement we could have continued the expression with adds, shifts, logical operations, etc. Such operations would all affect the assigned register, not the reverse assigned cell. But when the assigned item is a cell, the expression CANNOT be continued with arithmetic or shift operations. There are no computer instructions corresponding to such operations with cells.
So the basic cell assignment statement is:
The cell may be fully indexed; that is, an index register may be specified by a non-zero X-field component in the cell reference. Also, the cell and register types must be compatible. The contents of an INTEGER register may be stored into an INTEGER cell, or the lower half of an INTEGER register may be stored into a SHORT INTEGER cell (whatever is in the upper half of the register is ignored). Similarly, the contents of a LONG REAL register may be stored into a LONG REAL cell; and the upper half of a LONG REAL register or all of a REAL register may be stored into a REAL cell.
The following simple example demonstrates cell assignment.
R1 := X; |-- fetch X --| R0 := R1 SHRA 31; |-- sign extend --| R1 := R1 / 5; |-- divide by 5 --| Y := R0; |-- store remainder into Y --|
The storage-to-storage cell assignment facility is built around a small set of machine instructions: MOVE, AND, OR, and XOR character and immediate instructions (MVC, NC, OC, XC, MVI, NI, OI, XI assembly mnemonics). These instructions do not allow an X-field for cell references. So NONE of the cell references in the cell assignment statements described in this section are allowed to specify an index register. The only registers that may occur in such references are the base registers associated with the referenced quantities. The general form of a storage-to-storage cell assignment statement is:
where 'cell' is a unindexed cell reference; the 'terms' are a mixture of constants, "strings", or other unindexed cell references; and the 'op's' are logical operators. The first 'term' may also be a register identifier. Thus,
LINE := "APPLES" Z := 50 X := X AND Y B2 := R1 AND B3
When the term is a variable-length string, the number of bytes affected is determined by the string length regardless of cell type. So the first assignment statement shown above reads: "Beginning with the 0th byte of LINE, assign the six consecutive characters APPLES". If the statement had been LINE(17) := "APPLES", the only difference would have been the starting point, the 17th byte of LINE for six consecutive characters. No check is made for overrunning the space reserved for the referenced cell: that's your responsibility!
When the term is a constant, the basic length of the cell must be less than or equal to the constant length (see section 4.3). Therefore, LONG REAL cells must be assigned LONG REAL constants. REAL or INTEGER cells may be assigned REAL or INTEGER constants. SHORT INTEGER and BYTE cells may be assigned corresponding constants, but SHORT INTEGER cells may also be assigned the lower half of INTEGER constants. Likewise, BYTE cells may be assigned the lowest byte of SHORT INTEGER or INTEGER constants. The only restriction in such cases is that the unused portion of a constant contain only like sign bits (all zeros or ones). Since EQUATE declarations define INTEGER constants, these quantities may be assigned to REAL, INTEGER, SHORT INTEGER, and BYTE cells.
When the term is a cell, the cell types must agree in basic length (not ARRAY length). That is, BYTE with BYTE, SHORT INTEGER with SHORT INTEGER, INTEGER with INTEGER or REAL, REAL with REAL or INTEGER, and LONG REAL with LONG REAL.
Because the machine instructions generated operate on a byte-by- byte basis, cell alignment is not checked. This makes it possible for us to overlay bytes by writing: X(1) := X which would propagate the first byte of X to X(1), then X(1) to X(2), and so on. The basic cell length or string length determines the number of bytes affected.
These machine instructions also allow from one to 256 bytes to be processed. To allow for this variable-length ability, cell assignment statements may specify lengths within this range. The assigned cell must include a subscript in one of the following forms:
identifier(Rb+k/length) or identifier(Rb-k/length) identifier(Rb/length) identifier(k/length)
'k' is as described in section 4.4; and 'length' is either a single integer value or an expression of added and/or subtracted integer values. The final length must satisfy: 1 <= length <= 256. The slash (/) character does not indicate division. It acts as a separator between the Base-Displacement and length portions of the subscript. Notice that if we wanted to specify just a length for a particular cell reference, we would have to supply a 'k' of zero. Thus, X(0/4) indicates a 4-byte length beginning with the 0th byte of X, even if X were not an INTEGER or REAL cell.
When a length is specified, cell types need not agree in basic length! If fact, cell types become meaningless and we can mix types if necessary. With constants, the specified length may not exceed the basic constant length. With "strings", the shorter of the specified length or string length determines the number of bytes affected. If the first term is a register, the register is stored according to the rules for register-to-storage regardless of length specification.
Here are some examples:
(1) B6(0/4) := "Sample"; |-- B6 := "Samp" --| B4(4/9) := "Sample"; |-- B4(4) := "Sample" --| LINE(0/2) := 30; |-- LINE(0/2) := 30S --|
(2) Blank out the first 120 bytes of LINE. LINE := " "; |-- Blank the first byte of LINE --| LINE(1/119) := LINE; |-- Propagate the blank --|
(3) Copy the lower LEN bytes of the integer cell based on R2 to the lower LEN bytes of the integer cell based on R5. LEN is assumed to vary from one compilation to the next.
BEGIN EQUATE LEN SYN 3; |-- 1 <= LEN <= 4 --| B5(4-LEN/LEN) := B2(4-LEN); |-- B5(1/3) := B2(1); --| END;
(4) Reference an indexed cell using a temporary base register. R9 := @X(R2); |-- R2 is an index register --| B9 := Y; |-- move 4 bytes from Y to X(R2) --|
(5) Interchange SQUARES bytes of TABLE1 and TABLE2 (no overlap). TABLE1(1/SQUARES) := TABLE1(1) XOR TABLE2(1); TABLE2(1/SQUARES) := TABLE2(1) XOR TABLE1(1); TABLE1(1/SQUARES) := TABLE1(1) XOR TABLE2(1);
Assume that all the standard identifiers are valid in the following problems (B1,B2,...,R0,R1,R2,...,etc.).
One of the assignment statements below does NOT cause a compiler diagnostic. All the others have an error. Determine which statement is correct. What basic error exists in the others?
(a) F0 := F0 SHRA 3; (b) R1 := B3 AND 4S; (c) R4 := R5 * R6; (d) B1(4/4) := B1 + R2; (e) B3(0/2) := R1 XOR B3(2); (f) R6 := R5 + @B2; (g) R8 := B3 / B2; (h) B1(4) := B2(_4);
One of the assignment statements below causes a compiler diagnostic. All the others are free of errors. Determine which statement is incorrect and why.
(a) R0 := R0 SHLL 7; (b) R1 := 4S AND B3; (c) R5 := R5 * R6; (d) F0 := F0 / 8R; (e) B3(0/2) := B3(2) XOR R1; (f) R6 := @B2(R5); (g) R8 := B3 - B2; (h) R2 := NEG _2 + _2;
Starting with: R1 := #1100; R2 := #1010; do the following assignment statements in sequence; and show what the result is after each assignment (see the logic tables in section 1.6).
R1 := R1 XOR R2; |start with R1 and R2 values given| R2 := R2 XOR R1; |use R1 from preceding statement| R1 := R1 XOR R2; |use R1 and R2 from preceding statements|
This problem refers to the complete program given at the end of section 4.9, problem 2 (page 41).
(a) Rewrite the statement IJ := I SHLL 4 + J SHLL 2; appearing on lines |11| and |16| using multiplication instead of shifts.
(b) Compute the assigned value for: IJ := I SHLL 4 + J SHLL 2; when: I=2 and J=4, I=3 and J=3, I=4 and J=1 .
(c) What positive non-zero values for I and J generate the value IJ=68 such that XX(IJ-68) on lines |12| and |17| references XX(0) ?
Very few programs, if any, are written as just a straight line sequence of computations. One might as well do the computations on a hand calculator if no decision-making is required! So the concept of branching is very important; and PL360 provides very powerful branching facilities. Most of the statements look very much like normal English sentences, which makes them very readable. This chapter covers almost all of these branching and decision-making statements.
GOTO statements are unconditional branching statements. Their form is simply:
where 'label' is an identifier naming some point in a block. A label definition is simply an identifier followed by a colon (label:). Such 'labels' may occur wherever a 'statement' may occur within a block. GOTO statements constitute the ONLY means for referencing labels. Therefore, label names may be chosen freely, independent of other user defined identifiers. In fact, label X and cell X may coexist! The only restriction on label names is that they be unique within any block. That does not imply that they cannot be redefined in a separate or nested block--they may be redefined. For example:
BEGIN X: |-- Label #1 --| BEGIN GOTO X; |-- Goto #3 --| Y: |-- Label #2 --| GOTO Z; |-- Goto #4 --| X: |-- Label #3 --| GOTO Y; |-- Goto #2 --| END; Z: |-- Label #4 --| GOTO X; |-- Goto #1 --| END
This example not only illustrates label redefinition (X in this case); but also illustrates the 'scope' of GOTO statements.
The first GOTO X branches to the X label in the block containing that GOTO statement. That's also true of the last GOTO X which branches to the X label in its same block. If the first X label had been missing, the last GOTO X would be invalid. A GOTO statement may not branch into a block! The GOTO Z statement branches out of the block containing that GOTO statement to a label defined within a containing block.
As a general rule, a label may be referenced by a GOTO statement occurring in the same block, or by a GOTO statement in any contained blocks if the label isn't redefined in those blocks.
The following example further illustrates the rule:
BEGIN BEGIN BEGIN X: |-- Label #1 --| END; GOTO X; |-- Goto #3 --| END; BEGIN X: |-- Label #2 --| END; X: |-- Label #3 --| END
There's an exception to the previously stated rule. A GOTO cannot reference a label defined in a different program segment. We shall cover this restriction in Chapter 8, Procedures.
Conditions provide the basic decision-making facilities in PL360. They are only used in IF and WHILE statements, such as:
We will cover IF and WHILE statements in detail in sections 6.3 and 6.4; but we'll use the start of IF statements in this section to illustrate how conditions are used.
Simple conditions fall into two categories: those which generate only a conditional branch instruction, and those which generate a compare instruction followed by a conditional branch instruction.
Conditions which generate only a branch instruction test the machine's 'condition code' established by some preceding statement. Thus, if we had done: R1 := R1 + R2; we could test the result of the addition because addition affects the machine's condition code. The relationship between the condition code and the testing conditions is as follows.
Within the computer's program status word are two bits reserved for the condition code. So it is possible to have four different condition code values:
Code Arithmetic Comparison Logical result ---- ---------- ---------- -------------- 0 result = 0 operands are equal zero, no carry 1 result < 0 first operand is low non-zero, no carry 2 result > 0 first operand is high zero, carry 3 OVERFLOW non-zero, carry
Since there are four different condition codes and we may want to test for one or more of them simultaneously, the branch instructions provide a 4-bit mask field. These bits are numbered left to right: 0, 1, 2, 3; and a bit turned on in the mask indicates testing the corresponding condition code value. It is possible for the mask field to assume values from 0 through 15. A 0 corresponds to no conditions: no branch. A 15 corresponds to all conditions: an unconditional branch just like GOTO. The values in between test for one or more condition code values and branch accordingly. The table below illustrates the relationship between the mask value and the condition code value.
Mask Value Condition Code Value ---------- -------------------- 8 0 4 1 2 2 1 3
In PL360, we can specify the mask value directly as an integer value from 0 through 15 (hexadecimal values #0 through #F). We may also preface these integer values by a not symbol (~) to indicate the complementary set of condition codes. This is an awkward way of testing condition codes, to say the least. But by using EQUATE declarations, you can devise meaningful names which indicate the codes being tested. Some of the pre-declared EQUATE identifiers given in section 4.7 do just that!
Identifier Value Condition Code Tested ---------- ----- --------------------- OVERFLOW 1 3 ON 1 3 MIXED 4 1 OFF 8 0 CARRY 3 2 or 3
OVERFLOW tests for the arithmetic overflow condition. ON, MIXED, and OFF test for condition codes resulting from a test-under-mask (TM) instruction which we shall cover in Chapter 7, Functions. CARRY tests the result of logical addition and subtraction (++ and --). Preceding any of these pre-declared identifiers by the not symbol (~) tests the complementary set of condition codes. Thus, ~MIXED tests the condition code values: 0, 2, or 3.
We could invent our own names with EQUATE declarations for any other conditions, such as: EQUATE EQUAL SYN 8; to test for an equals condition. But that isn't necessary because PL360 provides special relational operators that represent most conditions.
Symbol Equivalent Value Condition Codes Tested ------ ---------------- ---------------------- = 8 0 ~= 7 1, 2, or 3 > 2 2 >= 10 0 or 2 < 4 1 <= 12 0 or 1
The relational operators by themselves constitute valid conditions, just as do the integer values (or equates) mentioned earlier. Thus,
R1 := R1 + R2; IF = THEN ... R1 := R1 + R2; IF OVERFLOW THEN ... Z := X XOR Y; IF ~= THEN ...
A condition may also be defined as the relationship between two quantities. The general form of such a relationship is:
item1 relation item2
where 'relation' is one of the relational operators, and the 'items' may be any of the following pairs:
item1 item2 ----- ----- register register register constant register cell cell constant cell cell cell "string" integer reg. "string"
The same rules and restrictions described for assignment statements apply to the above pairs as well. Just substitute the relational operator for the assignment operator and you won't go wrong. Here are some examples:
R1 > 0 F01 ~= 1.5L X <= Y TABLE1(1/SQUARES) = TABLE2(1)
All of these relationships generate two instructions. The first compares the two quantities to establish the condition code. The other is a conditional branch instruction which depends on the relation. When item1 is a register, the comparison is a signed compare. That is, a positive value in the register compares 'greater than' any negative value. Thus, R1 > _1 is satisfied by any value in R1 greater than or equal to 0.
When item1 is a cell, the comparison is done logically (unsigned). That's also true of an integer register compared to a "string". Thus, X > _1 can never be satisfied if X is an integer cell because _1 is a value with all bits set, making it the largest possible value. Nothing could be greater than the largest value!
There is another condition which could be called a 'flag' condition. This type of condition is defined as just a bytecell, or as a bytecell preceded by the not symbol (~). Thus,
IF bytecell THEN ... or IF ~bytecell THEN ...
The contents of the specified bytecell is tested for the value #FF (all bits set). IF bytecell THEN ... is satisfied by a bytecell containing #FF, and is not satisfied by any other value. IF ~bytecell THEN ... is not satisfied by a bytecell containing #FF, and is satisfied by any other value.
These 'flag' conditions are used in testing on/off flag bytes (switches). The EQUATE values TRUE (_1) and FALSE (0) may be used to turn flags on or off. TRUE is equivalent to #FF in a bytecell, and FALSE is equivalent to #00 (different from #FF). So the assignment statements:
bytecell := TRUE | SET bytecell flag | and bytecell := FALSE | RESET bytecell flag |
would SET or RESET the specified bytecell. A 'flag' condition is nothing more than a shorthand for:
IF bytecell = TRUE THEN ... | bytecell | and IF bytecell ~= TRUE THEN ... | ~bytecell |
The compare instructions mentioned earlier vary depending on the items being compared. The list below contains representative conditions for every possible compare instruction that can be generated. Assembly language equivalents are given as comments. Only constants, strings, and pre-declared identifiers are used in these examples; and the relation is 'equals' (=), although any relational operator could be substituted. (Conditional branch instructions are not shown.)
R1 = R2 | CR 1,2 | F0 = F4 | CER 0,4 | F45 = F67 | CDR 4,6 | R2 = 0 | LTR 2,2 | F6 = 0R | LTER 6,6 | F23 = 0L | LTDR 2,2 | R3 = 27 | C 3,=F'27' | R4 = 12S | CH 4,=H'12' | F2 = 3.14 | CE 2,=E'3.14' | F01 = 1.5L | CD 0,=D'1.5' | | Note: constants in the preceding four (4) examples | | may be replaced by cells of equivalent type. | R5 = "VALS" | CL 5,=C'VALS' | B3 = B7 | CLC 0(4,3),0(7) | B9 = "A" | CLI 0(9),C'A' |
Finally, it should be noted that any condition may be immediately preceded by one or more statements, each terminated by a semicolon. So instead of:
R1 := R1 + R2; IF > THEN ... or R0 := R0-R0; R1 := R1 / 5; IF R0 ~= 0 THEN ...
we could write:
IF R1 := R1 + R2; > THEN ... or IF R0 := R0-R0; R1 := R1 / 5; R0 ~= 0 THEN ...
This facility is particularly useful with WHILE statements, as we shall see later.
A compound condition is nothing more than a series of simple conditions joined together by AND's or OR's or any combination of those operators. Parenthesis may be used to group conditions. So compound conditions look like this:
c1 AND c2 AND c3 ... or c1 OR c2 OR c3 ... or c1 AND c2 OR c3 ... or c1 AND (c2 OR c3) ...
where the c's represent simple conditions, each of which may be preceded by one or more statements.
A compound condition consisting of only AND'ed simple conditions is satisfied if and only if ALL simple conditions are satisfied. Failure of any simple condition is a failure for the entire compound condition. Once a failure is detected, the remaining simple conditions are not tested, and any statements which may have preceded them are not done.
Therefore, IF R1 > 0 AND R2 := B1 + B1(4); ~= THEN ... succeeds if both conditions succeed. However, if the first simple condition fails, the register assignment statement is not done. Clearly, if R1 doesn't contain a valid address, we don't want the assignment statement to be done!
A compound condition consisting of only OR'ed simple conditions is NOT satisfied if and only if ALL simple conditions are NOT satisfied. Success of any simple condition is a success for the entire compound condition. Once a success is detected, the remaining simple conditions are not tested, and any statements which may have preceded them are not done.
As a general rule, the simple conditions most likely to succeed should be written first in an OR'ed compound condition. This will speed up processing because unnecessary tests are frequently bypassed. Similarly, the simple conditions most likely to fail (or which must succeed before another condition can be tested) should be written first in an AND'ed compound condition.
When logical operators are mixed, the expression is evaluated strictly left to right, unless parenthesis are used to group subexpressions. For example:
c1 OR c2 AND c3
This expression is TRUE if either c1 or c2 is TRUE and c3 is TRUE. You could rewrite this expression in either of the following ways:
c3 AND (c2 OR c1) (c1 AND c3) OR (c2 AND c3)
The DEC, LE, and GT operators can be used to create BCT, BXLE, and BXH machine instructions. These operators were added to the PL360 language only for completeness. They are usually used to control WHILE or REPEAT/UNTIL loops. All of these operators make use of Integer Registers (Rn).
The DEC operator is used in the following forms:
DEC Rn ~DEC Rn
The contents of Rn is reduced by 1 and the result is tested for zero. DEC Rn is TRUE is the result is non-zero, and ~DEC Rn is TRUE if the result is zero. Therefore, IF DEC R5 THEN R6 := R5; usually does two things. It always decrements the contents of R5 by 1, and unless the result is zero, it places the results in R6. But if R5 originally contained 1, this statement would not alter R6 and would leave 0 in R5.
The GT and LE operators are used in the following forms:
Rx GT Ry Rx LE Ry
The contents of Ry is added to the value in Rx and the result is compared to a value contained in another register, either Ry or R(y+1). If "y" of Ry is odd, then Ry contains both the increment and comparison value. If "y" of Ry is even, then an even-odd pair of registers is designated, with Ry containing the increment, and R(y+1) containing the comparison value. For example: R4 GT R6 will add R6 to R4 and then compare the results in R4 to the value contained in R7. In a sense, its like the statement and condition: R4 := R4 + R6; R4 > R7 but done with a single instruction without affecting the condition code.
Rx GT Ry is TRUE if the result in Rx is greater than the comprison value. Rx LE Ry is TRUE if the result in Rx is less than or equal to the comparison value.
There are two forms of IF statement. The first is:
Conditions may be simple or compound. If the conditions are satisfied, the statement following THEN is done. If the conditions are not satisfied, the statement is bypassed.
The statement following THEN may be any statement: a GOTO label, a BEGIN
END block, an assignment statement, even another IF statement. But whatever the statement may be, it cannot be labeled. We are not allowed to branch into the middle of an IF statement.
The fact that an IF conditions THEN may be followed by another IF conditions THEN leads to a very interesting side effect.
IF c1 THEN IF c2 THEN statement;
acts very much like:
IF c1 AND c2 THEN statement;
In other words, the final statement is done only if both sets of conditions are satisfied.
This second form of IF statement provides a statement to be done when conditions succeed, and another statement to be done when they fail.
The statement following THEN (before ELSE) may be any statement except an IF, WHILE, REPEAT/UNTIL, or FOR statement. Of course, it may be a block containing any statements. The statement following ELSE may be any statement, including an IF, WHILE, REPEAT/UNTIL, or FOR statement. We shall call these statements the THEN statement (following THEN) and the ELSE statement (following ELSE). Neither statement may be labeled.
At the beginning of Chapter 5 we stated that statements are normally terminated by a semicolon, but that many examples would not include a terminating semicolon. The reason for this is that the THEN statement of an IF-THEN-ELSE is not terminated by a semicolon! ELSE acts as the terminator of the THEN statement. So all of the examples of statements (including blocks) which did not end in a semicolon were examples of statements that could occur between THEN and ELSE. Each of those statements would be terminated by a semicolon if they were not used as the THEN statement of an IF-THEN-ELSE.
An IF-THEN-ELSE statement is processed in the following way. If the conditions are satisfied, the THEN statement is done and the ELSE statement is bypassed. If the conditions are not satisfied, the THEN statement is bypassed and the ELSE statement is done.
Since the ELSE statement may be another IF statement of either form, we could write something like:
IF c1 THEN s1 ELSE IF c2 THEN s2 ELSE s3;
s1 is done if c1 succeeds; s2 is done if c1 fails and c2 succeeds; s3 is done when both c1 and c2 fail. Note that s2 is not done if both c1 and c2 would succeed. Only s1 is done and c2 isn't even tested.
Consider the following chained IF statement:
IF c1 THEN IF c2 THEN s2 ELSE s3;
You cannot have an ELSE associated with the first IF statement. Only the second IF statement can have an associated ELSE statement. In this example, if c1 fails, the remainder of the statement is skipped; c2 is not evaluated and neither s2 or s3 are done. If c1 succeeds, then s2 is done if c2 succeeds, otherwise s3 is done.
Let's examine the following chained compound condition.
IF c1 OR c2 THEN IF c3 OR c4 THEN s1 ELSE s2;
By substituting AND for THEN IF and using parenthesis, we would get:
IF (c1 OR c2) AND (c3 OR c4) THEN s1 ELSE s2;
This statement executes differently than the chained IF statement. I leave it to the reader to determine why they are different.
If the THEN statement is a BEGIN...END block ending with a GOTO statement (where the END of the block isn't labeled), there's no reason for coding an ELSE. One might as well write:
IF conditions THEN BEGIN : : : : : GOTO label; END; statement; instead of: IF conditions THEN BEGIN : : : : : GOTO label; END ELSE statement;
Clearly, if the conditions are satisfied, the block will be done; and it unconditionally branches, thus bypassing the final statement (and possibly others). If the conditions are not satisfied, control passes around the block to the final statement.
There are two advantages to writing this IF statement without the ELSE. First, we can eliminate an unnecessary branch instruction that the compiler would generate following the block intended to branch around the ELSE statement. No ELSE, then no compiler generated branch; it's that simple. The compiler does check for THEN GOTO label and ELSE GOTO label; so that unnecessary branch instructions are not generated in those cases, but blocks are not checked.
The second advantage is that the final statement can be labeled if we don't use the ELSE. That makes the statement accessible by GOTO statements in other sections of the program. An IF statement like:
IF conditions THEN GOTO label ELSE statement; might as well be written as: IF conditions THEN GOTO label; statement; to allow for possible labeling of the statement.
Note: IF conditions THEN GOTO label; generates a single 4-byte conditional branch instruction when 'conditions' are only an integer value condition or relational operator.
To conclude our discussion of IF statements, let's examine the concept of inverse conditions. Many times a set of conditions may be given which are just the inverse of what's desired. Using a bicycle as an example: "If the front tire is not flat AND the back tire is not flat THEN DON'T fix a tire." If we called 'the front tire is not flat' condition c1, 'the back tire is not flat' condition c2, and 'fix a tire' statement s, then we could restate the problem as:
IF c1 AND c2 THEN DON'T s;
What we'd really like to know is when should we 'fix a tire'; when should we do statement s ? There are two ways to accomplish this in PL360.
The first way is by means of the NULL statement: a statement which does nothing. We could restate the problem as:
IF c1 AND c2 THEN NULL ELSE s;
If the conditions are satisfied, we do nothing (NULL). Only when a condition fails do we 'fix a tire'.
The second method is better, but requires some understanding of Boolean logic. Let's look at the inversion rule first. The inverse of a simple condition such as c1 is NOT c1 (~c1). The inverse of 'the front tire is not flat' would be 'the front tire is flat'. Below are a few examples of simple conditions and their inverses.
Condition (c1) Inverse (~c1) -------------- ------------- R1 < 17S R1 >= 17S F01 <= F02 F01 > F02 X = "FLAT" X ~= "FLAT"
The inverse of an inverse condition is the original condition again! So the six relational operators shown above are properly paired on each line. The conditions such as OVERFLOW, ON, MIXED, etc. would have ~OVERFLOW, ~ON, ~MIXED, etc. as their inverses.
The second Boolean logic rule involves the distribution of NOT (~) throughout a compound condition. This rule states that:
~ [ c1 AND c2 AND c3...] becomes ~c1 OR ~c2 OR ~c3... and ~ [ c1 OR c2 OR c3...] becomes ~c1 AND ~c2 AND ~c3...
All of the logical operators are 'inverted' (AND's replace OR's, OR's replace AND's), and each simple condition is replaced by its inverse. In our bicycle problem, we wanted:
IF ~[c1 AND c2] THEN s;
Using the Boolean logic rules, we can write:
IF ~c1 OR ~c2 THEN s;
which makes our original problem read: "IF the front tire is flat OR the back tire is flat THEN fix a tire."
A WHILE statement is defined as:
The WHILE statement allows repeated execution of the DO statement as long as the specified conditions are satisfied. If the conditions fail the first time, the DO statement is bypassed entirely. Any statement, including another WHILE statement, is allowed to follow DO; however, a GOTO statement would serve no useful purpose.
We could write the equivalent of a WHILE statement using a labeled IF, GOTO, and block. Thus,
label: IF conditions THEN BEGIN statement; GOTO label; END;
In the preceding section (IF statements), we presented a bicycle problem that could be done with a WHILE statement. What if both tires were flat? We'd want to fix both of them. Using the same terminology (c1, c2, and s), we could write the following WHILE statement:
WHILE ~c1 OR ~c2 DO s;
where statement s is now: 'fix the appropriate tire'. In other words, "WHILE the front tire is flat OR the back tire is flat DO fix the appropriate tire." Obviously, the conditions should eventually fail or we'd have an infinite loop!
One last point: the fact that simple conditions may be preceded by one or more statements means we can write some strange looking WHILE statements:
WHILE statement; condition DO NULL; In this case, the statement is done BEFORE the condition is tested. If the condition is satisfied, we repeat the entire process. When the condition fails, we go on to whatever follows the WHILE statement.
A REPEAT/UNTIL statement is defined as:
The REPEAT/UNTIL statement allows repeated execution of the statements between REPEAT and its matching UNTIL. The statements are executed at least once. When the UNTIL is encountered, the conditions are evaluated. If the conditions fail, the REPEAT/UNTIL is repeated. If the conditions are true, execution continues with the next statement following the UNTIL. Any statements are allowed to follow REPEAT, including other REPEAT/UNTIL statements. A GOTO statement may branch to any label within scope, including those which might be defined within the REPEAT/UNTIL statement.
REPEAT/UNTIL statements may be statements following THEN or ELSE of IF statements, but UNTIL may not be directly followed by ELSE. Example:
IF initial_conditions THEN REPEAT .... UNTIL stop_conditions;
If the initial_conditions are not true, the entire REPEAT/UNTIL is not executed.
We could write the equivalent of a REPEAT/UNTIL statement using a label, IF, and GOTO. Thus,
label: statement; : : : : : statement; if ~(conditions) THEN GOTO label;
Compare REPEAT/UNTIL to the WHILE statement example given at the end of the previous section:
WHILE statement; condition DO NULL;
REPEAT statement; UNTIL condition;
The difference is that a true condition causes repetition of the WHILE statement, and a false condition causes repetition of the REPEAT/UNTIL statement.
The FOR statement is used primarily to control indexing through an array. Its basic form is:
The integer register assignment beginning the FOR statement establishes the initial value of the control register, Rn. The increment must be an INTEGER constant, either positive or negative. This increment is added to the contents of the control register after each execution of the DO statement. The limit must be an INTEGER register, constant or cell, or a SHORT INTEGER cell (a SHORT INTEGER constant is treated as if it were an INTEGER constant). The initial value in the control register, and each succeeding incremented value, is tested against this limit. When the specified increment is positive, the DO statement is executed as long as the control register's value is less than or equal to the limit (Rn <= limit). When the specified increment is negative, the DO statement is executed as long as the control register's value is greater than or equal to the limit (Rn >= limit).
FOR examples: FOR R7 := 20 STEP _4 UNTIL 0 DO statement; FOR R5 := R5-R5 STEP 5 UNTIL 100 DO statement; FOR R0 := 4 STEP 4 UNTIL 64 DO statement; FOR R3 STEP 2 UNTIL R9 DO statement;
The last example assumes that the initial value in R3 had been previously computed, and that R9 contains an upper limit.
We can simulate a FOR statement using a block, register assignment statements, and a WHILE statement. Thus,
Rn := expression; WHILE Rn relation limit DO BEGIN statement; Rn := Rn + increment; END;
The 'relation' would be >= if the increment were negative, and <= if the increment were positive. Notice, however, that the WHILE statement method is much more powerful than the simple FOR statement. We are allowed to have a register or cell increment, not just a constant. Also, the conditions of the WHILE statement may be compound conditions. In fact, the loop could be controlled by cell-to-cell or floating-point register relations, not just an integer register relation.
The last statement we will cover in this chapter is the CASE statement. Unlike the IF, WHILE, and FOR statements which allowed only one or two subordinate statements, the CASE statement allows a large set of subordinate statements. However, only one of these statements is executed at each execution of the CASE statement. The general form of a CASE statement is:
CASE Rn OF BEGIN statement; | 1st subordinate | statement; | 2nd subordinate | : : : : statement; | i-th subordinate | : : : : statement; | m-th subordinate | END
The BEGIN ... END block is required. Neither the block nor any of the subordinate statements may be labeled. Also, register Rn must be an integer register other than R0 (an index register, R1 through R15).
The contents of Rn must be a value from 0 through m, where m is the number of subordinate statements within the CASE statement's block. The subordinate statement whose ordinal number corresponds to the register's value is selected for execution, and all the other subordinate statements are bypassed. If Rn=0, no subordinate statement is executed; if Rn=1, the first subordinate statement is executed; if Rn=i, the i-th subordinate statement is executed; and so on. The original value in Rn is modified by this selection process, becoming the relative address within the program segment of the start of the selected subordinate statement. For example:
CASE R8 OF BEGIN R1 := R1 AND R2; | R8 = 1 | R1 := R1 OR R2; | R8 = 2 | NULL; | R8 = 3 | R1 := R1 XOR R2; | R8 = 4 | BEGIN R0 := R1 SHRA 31; R1 := R1 / R2; | R8 = 5 | END; R1 := R1 * R2; | R8 = 6 | END
A word of caution: the CASE statement does not check the validity of the original value in Rn! Values outside the range 0 through m will cause unpredictable execution or abnormal program termination. Therefore, when in doubt about the initial value in Rn, it's wise to make the CASE statement part of an IF statement.
IF Rn >= 0 AND Rn <= m THEN CASE Rn OF BEGIN statement; | 1st subordinate | statement; | 2nd subordinate | : : : : statement; | m-th subordinate | END ELSE statement; | error processing |
If no error processing statement is desired, we would terminate with just END; instead of the END ELSE statement; termination shown.
We could approximate a CASE statement with IF statements. Thus,
IF Rn = 0 THEN NULL ELSE IF Rn = 1 THEN statement ELSE IF Rn = 2 THEN statement ELSE : : : : : : : : : : : IF Rn = m THEN statement;
However, the CASE statement is much more efficient than the IF statement approximation. CASE branches directly to the appropriate statement eliminating all the testing required by the IF's. Also, the subordinate statements of CASE may be any statements including IF's, WHILE's, and FOR's. Those statements would not be allowed between THEN and ELSE in our approximation unless they were contained in blocks.
The approximation was presented to help clarify what logically happens in a CASE statement. But it also points out the fact that a particular task may be accomplished in more than one way. We've seen alternatives for WHILE and FOR statements in preceding sections; and sometimes, Rn := @Bn(k) can be used instead of Rn := Rn+k . This wide variety of alternatives in solving a problem is what makes programming such a challenge. Knowing what statements to choose, and when, is more than a matter of taste: it's an art.
Write the relationship for the inverse of the conditions given:
(a) R1 > R2 (b) F01 <= 0L (c) X ~= Y (d) OVERFLOW
|1| BEGIN |2| X: BEGIN |3| X: BEGIN GOTO X; |4| X: END; GOTO X; |5| END; GOTO X; |6| END
Does each GOTO refer to only one label? Match the line number of each label with the line number of the GOTO's that refer to that label.
Change the labels on lines |3| and |4| in the preceding problem to Y and Z. Does any GOTO X refer to an unknown label? If so, what are the line numbers of those GOTO's?
IF R1 = 0 THEN s1 ELSE IF < THEN s2 ELSE s3;
In this IF statement, s1, s2, and s3 each represent an assignment statement. Specify the conditions (or inverse conditions) which must be satisfied to:
(a) cause s1 to be executed (b) cause s2 to be executed (c) cause s3 to be executed
IF R1 > R2 OR R2 < R3 THEN IF R4 > R5 THEN s1 ELSE s2;
In this complex IF statement, s1 and s2 each represent an assignment statement. Specify the conditions (or inverse conditions) which must be satisfied to:
(a) cause s1 to be executed (b) cause s2 to be executed (c) cause both s1 and s2 to be bypassed
Do not include untested conditions, but do specify all combinations.
BEGIN |-- BINARY SEARCH CORE ENTRIES THRU SORTED TABLE --| EQUATE TABSIZ SYN 4, KEYSIZ SYN 6; |-- SIZES --| BYTE KEY SYN 3; |-- RELATIVE START OF KEY IN ENTRY --| INTEGER TABLE SYN 0; |-- RELATIVE START OF TABLE --| INTEGER REGISTER LO SYN R1, HI SYN R2, POINT SYN R3, MID SYN R4, INCR SYN R5, MASK SYN R6, ENTRY SYN R7; |-- ASSUME LO, HI, AND POINT ALREADY CONTAIN ADDRESSES --| INCR := TABSIZ; MASK := NEG INCR; |-- INCREMENT VALUES --| WHILE LO <= HI DO |-- SEARCH FOR ENTRY THRU TABLE --| BEGIN MID := LO + HI SHRA 1 AND MASK; |-- MIDDLE --| ENTRY := TABLE(MID); |-- ADDR OF ENTRY FROM TABLE --| IF KEY(ENTRY/KEYSIZ) = KEY(POINT) THEN GOTO EXIT; IF > THEN LO := MID + INCR |-- RAISE LO BOUND --| ELSE HI := MID - INCR; |-- LOWER HI BOUND --| END; MID := MID - MID; |-- NO FIND, SET MID TO ZERO --| EXIT: |-- MID = 0 IF NO FIND, ELSE MID IS TABLE LOCATION --| END
The preceding is an example of a binary search technique.
(a) What would be the first value assigned to MID (in terms of R1) if we assumed: R2 := 14 SHLL 2 + R1; before beginning the binary search? (Note: R1 is LO, R2 is HI.)
(b) What is the maximum number of times that the TABLE addresses could be referenced assuming: R2 := 14 SHLL 2 + R1; before beginning the binary search? (Note: R1 is LO, R2 is HI.)
(c) Why must we AND MASK in the evaluation of MID?
The statements covered in Chapters 5 and 6 are capable of generating nearly 100 different machine instructions (see Appendix D). However, there are other instructions available in the hardware which cannot be generated by those statements. Functions provide us with the means for generating most of these other instructions. Also, there may be instances where we'd like to generate a compare instruction without the branch instruction which normally follows it. Again, functions come to the rescue by allowing us to generate independent instructions.
Every function statement generates a single machine instruction by invoking a function defined by a function declaration or pre-declared by the PL360 compiler. We shall examine function definitions first because they are the link between function statements and machine instructions.
A function is defined by a function declaration. Just like all other PL360 declarations, function declarations MUST occur before any statements or labels in a block. The general form of a function declaration is:
A single function is defined by the three quantities: id, type, and code. The 'id' is an identifier which names the function being defined. In a sense, we can think of this function name as being the 'name' of a machine instruction: an instruction mnemonic.
The 'type' of a function definition must be an integer value from 0 through 19. This value selects one of 20 possible instruction formats. We examined instruction formats briefly in section 1.4 where we mentioned that instructions can be either one, two, or three halfwords in length. However, there are many formats possible depending on instruction length and how various fields are to be interpreted within an instruction. For example, a halfword instruction (two bytes) could have its second byte interpreted as an integer value, a pair of register designators, or as a mask and register.
The 20 function types not only specify instruction length, but also the number of fields interpreted and what each is interpreted to be: a value, register, cell address, etc. These fields are defined by the 'parameters' of a function statement. Each parameter is a value, register, cell address, etc., that occupies a specific field position within the final instruction generated. A PL360 label is not an allowed parameter, so instructions corresponding to BCT, BXH, and BXLE in assembly language are difficult to generate, although "<reg> GT <reg>" and "<reg> LE <reg>" conditions can create the BXH and BXLE instructions. Also, a PSTAR cell reference defines the "current program address" as a base-displacement field, which can be used to create indexed branches.
The 'code' of a function definition must be an integer (or short integer) value. The lower two bytes of this value define the first two bytes of the machine instruction to be generated. Therefore, 'code' is usually written as a hexadecimal value of the form: #hhhh, where the h's represent hexadecimal digits. This is the only form we shall use throughout this text.
Since the first byte of every machine instruction is its op-code (operation code), the first two h's define the instruction's op-code. Not all 256 possible op-codes are valid, so reference should be made to IBM's Principles of Operation manuals when selecting op-codes. The PL360 compiler does NOT check op-code validity; but it does check that the op-code is proper for the 'type' of function being defined. The relationship between op-code and instruction length is:
Op-codes: #00 through #3F Length: 1 halfword Op-codes: #40 through #BF Length: 2 halfwords Op-codes: #C0 through #FF Length: 3 halfwords
The last two h's of 'code' define the second byte of the instruction. Frequently this byte is defined as 00 because parameters are usually OR'ed or XOR'ed into it by the PL360 compiler. But this byte could be defined with other values depending on the function type specified and op-codes chosen.
Function Number of Instruction Fields Type Parameters 0 8 16 32 48 --------- 0 0 | | ---------
--------- 1 2 | |R|R| ---------
------------------ 2 2 | |R| XL | ------------------
------------------ 3 3 | |R|R| C | ------------------
------------------ 4 2 | | S | C | ------------------
--------------------------- 5 3 | | S | C | CL | ---------------------------
--------- 6 1 | |R| | ---------
--------- 7 1 | | S | ---------
------------------ 8 1 | | C | ------------------
------------------ 9 2 | |R| | CI | ------------------
--------------------------- 10 4 | |N|N| C | CL | ---------------------------
------------------ 11 2 | |R| XD | ------------------
------------------ 12 2 | |R| X | ------------------
--------------------------- 13 3 | | S | CL | CL | ---------------------------
--------------------------- 14 2 | | C | CL | ---------------------------
------------------ 15 1 | | XL | ------------------
------------------ 16 3 | |R|N| CL | ------------------
------------------ 17 3 | |R|N| C | ------------------
------------------ 18 1 | | 0 0 R 0| ------------------
------------------ 19 1 | | CI | ------------------
Note: INTEGER MEM SYN 0; |See section 4.5.| and EQUATE ON SYN 1, OFF SYN 8; |See section 4.7.|
A function statement causes a single machine instruction to be generated. Since function definitions allow from zero through four parameters, there are five possible forms of function statement:
id id(p1) id(p1,p2) id(p1,p2,p3) id(p1,p2,p3,p4)
The 'id' is the function name associated with a function definition. The p's are parameters of the function. The number of parameters and what each must be is determined by the function 'type' and associated field definition codes given in section 7.1. For functions with more than one parameter, the left-to-right sequence of parameters in the function statement must correspond to the left-to-right sequence of fields defined within the instruction to be generated.
For example: FUNCTION LOAD(12,#5800); invoked by the statement: LOAD(R1,B3(R5+4)) would generate a machine instruction that looks like this: 58153004. This instruction is identical to the instruction generated by the assignment statement: R1 := B3(R5+4).
A function statement consisting of just a function name is only valid for function type 0 which has no parameters. The single halfword instruction generated consists of just the function 'code' given in the function definition, #hhhh.
For example: FUNCTION SR34(0,#1B34); invoked by the statement: SR34 would generate a machine instruction which looks like this: 1B34. This instruction is identical to the instruction generated by the assignment statement: R3 := R3 -- R4 .
Neither of these examples are meant to imply that a function statement should replace an assignment statement. But they do show that most machine instructions generated by an assignment statement can also be generated by a function statement.
A large group of functions are pre-declared by the PL360 compiler. Except for TEST, SET, and RESET, all function names were chosen to be the assembly language mnemonics corresponding to the instructions these functions would generate. The pre-declared function definitions are:
BALR(1,#0500) MVC(5,#D200) SRDL(9,#8C00) CLC(13,#D500) MVI(4,#9200) STC(12,#4200) CLI(4,#9500) MVN(5,#D100) STCM(17,#BE00) CLM(16,#BD00) MVZ(5,#D300) STH(12,#4000) CVB(12,#4F00) NC(5,#D400) STM(3,#9000) CVD(12,#4E00) NI(4,#9400) SVC(7,#0A00) ED(5,#DE00) OC(5,#D600) TEST(8,#95FF) EDMK(5,#DF00) OI(4,#9600) TM(4,#9100) EX(2,#4400) PACK(10,#F200) TR(5,#DC00) IC(2,#4300) RESET(8,#9200) TRT(5,#DD00) ICM(16,#BF00) SET(8,#92FF) TS(8,#9300) LA(2,#4100) SLDA(9,#8F00) UNPK(10,#F300) LH(12,#4800) SLDL(9,#8D00) XC(5,#D700) LM(3,#9800) SPM(6,#0400) XI(4,#9700) LTR(1,#1200) SRDA(9,#8E00)
We shall study some of these function in the remainder of this section, but for complete and accurate descriptions of most of these functions, see IBM's Principles of Operation manuals.
The headings for each function description are of the form:
These instructions shift two integer registers at once in a manner similar to the single register shift operations associated with integer register assignment statements. The specified register (R) must be an even-numbered integer register (R0,R2,R4,etc.). The contents of the even-odd register pair so specified is left- or right-shifted as a unit an amount determined by CI. Thus, SRDL(R2,28) would shift the contents of R2-R3 right logically by 28 bits. The shift amount (I) may range from 0 through 63 bits. A cell reference (C) indicates doing address arithmetic and the lower six bits of the resultant address determines the shift amount. Thus, SLDA(R4,B1(3)) with R1=7 would yield a shift amount of 10 bits (7+3). The contents of R4-R5 would be shifted left arithmetically by 10 bits.
In all these double register shifts, the lower bits in the even- numbered register are followed immediately by the upper bits of the odd-numbered register. For arithmetic shifts (SRDA,SLDA), condition codes are affected, and the top bit in the even-numbered register is the sign bit. SRDA(R,0) can be used to 'test double integer register'.
The IC function causes the lowest byte of an integer register (R) to be replaced by the contents of a single byte from storage (XL). The upper three bytes of the integer register are unaffected! Thus IC(R0,B3(R2+1)) would replace the lowest byte of R0 with the byte contained in the cell whose address is determined by B3(R2+1). Similarly, IC(R5,"A") would cause the character A to be placed in the current program segment's literal pool, and the instruction would cause the lowest byte of R5 to be replaced by the character A.
The STC function stores the lowest byte of an integer register (R) into a single storage byte (X). Thus, STC(R7,B6) would store the lowest byte of R7 into the single byte cell whose address is given by the contents of R6.
These instructions allow many (multiple) integer registers to be loaded or stored at one time. If the 16 integer registers were assumed to occupy positions similar to the numerals on the face of a clock, then the range of registers processed would be Rx through Ry taken clockwise (R15 followed by R0).
C is the address of the first of as many consecutive words of storage as needed to handle the range of registers determined by Rx through Ry. Usually this is an ARRAY of INTEGER cells. Thus, STM(R1,R4,B7) would generate a single instruction whose affect would be the same as the following set of assignment statements:
B7 := R1; B7(4) := R2; B7(8) := R3; B7(12) := R4;
Example: ARRAY 16 INTEGER REGS; |Storage area| : : : : : : STM(R1,R0,REGS) |Store all registers| : : : : : : LM(R3,R0,REGS(8)) |Reload all registers| | except R1 and R2 |
Note: the base register associated with REGS is being stored by the STM instruction. Since the LM instruction requires that the base register be correct in order to do the load, its content would be replaced by its original (identical) content if it was not R1 or R2.
All of these instructions use a byte from the instruction (S) and a byte of storage (address defined by C). The CLI instruction logically compares S with C's byte. MVI moves S to C's byte. NI, OI, and XI all logically combine S into C's byte.
For example, CLI("A",B1) generates the same compare instruction as would be generated by IF B1 = "A" THEN (excluding IF's branch instruction). Similarly, CLI(0,B5(3)) and IF B5(3/1) = 0 THEN generate equivalent compare instructions. But, CLI(MEM(MIXED+OFF),B7) could not be duplicated by a simple IF statement without inventing an EQUATE symbol whose value was MIXED+OFF. However, we could write:
IF CLI(MEM(MIXED+OFF),B7); relation THEN ...
where 'relation' is any desired relational operator.
These instructions parallel the 'immediate' instructions just discussed, but on a storage-to-storage basis rather than on an instruction-to-storage basis. The C and/or CL fields define the storage addresses of the items to be processed. The S field value defines a count MINUS ONE of the number of bytes to be processed (S=count-1). Thus, MVC(3,B5,B6) would copy four consecutive bytes from B6 to B5 (R6 and R5 would contain the starting address of each storage area). This would be equivalent to the cell assignment: B5(0/4) := B6.
An instruction such as CLC(0,"1","2") could not be duplicated as a simple condition because two literals are involved. Such an instruction would force a LESS THAN (<) condition because "1" is less than "2". Both "1" and "2" would be placed in the literal pool.
This instruction's byte value (S) acts as a bit mask in selecting bits of a storage byte (C) to be tested. Each non-zero bit in mask S tests the corresponding bit of byte C to establish the machine's condition code. An OFF condition occurs if all tested bits are 0's (or the mask is entirely 0). An ON condition occurs if all tested bits are 1's. Otherwise, a MIXED condition occurs when some of the tested bits are 0's AND some are 1's. Thus, TM(3,B6) tests the lowest two bits of the byte at B6 to establish one of the three possible conditions. If only a single bit is tested, then only ON/OFF conditions are possible.
These functions are both MVI instructions with the S field preset. RESET(C) is equivalent to MVI(0,C) and SET(C) is equivalent to MVI(#FF,C). Most often these functions are used with bytecell flags in conjunction with the 'bytecell' and '~bytecell' conditions.
This function requires an integer register designator (R) and either a cell reference (X) or literal (L). When used with a cell, LA is equivalent to an assignment statement such as: R := @X; but, when used with a literal, LA provides a unique facility which cannot be duplicated with an assignment statement. The literal is placed in the literal pool, and LA causes the absolute address of that literal to be placed in the specified integer register (R). For example: LA(R1,"Messages are made like this.") would cause the absolute address of the "string" to be placed in R1.
This function loads integer register Rx with the contents of integer register Ry, in a manner similar to Rx := Ry, but it also establishes the machine's condition code. The result in Rx is either (0) equal to, (1) less than, or (2) greater than zero. Thus, IF LTR(R4,R7); = THEN ... would have the same effect as the statements R4 := R7; IF R4 = 0 THEN ... but would take one less instruction. In fact, the compare instruction for R4 = 0 is equivalent to LTR(R4,R4).
An SVC instruction causes a 'supervisor interrupt' thereby giving control to the operating system (VS,OS,MTS,ORVYL,etc.). The S field of an SVC instruction is usually written as an integer value, such as: SVC(13). The operating system interrogates the S field to determine what task to perform for the requesting program. Normally, other parameters are passed to and from the program in registers R0, R1, R14, and R15. Because R15 can be affected by an SVC, we would either have to choose a different program base register, or be very careful about saving and restoring R15. We shall see how registers other than R15 can be chosen as program base registers in Chapter 8.
Before discussing BALR, let's look at the program status word (PSW). Although the PSW is a 64-bit quantity, we are only interested in the lower 32 bits.
---------------------------------------- | ILC | CC | MASK | INSTRUCTION ADDRESS | ---------------------------------------- 2 2 4 24 (bit lengths)
ILC is the 'instruction length code' in halfwords: 1, 2, or 3.
CC is the current 'condition code': 0, 1, 2, or 3.
MASK is a 4-bit quantity with each bit associated with a different program exception (interrupt). The bits in order from left to right correspond to:
Fixed-point overflow (OVERFLOW interrupt) Decimal overflow (Packed-decimal exception) Exponent underflow (Floating-point interrupt) Significance (Floating-point processing)
INSTRUCTION ADDRESS is the address of the 'next' instruction. The current instruction started at this address minus ILC halfwords.
The BALR instruction first determines a branch address dependent upon integer register Ry. If Ry = R0, there is no branch address! If Ry ~= R0 (i.e., R1 through R15), the branch address is contained in the lower 24 bits of Ry. This branch address is saved aside by the computer. Then BALR places the lower 32 bits of the PSW into integer register Rx. Thus, Rx receives the next instruction address, condition code, and other information shown above. Finally, when Ry ~= R0, BALR replaces the lower 24 bits of the PSW by the saved aside branch address determined earlier. This causes an unconditional branch to the address originally contained in Ry. If Ry = R0, no branching takes place.
The BALR instruction is usually generated only by procedure statements as we shall see in the chapter on procedures (Chapter 8). Very rarely is a BALR function used directly in PL360 programming. However, we shall examine uses for BALR(Rx,R0) in the next section (7.4) and with the SPM function below.
The SPM instruction replaces the CC and MASK fields of the PSW by corresponding fields taken from integer register R (same bit positions). For example, we could use BALR(Rx,R0) to save the condition code field (CC) in register Rx, and later restore that condition code with an SPM(Rx) instruction. The MASK, of course, would also be saved and restored by this process, but CC is more volatile than the MASK and is more likely to change.
The EX instruction is probably one of the handiest instructions available. It executes another instruction in storage (address defined by XL). This object instruction may be one, two, or three halfwords in length. Therefore, EX provides a one instruction subroutine facility!
The op-code of EX is #44X, and the EX instruction format is two halfwords in length consisting primarily of an integer register designator (R) and an indexable cell address (XL). Regardless of function name, an Execute Instruction is defined as any function whose function 'code' begins with #44X and whose function 'type' specifies a two-halfword instruction format (most likely types 2 or 15). For example, EXEC(15,#4410) defines an Execute Instruction with the register designator preset to 1 (R1). Only the cell address (XL) would have to be specified by an EXEC function statement.
The Execute Instruction operates in essentially the following manner: The next instruction address portion of the program status word (PSW) is updated based on the length of the Execute Instruction. Then the object instruction to be executed is fetched from storage and prepared for execution. The lowest byte of the integer register specified by the R field of the Execute Instruction is logically OR'ed into the second byte of the object instruction (bits 8-15). If register R0 is specified, this modification does not take place. Finally, the (modified) object instruction is executed in a normal manner. The object instruction may not be another Execute Instruction.
There are three important points worth noting. First, the address of the next instruction is determined by the length of the Execute Instruction, not the object instruction. However, if the object instruction were a branch instruction whose conditions were satisfied, then branching would take place. Second, after the object instruction has been fetched from storage, its second byte is modified by having the lowest byte of a specified integer register (R1 through R15) OR'ed into it. The object instruction is not modified in storage. Third, the address field of the Execute Instruction is an indexable address allowing us to choose one object instruction out of a list of instructions depending on the contents of an index register. Here are some examples to demonstrate these points.
Example 1: BYTE RESULT; ARRAY 6 SHORT INTEGER LOGINS = ( #9400,@RESULT, | NI(0,RESULT) | #9600,@RESULT, | OI(0,RESULT) | #9700,@RESULT); | XI(0,RESULT) | : : : : : : : EX(R1,LOGINS(R2)) |Execute selected instruction|
Assuming the declared cells (RESULT and LOGINS) are based on a register other than R1 or R2; and assuming that R2 contains either a 0, 4, or 8; then the EX instruction would either AND, OR, or XOR the lowest byte of R1 into the RESULT cell.
Example 2: EX(R5,#95003004)
The literal #95003004 is an integer value equivalent to the function statement CLI(0,B3(4)), so this EX instruction would compare the lowest byte in R5 with the contents of the byte cell whose address was determined by B3(4). Using a literal such as the one in this example would be an awkward way to specify object instructions. Fortunately, PL360 allows the literal field of an Execute Instruction to be another function statement!
Example 3: EX(R5,CLI(0,B3(4)))
This is the same as example 2, but using a CLI function statement as the literal field of the EX instruction.
Example 4: EX(R7,CLC(0,B2,B3))
This EX instruction would compare up to 256 characters. Registers R2 and R3 contain the starting addresses of the operands to be compared and the lowest byte of R7 contains the count MINUS ONE of the number of characters to be compared. If the object instruction had been an MVC, then a variable number of characters could have been copied from B3 to B2. The important point is that the lowest byte of the EX instruction's modification register (R) must contain one less than the actual number of bytes to be processed. So values from 0 through 255 in the modification register process from one through 256 bytes. This applies to all storage-to-storage instructions which can process variable-length operands up to 256 bytes (including NC, OC, and XC).
These functions have three parameters: S, C, and CL. C designates the address of the first byte of an operand in storage that is S+1 bytes long. CL designates the address of the first byte of a table which is normally 256 bytes long.
For the TR function, each byte of the C-operand acts as an index into the CL-table. The byte selected from the CL-table replaces the selecting byte in the C-operand. Therefore, the TR function is normally used to convert an operand from one set of codes to another set of codes. However, TR may also be used to rearrange the bytes of a storage value. For example:
INTEGER PATTERN, DATAVALUE; |-- declarations --| PATTERN := #02010300X; |-- cell assignment of pattern --| TR(3,PATTERN,DATAVALUE); |-- rearrange --|
If DATAVALUE contained the string "TOPS", PATTERN would contain the string "POST" following the TR function execution. Note that the CL-table is only the 4-byte DATAVALUE in this case.
For the TRT function, each byte of the C-operand again acts as an index into the CL-table. However, the bytes selected from the CL-table do NOT replace the selecting bytes of the C-operand. Instead, each selected table byte is tested. If the selected byte is zero (=#00X), the TRT function continues to the next selecting byte of the C-operand. If the selected byte is non-zero (>#00X), the TRT function sets the condition code to 1 if other C-operand bytes remain untested, otherwise the condition code is set to 2; then TRT terminates by placing the absolute address of the C-operand selecting byte into the lower 24 bits of R1, and the non-zero byte selected from the CL-table into the lowest byte of R2. If all the bytes selected from the CL-table by the C-operand are zero, the TRT function sets the condition code to 0 and terminates without altering R1 or R2. Essentially, TRT is used to scan an operand for characters of special significance. The CL-table would contain zero bytes for all characters to be skipped, and non-zero bytes for those characters of special significance.
These functions both involve a binary value contained in an integer register (R), and an 8-byte packed-decimal value in storage (X). The storage address (X) must designate a double-word boundary. CVD converts a binary value in R into a packed-decimal value in X. CVB converts a packed-decimal value in X into a binary value in R.
Although the packed-decimal value is eight bytes long for these functions, in general, packed-decimal values can vary in length. Each byte of a packed-decimal value consists of a pair of decimal digits, except for the last byte which contains a decimal digit followed by a sign digit. Each digit takes four bits within a byte. The hexadecimal digits A through F are invalid decimal digits. They are recognized as sign digits with A, C, E, or F interpreted as plus, and B or D interpreted as minus. Normally, a sign digit is either C or D (plus or minus).
If register R1 contained the decimal value: positive 4095; then R1 would contain: 00000FFF as a hexadecimal (binary) value. Assuming the declaration: LONG REAL DOUBCELL; the instruction: CVD(R1,DOUBCELL) would convert the contents of R1 into a packed-decimal value in DOUBCELL. The eight bytes of DOUBCELL would contain: 000000000004095C after the conversion.
If DOUBCELL contained the packed-decimal value: 000000000065536D, then the instruction: CVB(R2,DOUBCELL) would convert the packed- decimal value in DOUBCELL into a binary value in R2. R2 would contain: FFFF0000 which is the hexadecimal representation of negative 65536.
The range of packed-decimal values that can be converted by the CVB function is: 000002147483648D through 000002147483647C. Values outside this range cause a program interrupt for fixed-point division.
These functions operate with two variable-length storage operands, one in packed format and the other in zoned format.
A packed format operand has the general form:
---------- ... ---------- |dd|dd|dd| |dd|dd|dS| ---------- ... ----------
where the d's and S are any hexadecimal digits. Normally, d's are decimal digits and S is a sign digit, i.e., a packed-decimal value.
A zoned format operand has the general form:
---------- ... ---------- |Fd|Fd|Fd| |Fd|Fd|Sd| ---------- ... ----------
where the d's and S are any hexadecimal digits; and the F's are usually hexadecimal F digits, called zone digits. If the d's are decimal digits, then the Fd's are the EBCDIC characters "0" through "9". Thus, the zoned format value F0F2F5F7 corresponds to the character string "0257".
C and CL designate the address of the left-most byte of each operand. The operands can vary in length from one through 16 bytes. N1+1 is the length of the C-operand, and N2+1 is the length of the CL-operand.
For the UNPK function, a zoned format C-operand is created from a packed format CL-operand. The processing proceeds right to left beginning with the right-most byte of each operand. The following example illustrates what happens:
------------- for START = |10|25|63|7D| ------------- UNPK(6,3,RESULT,START) ---------------------- yields RESULT = |F1|F0|F2|F5|F6|F3|D7| ----------------------
Normally, N1 = 2*N2. If N1 > 2*N2, then F0 bytes are used to fill out the C-operand. If N1 < 2*N2, then excess digits of the CL-operand are ignored. Note that the hexadecimal digits of the right-most byte are reversed in position.
UNPK is usually used following a CVD function to convert the packed-decimal value into a printable form. Only the sign digit of the packed-decimal value needs to be modified before doing the UNPK. For example:
CVD(R1,X); OI(#F,X(7)); UNPK(LEN,7,Y,X);
where X is eight bytes on a double-word boundary, and Y is LEN+1 bytes.
For the PACK function, a packed format C-operand is created from a zoned format CL-operand. The processing proceeds right to left beginning with the right-most byte of each operand. The following example illustrates what happens:
---------------- for ORIGIN = |F3|F2|F5|F7|C6| ---------------- PACK(2,4,ANSWER,ORIGIN) ---------- yields ANSWER = |32|57|6C| ----------
Normally, N2 = 2*N1. If N2 < 2*N1, then 0 digits are used to fill out the C-operand. If N2 > 2*N1, then excess bytes of the CL-operand are ignored. Note that the zone digits of the CL-operand are ignored, and that the hexadecimal digits of the right-most byte are reversed in position.
This function is very complex, but very usful in generating reports. Basically, this function converts one or more packed-decimal values in the CL-operand into zoned format, digit by digit, left to right. The C-operand is S+1 bytes long, and special characters in the C-operand indicate where the zoned digits are to be placed. The special characters, called digit selectors, are: #20X and #21X (20 and 21). Each digit selector always causes one decimal digit to be processed from the CL-operand.
The first character of the C-operand is a fill character (usually a blank, asterisk, or other printable character). Processing of the C-operand proceeds in phases:
Phase 1: The fill character replaces all characters in the C-operand, including digit selectors. Phase 1 ends and phase 2 begins when one of two things happens: (1) a digit selector selects a non-zero decimal digit from the CL-operand, in which case the non-zero zoned digit replaces the digit selector; or (2) a #21X digit selector is replaced by the fill character.
Phase 2: Digit selectors are replaced by zoned digits and other characters are left unchanged until one of two things happens: (1) a #22X special character is encountered; or (2) the byte from the CL-operand containing a decimal digit and sign digit is processed by a digit selector. For (1), the #22X character is replaced by the fill character and processing continues as in phase 1. For (2), the digit is processed, then the sign is examined and discarded. If the sign was positive, processing continues as in phase 1; otherwise, processing continues in the current phase (either 1 or 2).
An example of the use of this function is given in the INPUT procedure of the record compare program in Chapter 9.
The functions covered in this section are not pre-declared. They are user-defined functions that serve a useful purpose. Their names were freely chosen, and you could define similar functions with other names if desired. Each function is described by a heading line that contains a general form function statement followed by the function's declaration as a comment.
This function corresponds to a BCTR R,0 instruction in assembly language. REDUCE subtracts one from the contents of integer register R without affecting the machine's condition code. Unlike Rx := Rx -- 1 which requires a constant in storage referenced by a 4-byte subtract instruction, REDUCE(Rx) does not require a constant and REDUCE is only a 2-byte instruction.
This function corresponds to a BALR R,0 instruction in assembly language or the BALR(Rx,R0) function defined in section 7.3. SETUP places the lower 32 bits of the PSW into integer register R. This gives us a convenient means for saving the current condition code, and also provides us with the address of the next instruction. Since we can't use a label in any function, there are some interesting applications for SETUP which circumvent that restriction. The following example demonstrates such an application.
| R3 contains 1,2,3,...,n | | Select one message from a list | R3 := R3 SHLL 3; |-- Multiply by 8 --| SETUP(R4); |-- Next instruction address to R4 --| | B4(0) | EX(R0,B4(R3+4)); |-- Does one LA instruction --| | B4(4) | EX(R0,B4(R3+8)); |-- Does one R2 := STRING; --| | B4(8) | GOTO TAG; |-- Branch around list --| | B4(12) | LA(R1,"Message 1 ... "); R2 := STRING; | B4(20) | LA(R1,"Message 2 ... "); R2 := STRING; | : : : : : : : : : : : : : : : : : : : | LA(R1,"Message n ... "); R2 := STRING; TAG: |-- R1 and R2 contain message address and length --|
Since R3 contains 8,16,24,... and R4 contains the absolute address of the first EX instruction, we can use R4 as the base register of a stack of 4-byte instructions and do address arithmetic relative to that base!
We could have done this problem with a CASE statement (those interested might like to try it). But CASE would have generated about 6*n additional instruction bytes. So for 25 messages we have saved nearly 150 bytes.
This function is used in conjunction with the SETUP function previously described. BRANCH provides a computed GOTO facility. Thus:
| R3 contains 0,1,2,...,n | R3 := R3 SHLL 2; |-- Multiply by 4 --| SETUP(R2); BRANCH(B2(R3+4)); GOTO TAG0; GOTO TAG1; GOTO TAG2; | : : : : : : : | GOTO TAGx; | x = n-1 | TAGn: | Statements for R3 = n | TAGx: | Statements for R3 = n-1 | | : : : : : : : : : : : : : : : : | TAG2: | Statements for R3 = 2 | TAG1: | Statements for R3 = 1 | TAG0: | Statements for R3 = 0 |
Note that BRANCH provides a facility similar to a CASE statement. But unlike CASE, it is possible for statements at any TAG to GOTO any other TAG thus allowing code to be shared. Also note that we didn't have a GOTO TAGn; at the end of the list of GOTO statements because BRANCH jumps around the list directly to TAGn when R3 = n.
The CR instruction compares integer register Rx against integer register Ry to establish the machine's condition code. This is the same compare instruction as would be generated by: IF Rx = Ry THEN... (excluding IF's branch instruction). CR is frequently used when two variable-length strings are to be compared. For example:
| 1ST string defined by starting address in R2, length in R1 | | 2ND string defined by starting address in R4, length in R3 | | Both R1 and R3 contain string lengths: 1 <= length <= 256 | REDUCE(R1); REDUCE(R3); |-- Reduce both lengths by 1 --| IF R1 <= R3 THEN EX(R1,CLC(0,B2,B4)) ELSE EX(R3,CLC(0,B2,B4)); IF = THEN CR(R1,R3); |-- Compare lengths --| | Condition Code established by CLC or CR |
The strings are compared through the length of the shorter, and when that results in an ='s condition, the lengths are compared with CR.
These functions correspond to OI and NI functions. They allow bits within a byte to be set or reset. Thus, SETB(1,cell) sets a bit, and RESETB(1,cell) resets that same bit. TM(1,cell) would result in an ON condition if the bit was set, or an OFF condition if it was reset.
Write function statements in general form for the following function declarations:
(a) FUNCTION PAUSE(0,#0AFF); (b) FUNCTION SETZONE(8,#960F); (c) FUNCTION STCK(8,#B205); (d) FUNCTION AP(10,#FA00);
Assume the following data segment has been defined:
SEGMENT BASE R6; INTEGER X = 0, Y = #5040605C; LONG REAL FILLER; ARRAY 20 INTEGER VALUES; CLOSE BASE;
True or false: the value in Y can be interpreted as:
(a) a quoted string of 4 characters ("string") (b) a computer instruction (c) a binary value (d) a packed-decimal value
For each item marked TRUE in the above problem, do the following:
(a) Using the EBCDIC table in Appendix C, write the value in Y as a quoted string.
(b) Using Appendix D and the identifiers from the data segment above write a PL360 construct corresponding to the instructon executed by EX(R0,Y) .
(c) Write as a variable-length hexadecimal string the packed-decimal value in FILLER resulting from: R4 := Y; CVD(R4,FILLER);
(d) Can the functions: CVB(R5,X); and UNPK(15,7,X,X); both be done successfully (in the order given)? For the CVB function, what is the binary value in R5 written in hexadecimal form? For the UNPK function, using the EBCDIC table in Appendix C, write the unpacked result as a quoted string.
In the preceding chapters we've covered all of the declarations, statements, constants, and comments available in writing PL360 programs; everything except procedure declarations and statements. We've saved procedures until last because they can utilize everything, including themselves! Essentially, a procedure is a subroutine: a body of code that can be called upon from many different points to do a particular task, and which has the capability of returning to the caller upon completion. By that definition, a procedure needs: (1) an identifying 'name' to make it known to a caller, (2) a 'return' mechanism so the procedure can return to its caller, and (3) the ability to define code. The basic PL360 procedure declaration satisfies these requirements:
where 'name' is the procedure's identifier; Ra, called the 'return address' register, is an integer register designator other then R0; and 'statement' is any PL360 statement which defines the procedure's code. By 'code' we mean the machine instructions generated by the procedure's 'statement'.
Once a procedure has been defined, it may be called upon by a procedure statement. The basic form of a procedure statement is:
where 'name' is the name of the procedure to be called. Such a call transfers control to the start of the procedure's code, and also places a return address in Ra. Upon completion of the procedure's code, the procedure normally returns to the statement following the procedure statement by transferring control to the address contained in Ra. The machine instructions used to accomplish these feats are the BAL or BALR instructions of a procedure statement, and a BR instruction following the procedure's code.
There are five types of procedures available in PL360: Simple, COMMON, SEGMENT, GLOBAL, and EXTERNAL. Simple and COMMON procedures are called local procedures and they generate code in the program segment containing their definition. Therefore, a local procedure contributes to the 4096-byte limit of the program segment containing it. SEGMENT and GLOBAL procedures are called global procedures and they generate separate program segments, each with a 4096-byte limit. These segments can have the same or different program base registers, and by having many such segments we can compile very large programs. (The main program segment defined in section 3.5 is actually a global procedure as we shall see later.) EXTERNAL procedures do not generate code. Instead, they refer to procedures which have been generated elsewhere. Pre-compiled subroutines in a library would be an example.
Procedure synonyms provide a mechanism for giving another name to a previously declared procedure. This is most handy in $IF-tested code where different procedure names might be involved.
There are two types of local procedure:
COMMON procedures have all the properties of Simple procedures, but they also define an entry point, a property we will cover in the next section. We will use only Simple procedures in this section.
|010| BEGIN |-- Outer block containing Simple procedure --| |020| EQUATE COPYLEN SYN 16; |030| PROCEDURE COPYCORE (R4); IF R1 > 0 THEN |040| BEGIN |-- Copy R1 bytes from B2 to B3 --| |050| FUNCTION REDUCE(6,#0600); |060| REDUCE(R1); WHILE R1 >= COPYLEN DO |070| BEGIN B3(0/COPYLEN) := B2; |080| R3 := @B3(COPYLEN); |090| R2 := @B2(COPYLEN); |100| R1 := R1 - COPYLEN; |110| END; |120| EX(R1,MVC(0,B3,B2)); |130| END; |140| ARRAY 100 BYTE AREA; |150| |-- Main Code of outer block --| |160| R3 := @AREA; |170| LA(R2,"This string will be placed in AREA"); |180| R1 := STRING; |190| COPYCORE; |200| R3 := @AREA(STRING); |210| LA(R2," followed by this string."); |220| R1 := STRING; |230| COPYCORE; |240| END
Although the example is correct, assuming there's a data segment to which AREA can belong, we could have placed the strings into the AREA using only cell assignment statements:
AREA := "This string will be placed in AREA"; AREA(STRING) := " followed by this string.";
and thus would have completely obviated the need for a procedure. But the example does provide us with a vehicle for further discussion, and that is its main purpose.
In our example there is one procedure declaration for COPYCORE (lines 030 through 130), and two procedure statements which call upon that procedure (lines 190 and 230). The procedure is valid and very useful for copying data of any length from one section of storage to another, although it probably would be coded with 256 instead of COPYLEN to be most efficient.
The procedure 'name' is COPYCORE, its Ra = R4, and its 'statement' is an IF statement whose THEN statement is a block (BEGIN ... END). Within that block there is a function declaration for REDUCE, some function statements, and a WHILE statement whose DO statement is another block containing cell and register assignment statements.
The REDUCE function is not known outside of the procedure. That would be true of any declarations occurring within the procedure's block. So, COPYLEN, COPYCORE, and AREA are known to the main code of the outer block; and COPYLEN, COPYCORE, and REDUCE are known to the main code of the procedure's block. The fact that a procedure declaration's 'statement' knows its own procedure 'name' means we can write recursive procedures (procedures which call themselves)!
Each call to a procedure establishes a return address in that procedure's return address register (Ra). If the procedure is to return to its caller, the lower 24-bit address portion of Ra must be the same at the end of the procedure's code as it was at the time of the call: it must contain the return address. If during the execution of the procedure's code the contents of Ra is changed, then it is YOUR responsibility to save and restore Ra's return address. Typically, Ra is saved in storage either with a cell assignment statement or by including Ra in the Rx through Ry register range of an STM function.
In our example, none of the registers used by COPYCORE are saved, and R4's content is not changed by the procedure's code. By including a declaration such as:
|045| ARRAY 3 INTEGER SAVEREGS;
we could save and restore COPYCORE's work registers with:
|055| STM(R1,R3,SAVEREGS); and |125| LM(R1,R3,SAVEREGS);
If saving R4 was necessary, we need only change '3' to '4' in all of these new lines.
While we're on the subject of return address registers, let's look at a frequent programming error involving Ra.
|010| BEGIN | outer block | |020| PROCEDURE A (R14); |030| BEGIN | Code not involving R14 | |040| END; |050| PROCEDURE B (R14); |060| BEGIN | Code not involving R14 | |070| A; | Call procedure A | |080| | More code not involving R14 | |090| END; |100| | Main code of outer block | |110| B; | Call procedure B | |120| END | of outer block |
When procedure B is called from line 110, a return address is placed in R14. Procedure B then calls procedure A from line 070 thus placing a new return address in R14. Procedure A executes and returns to line 080. When procedure B finishes execution at line 090, it returns to the address given in R14 which happens to be a return to line 080 (established by the call to A at line 070). We now have an infinite loop! Clearly, either procedure B must save and restore R14 (using storage or some other temporary register), or procedures A and B need different return address registers.
There are instances when a procedure need not return to its caller. A local procedure may GOTO a label in a block containing the procedure's definition. Consider the following example:
|010| BEGIN | outer block | |020| PROCEDURE SCAN (R7); |030| BEGIN | Statements which establish condition code | |040| IF = THEN GOTO TAG; | Conditional branch | |050| END; |060| LOOP: | Main code of outer block | |070| SCAN; | call procedure | |080| | other statements following return | |090| SCAN; | call procedure again | |100| | other statements following return | |110| GOTO LOOP; | repeat the process | |120| TAG: | procedure eventually branches directly here | |130| END | of outer block |
In this example, SCAN can return normally (to line 080 or 100), or branch directly to the label (TAG at line 120). The effect would be similar to our replacing the procedure calls at both lines 070 and 090 by lines 030 through 050, eliminating the procedure entirely.
There's another fact concerning local procedures and the block containing their definitions that is often overlooked. Since local procedures generate code prior to the main code of the containing block, the compiler must generate an unconditional branch instruction at the beginning of the block to branch around the local procedures. Otherwise, upon entry to the block, we would execute the code of the first local procedure rather than the main code of the block. Normally, only one such branch is necessary; but if local procedures and certain data segment declarations are alternated, the compiler may have to generate more than one branch (branch around local procedures, load a data segment base register, branch around more local procedures, etc.). Notice the assembly language comments in the following example.
|010| IF R5 = R6 THEN | CR 5,6 | |020| BEGIN | outer block | | BNE T.2 | |030| EXTERNAL DATA BLANKS BASE R9; | L 9,=V(BLANKS) | |040| PROCEDURE SWAP (R7); | B T.1 | |050| BEGIN B2 := B2 XOR B3; | SWAP XC 0(4,2),0(3) | |060| B3 := B3 XOR B2; | XC 0(4,3),0(2) | |070| B2 := B2 XOR B3; | XC 0(4,2),0(3) | |080| END; | of procedure | | BR 7 | |090| B3 := B3 OR B9; | main code | | T.1 OC 0(4,3),0(9) | |100| SWAP; | local procedure call | | BAL 7,SWAP | |110| END; | of outer block | | T.2 EQU * |
There are two types of global procedure:
Both generate separate program segments. The concepts of segmentation and segment naming were introduced in Chapter 3. The only difference between these two declarations is that for SEGMENT procedures the compiler generates the program segment name; whereas, for GLOBAL procedures the programmer explicitly specifies the program segment name. So for SEGMENT procedures, the 'name' given in the declaration only serves to identify the procedure for call purposes in a manner similar to Simple procedures. The program segment name is chosen by the compiler. But for GLOBAL procedures, 'name' not only identifies the procedure, but also names the program segment.
Segment names are called Control Section or CSECT names. They identify each segment to the LOADER or linkage editor programs that process the object decks created by the compiler. These names define entry points that can be referenced by EXTERNAL declarations. COMMON procedures also define additional entry points within program segments. It is therefore possible to have a single program segment with multiple entry points. The main advantage in having multiple entry points is that all the procedures making up a single program segment share one literal pool and one program base register.
Global procedures may specify the program base register in their declaration. Thus,
where 'Rp' is an integer register identifier other than R0 or 'Ra'. When the BASE portion of the declaration is not given, the default program base register is assumed. The default is usually R15, but the $BASE=nn compiler control card may be used to change the default program base register setting. (See section E.4)
In any of these global procedure definitions, if the 'statement' were to be terminated by a period (.), rather than a semicolon (;), then we would have the definition of a global procedure main program! We've mentioned several times before that the main program block could be written as a global procedure. On the next page you will find a compilation of both a main program block and an equivalent global procedure main program. Study the compilation carefully and notice the columns of numbers to the left of the PL360 source. These columns are described in Appendix F.
Following the source in each example is information about each segment including the segment's number, name, length in hexadecimal, base register, entry points, and external references. Also, a listing of the program segment's machine instructions is included.
PL360 COMPILATION MAIN PROGRAM DEFINED AS <BLOCK> . 001 0000 000 0000 0001 BEGIN |-- START OF MAIN PROGRAM --| 001 0018 000 0048 0002 01 001 0018 000 0048 0003 END. |-- END OF MAIN PROGRAM --| SEGMENT 000 NAME = SEGN000 LENGTH = 0048 BASE REG = 13 EXTERNAL SYMBOL DICTIONARY SEGN000 ENTRY (SD) AT 0000 SEGMENT 001 NAME = SEGN001 LENGTH = 0028 BASE REG = 15 0000 90ECD00C 18ED58D0 F02450E0 D00450D0 0010 E008D703 E010E010 58D0D004 98ECD00C 0020 07FE0000 00000000 EXTERNAL SYMBOL DICTIONARY SEGN001 ENTRY (SD) AT 0000 SEGN000 EXTERNAL REFERENCE
PL360 COMPILATION MAIN PROGRAM DEFINED AS GLOBAL PROCEDURE . 001 0000 000 0000 0001 GLOBAL PROCEDURE SEGN001 (R14) BASE R15; 014 0000 000 0000 0002 BEGIN STM(R14,R12,B13(12)); R14 := R13; 014 0006 000 0000 0003 01 BEGIN GLOBAL DATA SEGN000 BASE R13; 014 000A 015 0000 0004 02 ARRAY 18 INTEGER B13; 014 000A 015 0048 0005 B13(4) := R14; B14(8) := R13; 014 0012 015 0048 0006 B14(16) := B14(16) XOR B14(16); 014 0018 015 0048 0007 014 0018 015 0048 0008 BEGIN |-- START OF MAIN PROGRAM --| 014 0018 015 0048 0009 03 014 0018 015 0048 0010 END; |-- END OF MAIN PROGRAM --| 014 0018 015 0048 0011 02 014 0018 015 0048 0012 R13 := B13(4); LM(R14,R12,B13(12)); 014 0020 015 0048 0013 END; 014 0020 000 0000 0014 01 END. SEGMENT 015 NAME = SEGN000 LENGTH = 0048 BASE REG = 13 EXTERNAL SYMBOL DICTIONARY SEGN000 ENTRY (SD) AT 0000 SEGMENT 014 NAME = SEGN001 LENGTH = 0028 BASE REG = 15 0000 90ECD00C 18ED58D0 F02450E0 D00450D0 0010 E008D703 E010E010 58D0D004 98ECD00C 0020 07FE0000 00000000 EXTERNAL SYMBOL DICTIONARY SEGN001 ENTRY (SD) AT 0000 SEGN000 EXTERNAL REFERENCE
The preceding block and global procedure programs generate the same number of segments with the same segment names, lengths, base registers, entry points, and external references. Even the generated machine instructions are identical! The segment numbers assigned by the compiler are different, but that difference is not carried beyond the compilation. The only major difference is that for a block program, entry and exit instructions are generated by the compiler; whereas for a global procedure program, all instructions must be explicitly generated by statements or declarations. There is one other difference that is not apparent from looking at the examples. The object decks produced would be identical except for the END cards (see Appendix F).
A close examination of the machine instructions composing SEGN001 reveals that the third instruction (58D0F024) would load register R13 from an integer cell at displacement 024 based on register R15 (a cell within SEGN001). Relative address 0024 of SEGN001 is compiled as an integer cell containing zeros. When the segments are loaded for execution, the external reference to SEGN000 directs the LOADER to fill in this cell with the absolute address assigned to the SEGN000 entry point. Eventually SEGN001 receives control from the LOADER with R15 containing SEGN001's entry point address, R14 containing a return address, and R13 containing the address of an 18-integer save area. SEGN001 first saves all registers except R13 in the LOADER's save area, places R13 in R14, and then loads R13 with the address of our own save area (SEGN000). Then the LOADER's save area address is stored into our save area, our save area address is stored into the LOADER's save area, and the stored value of R15 is zeroed out in the LOADER's save area. These actions complete what is known as the standard OS linkage convention entry process (see Appendix G).
We now begin the main execution phase. In our example programs there are no instructions for this phase, but if there had been instructions (statements, etc.) we would have to be careful about preserving the contents of our program and data area base registers (R15 and R13). The program base register is used to reference all literals (including external references) and for branching (GOTO's, WHILE's, etc.). The data area base register points to our save area (used by procedures we call) and any other cells declared for that segment.
Upon exit from a main program, R13 is reloaded with the LOADER's save area address using the stored value in our save area, all the other saved registers are reloaded from the LOADER's save area, and finally we branch back to the LOADER using the return address register (R14). Note that we return R15 containing zero because we zeroed out the stored value of R15 upon entry. Using the program base register for returning a response to the caller is a fairly common practice. We will examine this concept in more detail a little later.
Let's look now at nested global procedures. The following example contains a wealth of information and we shall be using this example or variations of it throughout the remainder of this section.
001 0000 000 0000 0001 GLOBAL PROCEDURE A (R14); 014 0000 000 0000 0002 BEGIN 014 0000 000 0000 0003 01 PROCEDURE B (R1); 014 0004 000 0000 0004 BEGIN 014 0004 000 0000 0005 02 A; 014 0006 000 0000 0006 B; 014 000A 000 0000 0007 END; 014 000C 000 0000 0008 01 GLOBAL PROCEDURE C (R2) BASE R9; 015 0000 000 0000 0009 BEGIN 015 0000 000 0000 0010 02 PROCEDURE D (R3); 015 0004 000 0000 0011 BEGIN 015 0004 000 0000 0012 03 A; 015 000E 000 0000 0013 B; 015 001A 000 0000 0014 C; 015 001C 000 0000 0015 D; 015 0020 000 0000 0016 END; 015 0022 000 0000 0017 02 A; 015 002C 000 0000 0018 B; 015 0038 000 0000 0019 C; 015 003A 000 0000 0020 D; 015 003E 000 0000 0021 END; 014 000C 000 0000 0022 01 A; 014 000E 000 0000 0023 B; 014 0012 000 0000 0024 C; 014 001C 000 0000 0025 END. SEGMENT 015 NAME = C LENGTH = 0048 BASE REG = 09 0000 47F09022 58F09044 05EF5890 E03658F0 0010 90444510 F0045890 102A0529 45309004 0020 07F358F0 904405EF 5890E018 58F09044 0030 4510F004 5890100C 05294530 900407F2 0040 00000000 00000000 EXTERNAL SYMBOL DICTIONARY C ENTRY (SD) AT 0000 A EXTERNAL REFERENCE SEGMENT 014 NAME = A LENGTH = 0028 BASE REG = 15 0000 47F0F00C 05EF4510 F00407F1 05EF4510 0010 F0045890 F0240529 58F02008 07FE0000 0020 00000000 00000000 EXTERNAL SYMBOL DICTIONARY A ENTRY (SD) AT 0000 C EXTERNAL REFERENCE
The first thing to notice about the preceding example is the pair of integer cells that were compiled as zeros at the end of each segment. When the segments are loaded, the LOADER will place into these cells the absolute address of the entry and external reference associated with each segment.
Next you should notice that B is a local procedure of A, and D is a local procedure of C. A and C are program segments which begin with an unconditional branch instruction to branch around their local procedure. Both B and D begin at relative location 0004 within their respective segments.
The procedure statements in the example are all the procedure statements possible. Procedure B cannot call upon C or D because they are defined after B; and A cannot call upon D because D is defined within C and is not known outside of C. (We will examine a way of making D known to A in the next section on external procedures.)
The machine instructions generated by the various procedure statements fall into the following categories. If we use 'a' to mean a return address register (Ra), and 'p' or 'q' to mean a program base register (Rp), then global procedures are called by a BALR instruction (05ap), and local procedures are called by a BAL instruction (45a0pddd). If the procedure being called is in a segment other than the calling segment, the the call instruction is preceded and followed by a load instruction (58p0qddd and 58q0addd).
We mentioned in the main program example that a procedure can return a response in its program base register. In this example there's no problem for procedures of one segment to return such a response to calls from the other segment because the segments have different program base registers. But what about calls to procedures within the same segment, or calls between segments that have the same program base register? PL360 provides the answer with another form of procedure statement:
Rm is an integer register designator other than the program base register of the segment containing such a call. Immediately following the BALR or BAL instruction, the compiler generates instructions that place the called procedure's program base register into Rm (using an LTR m,p) before restoring the calling procedure's program base register (see Appendix G).
Finally let's consider restrictions on GOTO statements. Since global procedure segments could be loaded anywhere in relation to each other, there's no guarantee that a GOTO statement can generate a branch instruction to a label that would be within range of the program base register. Therefore, GOTO statements are not allowed to branch out of a segment! A 'label' defined in A would not satisfy a 'GOTO label' occurring in C or D. However, if we rewrote B as:
PROCEDURE B (R1); GOTO label;
then either C or D could call upon B to effect a branch to the 'label' defined in A! Procedures permit us to circumvent the GOTO restriction.
The basic form of an external procedure declaration is:
Since external procedure declarations generate no code, the 'statement' is usually the NULL statement. Any other statement would be compiled and syntax checked as if code were being generated, but no program segment would be produced. However, if the statement was a block containing a global data segment declaration, the data segment would be produced!
An external procedure declaration is used to reference a procedure entry point or program segment not otherwise known to the block containing the external declaration. In effect, an external procedure declaration provides the compiler with all the information needed to satisfy a call to the named procedure. When such a call is made with a procedure statement, the compiler generates the code necessary for linking to the external procedure. The call also causes the compiler to place information in the object deck of the program segment containing the call, information that directs the LOADER to resolve the external reference.
In the nested global procedure example originally given in the preceding section, if local procedure D had been declared as a COMMON procedure, then D would be defined as an entry point. However, D would still not be known to global procedure A because of the rules of 'scope' first discussed in section 3.4.2. By including an external declaration in procedure A such as:
EXTERNAL PROCEDURE D (R3) BASE R9; NULL;
it would be possible for procedure A to call upon D. Of course if D was not an entry point, such a call would still require resolution at load time or would result in an undefined external reference.
Notice that the external declaration for D defines D's return address register (R3), but C's program base register (R9). As a COMMON procedure, D is local to C and so uses C's program base register.
If E were declared as an external procedure at the very beginning of global procedure A, then any call to A could be replaced by a call to E. Any reference to E would have to be resolved at load time for the segment containing the reference; but if E was never referenced, we could leave out the external procedure declaration. An external procedure declaration by itself does not force resolution; only a call or similar reference forces resolution at load time.
There are 12 external procedures pre-declared by the PL360 compiler. The equivalent declarations follow:
EXTERNAL PROCEDURE READ (R14) BASE R15; NULL; EXTERNAL PROCEDURE WRITE (R14) BASE R15; NULL; EXTERNAL PROCEDURE PAGE (R14) BASE R15; NULL; EXTERNAL PROCEDURE PUNCH (R14) BASE R15; NULL; EXTERNAL PROCEDURE PRINT (R14) BASE R15; NULL; EXTERNAL PROCEDURE OPEN (R14) BASE R15; NULL; EXTERNAL PROCEDURE GET (R14) BASE R15; NULL; EXTERNAL PROCEDURE PUT (R14) BASE R15; NULL; EXTERNAL PROCEDURE KLOSE (R14) BASE R15; NULL; EXTERNAL PROCEDURE CANCEL (R14) BASE R15; NULL; EXTERNAL PROCEDURE VALTOBCD (R14) BASE R15; NULL; EXTERNAL PROCEDURE BCDTOVAL (R14) BASE R15; NULL;
If any of these procedures are referenced, suitably compiled or assembled routines must be provided in the link/loading process. At most installations there is a PL360 runtime library containing these routines. All assume that R13 contains the address of an 18-integer save area. The remaining specifications for these routines follows:
READ Read an 80-character record from the system input data set and assign that record to the memory area designated by the address in register R0. The condition code returned is set to 2 if no record could be returned due to an end-of-file condition; otherwise, it is set to 0.
WRITE Write a 133-character record to the system listing data set. A 132-character record is taken from the memory area designated by the address in register R0 and prefixed by an appropriate carriage control character. A control character indicating a new page is used after 60 lines have been written on a page, otherwise a control character indicating the next line is used. The first line is written on a new page.
PAGE A control character indicating a new page is established for the next output record transmitted by WRITE to the system listing data set.
PUNCH Write the 80-character record designated by the address in register R0 to the system punch data set.
PRINT Write the 133-character record designated by the address in register R0 to the system listing data set. The calling program provides a control character as the first character. These lines are not counted.
All of the preceding routines restore all registers before return. The system input, listing, and punch data sets are: SYSIN, SYSPRINT, and SYSPUNCH in an OS, VS, or DOS environment. In MTS, they are: SCARDS, SPRINT, and SPUNCH. Each of the data sets is opened upon initial reference and is closed by the operating system at the end of the run.
The following routines are not defined in an MTS environment.
OPEN At entry, register R0 must be 0 if the data set is to be an output data set, or 1 if the data set is to be an input data set. Register R2 must contain the address of an 8-byte area containing a unique data set name. (This is taken as the ddname in an OS environment and as the symbolic data set name in a DOS environment.) In an OS environment, register R1 must contain the address of a 100-byte fullword aligned area which, following the open, will contain the data control block. In a DOS environment, register R1 must contain the address of a separately assembled DTF table that describes the data set. The data set is made ready for input/output operations. All registers are restored.
GET At entry, register R1 must contain the address of a table which describes the data set. (In an OS environment this table is called the data control block and in a DOS environment it is called the DFT table.) Upon return, register R1 contains the address of the next logical record in the data set. (The first call of GET returns with the address of the first logical record.) When an end-of-file is reached, the condition code is set to 2; normally it is set to 0. All registers, except R1, are restored.
PUT At entry, register R1 must contain the address of a table which describes the data set. Upon return, register R1 contains the address of an area in which the next logical record to be output is to be built. All other registers are restored.
KLOSE At entry, register R1 must contain the address of a table which describes the data set. The corresponding data set is closed and no further input/output operations can be performed with it unless it is opened again. In an OS environment, the contents of register R0 is also an input parameter to this subroutine: if R0 = 0, the DISP option of the DD statement is used to determine final volume positioning; if R0 <= 0, the volume is positioned at the end of the data set; and if R0 > 0, the volume is positioned at the beginning of the data set. All registers are restored.
CANCEL The job, including all future job steps, is cancelled. This routine is defined only in a DOS environment.
The two subroutines described next are used to convert the EBCDIC representation of a number into an internal representation of that number, or vice versa. A slightly more conventional number representation is used by these routines than that of the PL360 compiler. Numbers are interpreted according to conventional decimal notation. Scale factors for REAL and LONG REAL numbers are indicated by an apostrophe, and LONG REAL numbers must end in an L. Positive and negative numbers are indicated by standard + and -- signs (no sign indicates positive). A number cannot have imbedded blanks and must be terminated by a blank. The imaginary part of a complex number must end in the character I (e.g., 5.7+3.8I).
VALTOBCD This procedure converts an internally stored value to an EBCDIC representation. At entry, R1 contains the address of an area to receive the EBCDIC representation. R2 indicates the type: 1 = integer 2 = real 3 = long real 4 = complex 5 = long complex R3 contains the field length (>= 1). The value to be converted is in R0, F0, F01, F0 and F2, or F01 and F23, depending on the type (in that order). A return code is left in R15: 0 -> successful conversion 1 -> field size too small 2 -> invalid field size When the field size is too small to receive the value, the field is filled with asterisks (*). All registers, except R14 and R15, are preserved.
BCDTOVAL This procedure converts an EBCDIC representation of a number to an internal number. At entry, R1 contains the address of the EBCDIC representation (possibly preceded by blanks). R2 indicates type (see VALTOBCD). The resulting value is left in R0, F0, F01, F0 and F2, or F01 and F23, depending upon the type. A return code is left in R15: 0 -> successful scan 1 -> invalid character in input string 2 -> missing 'I' on imaginary part 3 -> nonblank terminator 4 -> number scanned is not assignment compatible (e.g., a decimal point is found when R2 = 1) 5 -> integer too large Upon exit, R1 contains the address of the terminator. Registers R2-R13 are restored.
Since both of these routines return a response code in their program base register (R15), they are frequently called by that form of procedure statement which allows the program base register to be saved in some other register, such as: VALTOBCD(R6) or BCDTOVAL(R7).
The examples which follow demonstrate the use of some of these pre-declared procedures.
BEGIN |--- First sample program ---| FUNCTION REDUCE(6,#0600); |-- This function is used to subtract 1 from the contents of an integer register. --| ARRAY 80 BYTE CARD; |-- Input area for READ --| ARRAY 133 BYTE LINE = 133(" "); |-- Output area for WRITE or PRINT, pre-blanked --| |-- End of declarations, begin sample statements. --| R0 := @CARD; IF READ; ~= THEN GOTO EXIT; |-- Read an input card image from the system input data set into the CARD area, and exit if no card read. --| R1 := @CARD; R2 := 1; IF BCDTOVAL(R5); ~= THEN GOTO EXIT; |-- Assuming CARD contains an integer value in character form terminated by a blank, then convert to binary and place in R0. R5 receives the return code indicating success or failure of the conversion. If ~= condition, then exit. --| REDUCE(R0); |-- Subtract 1 from the returned value. --| R1 := @LINE(3); R2 := 1; R3 := 15; VALTOBCD; |-- Convert the contents of R0 to character form and store right-justified in a 15-character field beginning at LINE(3); i.e., LINE(3) through LINE(17). --| R0 := @LINE; WRITE; |-- Write the LINE image to the system listing data set with appropriate control character supplied by WRITE procedure. --| EXIT: |-- End of program --| END. BEGIN |--- Second sample program ---| ARRAY 25 INTEGER DCB1, DCB2; |-- Data Control Block space for two data sets --| SHORT INTEGER LRECL1 SYN DCB1(#52), LRECL2 SYN DCB2(#52); |-- Halfwords within the Data Control Blocks which contain record lengths. Normally, used with an input data set to determine the length of an input record, and with an output data set to establish the length of an output record. --| |-- End of declarations, begin sample statements. --| LA(R2,"INFILE "); R0 := 1; R1 := @DCB1; OPEN; |-- Open an input data set for processing. --| LA(R2,"OUTFILE "); R0 := R0-R0; R1 := @DCB2; OPEN; |-- Open an output data set for processing. --| LOOP: |-- Assume both data sets define records of unknown length. --| R1 := @DCB1; IF GET; ~= THEN GOTO FINISH; |-- Input a record from the data set defined by the first Data Control Block (INFILE). The address of the first byte of the input record is in R1, and the record length is contained in LRECL1. If no record was input, then branch to FINISH (end-of-file condition). --| R2 := R1; |-- Save the input record address. --| R3 := LRECL1 =: LRECL2; |-- Establish the length of the output record to be the same as that of the input record. --| R1 := @DCB2; PUT; |-- Determine the address of where to put the output record for the data set defined by the second Data Control Block (OUTFILE). The destination address is contained in R1 upon return from PUT. --| REDUCE(R3); WHILE R3 >= 256S DO BEGIN B1(0/256) := B2; R3 := R3 - 256S; R1 := @B1(256); R2 := @B2(256); END; EX(R3,MVC(0,B1,B2)); |-- Move the input record into the output record area. --| GOTO LOOP; |-- Loop till end-of-file condition. --| FINISH: |-- end-of-file --| R1 := @DCB1; R0 := R0-R0; KLOSE; R1 := @DCB2; R0 := R0-R0; KLOSE; |-- Close input and output data sets. --| |-- End of program --| END.
The PROCEDURE synonym declaration provides an alternate procedure name for a previously declared procedure. For example:
EXTERNAL PROCEDURE GETMAIN (R14); NULL; PROCEDURE CALLMAIN SYN GETMAIN; PROCEDURE NEWCALL SYN CALLMAIN;
Any procedure statement that refers to either CALLMAIN or NEWCALL will be treated like a reference to GETMAIN. Within $IF-tested code, this facility makes it possible for different versions. For example:
EXTERNAL PROCEDURE APPLE (R14); NULL;
$IFT EXTERNAL PROCEDURE ORANGE (R14); NULL; $END $IFF PROCEDURE ORANGE SYN APPLE; $END |-- Call 1st procedure --| APPLE; |-- Call 2nd procedure --| ORANGE;
In one environment, APPLE and ORANGE are different procedures. In the other environment, ORANGE is an alternate name for APPLE. Here's another example:
$IFT EXTERNAL PROCEDURE VI (R14); NULL; PROCEDURE EDITOR SYN VI; $END $IFF EXTERNAL PROCEDURE XEDIT (R14); NULL; PROCEDURE EDITOR SYN XEDIT; $END EDITOR; |-- call the appropriate editor --|
A number of items were touched upon in earlier chapters concerning procedures. One was the GOTO restriction mentioned in Chapter 6. At the end of section 8.2, we discussed how local and COMMON procedures allow us to circumvent the GOTO restriction. Here is an example:
GLOBAL PROCEDURE A (R14); BEGIN COMMON PROCEDURE B (R14); GOTO TAG; EXTERNAL PROCEDURE C (R14); NULL; |-- Main code of A --| C; |-- call to C --| |-- More code in A --| TAG: |-- location in A of concern to C --| |-- More code in A --| END. |-- of procedure A --| GLOBAL PROCEDURE C (R14); BEGIN EXTERNAL PROCEDURE B (R14); NULL; |-- Main code of C --| IF conditions THEN B; |-- conditional call to B --| END. |-- of procedure C --|
Notice that C either exits normally or calls upon B. A normal return would take us back to the code immediately following the call to C within A; but a call to B would take us back to TAG: within A since B branches directly to TAG.
We also deferred discussion of @-operators and @@-operators in cell initialization and integer register assignment statements when these operators were used with procedure names. For example:
BEGIN PROCEDURE FIXER (R5); statement; SHORT INTEGER X = @FIXER; INTEGER Y = @FIXER; INTEGER Z = @@FIXER; R1 := @FIXER; R2 := Z; END
@FIXER in the cell initializations for X and Y creates a Bddd field where B is the program base register number associated with the program segment containing FIXER, and ddd is the displacement within that program segment of the start of FIXER's statement. The SHORT INTEGER cell would contain Bddd in hexadecimal, and the INTEGER cell would contain 0000Bddd in hexadecimal. The INTEGER cell Z would be initialized to the absolute address of the start of FIXER's statement by the loader or linkage editor.
The register assignment: R1 := @FIXER; creates an integer cell in the program's literal pool which is initialized by the loader or linkage editor in the same manner as cell Z. Thus, R1 := @FIXER; produces the same absolute address in R1 as R2 := Z; produces in R2.
We can make use of @procname in register assignments in the following way:
BEGIN PROCEDURE LITERALS (R1); BEGIN LA(R1,"message 1"); R2 := STRING; LA(R1,"message 2"); R2 := STRING; LA(R1,"longer message"); R2 := STRING; |-- other messages, total of n-1 messages --| END; |-- assume R3 contains value from 0 thru n --| R3 := R3 SHLL 3; |-- i-th value becomes i*8 --| R4 := @LITERALS; |-- address of start of LITERALS --| EX(R0,B4(R3)); |-- sets R1 = address of message --| EX(R0,B4(R3+4)); |-- sets R2 = message length --| |-- other code making use of R1 and R2 --| END
This example demonstrates the use of a procedure as a code generator. We saw a similar example of code generation in section 7.4 under the discussion of the SETUP function; but using a procedure to generate code is a much better method.
Sometimes you don't know if an external procedure actually exists. You can create a "weak external reference" by using the @@-operator.
BEGIN EXTERNAL PROCEDURE module (Rn); NULL; Rx := @@module; |-- creates weak external reference --| IF Rx ~= 0 THEN module; |-- Only call if it exists. --| END
The module must not have been referenced in this program segment prior to the @@module usage in the register assignment statement. Notice that the second reference is a normal call to the module, but because the @@module reference occurred first, the module is assigned a weak external reference in this program segement. If the module is linked, Rx would be non-zero, and the normal call can be made. This technique can be used with any optionally linked modules, including data segments.
BEGIN EXTERNAL PROCEDURE table (R1); NULL; Rx := @@table; IF Rx ~= 0 THEN |-- table linked --| BEGIN |-- process the contents of the table --| END END
Finally, it should be noted that the form of a procedure statement and the form of a function statement (for functions with no parameters) are exactly the same! Both are just the 'name' of the procedure or function. This fact can be used during the development of a program where it may be inconvenient to code some procedure right away, but where you may still wish to have calls to such procedures in the main code. This can be accomplished by defining a type-0 function in place of the procedure. For example:
BEGIN FUNCTION ALPHA(0,#0700); |-- main code of the program --| ALPHA; |-- call to procedure ALPHA --| END
The 'call' is just a halfword branch instruction that does not branch (0700)! At some future time, ALPHA will be defined as a procedure. If the main code assumes that ALPHA establishes a condition code dependent upon some register content, then a type-0 function might still be used to establish both the register content and condition code. For example, FUNCTION ALPHA(0,#1B22); defines an instruction equivalent to R2 := R2-R2. Of course, if a function can't serve the purpose, then a mock version of the procedure would have be coded.
Write a main block program that will READ a card containing three (3) numbers seperated from each other by blanks.
These numbers represent respectively:
MONTH (1 through 12) DAY (1 through 31) YEAR (00 through 99 of 20th century, i.e., 19xx)
Compute the 'Day-of-the-Week', such as: FRIDAY
Write out: 'Day-of-the-Week' 'Name-of-the-Month' 'Day', 19'Year' such as: FRIDAY JAN. 26, 1973
The 'Day-of-the-Week' takes 11 character positions (blank filled)
The 'Name-of-the-Month' takes 5 character positions (blank filled): JAN. FEB. MARCH APRIL MAY JUNE JULY AUG. SEPT. OCT. NOV. DEC.
Check that the DAY specified is correct for the MONTH specified. Also, check that the MONTH and YEAR are valid; i.e., 29 is valid for FEB. if it is a leap year. Note: 1900 was not a leap year.
If an error is detected, write out the input card prefaced by:
*** ERROR ***
The algorithm for computing the 'Day-of-the-Week' is:
Given the three numbers M, D, and Y; then using integer arithmetic compute the remainder of the division by 7 of
Y/4 + Y + D + Offset(M)
where 'Offset' is the following table of numbers:
and M has been incremented by 12 if JAN. or FEB. of leap year; the remainder, 0 through 6, corresponds to the 'Day-of-the-Week' from SUNDAY through SATURDAY. For years in the 21st century (20xx), add 6 in the formula above.
This chapter contains various sample programs. Some are complete programs; others are global procedure programs that would be linked as a subroutine of other main programs.
Our first example is a complete main program. The first page of this example lists the Job Control Language (JCL) and other information associated with running the program on Stanford University's VS operating system. The job required two steps: the PL360 compile step, and the GO or execution step. Notice that each step requests some PGM to be EXECuted. The PL360 step invokes the PL360 compiler with program source input to come from SYSIN, listing output to go to SYSPRINT, and object deck output to go to SYSGO (or SYSPUNCH). The GO step invokes the LOADER with object deck input to come from SYSLIN, pre-compiled subroutines to come from SYSLIB, list output to go to SYSLOUT, and execution list output to go to SYSPRINT. SYSPUNCH would receive any punch output generated by the program. In this case, the program does not generate punch output, so SYSPUNCH is not used. SYSUDUMP would receive abnormal termination dump output if the program were to abnormally terminate. The IEF messages output by VS indicate what resources were used by each step.
The pages entitled 'PL360 ... PATH FINDER PROGRAM' are all output by the PL360 compile step. Some of the original output pages have been modified in order to fit them on the pages in this text. The first and second PL360 pages contain a portion of the compiler output. Only the 'reference number' and 'block level' fields are shown, along with the source program. The third PL360 page contains segment information including a dump in hexadecimal of the generated segments. The last PL360 page is a cross-reference listing of the identifiers used by the compiled program. (See Appendixes E and F.)
The last page of the example contains output from the LOADER and the executed PL360 program. The LOADER reads the PL360 object decks and loads the program into core resolving any undefined external references using SYSLIB. The LOADER map output shows the absolute address assigned to each segment (SD) or entry point (LR). The output from the executed PL360 program follows beginning with the first ANSWER and continuing to the end of the example.
This first example contains a local procedure named CHECK which is recursively called. The program computes the number of unique paths that can be taken by a chess rook moving from the lower left corner to the upper right corner of a square board (RANK x RANK). The rook is not allowed to cross its path while traversing the board. The ANSWERs given are the total number of unique paths for a board of order RANK. The numbers below each ANSWER indicate half the total number of paths of length 'm' squares. (Note: the answer for a 7 x 7 board was computed once, requiring a stand-alone computer and eight hours of compute time!)
H A S P J O B L O G $16.17.26 JOB 537 -- RLGQG187 -- BEGINNING EXEC - INIT 11- CLASS E $16.19.22 JOB 537 END EXECUTION. //RLGQG187 JOB RLG$GQ,CLASS=E ***JOBPARM HOLD=OUTPUT //PL360 EXEC PL360CG XXPL360CG PROC XXPL360 EXEC PGM=PL360 *** *** PL360 STEP *** XXSYSPRINT DD SYSOUT=A XXSYSPUNCH DD SYSOUT=B XXSYSGO DD DSN=&&LOADSET,UNIT=SYSDA,SPACE=(3120,(80,40),,,ROUND), XX DCB=(RECFM=FB,BLKSIZE=3120,LRECL=80),DISP=(MOD,PASS) //PL360.SYSIN DD * IEF236I ALLOC. FOR RLGQG187 PL360 PL360 IEF237I D32 ALLOCATED TO SYSPRINT IEF237I D20 ALLOCATED TO SYSPUNCH IEF237I 517 ALLOCATED TO SYSGO IEF237I D00 ALLOCATED TO SYSIN IEF142I - STEP WAS EXECUTED - COND CODE 0000 IEF285I SYS75275.T103115.RV000.RLGQG187.LOADSET PASSED IEF285I VOL SER NOS= SCR003. XXGO EXEC PGM=LOADER,PARM='MAP',COND=(0,NE,PL360) XXSYSLOUT DD SYSOUT=A XXSYSPRINT DD SYSOUT=A XXSYSPUNCH DD SYSOUT=B XXSYSUDUMP DD SYSOUT=A XXSYSLIB DD DSN=SYS2.PL360LIB,DISP=SHR XXSYSLIN DD DSN=*.PL360.SYSGO,DISP=(OLD,DELETE) // IEF236I ALLOC. FOR RLGQG187 GO PL360 IEF237I D32 ALLOCATED TO SYSLOUT IEF237I D34 ALLOCATED TO SYSPRINT IEF237I D20 ALLOCATED TO SYSPUNCH IEF237I D35 ALLOCATED TO SYSUDUMP IEF237I 511 ALLOCATED TO SYSLIB IEF237I 517 ALLOCATED TO SYSLIN IEF142I - STEP WAS EXECUTED - COND CODE 0000 IEF285I SYS2.PL360LIB KEPT IEF285I VOL SER NOS= SYS16E. IEF285I SYS75275.T103115.RV000.RLGQG187.LOADSET DELETED IEF285I VOL SER NOS= SCR003. IEF375I JOB /RLGQG187/ START 75275.1617 IEF376I JOB /RLGQG187/ STOP 75275.1619 CPU 1MIN 07.45SEC
PL360 COMPILATION PATH FINDER PROGRAM .. Reference Number : .. Block Level Number : : .. Pl360 Program Source : : : : : : 0001 BEGIN |-- FIND NUMBER OF PATHS --| 0002 01 INTEGER RANK = 2; 0003 ARRAY 132 BYTE ANSWER = 132(" "); 0004 EQUATE SQUARES SYN 64, 0005 STAKL SYN SQUARES * 3, 0006 SPACE SYN SQUARES * 5; 0007 ARRAY STAKL INTEGER STACK; 0008 ARRAY SPACE BYTE TABLES; 0009 BYTE FLAGS SYN TABLES(_1), |-- SYNONYMS used --| 0010 TABLE1 SYN FLAGS(SQUARES), |-- to allow for --| 0011 TABLE2 SYN TABLE1(SQUARES), |-- references from --| 0012 TABLE3 SYN TABLE2(SQUARES), |-- 1 thru SQUARES --| 0013 TABLE4 SYN TABLE3(SQUARES); 0014 0015 |-- CHECK SUBROUTINE, SQUARE PROCESSOR --| 0016 0017 PROCEDURE CHECK (R2); IF R4 ~= 0 THEN |-- VALID SQUARE --| 0018 BEGIN IC(R7,FLAGS(R4)); IF R7 = 0 THEN |-- EMPTY --| 0019 02 IF R4 = R9 THEN |-- DESTINATION SQUARE --| 0020 BEGIN R5 := @B5(1); R8 := 1 + B1(8); B1(8) := R8; 0021 03 END ELSE |-- ENTER NEW SQUARE --| 0022 02 BEGIN STM(R2,R3,B1); 0023 03 R1 := @B1(12); R3 := R4; 0024 STC(R6,FLAGS(R3)); |-- OCCUPY CURRENT SQUARE --| 0025 IC(R4,TABLE1(R3)); CHECK; |-- TRY 'UP' --| 0026 IC(R4,TABLE2(R3)); CHECK; |-- TRY 'RIGHT' --| 0027 IC(R4,TABLE3(R3)); CHECK; |-- TRY 'DOWN' --| 0028 IC(R4,TABLE4(R3)); CHECK; |-- TRY 'LEFT' --| 0029 STC(R0,FLAGS(R3)); |-- UNOCCUPY CURRENT SQUARE --| 0030 R1 := R1 - 12; LM(R2,R3,B1); 0031 END; |-- RETURN TO CALLING SQUARE OR MAIN CODE --| 0032 02 END; 0033 01
PL360 COMPILATION PATH FINDER PROGRAM .. Reference Number : .. Block Level Number : : .. Pl360 Program Source : : : : : : 0034 |-- MAIN CODE --| 0035 0036 WHILE RANK <= 6 DO 0037 BEGIN |-- PROCESS PATH FOR CURRENT RANK --| 0038 02 R0 := R0-R0; R1 := @STACK; R2 := @TABLES(SPACE); 0039 WHILE R1 < R2 DO |-- INITIALIZE TABLES --| 0040 BEGIN B1 := R0; R1 := @B1(4); 0041 03 END; |-- STORE 'UP' SQUARE NUMBERS --| 0042 02 R10 := RANK; R9 := R10 * R9; |-- RANK * RANK --| 0043 R1 := 1; R2 := R1 + R10; WHILE R2 <= R9 DO 0044 BEGIN STC(R2,TABLE1(R1)); R1 := @B1(1); R2 := @B2(1); 0045 03 END; |-- STORE 'RIGHT' SQUARE NUMBERS --| 0046 02 R1 := 2; R2 := 3; WHILE R2 <= R9 DO 0047 BEGIN STC(R2,TABLE2(R1)); R1 := @B1(1); R2 := @B2(1); 0048 03 END; R3 := R9 - R10; |-- STORE 'DOWN' SQUARES --| 0049 02 R2 := 2; R1 := R2 + R10; WHILE R2 < R3 DO 0050 BEGIN STC(R2,TABLE3(R1)); R1 := @B1(1); R2 := @B2(1); 0051 03 END; |-- STORE 'LEFT' SQUARE NUMBERS --| 0052 02 R2 := 1 + R10; R1 := @B2(1); WHILE R2 < R3 DO 0053 BEGIN STC(R2,TABLE4(R1)); R1 := @B1(1); R2 := @B2(1); 0054 03 END; |-- FIX THE EDGES OF THE BOARD --| 0055 02 R1 := R10; WHILE R1 < R9 DO 0056 BEGIN STC(R0,TABLE2(R1)); STC(R0,TABLE3(R1)); 0057 03 STC(R0,TABLE3(R1+1)); STC(R0,TABLE4(R1+1)); 0058 R1 := @B1(R10); 0059 END; R3 := R0; R7 := R0; R5 := R0; 0060 02 R1 := @STACK; R6 := #FF; R4 := 1; CHECK; 0061 R5 := R5 + R5; R0 := R5; ANSWER := "ANSWER: "; 0062 R1 := "0" OR RANK; STC(R1,ANSWER(8)); 0063 R1 := @ANSWER(9); R2 := 1; R3 := 9; VALTOBCD; 0064 R0 := @ANSWER; WRITE; R4 := @STACK; R5 := @TABLES; 0065 R6 := R6-R6; WHILE R4 < R5 DO 0066 BEGIN R6 := @B6(1); R0 := B4(8); IF R0 ~= 0 THEN 0067 03 BEGIN R1 := @ANSWER(R3); VALTOBCD; 0068 04 R0 := R6; R1 := @ANSWER; VALTOBCD; 0069 R0 := @ANSWER; WRITE; 0070 END; R4 := @B4(12); 0071 03 END; |-- REPEAT FOR NEXT BOARD SIZE --| 0072 02 PAGE; R1 := 1 + RANK; RANK := R1; 0073 END; 0074 01 END.
PL360 COMPILATION PATH FINDER PROGRAM SEGMENT 000 NAME = SEGN000 LENGTH = 0510 BASE REG = 13 0048 00000002 40404040 40404040 40404040 0058 TO 00C4 40404040 00C8 40404040 40404040 EXTERNAL SYMBOL DICTIONARY SEGN000 ENTRY (SD) AT 0000 SEGMENT 001 NAME = SEGN001 LENGTH = 0240 BASE REG = 15 0000 90ECD00C 18ED58D0 F22C50E0 D00450D0 0010 E008D703 E010E010 47F0F082 12444790 0020 F0804374 D3CF1277 4770F080 19494770 0030 F0464150 50014180 00015A80 10085080 0040 100847F0 F0809023 10004110 100C1834 0050 4263D3CF 4343D40F 4520F01C 4343D44F 0060 4520F01C 4343D48F 4520F01C 4343D4CF 0070 4520F01C 4203D3CF 5B10F220 98231000 0080 07F2D503 D048F224 4730F20A 1B004110 0090 D0D04120 D5101912 47B0F0A8 50001000 00A0 41101004 47F0F096 58A0D048 189A1C89 00B0 41100001 18211A2A 19294730 F0CE4221 00C0 D40F4110 10014120 200147F0 F0B84110 00D0 00024120 00031929 4730F0EC 4221D44F 00E0 41101001 41202001 47F0F0D6 18391B3A 00F0 41200002 18121A1A 192347B0 F10E4221 0100 D48F4110 10014120 200147F0 F0F84120 0110 00011A2A 41102001 192347B0 F12E4221 0120 D4CF4110 10014120 200147F0 F118181A 0130 191947B0 F14E4201 D44F4201 D48F4201 0140 D4904201 D4D0411A 100047F0 F1301830 0150 18701850 4110D0D0 416000FF 41400001 0160 4520F01C 1A551805 D208D04C F2144110 0170 00F05610 D0484210 D0544110 D0554120 0180 00014130 000958F0 F23005EF 58F0E09C 0190 4100D04C 58F0F234 05EF58F0 E08E4140 01A0 D0D04150 D3D01B66 194547B0 F1F04160 01B0 60015800 40081200 4790F1E8 4113D04C 01C0 58F0F230 05EF58F0 E0621806 4110D04C 01D0 58F0F230 05EF58F0 E0524100 D04C58F0 01E0 F23405EF 58F0E044 4140400C 47F0F1A8 01F0 58F0F238 05EF58F0 E0324110 00015A10 0200 D0485010 D04847F0 F08258D0 D00498EC 0210 D00C07FE C1D5E2E6 C5D97A40 40000000 0220 0000000C 00000006 00000000 00000000 EXTERNAL SYMBOL DICTIONARY SEGN001 ENTRY (SD) AT 0000 SEGN000 EXTERNAL REFERENCE
PL360 CROSS REFERENCE PATH FINDER PROGRAM 43 SYMBOLS, 246 REFERENCES ANSWER 0003 0061 0062 0063 0064 0067 0068 0069 B1 0020 0020 0022 0023 0030 0040 0040 0044 0047 0050 0053 0058 B2 0044 0047 0050 0052 0053 B4 0066 0070 B5 0020 B6 0066 CHECK 0017 0025 0026 0027 0028 0060 FLAGS 0009 0010 0018 0024 0029 IC 0018 0025 0026 0027 0028 LM 0030 PAGE 0072 RANK 0002 0036 0042 0062 0072 0072 R0 0029 0038 0038 0038 0040 0056 0056 0057 0057 0059 0059 0059 0061 0064 0066 0066 0068 0069 R1 0023 0030 0030 0038 0039 0040 0043 0043 0044 0044 0046 0047 0047 0049 0050 0050 0052 0053 0053 0055 0055 0056 0056 0057 0057 0058 0060 0062 0062 0063 0067 0068 0072 0072 R10 0042 0042 0043 0048 0049 0052 0055 0058 R2 0017 0022 0030 0038 0039 0043 0043 0044 0044 0046 0046 0047 0047 0049 0049 0049 0050 0050 0052 0052 0053 0053 0063 R3 0022 0023 0024 0025 0026 0027 0028 0029 0030 0048 0049 0052 0059 0063 0067 R4 0017 0018 0019 0023 0025 0026 0027 0028 0060 0064 0065 0070 R5 0020 0059 0061 0061 0061 0061 0064 0065 R6 0024 0060 0065 0065 0065 0066 0068 R7 0018 0018 0059 R8 0020 0020 R9 0019 0042 0042 0043 0046 0048 0055 SPACE 0006 0008 0038 SQUARES 0004 0005 0006 0010 0011 0012 0013 STACK 0007 0038 0060 0064 STAKL 0005 0007 STC 0024 0029 0044 0047 0050 0053 0056 0056 0057 0057 0062 STM 0022 TABLES 0008 0009 0038 0064 TABLE1 0010 0011 0025 0044 TABLE2 0011 0012 0026 0047 0056 TABLE3 0012 0013 0027 0050 0056 0057 TABLE4 0013 0028 0053 0057 VALTOBCD 0063 0067 0068 WRITE 0064 0069
VS LOADER OPTIONS USED - PRINT,MAP,LET,CALL,RES,NOTERM,SIZE=163840,NAME=**GO NAME TYPE ADDR NAME TYPE ADDR NAME TYPE ADDR SEGN000 SD 170010 SEGN001 SD 170520 $PL360IO* SD 170760 READ * LR 170760 WRITE * LR 170800 PAGE * LR 170876 PUNCH * LR 17087C PRINT * LR 1708B4 NUMEDTR * SD 170A68 BCDTOVAL* SD 170BF8 VALN015 * SD 170F20 VALTOBCD* SD 170F90 SHELSORT* SD 171428 BISEARCH* SD 171510 BCDN013 * SD 171590 BCDN015 * SD 1715D8 VALN013 * SD 1715E8 SHEN013 * SD 171640 BISN013 * SD 171688 TOTAL LENGTH 16C0 ENTRY ADDRESS 170520 ANSWER: 2 2 3 1 ANSWER: 3 12 5 3 7 2 9 1 ANSWER: 4 184 7 10 9 18 11 24 13 24 15 16 ANSWER: 5 8512 9 35 11 112 13 255 15 478 17 793 19 1112 21 1053 23 366 25 52 ANSWER: 6 1262816 11 126 13 600 15 1952 17 5280 19 12914 21 29356 23 60934 25 108718 27 150190 29 140388 31 85192 33 30668 35 5090
The next program compares two data sets to determine where changes were made. The OLD data set is assumed to be the original; the NEW is assumed to be an altered version of OLD but with essentially the same line numbering. Most text editing systems provide the capability of inserting or deleting lines without altering other line numbers. This program was designed for such systems. In this case, the program processes card input with numbering in columns 73-80, or processes either one of two Stanford Wylbur Edit data formats depending on compile time IF's ($IFT or $IFF). The two input data sets may be either both card, both Edit, or one of each type.
The program makes use of a DUMMY data area that acts as an overlay for both AREA1 and AREA2. You will notice that register synonyms are used to make the program more readable. Register I points to AREA1, register J points to AREA2, and register K may point to either area.
Note the use of the CVD and ED functions within the INPUT procedure.
BEGIN |-- RECORD COMPARE PROGRAM --| FUNCTION REDUCE(6,#0600); |-- SUBRTACT 1 FROM REGISTER --| INTEGER REGISTER I SYN R8, J SYN R9, K SYN R10, RETURN SYN R11; DUMMY BASE R0; |-- WORK AREA DUMMY SECTION --| ARRAY 25 INTEGER DCB; SHORT INTEGER LRECL SYN DCB(#52); BYTE ENDFILE SYN DCB(96); INTEGER NXTLINE, ENDLINE; ARRAY 2 INTEGER LINENUM; SHORT INTEGER LINE1 SYN LINENUM, LINE2 SYN LINE1(2); ARRAY 167 BYTE LINE; BYTE CARDS; |-- CARD INPUT FLAG --| CLOSE BASE; |-- COMPUTE SIZE OF DUMMY SECTION IN INTEGER AMOUNTS --| EQUATE DSLEN SYN CARDS(1) - MEM + 3 SHRL 2; |-- ASSIGN WORK AREAS --| LONG REAL CONWORK; ARRAY DSLEN INTEGER AREA1, AREA2; BYTE TYPEF = 0; ARRAY 134 BYTE RPLMSG = ("-- REPLACE FOLLOWING --",111(" ")), INSMSG = ("-- INSERT FOLLOWING --", 112(" ")), DELMSG = ("-- DELETE FOLLOWING --", 112(" ")), COLMSG = (12("123456789-"),14(" ")); ARRAY 120 BYTE BLKMSG = 120(" "); ARRAY 134 BYTE BLANKS SYN BLKMSG(_14);
|-- DECLARE INTERNAL PROCEDURES --| PROCEDURE INPUT (RETURN); IF ~ENDFILE(K) THEN BEGIN LINE(K/134) := BLANKS; R3 := NXTLINE(K); IF R3 >= ENDLINE(K) THEN BEGIN R1 := @DCB(K); GET; IF ~= THEN BEGIN LINENUM(K) := "9999.999"; GOTO BOTH; END; R3 := R1; R1 := R1 + LRECL(K); NXTLINE(K) := R3; ENDLINE(K) := R1; IF CARDS(K) THEN GOTO CARDIN; LINENUM(K) := B3; $IFT |-- $SET FOR CAMPUS CONVERSION --| IF LINE1(K) > 3520 OR LINE2(K) > 9999 THEN $END $IFF |-- NO $SET FOR SLAC CONVERSION --| IF LINE1(K) > 3520 OR LINE2(K) > 1525 THEN $END BEGIN SET(CARDS(K)); GOTO CARDIN; END; R1 := R3 + LINE1(K); ENDLINE(K) := R1; R3 := @B3(2); |-- READY FOR TEXT --| END; IF CARDS(K) THEN GOTO CARDIN; LINENUM(K) := B3; $IFT |-- $SET FOR CAMPUS CONVERSION --| R1 := 1000 * LINE1(K) + LINE2(K); CVD(R1,CONWORK); $END $IFF |-- NO $SET FOR SLAC CONVERSION --| R1 := LINENUM(K); CVD(R1,CONWORK); $END LINE(K+134) := #202021204B202020X; ED(8,LINE(K+133),CONWORK(4)); |-- ED converts line numbers into strings --| |-- from " 0.000" through "9999.999" --| LINENUM(K/8) := LINE(K+134); R1 := @LINENUM(K+4); R2 := @B1(3); WHILE R2 > R1 AND B2 = "0" DO BEGIN B2 := " "; REDUCE(R2); END; |-- LINE NUMBER PROCESSED --| R5 := R5-R5; IC(R5,B3(4)); R3 := @B3(5); R2 := @LINE(K+2); R6 := @B2(134); WHILE R5 > 0 DO BEGIN IC(R1,B3); REDUCE(R5); R3 := @B3(1); R4 := #F AND R1; R1 := R1 SHRL 4 AND #F; R2 := @B2(R1); IF R4 > 0 THEN BEGIN R5 := R5-R4; REDUCE(R4); EX(R4,MVC(0,B2,B3)); R2 := @B2(R4+1); R3 := @B3(R4+1); END; IF R2 >= R6 THEN |-- TOO MUCH --| BEGIN SET(ENDFILE(K)); GOTO BOTH; END; END; GOTO BOTH; CARDIN: |-- CARD IMAGE INPUT --| LINENUM(K/8) := B3(72); LINE(K+2/72) := B3; R3 := @B3(80); |-- UPDATE TO NEXT CARD --| BOTH: NXTLINE(K) := R3; END;
PROCEDURE CHKBLANK (RETURN); BEGIN IF B3(10/131) = BLANKS THEN B3(9) := "I"; END; PROCEDURE START (RETURN); BEGIN RESET(CARDS(K)); ENDLINE(K) := K; NXTLINE(K) := K; R1 := @DCB(K); R0 := 1; END; |-- MAIN CODE OF COMPARE PROGRAM --| I := @AREA1; J := @AREA2; K := I; START; BLANKS := "OLD"; R2 := @BLANKS; OPEN; K := J; START; BLANKS := "NEW"; R2 := @BLANKS; OPEN; BLANKS(0/3) := BLANKS(3); R0 := @COLMSG; WRITE; |-- WRITE HEADING LINE --| LOOP: |-- RECORD INPUT LOOP --| RESET(TYPEF); X: K := I; INPUT; Y: K := J; Z: INPUT; IF ENDFILE(I) AND ENDFILE(J) THEN GOTO EXIT; R2 := @LINENUM(I); R3 := @LINENUM(J); IF B2(0/141) = B3 THEN GOTO LOOP; IF B2(0/8) = B3 THEN |-- COMPARE LINE NUMBERS --| BEGIN IF TYPEF ~= 1 THEN BEGIN R0 := @RPLMSG; TYPEF := 1; END; CHKBLANK; R0 := R3; WRITE; B2 := " FOR "; R0 := R2; WRITE; GOTO X; END; IF < THEN BEGIN IF TYPEF ~= 2 THEN BEGIN R0 := @DELMSG; WRITE; TYPEF := 2; END; B2(9) := "D"; R0 := R2; WRITE; K := I; GOTO Z; END; COMMENT ELSE; IF TYPEF ~= 3 THEN BEGIN R0 := @INSMSG; WRITE; TYPEF := 3; END; CHKBLANK; R0 := R3; WRITE; GOTO Y; EXIT: R0 := R0-R0; R1 := @DCB(I); KLOSE; R1 := @DCB(J); KLOSE; END.
The next example uses a local procedure to illustrate Newton's method for taking square roots. The formula is:
R'' = ((X/R') + R')/2
where R' is an approximation for the root of X, and R'' is the next approximation. When the difference between R' and R'' becomes very small, R'' is the assumed root of X. The first approximation of R' is taken to be X with its exponent cut in half. Thus, the first approximation for the root of 128 would be 8. Likewise, 1/8 would be the approximation for 1/128. (128 = #80)
BEGIN COMMENT -- THIS IS A SAMPLE PROGRAM WHICH * MAKES USE OF MOST OF THE FEATURES OF PL360. * THE PROGRAM READS THE SIDES OF A RIGHT TRIANGLE, * COMPUTES THE HYPOTENUSE, AND WRITES THE RESULT.; COMMENT -- DECLARE EXTERNAL PROCEDURES, FUNCTIONS, * AND VARIABLES FIRST. --; PROCEDURE SQRT (R14); IF F01 > 0L THEN |-- THIS PROCEDURE TAKES THE SQUARE ROOT OF THE VALUE IN F01 --| BEGIN LONG REAL TEMPCELL; |-- TEMPORARY STORAGE --| LONG REAL REGISTER DIFF SYN F67, |-- TEMPORARY --| APROX1 SYN F23, APROX2 SYN F45; |-- REGISTERS --| TEMPCELL := F01; R1 := R1-R1; |-- COMPUTE HALF THE ORIGINAL EXPONENT, EXCESS 64 NOTATION --| IC(R1,TEMPCELL); R1 := R1 - #40S SHRA 1 + #40S; STC(R1,TEMPCELL); APROX2 := TEMPCELL; DIFF := 2L; WHILE DIFF > 10'_6L DO |-- FIND SQRT OF F01 --| BEGIN APROX1 := APROX2; |-- NEW APPROXIMATION FROM OLD --| APROX2 := F01/APROX1 + APROX1 / 2L; DIFF := APROX2 - APROX1; DIFF := ABS DIFF; END; F01 := APROX2; |-- REPLACE F01 BY SQRT OF F01 --| END; FUNCTION REDUCE (6,#0600); |-- SUBTRACT 1 FROM REGISTER --| ARRAY 134 BYTE OUTPUT = ( " HYPOTENUSE = FOR SIDES OF",100(" ")); BYTE CARD SYN OUTPUT(35), ANSWER SYN OUTPUT(14); |-- MAIN CODE --| WHILE R0 := @CARD; READ; = DO |-- PROCESS INPUT CARD --| BEGIN R1 := @CARD; R2 := 3; BCDTOVAL; |-- SIDE 1 --| F67 := F01 * F01; BCDTOVAL; |-- SIDE 2 --| F01 := F01 * F01 + F67; SQRT; |-- TAKE SQUARE ROOT --| R1 := @ANSWER; R3 := 7; VALTOBCD; R0 := @OUTPUT; WRITE; |-- HYPOTENUSE --| END; END.
Next is an example of a global procedure program that would be called as a subroutine of some main program by including:
EXTERNAL PROCEDURE DUMP (R14) BASE R15; NULL;
in the main program. A call to DUMP requires that registers R1 and R2 be initialized with the starting and ending absolute addresses of the storage region to be dumped. If the storage address in R1 exceeds the address in R2, then no dump is taken. All registers are restored. It is assumed that the main program has a standard 18-integer save area based on R13. Note the use of the UNPK and TR functions.
GLOBAL PROCEDURE DUMP (R14) BASE R15; BEGIN BYTE C1 SYN B1, C2 SYN B2, C3 SYN B3; FUNCTION REDUCE (6,#0600); |--- START DUMP HERE ---| STM(R14,R12,B13(12)); R14 := R13; BEGIN |--- DATA SEGMENT FOR DUMP PROCEDURE ---| SEGMENT BASE R13; ARRAY 18 INTEGER B13; ARRAY 2 INTEGER CON; ARRAY 256 BYTE LINE = (240(" "),"0123456789abcdef"); BYTE DLINE SYN LINE(8); ARRAY 256 BYTE TRTAB = (64(".")," ", 9("."),"^.<(+|&", 9("."),"!$*);~-/", 9("."),",%_>?", 10("."),":#@'="".abcdefghi", 7("."),"jklmnopqr", 8("."),"stuvwxyz", 23("."),"ABCDEFGHI", 7("."),"JKLMNOPQR", 8("."),"STUVWXYZ", 6("."),"0123456789", 6(".")); CLOSE BASE; COMMENT -- THE FORMAT OF DUMP DATA IS AS FOLLOWS: CORE REL. HEXADECIMAL FORM OF DATA ADDRESS DUMPED IN INTEGER AMOUNTS 600540 0000 02c5e2c4 40404040 40400010 40400001 600550 0010 d7d3c1e8 e2d8d9e2 00000000 40000d80 600560 0020 TO 003c 40404040 600580 0040 40404040 40404040 e2c5c7d5 f0f0f0f1 THE ORIGINAL DATA IS SHOWN AT THE END OF EACH OF THE ABOVE LINES WITH UNPRINTABLE CHARACTERS CONVERTED TO PERIODS (.) | SEGN0001| -- END OF COMMENT;
|--- MAIN DUMP CODE ---| B13(4) := R14; B14(8) := R13; B14(16) := B14(16) XOR B14(16); LINE := " "; LINE(1/132) := LINE; R7 := R1; R6 := R2 - R7; IF < THEN BEGIN LINE := "REQUEST ABORTED"; R0 := @LINE; WRITE; GOTO EXIT; END; R5 := R7 - 4; R4 := R4-R4; WHILE R4 < R6 DO |-- DUMP LOOP --| BEGIN CON := R4; DLINE(8) := 239; DLINE(9/40) := DLINE(8); UNPK(4,4,DLINE,CON); DLINE(49) := "|"; DLINE(66) := "|"; TR(3,DLINE,LINE); R3 := @B7(R4); CON := R3; UNPK(6,4,LINE,CON); TR(5,LINE,LINE); LINE(6) := " "; R3 := @B5(R4+4); IF B3(0/12) = B3(4) THEN BEGIN DLINE(4) := " TO"; UNPK(8,4,DLINE(18),B3); DLINE(26) := 239; DLINE(50/16) := B3; TR(15,DLINE(50),TRTAB); L1: R4 := @B4(16); R3 := @B5(R4); IF R4 < R6 AND B3(0/16) = B3(4) THEN GOTO L1; IF R4 > R6 THEN R4 := R6; R2 := R4 - 4; CON := R2; UNPK(4,4,DLINE(8),CON); DLINE(12) := 239; TR(40,DLINE(8),LINE); R0 := @LINE; WRITE; DLINE(5/2) := DLINE(4); END ELSE BEGIN DLINE(4) := " "; R9 := R6 - R4; R4 := @B4(16); R2 := @DLINE(8); IF R9 >= 16 THEN BEGIN DLINE(50/16) := B3; UNPK(8,4,B2,B3); C2(8) := 239; UNPK(8,4,B2(10),B3(4)); C2(18) := 239; UNPK(8,4,B2(20),B3(8)); C2(28) := 239; UNPK(8,4,B2(30),B3(12)); C2(38) := 239; R2 := @B2(41); R3 := @B3(16); END ELSE BEGIN R1 := @DLINE(50); C1 := "."; B1(1/15) := B1; WHILE R9 >= 4 DO BEGIN UNPK(8,4,B2,B3); C2(8) := 239; B1 := B3; R1 := @B1(4); R9 := R9 - 4; R2 := @B2(10); R3 := @B3(4); END; WHILE R9 > 0 DO BEGIN UNPK(2,1,B2,B3); REDUCE(R9); C1 := C3; R1 := @B1(1); R2 := @B2(2); R3 := @B3(1); END; C2 := 239; END; TR(40,DLINE(8),LINE); TR(15,DLINE(50),TRTAB); R0 := @LINE; WRITE; END; END; EXIT: R13 := B13(4); LM(R14,R12,B13(12)); END; END.
The following global procedure program is also a subroutine which might be linked with a main program. Note the use of the TRT function.
.. Program Segment Number : .. Relative Program Address : : .. Reference Number : : : .. Block Level Number : : : : .. PL360 Program Source : : : : : 001 0000 0001 GLOBAL PROCEDURE TRTEST (R14); BEGIN 014 0000 0002 01 COMMENT THIS ROUTINE TESTS AN INPUT STRING 014 0000 0003 * AGAINST A 256-BYTE SCAN TABLE. 014 0000 0004 * ENTER WITH R1 = @ OF STRING TO BE TESTED. 014 0000 0005 * R2 = @ OF TABLE. 014 0000 0006 * R3 = LENGTH OF STRING TO BE TESTED. 014 0000 0007 * EXITS WITH R1 = LENGTH OF SCANNED STRING. 014 0000 0008 * R2 = SCAN TABLE CHARACTER WHICH 014 0000 0009 * STOPPED THE SCAN. 014 0000 0010 * ALSO, CONDITION CODE SET BASED ON R2; 014 0000 0011 FUNCTION REDUCE(6,#0600); 014 0000 0012 STM(R3,R6,B13(12)); COMMENT SAVE REGISTERS; 014 0004 0013 R4 := R2; R5 := @B1; R2 := R2-R2; R1 := R2; 014 000E 0014 IF R3 > 0 THEN 014 0014 0015 BEGIN REDUCE(R3); R6 := R2; 014 0018 0016 02 FOR R3 := R3 STEP _256 UNTIL 256 DO 014 0018 0017 BEGIN TRT(255,B5,B4); IF ~= THEN 014 0026 0018 03 BEGIN R1 := @B1(R6)-R5; GOTO EXIT; 014 0030 0019 04 END ELSE 014 0030 0020 03 BEGIN R6 := @B6(256); R5 := @B5(256); 014 003C 0021 04 END; 014 003C 0022 03 END; EX(R3,TRT(0,B5,B4)); 014 004C 0023 02 IF = THEN R1 := @B5(R3+1); 014 0054 0024 R1 := @B1(R6) - R5; 014 005A 0025 END; 014 005A 0026 01 EXIT: LM(R3,R6,B13(12)); LTR(R2,R2); 014 0060 0027 END. SEGMENT 014 NAME = TRTEST LENGTH = 0070 BASE REG = 15 0000 9036D00C 18424150 10001B22 18121233 0010 47D0F05A 06301862 47F0F040 DDFF5000 0020 40004790 F0344116 10001B15 47F0F05A 0030 47F0F03C 41606100 41505100 5A30F068 0040 5930F06C 47A0F01C 4430F062 4770F054 0050 41135001 41161000 1B159836 D00C1222 0060 07FEDD00 50004000 FFFFFF00 00000100 EXTERNAL SYMBOL DICTIONARY TRTEST ENTRY (SD) AT 0000
The following global procedure program makes use of SVC functions and is designed to run as a stand-alone program. The operating system in which it executes is NOT a standard operating system. However, the concepts presented by this program could be applied to standard operating systems.
GLOBAL PROCEDURE SORTWYL (R14) BASE R10; |-- REGISTER CONTENTS UNKNOWN, ESTABLISH ADDRESSABILITY --| BEGIN BALR(R10,R0); R15 := 2; R10 := R10 - R15; BEGIN GLOBAL DATA SORTSPAC BASE R13; INTEGER HIGHCORE, PAGETAB, LOCATORS, LOCEND; ARRAY 2 SHORT INTEGER CONTROL = (3,0); |-- TEXT CONTROL --| ARRAY 2 INTEGER TEMP = (0,0); |-- FIRST/LAST LINE RANGES --| SHORT INTEGER DELTA = 0; |-- START COLUMN POSITION --| EQUATE LIMITS SYN 10000 SHLL 3; |-- LINE COUNT LIMIT --| EQUATE LL SYN 4, LLEN SYN LL+1; |-- LINE NUMBER LENGTHS --| ARRAY 3 SHORT INTEGER CLCMD = (#D500,@B14(LLEN),@B15(LLEN)); DUMMY BASE R2; INTEGER LOCATION; SHORT INTEGER LENGTH, COUNT; CLOSE BASE; FUNCTION CR(1,#1900), REDUCE(6,#0600); PROCEDURE TPUT (R9); |-- SEND MESSAGE TO TERMINAL --| BEGIN R0 := 1; SVC(246); R0 := 1; SVC(242); END; PROCEDURE WYLBUR (R9); |-- SEND COMMAND TO EDITOR --| BEGIN R1 := NEG R1; R0 := R0-R0; SVC(254); END; |-- START MAIN CODE, GETMAIN CORE LIMITS WITH 4095 BYTE MIN. --| R0 := 14; R1 := 4095; R15 := R15-R15; SVC(251); PAGETAB := R1; R2 := R0; R2 := @B1(R2) =: HIGHCORE; |-- SENSE THE PARMS FROM THE CALLING COMMAND. --| R3 := R3-R3; R0 := 19; R15 := 132; SVC(250); IF R1 = 0 THEN GOTO START; |-- JUMP IF NO PARMS --| R4 := PAGETAB; R5 := R5-R5; R6 := R5; WHILE R1 > 0 DO BEGIN REDUCE(R1); IF B4 >= "0" THEN BEGIN NI(#F,B4); IC(R6,B4); R5 := R5 * 10S + R6; END; R4 := @B4(1); END; IF R5 > 0 THEN REDUCE(R5); DELTA := R5; R6 := R5 + CLCMD(2) =: CLCMD(2); |-- UPDATE DISP FIELDS --| R5 := R5 + CLCMD(4) =: CLCMD(4); START: R1 := PAGETAB; |-- READ THE ACTIVE FILE INTO CORE --| LOOP: R2 := R2 + _8; LOCATION := R1; R0 := 1; R1 := NEG R1; R15 := R2 + R1; IF R15 <= R3 THEN |-- NO MORE ROOM --| BEGIN R3 := LIMITS; GOTO FILLER; END; IF R15 > 4096 THEN R15 := 4096; |-- READ A BLOCK OF TEXT INTO CORE. --| R14 := @CONTROL; SVC(247); R0 := 1; SVC(242); IF ~= OR R14 = 0 THEN GOTO FILLER; COUNT := R14; LENGTH := R1; TEMP := _1; R14 := R14 SHLL 3; R3 := R3 + R14; R1 := R1 + LOCATION; B1(0/1) := 0; R1 := @B1(7) AND _8; GOTO LOOP; FILLER: |-- ALL TEXT OF ACTIVE FILE HAS BEEN INPUT --| R1 := LOCATION =: LOCATORS; R2 := @B2(8); PAGETAB := R2; R4 := R1 + R3; IF R3 >= LIMITS OR R4 > PAGETAB THEN BEGIN LA(R1,"TOO MUCH TEXT TO SORT."); R15 := STRING; GOTO EXIT; |-- NO SORT --| END; R2 := HIGHCORE; |-- BUILD LOCATOR TABLE --| WHILE R2 := R2 + _8; R2 >= PAGETAB DO BEGIN R3 := LOCATION; R4 := LENGTH + R3; R5 := R5-R5; WHILE R3 < R4 DO BEGIN IC(R5,B3(LL)); B1(4) := R3; R3 := @B3(R5+LLEN); R8 := R5 - DELTA =: B1; R1 := @B1(8); |-- INCREMENT TO NEXT LOCATOR --| END; END; LOCEND := R1; R2 := LOCATORS; |-- SORT THE LOCATOR TABLE DEPENDENT ON TEXT COMPARISONS --| R1 := R1 - R2; R8 := R1; R7 := 8; R9 := R2 - R7; X1: R1 := R1 SHRL 1 AND _8; IF = THEN GOTO X5; R2 := R8-R1; R3 := R7; X2: R4 := R3; X3: R5 := R9+R4; R6 := R5+R1; R11 := B5; R12 := B6; IF R11 < 0 OR R12 < 0 THEN GOTO CREGS; R14 := B5(4); R15 := B6(4); IF R11 > R12 THEN EX(R12,CLCMD) ELSE EX(R11,CLCMD); IF ~= THEN GOTO CEQUAL; CREGS: CR(R11,R12); CEQUAL: IF <= THEN GOTO X4; XC(7,B6,B5); XC(7,B5,B6); XC(7,B6,B5); IF R4 <= R1 THEN GOTO X4; R4 := R4 - R1; GOTO X3; X4: IF R2 = R3 THEN GOTO X1; R3 := R3 + R7; GOTO X2; X5: |-- SORT FINISHED, NOW CHANGE LINE NUMBERS --| R6 := 1000; R1 := LOCATORS; WHILE R1 < LOCEND DO BEGIN R3 := B1(4); TEMP := R6; B3(0/LL) := TEMP(4-LL); R6 := @B6(1000); R1 := @B1(8); END; LA(R1,"CLEAR TEXT"); R15 := STRING; WYLBUR; |-- RUN THRU PAGE TABLE TO REWRITE BLOCKS OF TEXT --| R2 := PAGETAB; WHILE R2 < HIGHCORE DO BEGIN R1 := NEG LOCATION; R15 := LENGTH; R14 := COUNT; R0 := 1; SVC(246); R0 := 1; SVC(242); R2 := @B2(8); END; LA(R1,"ACTIVE FILE SORTED."); R15 := STRING; EXIT: TPUT; SVC(253); END; END.
This last sample program is one possible answer to the exercise problem given in Chapter 8. If you have not tried the problem, do so before reading the program given here.
$TITLE PROGRAM WHICH COMPUTES DAY-OF-THE-WEEK FROM DATE BEGIN |-- PROGRAM TO COMPUTE DAY OF THE WEEK --| SHORT INTEGER YEAR, DAY, MONTH; ARRAY 14 BYTE LIMIT = (31,28,31,30,31,30,31, 31,30,31,30,31,31,29), OFFSET = (0,3,3,6,1,4,6,2,5,0,3,5,6,2); ARRAY 70 BYTE MONTHNAMES = ( "JAN. ","FEB. ", "MARCH","APRIL","MAY ","JUNE ","JULY ", "AUG. ","SEPT.","OCT. ","NOV. ","DEC. ", "JAN. ","FEB. "); |-- LEAP YEAR --| ARRAY 63 BYTE DAYNAMES = ( "SUNDAY ","MONDAY ", "TUESDAY ","WEDNESDAY","THURSDAY ","FRIDAY ","SATURDAY "); ARRAY 133 BYTE OUTPUT = 133(" "); ARRAY 133 BYTE ERRORMSG = ("*** ERROR ***",120(" ")); BYTE CARD SYN ERRORMSG(14); FUNCTION REDUCE(6,#0600); |-- MAIN CODE --| OUTPUT(19) := ","; |-- SET COMMA IN OUTPUT LINE --| TOP: R0 := @CARD; READ; IF ~= THEN GOTO EXIT; R1 := @CARD; R2 := 1; R3 := 4; WHILE R3 >= 0 DO |-- EXTRACT VALUES --| BEGIN BCDTOVAL; YEAR(R3) := R0; R3 := R3 - 2S; END; IF YEAR > 99 THEN GOTO ERROR; R4 := MONTH; REDUCE(R4); IF R4 < 0 OR R4 >= 12S THEN GOTO ERROR; R0 := R0-R0; R1 := YEAR / 4; IF R0 = 0 AND R1 ~= 0 AND R4 < 2S THEN R4 := @B4(12); R3 := R3-R3; IC(R3,LIMIT(R4)); R5 := DAY; IF R5 > R3 OR R5 <= 0 THEN GOTO ERROR; IC(R3,OFFSET(R4)); R2 := R2-R2; R3 := R3 + R5 + R1 + YEAR / 7; R2 := R2 * 9S; R4 := R4 * 5S; R2 := @DAYNAMES(R2); R4 := @MONTHNAMES(R4); OUTPUT(0/9) := B2; R0 := R5; R2 := 1; R3 := 5; R1 := @OUTPUT(14); VALTOBCD; R0 := 1900 + YEAR; R1 := @OUTPUT(20); VALTOBCD; OUTPUT(11/5) := B4; R0 := @OUTPUT; PUTIT: WRITE; GOTO TOP; ERROR: R0 := @ERRORMSG; GOTO PUTIT; EXIT: END.
Two's complement, one's complement, ten's complement: what's it all about? To answer that question, let's look at some properties of numbers. If you were asked to write the value 'one million', you would probably write 1,000,000 (with the commas used to group digits); and if you were asked to write the value 'one', you would probably write the single digit 1. As a human being there is no limit to the number of digits you can write to express a value. However, within a computer there generally is a limit to the number of digits which can be used to express a value. For example, let us assume a hypothetical decimal computer where every value is expressed by a 6-digit field. We could then express within this computer values from zero (0) to 999,999. However, the computer requires that every value be exactly six digits, no more, no less. Therefore, the value 'zero' would be expressed as 000000. The value 'one' would be expressed as 000001, etc. Now if you were asked to place the value 'one million' within this computer, you would say, "It can't be done because 'one million' requires seven digits which exceeds the limits for this computer." You would be absolutely correct!
This brings us to the first point: fixed-length values within a computer have specific limits. Values beyond that limit cannot be expressed. In our 6-digit decimal computer, we can express values from 000000 to 999999, a total of one million values. However, we have not taken into consideration negative numbers. So far, everything is positive. Well, in order to allow for negative values, we will have to divide the set of possible values into two parts: one-half million for positive values, and one-half million for negative values. Therefore, the values from 000000 through 499999 could be considered positive, and the values from 500000 through 999999 could be considered negative.
But now the question arises, what is the correspondence between positive and negative numbers? To answer this question, mathematicians long ago discovered the following algorithm:
If any fixed-length value is subtracted from a value that is one larger than the greatest possible fixed-length value, then the right-most fixed-length portion of the result could be considered the negative value.
In our hypothetical 6-digit decimal computer, 1000000 would be the value from which we would subtract any 6-digit value to obtain its negative. Therefore, subtracting 000001 from 1000000 results in 999999. Adding 000001 (positive one) to 999999 (negative one) results in one million again, but if we ignore the excess digits above our limit of six digits, the answer is 000000 (zero). Similarly, subtracting 499999 from one million results in 500001 which we can consider as the negative of 499999. If we subtract 000000 (zero) from one million, the answer is still 000000 (zero). We therefore have a correspondence scheme for positive and negative values, as follows:
Positive 000000 000001 000002 ... 499998 499999 -- Negative -- 999999 999998 ... 500002 500001 500000 Ten's Complement
Notice that 000000 does not have a negative form, and that 500000 does not have a positive form (subtracting 500000 from one million results in 500000). The above process is known as ten's complement notation. Nine's complement is similar: every value is subtracted from the largest possible value to obtain the negative. That is, every value is subtracted from 999999 in our hypothetical computer to obtain the negative. The result then is:
Positive 000000 000001 000002 ... 499998 499999 Negative 999999 999998 999997 ... 500001 500000 Nine's Complement
A general practice for finding the nine's complement of any decimal value is to replace each digit of the original value by the nine's complement of that digit (the sum of the original digit and nine's complement digit is 9). The following table shows the nine's complement of each decimal digit.
0 1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 1 0 Nine's Complement Table
The rule for finding ten's complement values is: replace the original value by the nine's complement value and then add one.
Now for the IBM System/360 computer, values are expressed in binary digits (0 and 1 only), and a typical fixed-length value is 32 digits (called bits). Just as we used commas to group digits when we wrote 1,000,000; so also, groups of four bits are written as single hexadecimal digits. This simply saves on space for expressing binary values. Thus instead of:
0011 0111 0001 1111 1010 0110 0000 0000 we write: 3 7 1 F A 6 0 0
Now the range of possible values is 00000000 through FFFFFFFF, and dividing that range in half as we did with our hypothetical computer results in 00000000 through 7FFFFFF as positive values, and 80000000 through FFFFFFFF as negative values. Two's complement notation is similar to ten's complement, and one's complement is similar to nine's complement. The only difference is that we are using hexadecimal digits instead of decimal digits. The following table shows the one's complement of each hexadecimal digit.
0 1 2 3 4 5 6 7 8 9 A B C D E F F E D C B A 9 8 7 6 5 4 3 2 1 0 One's Complement Table
Since the IBM System/360 computer uses two's complement, the rule for finding the negative of any value is: replace the original value by the one's complement value and add one (similar to the ten's complement rule). Thus, the negative of 00000000 is still 00000000, the negative of 00000001 is FFFFFFFF, and so on. (Notice that the negative of FFFFFFFF is 00000001.) Again we have a correspondence scheme for positive and negative values, as follows:
Positive 00000000 00000001 ... 7FFFFFFF -- Negative -- FFFFFFFF ... 80000001 80000000 Two's Complement (Hexadecimal)
One of the problems of a fixed-length system is that when two large positive or negative values are added together, the result may be a value of opposite sign. This is also true when subtracting a negative from a positive or a positive from a negative. Thus, in our decimal system, if we were to add 300000 to 300000 the result would be 600000 which should be interpreted as a negative answer. This is known as the OVERFLOW problem. Digital computer systems take this into account and provide the programmer with a means of checking the result of a fixed-length addition or subtraction. In IBM System/360 computers, the 'condition code' of the machine is set to indicate OVERFLOW whenever fixed-length addition or subtraction yields an answer of opposite sign. To understand what is happening internally, consider all possible fixed-length values as points on a circle with 0 at the top, 80000000 (hexadecimal) at the bottom, negative values on the left side of the circle with -1 next to 0, and positive values on the right side of the circle with +1 next to 0. Addition of positive values moves in a clockwise direction from any point, and subtraction of a positive value moves in a counterclockwise direction. Any time the point at the bottom of the circle between 80000000 and 7FFFFFFF is crossed, an OVERFLOW condition occurs.
In Appendix A we introduced a hypothetical decimal computer to illustrate two's complement arithmetic. We shall do the same here because decimal arithmetic is easier for us to understand and because the concepts involving floating-point representations and operations within a computer would be the same regardless of which base system we chose.
Before we go any further, let's review some basic mathematics. Any real number can be written in what's called exponential notation: that is, of the form '0.n times 10 to the integer power p'. We shall use an apostrophe character in place of the phrase 'times 10 to the integer power' and simply write 0.n'p . It doesn't matter what base system we are using as long as n, p, and 10 are all of that same base.
For example, we could represent the integer value 'eleven decimal' in different base systems as follows (all comments are in decimal base):
(a) 0.11'2 (decimal) | (1/10 + 1/100) x 10'2 | (b) 0.B'1 (hexadecimal) | (11/16) x 16'1 | (c) 0.13'2 (octal) | (1/8 + 3/64) x 8'2 | (d) 0.1011'100 (binary) | (1/2 + 1/8 + 1/16) x 2'4 |
Notice that the first digit following the radix point was not 0 in any of our representations. In such a case, the number is said to be expressed in normalized form. When the first digit following the radix point is 0, we have an unnormalized form. Thus, using just the decimal base system:
11.0'0 and 1.1'1 are not proper since the form is not 0.n'p 0.11'2 is of normalized form 0.011'3 and 0.0011'4 are of unnormalized form, etc.
As you can see, a floating-point number has two sets of values associated with it. One set represents the significant digits of the fraction (n), and the other specifies the power (p) to which the base must be raised to indicate the proper location of the radix point within the number. Since both n and p are signed quantities, we must devise a method for expressing the four possible combinations of signed n and p in our computer representation.
0.n'p 0.n'_p _0.n'p _0.n'_p
Let's assume a hypothetical 8-digit decimal representation as follows: two digits for p and its sign and the sign of n, which we we shall call the P-field; then an assumed radix point followed by six digits for n, which we shall call the N-field. Pictorially our representation looks like this: PPNNNNNN .
The N-field can represent one million possible fractions from 0.000000 through 0.999999 with the first hundred thousand being of unnormalized form (0.000000 through 0.099999) and the rest being of normalized form (0.100000 through 0.999999).
The P-field can represent one hundred possibilities (00 through 99) but since we need to represent four combinations of signs as well as p, we will have to divide the P-field into fourths. We can take care of the sign of n first by letting 00 through 49 represent positive n's, and 50 through 99 represent negative n's. The P-field for a negative number is simply 50 more than that of the corresponding positive number. All we have left now is p and its sign. To understand what we can do about signed p's, let's work with just positive n's for the moment and look at a scale of normalized numbers written in exponential form arranged in ascending order of magnitude.
0.n'_p, 0.n'0, 0.n'p |--> Ascending order -->| Notice that the exponent '0 would occur near the center of the scale if we wanted about as many negative p's as positive p's across the entire scale. Therefore, we shall arrange our fifty possible P-field values (00 through 49 for positive n's) so that p=0 occurs near the center of the range at 25. In other words, we add 25 to the signed value of p to obtain the corresponding P-field value. This makes p=_25 the smallest possible exponent corresponding to a P-field value of 00, and p=24 the largest possible exponent corresponding to a P-field value of 49. The computer's representation is called 'excess 25 notation' since its value exceeds the value of the true exponent (p) by 25 (Note: 25 is one-fourth of the P-field range).
The computer's representation of a floating-point number is now arranged in such a way that any normalized numbers of like sign may be compared against each other to correctly determine their proper position on the scale shown above. Here are a few examples:
Number Exponential form Computer form largest positive 0.999999'24 49999999 758.5 0.7585'3 28758500 1.0 0.1'1 26100000 one half 0.5'0 25500000 smallest positive 0.1'_25 00100000 zero 0.0'_25 00000000 smallest negative _0.1'_25 50100000 negative one half _0.5'0 75500000 _1.0 _0.1'1 76100000 largest negative _0.99999'24 99999999
The conversion from exponential form (0.n'p) to computer form (PPNNNNNN) is quite simple. First, determine the P-field by taking p and add 25, then if n is negative, add 50 more. Now take the first six digits of n and write them following the P-field to get the final 8-digit computer representation.
We can apply the same technique to hexadecimal machine notation. By allowing two hexadecimal digits for the P-field and either six or 14 hexadecimal digits for the N-field, we have the System/360 forms of REAL and LONG REAL floating-point values. Thus, PPNNNNNN and PPNNNNNNNNNNNNNN represent REAL and LONG REAL values within the computer, with the P's and N's all being hexadecimal digits. P-fields from 00 through 7F indicate positive values, and 80 through FF indicate corresponding negative values. The true exponents range from _64 decimal (PP=00,80) through 00 (PP=40,C0) to 63 decimal (PP=7F,FF), all as powers of 16 decimal (10 hexadecimal).
Conversion of a decimal value in exponential form into the machine's hexadecimal form is not simple. For example, to convert 0.2005125'4 decimal into machine form requires the following steps:
Therefore, 0.2005125'4 decimal becomes 0.7D52'3 hexadecimal which becomes 437D5200 machine form. Conversion from machine form to exponential decimal form requires essentially the same steps in reverse.
0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 ^ . < ( + | 5 & ! $ * ) ; ~ 6 - / , % _ > ? 7 ` : # @ ' = " 8 a b c d e f g h i , < L J K 9 j k l m n o p q r - = M + A e c s t u v w x y z > ? [ ' 7 B @ A B C D E F G H I ; \ ] m ~ C A B C D E F G H I D J K L M N O P Q R E S T U V W X Y Z F 0 1 2 3 4 5 6 7 8 9
The above table represents all the normal characters available on an IBM/360. The table is read as follows:
The hexadecimal digits in the first column represent the first digit of the byte created by a character in the table, and the hexadecimal digits across the top are the second digit. Thus, the character "k" is represented by a byte containing the hexadecimal digits 92.
The 'blank' or 'space' character is hexadecimal 40; all other blanks in the table are unprintable characters.
Three principle postulates were used as guidelines in the design of the language:
The terminology below is used in describing general constructs:
Ireg R0 thru R15 INTEGER Rreg F0, F2, F4, F6 REAL Lreg F01, F23, F45, F67 LONG REAL
Subscript -a indicates the assignment register, as in
Ireg-a := Expression
Bcell BYTE Value X Scell SHORT INTEGER Value S Icell INTEGER Value Rcell REAL Value R Lcell LONG REAL Value L
Note: values may replace cells in an expression.
Ireg-a := Icell (Icell) Ireg-a := #FACE 0000FACE Ireg-a := "DROP" C4D9D6D7 Ireg-a := _4 FFFFFFFC
Cond represents one of: = , ~= , >= , <= , > , < , Number , ~Number
In the following tables, * preceding the CODE indicates the instruction does not change the condition code. CODE is the computer instruction operation code; and MNEMONIC is the assembly language mnemonic corresponding to the operation code.
CODE MNEMONIC COMPILER CONSTRUCT *05 BALR Procname (not local procedure call) *07 BCR END of any PROCEDURE 10 LPR Ireg-a := ABS Ireg 11 LNR Ireg-a := NEG ABS Ireg 12 LTR Ireg Cond 0 13 LCR Ireg-a := NEG Ireg 14 NR Ireg-a AND Ireg 16 OR Ireg-a OR Ireg 17 XR Ireg-a XOR Ireg *18 LR Ireg-a := Ireg Ireg-a =: Ireg (reverse assignment) Note: Ireg-a := Ireg-a generates no instruction. 19 CR Ireg-1 Cond Ireg-2 1A AR Ireg-a + Ireg 1B SR Ireg-a - Ireg *1C MR Ireg-a * Ireg Note: Ireg-a must be odd numbered *1D DR Ireg-a / Ireg Note: Ireg-a must be odd numbered 1E ALR Ireg-a ++ Ireg 1F SLR Ireg-a -- Ireg 20 LPDR Lreg-a := ABS Lreg 21 LNDR Lreg-a := NEG ABS Lreg 22 LTDR Lreg Cond 0L 23 LCDR Lreg-a := NEG Lreg *28 LDR Lreg-a := Lreg Lreg-a =: Lreg (reverse assignment) Note: Lreg-a := Lreg-a generates no instruction. 29 CDR Lreg-1 Cond Lreg-2 2A ADR Lreg-a + Lreg 2B SDR Lreg-a - Lreg *2C MDR Lreg-a * Lreg *2D DDR Lreg-a / Lreg 2E AWR Lreg-a ++ Lreg 2F SWR Lreg-a -- Lreg 30 LPER Rreg-a := ABS Rreg 31 LNER Rreg-a := NEG ABS Rreg 32 LTER Rreg Cond 0R 33 LCER Rreg-a := NEG Rreg *38 LER Rreg-a := Rreg Rreg-a =: Rreg (reverse assignment) Note: Rreg-a := Rreg-a generates no instruction. 39 CER Rreg-1 Cond Rreg-2 3A AER Rreg-a + Rreg 3B SER Rreg-a - Rreg *3C MER Rreg-a * Rreg *3D DER Rreg-a / Rreg 3E AUR Rreg-a ++ Rreg 3F SUR Rreg-a -- Rreg
All these instructions allow indexable cells. CODE MNEMONIC COMPILER CONSTRUCT *40 STH Scell := Ireg Ireg-a =: Scell (reverse assignment) *41 LA Ireg-a := @Cell Ireg-a := Ivalue (0 <= Ivalue < 4096) 45 BAL Procname (local procedure call) *47 BC GOTO, ELSE, Cond *48 LH Ireg-a := Scell 49 CH Ireg Cond Scell 4A AH Ireg-a + Scell 4B SH Ireg-a - Scell *4C MH Ireg-a * Scell *50 ST Icell := Ireg Ireg-a =: Icell (reverse assignment) 54 N Ireg-a AND Icell 55 CL Ireg Cond "string" (4 or less chars only) 56 O Ireg-a OR Icell 57 X Ireg-a XOR Icell *58 L Ireg-a := Icell 59 C Ireg Cond Icell (not "string") 5A A Ireg-a + Icell 5B S Ireg-a - Icell *5C M Ireg-a * Icell Note: Ireg-a must be odd numbered *5D D Ireg-a / Icell Note: Ireg-a must be odd numbered 5E AL Ireg-a ++ Icell 5F SL Ireg-a -- Icell *60 STD Lcell := Lreg Lreg-a =: Lcell (reverse assignment) *68 LD Lreg-a := Lcell 69 CD Lreg Cond Lcell 6A AD Lreg-a + Lcell 6B SD Lreg-a - Lcell *6C MD Lreg-a * Lcell *6D DD Lreg-a / Lcell 6E AW Lreg-a ++ Lcell 6F SW Lreg-a -- Lcell *70 STE Rcell := Rreg Rreg-a =: Rcell (reverse assignment) *78 LE Rreg-a := Rcell 79 CE Rreg Cond Rcell 7A AE Rreg-a + Rcell 7B SE Rreg-a - Rcell *7C ME Rreg-a * Rcell *7D DE Rreg-a / Rcell 7E AU Rreg-a ++ Rcell 7F SU Rreg-a -- Rcell *88 SRL Ireg-a SHRL Ivalue or Ireg *89 SLL Ireg-a SHLL Ivalue or Ireg 8A SRA Ireg-a SHRA Ivalue or Ireg 8B SLA Ireg-a SHLA Ivalue or Ireg
This table is in sort order by the function names. The parameters of each function are given in general form. See section 7.1 for a complete description of the parameters. See section 7.3 for descriptions of most of the functions defined in this table.
CODE COMPILER CONSTRUCT * 05 BALR (Rx,Ry) D5 CLC (S,CL,CL) 95 CLI (S,C) * 4F CVB (R,X) * 4E CVD (R,X) DE ED (S,C,CL) DF EDMK (S,C,CL) ? 44 EX (R,XL) * 43 IC (R,XL) * 41 LA (R,XL) * 48 LH (R,X) * 98 LM (Rx,Ry,C) 12 LTR (Rx,Ry) * D2 MVC (S,C,CL) * 92 MVI (S,C) * D1 MVN (S,C,CL) * D3 MVZ (S,C,CL) D4 NC (S,C,CL) 94 NI (S,C) D6 OC (S,C,CL) 96 OI (S,C) * F2 PACK (N1,N2,C,CL) * 9200 RESET (C) * 92FF SET (C) 8F SLDA (R,CI) * 8D SLDL (R,CI) 04 SPM (R) 8E SRDA (R,CI) * 8C SRDL (R,CI) * 42 STC (R,X) * 40 STH (R,X) * 90 STM (Rx,Ry,C) * 0A SVC (S) 95FF TEST (C) 91 TM (S,C) * DC TR (S,C,CL) DD TRT (S,C,CL) 9300 TS (C) * F3 UNPK (N1,N2,C,CL) D7 XC (S,C,CL) 97 XI (S,C)
CODE MNEMONIC COMPILER CONSTRUCT Single Byte Instructions (4-Byte instruction length) *92 MVI Cell := Value 94 NI Cell AND Value 95 CLI Cell Cond Value 96 OI Cell OR Value 97 XI Cell XOR Value Multi-Byte Instructions (6-Byte instruction length) *D2 MVC Cell-1 := Cell-2 D4 NC Cell-1 AND Cell-2 D5 CLC Cell-1 Cond Cell-2 D6 OC Cell-1 OR Cell-2 D7 XC Cell-1 XOR Cell-2
Single-byte instructions assume a Value used with a bytecell, or Cell with a length specification of 1. Value may also be a single character "string". Cell must always be an unindexed cell reference.
Cell-2 of multi-byte instructions may be a Value used with a Cell-1 of more than one byte, or a "string" of more than one character. Cell-2 may also be an unindexed cell reference. Cell-1 must always be an unindexed cell reference.
The following sections show the machine code into which various other constructs of the language are translated. Assembly language mnemonics are used to denote the individual instructions.
1. IF <condition-1> AND ... AND <condition-n-1> AND <condition-n> THEN <true statement> ELSE <false statement> (condition-1) BC c1,L1 ... (condition-n-1) BC cn-1,L1 (condition-n) BC cn,L1 (true statement) B L2 L1 (false statement) L2 EQU *
ci is determined by the i-th condition, which itself either translates into a compare instruction depending on the types of compared quantities, or has no corresponding instruction if it merely designates a relation or integer value.
2. IF <condition-1> OR ... OR <condition-n-1> OR <condition-n> THEN <true statement> ELSE <false statement> (condition-1) BC c1,L1 ... (condition-n-1) BC cn-1,L1 (condition-n) BC cn,L2 L1 (true statement) B L3 L2 (false statement) L3 EQU * Example: IF R1 < R2 THEN R0 := R3 ELSE R0 := R4 CR R1,R2 BC 10,L1 LR R0,R3 B L2 L1 LR R0,R4 L2 EQU *
3. WHILE <condition> DO <statement> L1 (condition) BC cond,L2 (statement) B L1 L2 EQU *
If the condition is compound, then code sequences similar to those given for the preceding IF statements are used.
4. FOR <Rn := expression> STEP <increment> UNTIL <limit> DO <statement> (Rn := expression) B L2 L1 (statement) A Rn,INC L2 C Rn,LIM BC cond,L1
Rn is the register specified by the assignment, INC the location where the increment is stored, and LIM the location where the limit is stored. The compare instruction at L2 may be either a C, CH, or CR instruction depending on the type of limit. Moreover, cond depends on the sign of the increment.
5. CASE <Rn> OF BEGIN <statement-1>; <statement-2>; ... <statement-m>; END AR Rn,Rn LH Rn,SW(Rn) B 0(Rn,Rp) L1 EQU *-ORIGIN (statement-1) B LX(Rp,0) L2 EQU *-ORIGIN (statement-2) B LX(Rp,0) . . . . . . Lm EQU *-ORIGIN (statement-m) B LX(Rp,0) SW EQU *-2 DC Y(L1) DC Y(L2) . . . . . . DC Y(Lm) LX EQU *-ORIGIN
ORIGIN is the address of the beginning of the program segment and register Rp is assumed to contain this address.
6. PROCEDURE <procname> (Ra); <statement> P (statement) BR Ra
It is assumed that P is a label corresponding to <procname>.
7. <procname> BALR Ra,Rp or BAL Ra,P or L Rp,newbase BALR Ra,Rp L Rp,oldbase or L Rp,newbase BAL Ra,P L Rp,oldbase
It is assumed here that P designates the relative address of the procedure to be called within the program segment in which it is declared, and Ra is the return address register specified in its declaration, and Rp is the program segment's base register. The first two versions of the code are obtained whenever the segment in which the procedure is declared is also the one in which it is invoked. If the procedure call is of the form
then the instruction sequences become:
BALR Ra,Rp LTR Rn,Rp BALR Rp,0 L Rp,oldbase or BAL Ra,P LTR Rn,Rp BALR Rp,0 L Rp,oldbase or L Rp,newbase BALR Ra,Rp LTR Rn,Rp BALR Rp,0 L Rp,oldbase or L Rp,newbase BAL Ra,P LTR Rn,Rp BALR Rp,0 L Rp,oldbase
The compiler accepts instructions inserted anywhere in the sequence of input records. These instructions affect subsequent records. A compiler instruction record is identified by the character '$' in column 1 and an instruction in columns 2-72. To be recognized, the first three characters following the '$' must be UPPER case alphabetics, digits or blanks. Remaining characters in the control word are not checked, but are assumed to be there. Therefore, $title is not recognized, but $TITle is recognized, as would be $TITLE or even $TITxx. Unrecognized control cards are simply ignored.
$LIST Increment List Counter (initial option: List Counter = 0). Source records are listed if List Counter is not negative.
$NOLIST Decrement List Counter. Source records are not listed if the List Counter is negative.
$PAGE Start a new page with the next listing record.
$TITLE Start a new page with the next listing record, and use the contents of columns 10 through 62 as the title for that and subsequent pages.
$STITLE This directive provides a subtitle line. The subtitle will remain in effect until the next $TITLE or $STITLE card. $STITLE cards may change the subtitle without affecting the main $TITLE. $STITLE also causes a page eject. The contents of columns 10 through 62 are taken as the subtitle.
$SPACE # This directive allows the user to line space a listing by # lines where # is a number from 1 to 99. If # is blank, then a single line space is assumed. If the number of lines remaining on the page is less than #, then a page eject is done instead of line spacing.
$EJECT This directive is equivalent to $PAGE.
$ON This directive enables the printing of all $-control cards except $TITLE, $STITLE, $EJECT, $PAGE, and $SPACE.
$OFF This directive disables the printing of all $-control cards. This is the default condition at the start of compilation.
$BOLD, $NOBold, $UNDerline, $NOUnder $BOLD overprints reserved words on impact printers. $UNDERLINE will underline reserved words on most printers.
$XREF All subsequent instances of identifiers are listed in an alphabetical cross-reference listing together with the line numbers at which they are defined or referenced in the source program. The cross-reference listing follows the program listing if $LIST is in effect at the end of the program. If there is not enough free storage to allocate the cross- reference tables, the $XREF instruction is ignored. The cross-reference listing will be single-spaced unless $XREF 2 is specified to double-space the listing.
$NOXREF This causes the previous option to be turned off (initial option). Any accumulated cross-references will be listed following the program as described above for $XREF.
$0 Print a summary line at the close of each segment. This is the initial option, and it applies to all other options. The other options make use of the following table: a. List the external symbol dictionary at the close of each segment. b. List the declared identifiers and associated value as each is declared. c. List the object text in hexadecimal notation at the close of each segment.
$1 = a $2 = a+b $3 = a+b+c $4 = c $5 = a+c $6 = b+c $7 = b
$OS Subsequent PL360 programs which are statements are compiled with entry and exit instruction sequences conforming to the program-calling conventions of an OS environment. This is a default option when the compiler is used with the OS interface.
$DOS Subsequent PL360 programs which are statements are compiled with entry and exit instruction sequences which conform to the program calling conventions of a DOS environment. This is the default option when the compiler is used with the DOS interface.
$BASE=xx This directive must precede the first non-control card. Program segments following this directive are compiled with xx taken as the program base register. This includes main programs, global procedures, segment procedures, and external procedures (which do not specify BASE). Procedure calls to such segments automatically set the specified base register to the entry point address. The decimal number xx must be between 01 and 15. Programs which are main blocks must not be compiled with base registers 13 or 14. The initial option is xx=15, and all predeclared external procedure declarations always have base register R15. It is recommended that this compiler directive only be used for programs which make use of SVC instructions that do not preserve the contents of register R15, the default program base register.
$XYY# This directive must precede the first non-control card. All compiler generated segment names will commence with XYY rather than SEG, and all object deck cards are identified by XYY in columns 73 through 75 followed by the letter N and a 4-digit number. X signifies any alphabetic and Y any alphanumeric characters.
$GEN If this directive precedes the first error detected (if any), then object decks are still produced if any have been requested. Otherwise object decks are suppressed after encountering an error.
$NOGO Compile, but suppress the GO step.
$COPY ddname $COPY ddname(member) These control cards specify that a sequential data set or member of a partitioned data set is to be copied into the compilation. The compiler temporarily suspends input from the standard input medium and continues compilation with the data set defined by the $COPY control card. When end-of-information is encountered on that data set, compilation continues from the standard input with the card image immediately following the $COPY control card. Note: $COPY is ignored in the data set being copied, i.e., $COPY may not nest. As many $COPY control cards as desired may occur in the standard input. When compiling under ORVYL, ddname or member is assumed to be the ORVYL data set name. An account number may follow to indicate a data set belonging to a different account.
At the start of compilation of each program, an array of flags is reset by the compiler. The following directives use this array. The array flags are specified by individual characters in the directives, and any characters may be used, including blank. Upper and lowercase characters are considered equivalent. The directives must be in uppercase in columns 1 through 4 on the control card.
$SET a where 'a' is any character in column 6. This directive sets the 'a' flag.
$IFT a b where 'a' is any character in column 6, and 'b' is any char- $IFF a b acter in column 8. These directives examine the 'a' flag. If the 'a' flag is set for $IFT, or reset for $IFF, this directive takes no action and compilation continues normally. If the 'a' flag is reset for $IFT, or set for $IFF, the compiler skip-reads source cards until a $END directive is encountered with its 'b' character matching the 'b' character of the $IFT or $IFF. Compilation then continues from that point. Note: '$IFF a b' is an unconditional skip to '$END b' if 'a' is currently set. '$IFT a b' is an unconditional skip to '$END b' if 'a' is currently reset.
$IFJ b where 'b' is any character in column 6. This is an unconditional skip to matching '$END b'.
$END b where 'b' is any character in column 6. This directive terminates $IFT, $IFF or $IFJ directives.
$RESET a where 'a' is any character in column 8. This directive resets the 'a' flag.
Examples of Conditional Compile: 1. $SET Z . . $IFT Z COMMENT Compile this if 'Z' is $SET; . . $END $IFF Z COMMENT Compile this if 'Z' is not $SET; . . $END 2. $SET 1 . . $IFF 0 X $IFF 1 X $IFT 2 Q $END X COMMENT Compile this if '0' or '1' or '2' is $SET; . . $END Q 3. $SET + $SET - . . $IFT + $IFT - COMMENT Compile this if both '+' and '-' are $SET; . . $IFJ X Skip over alternative. $END COMMENT Compile this if '+' and '-' are NOT both $SET; . . $END X
If listing is specified, each non-control record is listed as it is read. Source records in which errors are detected are always listed. Six columns of numbers appear at the left of each line. The first pair consists of the current internal program segment number (in decimal) followed by that program segment's relative address (in hexadecimal). The second pair consists of the current internal data segment number (in decimal) followed by that data segment's relative address (in hexadecimal). The fifth number is the reference number of the source record. The final number, the BEGIN/END level count, shows the excess of BEGIN symbols over END symbols at the beginning of the next line following an occurrence of BEGIN/END. This count is only printed when the BEGIN/END level changes. In addition, each page begins with a heading which includes the page number, date, time, and an optional title (see section E.1).
PL360 COMPILATION TITLE LINE ORVYL 07/04/76 PAGE 1 .. Program Segment Number : .. Relative Program Address : : .. Data Segment Number : : : .. Relative Data Address : : : : .. Reference Number : : : : : .. Block Level Number : : : : : : .. PL360 Program Source : : : : : : : 001 0000 000 0000 0001 GLOBAL PROCEDURE SORTWYL (R14) BASE R10; 014 0000 000 0000 0002 BEGIN BALR(R10,R0); |-- SET BASE --| 014 0002 000 0000 0003 01 R15 := 2; R10 := R10 - R15; 014 0008 000 0000 0004 BEGIN GLOBAL DATA SORTSPAC BASE R13; 014 000C 015 0000 0005 02 INTEGER HIGHCORE, PAGETAB, 014 000C 015 0008 0006 LOCATORS, LOCEND; 014 000C 015 0010 0007 ARRAY 2 SHORT INTEGER CONTROL; 014 000C 015 0014 0008 ARRAY 2 INTEGER TEMP = (0,0);
The PL360 compiler is designed to be used in conjunction with link/loader programs which resolve symbolic cross-references between the segments of one or more programs. Examples of programs capable of such resolution are the MTS loader, the IBM OS/VS linkage editor or loader, and the IBM DOS linkage editor. The remainder of this section uses the terminology of these programs.
The output of the PL360 compiler is a sequence of object modules. Each object module contains a single control section corresponding to a PL360 segment. It consists of 80-character records in the standard format of external symbol dictionary (ESD), text (TXT), relocation dictionary (RLD), and an end (END).
Every PL360 segment (except a dummy data segment) is associated with an object module in the following fashion:
In all cases, a control section has a single control section name and an entry point name which is identical. In the case of a PL360 program which is a block, a transfer address to the main entry point is provided in the END card of the object module for the implicit segment corresponding to this block. This transfer address is used by a loader to determine where to begin execution.
The task of the linkage editor/loader includes matching global, external, and common declarations, inserting absolute address constants, and completing tables of segment base addresses contained within each control section for a program segment in accordance with the external symbol dictionary and relocation dictionary generated by the compiler for that control section.
For PL360 programs which are blocks, control section names generated by the compiler for SEGMENT declarations are of the form SEGNnnn where nnn is the decimal internal segment number. If the PL360 program is a global procedure, the first three characters of the procedure identifier (extended on the right by NN if necessary) are used in place of the characters 'SEG' in naming control sections for SEGMENT declarations. These naming conventions may be overruled by use of the compiler directive $XYY# (see section E.5).
Each END card of the object module output by the compiler has the name 'PL360' followed by the date and time of compilation.
Additional information about linkage editor/loader processing of object decks is contained in Appendix G.
Although PL360 was designed for writing logically self-contained programs, it is possible to communicate with separately compiled programs if appropriate linkage and coding conventions are observed. These conventions are summarized below.
Addresses which correspond to external symbolic names and which are to be supplied by linkage editing can be specified by the external or common declarations of PL360. Entry to the block containing a data segment declaration causes the specified base register to be loaded with the corresponding address. External names appearing in procedure declarations are assumed to designate entry points to subroutines. In such declarations, the procedure body is normally the statement NULL. The call of the external procedure P2 from the procedure P1 is equivalent to the following 360 Assembler coding:
USING P1,Rp ... L Rc,=V(P2) DROP Rp BALR Ra,Rc USING *,Ra L Rp,=A(P1) USING P1,Rp DROP Ra
This linkage implies the following restrictions upon the called routine:
Any additional, non-conflicting conventions may be established by the programmer.
If the called procedure (P2) uses Rp to return information to the calling routine (P1), the procedure statement in P1 is usually of the form: P2(Rm), indicating that the return linkage must move the contents of Rp to Rm, thus setting the condition code before re-establishing the base address of P1 in Rp. The equivalent 360 Assembler coding for this type of call differs from that already given only in the last four lines which become:
LTR Rm,Rp BALR Rp,0 USING *,Rp L Rp,=A(P1) USING P1,Rp
OS type linkages are facilitated by the fact that if the calling PL360 program is a main block, the first 18 words of the implicit data segment (base register R13) are available for use as a save area (see section 4.2), and by the @@-operator which facilitates the construction of OS-type parameter lists at compile time.
SVC instructions are available in PL360 programs through the function statement. It should be noted, however, that in many operating systems the contents of R15 are destroyed by execution of some SVC instructions. In such cases, it is essential that saving and immediately restoring R15 be explicitly programmed. This tedious job of preserving the contents of the program base register can be avoided by using the $BASE compiler instruction (see section E.4), or by explicitly specifying a base register in the procedure heading (see sections 8.2 and 8.3).
Symbolic names and corresponding addresses to be made known to routines external to the PL360 program are specified by the global and common declarations of PL360. Global names specified in procedure declarations are associated with the corresponding procedure entry point. The external invocation of PL360 procedures must satisfy the following restrictions:
In addition, the following points should be noted:
(a) the symbolic name of the main program entry point will normally be SEGN001; the symbolic name of the implicit data segment (with base register R13) will normally be SEGN000 (see section E.5) (b) the return register for SEGN001 will be R14 (c) at entry to SEGN001, R13 must contain the address of an 18-word save area, if the $OS option is in effect (see section E.3) and R15 must contain the entry address even if a $BASE control card specifies a different base register (see section E.4) (d) at exit, all registers are restored from the save area, and the contents of R15 is set equal to zero (R15=0), if the $OS option is in effect (see section E.3)
Consider the following example:
GLOBAL PROCEDURE P1 (R1) BASE R7; The procedure P2 can be entered BEGIN GLOBAL DATA D1 BASE R10; with the base register for data INTEGER A; segment D1 incorrectly loaded, COMMON PROCEDURE P2 (R2); since it is possible to bypass BEGIN R0 := A; the entry code of the block con- END; taining the base declaration. In COMMON PROCEDURE P3 (R2); procedure P3, however, the exter- BEGIN EXTERNAL DATA D1 BASE R10; nal declaration causes correct R0 := A; register loading. In general, END; procedures which are to be R0 := 1 + A; entered independently should be END. declared as separate programs whenever possible.
It should be noted that the registers specified in corresponding global and external procedure declarations must be identical while the registers specified in corresponding global, external, and common data segment declarations may be different.
Also note that when common and external procedures are paired, return registers must be identical and any base register specified in the external declaration must match the base register of the global or segment procedure containing the common procedure declaration. Thus,
EXTERNAL PROCEDURE P2 (R2) BASE R7; NULL;
would be the proper declaration for P2 in a separately compiled segment linked with the above example.
The order in which object decks are output can be of critical importance at loading time. Object decks for each segment are output by the compiler at the time the segment is closed by CLOSE BASE or END. When multiple segments are ENDed at one time, the segments are output in decending order by assigned segment number (most recently declared segment is output first), except for segments 000 and 001 which are always output with 000 before 001.
When the loader or linkage editor reads the object decks, each END card is examined for a 'transfer address'. If none is found, then the program entry point is assumed to be the start of the first object deck read. If only one 'transfer address' is found, then it defines the program entry point. Any subsequent 'transfer address' definitions are ignored. Since global procedure programs do not define any 'transfer address', it is possible for execution to begin in a data segment! This would most likely cause an abnormal termination. To avoid such a circumstance, you would have to supply a loader or linkage editor control card which specifically defines the program entry point (an ENTRY control card). Any 'transfer address' determined by END cards is overruled by such a control card.
The loaders and linkage editors also have some interesting rules concerning multiply-defined segment or entry point names. If a segment is read whose name matches a segment or entry point name read from a previous object deck, then the new segment is completely bypassed, without an error indication. The following example should serve the demonstrate the concept.
BEGIN |-- start of Main program --| GLOBAL PROCEDURE A (R1) BASE R8; BEGIN |-- A is known to Main program --| GLOBAL PROCEDURE B (R2) BASE R9; BEGIN |-- B is known to A above --| COMMON PROCEDURE A (R3); BEGIN |-- A is redefined --| END; |-- end of Common A --| |-- statements of Global B --| A; |-- call to Common A --| END; |-- end of B, segment written --| |-- statements of Global A --| B; |-- call to Global B --| END; |-- end of A, segment written --| |-- statements of Main program --| A; |-- call to Global A --| END. |-- end of Main, segments written --|
The segments for the program above are output in the following order:
(1) Segment B with entry points B and A (defined by COMMON). (2) Segment A with entry point A, and external reference to B. (3) Segment SEGN000 (data segment) with entry point SEGN000. (4) Segment SEGN001 (Main program) with entry point SEGN001, and external reference to A. The code generated for the reference to A assumes the base and return registers of: GLOBAL PROCEDURE A (R1) BASE R8;
When the loader reads these object decks, it will bypass Segment A since Segment B was read first, and B already defined an entry point called A. The external reference to A from SEGN001 will be resolved by the COMMON A entry point, not the GLOBAL A segment. Since the code generated for COMMON A does not match the code generated in SEGN001 for the call to A (different A's), our program will probably terminate abnormally! Yet, there is no indication of error either from the PL360 compiler or from the loader. Clearly, one must be careful not to define a segment or entry point name more than once. GLOBAL, COMMON, and SEGMENT data and procedure declarations create segment or entry point names; but EXTERNAL data and procedure declarations do not create segment or entry point names: they only refer to such names defined elsewhere.
External declarations can also cause problems. For example:
BEGIN |-- start Main program --| EXTERNAL PROCEDURE OPEN (R14) BASE R15; NULL; GLOBAL PROCEDURE PUT (R5); BEGIN |-- user-defined PUT procedure --| END; |-- end of PUT, segment written --| |-- statements of Main program --| OPEN; |-- call to OPEN procedure --| |-- other statements of Main program --| PUT; |-- call to user's PUT routine --| |-- other statements of Main program --| END. |-- end of Main, segments written --|
When these object decks are loaded, an EXTERNAL reference to OPEN must be resolved by the Loader. Normally, this would be no problem since OPEN is defined in the PL360 runtime library. However, OPEN is one of many entry points in a single segment. Another entry point in that same segment is PUT. The loader will NOT load the segment containing the OPEN entry point since that segment also defines a PUT entry point, and PUT has already been defined by the programmer (GLOBAL PROCEDURE PUT). When loading is finished, we have an unresolved external reference for OPEN because a pre-declared external name (PUT) has been redefined by the programmer. Of course, if the programmer supplied an OPEN procedure, then the library would not be referenced, and the external reference could be resolved.
In general, do not define segment or entry point names more than once; and do not define segment or entry point names which match library names unless you truly intend to override the library. These problems involving loading of object decks are not restricted to PL360, but can occur with other languages as well.
Errors detected by the compiler are indicated by a message and a vertical bar (|) preceding the point where the error was detected. After about 50 errors, a message is provided, and further diagnostic messages are counted but not listed. The following is a list of error diagnostics and their meanings:
00 SYNTAX The source program violates the PL360 syntax. 01 VAR MIX TYPES The types of operands in a variable assignment are incompatible. 02 FOR PARAMETER In a FOR clause, the register is not an integer register, or the limit is not a register, cell, or number of the integer types. 03 REG ASS TYPES The types of the operands in a register assignment are incompatible. 04 BIN OP TYPES The types of operands of an arithmetic or logical operator are incompatible. 05 SHIFT OP A real instead of an integer register or number is specified in a shift operation. 06 COMPARE TYPES The types of operands in a comparison are incompatible. 07 REG TYPE OR # Either the type or the number of the register used is incorrect. 08 UNDEFINED ID An undeclared identifier has been referenced. The identifier is treated as if it were 'R1'. This may generate other errors. 09 MULT LAB DEF The same identifier is defined as a label more than once in the same block. 10 EXC INI VALUE The number of initializing values exceeds the the number of elements declared in an array, or a string attempts to initialize beyond the declared limits of a variable or array. 11 NOT INDEXABLE An index register is not allowed for the cell designator in this context. 12 DATA OVERFLOW The address of the declared variable in the data segment exceeds 4095. 13 NO OF ARGS An incorrect number of arguments is used for a function. 14 ILLEGAL CHAR An illegal character was encountered; it is skipped. 15 MULTIPLE ID The same identifier is declared more than once in the same block. This occurrence of the identifier is ignored. 16 PROGRAM OFLOW The current program segment is too large. It must be resegmented. 17 INITIAL OFLOW The area of initializing data in the compiler is full. This can usually be circumvented by suitable data segmentation or by re-ordering initialized data within the segment. 18 ADDRESS OFLOW The number used as index is such that the resulting relative address is less than 0 or greater than 4095. l9 NUMBER OFLOW The integer number is too large in magnitude. 20 MISSING . An end-of-file is encountered before a '.' terminating the program. The problem may be a missing string quote. 21 STRING LENGTH The length of a string is either 0 or greater than 256. 22 NULL CASE ST. A CASE statement did not contain any additional statements. 23 FUNC DEF NO. The format number in a function declaration is illegal. 24 ILLEGAL PARAM A parameter is incompatible with the specifi- cations of the function. 25 NUMBER A number has been used that has an illegal type or value. 26 SYN MIX Synonym declarations cannot mix cell and register declarations, or T-cell designators have different base registers. 27 SEG NO OFLOW The maximum allowed segment number has been exceeded. The limit is generally set at 255. 28 ILLEGAL CLOSE A segment close declaration is encountered when no data segment is open in the corresponding block head. 29 NO DATA SEG A variable is declared with no open data segment. A dummy data segment is opened. 30 ILLEGAL INIT Initialization is specified in a common data segment or replicates an absolute address. 31 GET MORE CORE There is insufficient core to compile.
At the end of each program segment, all occurrences of undefined labels are listed with an indication of where they were referenced.
This appendix contains the precedence grammar used to define the PL360 programming language. The grammar is in the form of Bacus-Naur notation where <X> represents 'the syntactic entity X', ::= represents 'is replaced by', and | represents alternative replacements. Thus, <X> ::= <X> <Y> | <Y> is read as follows: "The syntactic entity X is replaced by the syntactic entity X followed by the syntactic entity Y, or the syntactic entity X is replaced by only the syntactic entity Y." This is a recursive grammar in that some syntactic entities are defined in terms of themselves. In such cases, one of the alternatives not involving the syntactic entity being defined must be satisfied before alternatives involving the syntactic entity can be satisfied. In the example given, if <Y> were defined as: <Y> ::= 0 | 1 then <X> would be the definition of a binary number of arbitrary length.
Terms not enclosed in brackets <> are called 'terminal symbols'. Such symbols are to be taken literally (as written). The only special definition in this grammar is that for <CHAR1> where the first alternative would actually be 255 alternatives representing all 256 characters (#00X through #FFX) except for the quote character (").
<ADR OP> ::= @@ | @ <ALPHA> ::= A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z <ALPHA NUM> ::= <ALPHA> | <DIGIT> <ARITH OP> ::= -- | ++ | - | + | * | / | =: <ASS STEP> ::= <K REG ASS> STEP <T NUMBER> <BLOCKBODY> ::= <BLOCKBODY> <LABEL DEF> | <BLOCKBODY> <STATEMENT> ; | <BLOCKHEAD> <BLOCKHEAD> ::= <BLOCKHEAD> <DECL> ; | BEGIN <BRINDX OP> ::= GT | LE <BYTE NUM> ::= <INTEGER> X <CASE HEAD> ::= <CASE HEAD> <DECL> ; | CASE <K REG> OF BEGIN <CASE SEQ> ::= <CASE HEAD> | <CASE SEQ> <LABEL DEF> | <CASE SEQ> <STATEMENT> ; <CELL ST> ::= <CELL ST> <LOG OP> <STRING> | <CELL ST> <LOG OP> <T CELL> | <CELL ST> <LOG OP> <T NUMBER> | <T CELL> := <K REG> | <T CELL> := <STRING> | <T CELL> := <T CELL> | <T CELL> := <T NUMBER> <CHARS> ::= <CHARS> <CHAR1> | <CHAR1> <CHAR1> ::= Any character except " | "" <COMP AOR> ::= <COMP AOR> <STATEMENT> ; | <COMP COND> AND | <COMP COND> OR <COMP COND> ::= <COMP AOR> <COND> | <CONDITION> <COND DO> ::= <COMP COND> DO <COND THEN> ::= <COMP COND> THEN <COND> ::= <CONDITION> <CONDITION> ::= <K REG> <BRINDX OP> <K REG> | <K REG> <REL OP> <K REG> | <K REG> <REL OP> <STRING> | <K REG> <REL OP> <T CELL> | <K REG> <REL OP> <T NUMBER> | <REL OP> | <RP> <PRIM COND> ) | <T CELL> | <T CELL> <REL OP> <STRING> | <T CELL> <REL OP> <T CELL> | <T CELL> <REL OP> <T NUMBER> | <T NUMBER> | <UNARY REG> | ~ <T CELL> | ~ <T NUMBER> | ~ <UNARY REG> <DECL> ::= <DSEG TYPE> BASE <K REG> | <EQU SYN2> | <FUNC DC7> | <PROC HD7> <STATEMENT*> | <PROC SYN> <PROC ID> | <SYN DC2> | <T DECL4> | CLOSE BASE <DIGIT> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 <DO> ::= DO <DSEG TYPE> ::= COMMON | COMMON DATA <ID> | DUMMY | EXTERNAL DATA <ID> | GLOBAL DATA <ID> | SEGMENT <EQU SYN1> ::= <EQUATE> <ID> SYN <EQU SYN2> ::= <EQU SYN1> <K REG> | <EQU SYN1> <STRING> | <EQU SYN1> <T NUMBER> | <EQU SYN1> <UNARY NUM> | <EQU SYN2> <ARITH OP> <STRING> | <EQU SYN2> <ARITH OP> <T NUMBER> | <EQU SYN2> <LOG OP> <STRING> | <EQU SYN2> <LOG OP> <T NUMBER> | <EQU SYN2> <SHIFT OP> <T NUMBER> | <EQU SYN3> <ARITH OP> <T CELL> <EQU SYN3> ::= <EQU SYN1> <T CELL> <EQUATE> ::= <EQU SYN2> , | EQUATE | SHORT EQUATE <FILL> ::= <ADR OP> <PROC ID> | <ADR OP> <T CELL> | <REP LIST2> ) | <STRING> | <T NUMBER> <FRAC NUM> ::= <FRAC NUM> <DIGIT> | <INTEGER> . <FORUNTIL> ::= UNTIL <FUNC DC1> ::= <FUNC DC7> , | FUNCTION <FUNC DC2> ::= <FUNC DC1> <ID> <FUNC DC3> ::= <FUNC DC2> ( <FUNC DC4> ::= <FUNC DC3> <T NUMBER> <FUNC DC5> ::= <FUNC DC4> , <FUNC DC6> ::= <FUNC DC5> <T NUMBER> <FUNC DC7> ::= <FUNC DC6> ) <FUNC ID> ::= <ID> <FUNC1> ::= <FUNC2> <FUNC3> | <FUNC2> <K REG> | <FUNC2> <STRING> | <FUNC2> <T CELL> | <FUNC2> <T NUMBER> <FUNC2> ::= <FUNC ID> ( | <FUNC1> , <FUNC3> ::= <FUNC1> ) <GLOB HD> ::= <GLOB HD1> | <GLOB HD1> BASE <K REG> <GLOB HD1> ::= GLOBAL <PROC HD4> <GOTO ST*> ::= GOTO <ID> <GOTO ST> ::= <GOTO ST*> <HEX DIGIT> ::= <DIGIT> | A | B | C | D | E | F <HEX VAL> ::= <HEX VAL> <HEX DIGIT> | # <HEX DIGIT> <ID> ::= <ID> <ALPHA NUM> | <ALPHA> <IF> ::= <IF> <STATEMENT> ; | IF <INT NUM> ::= <HEX VAL> | <INTEGER> <INTEGER> ::= <INTEGER> <DIGIT> | <DIGIT> <K REG ASS> ::= <K REG ASS> <ARITH OP> <K REG> | <K REG ASS> <ARITH OP> <STRING> | <K REG ASS> <ARITH OP> <T CELL> | <K REG ASS> <ARITH OP> <T NUMBER> | <K REG ASS> <LOG OP> <K REG> | <K REG ASS> <LOG OP> <STRING> | <K REG ASS> <LOG OP> <T CELL> | <K REG ASS> <LOG OP> <T NUMBER> | <K REG ASS> <SHIFT OP> <K REG> | <K REG ASS> <SHIFT OP> <T NUMBER> | <K REG> | <K REG> := <ADR OP> <PROC ID> | <K REG> := <ADR OP> <T CELL> | <K REG> := <K REG> | <K REG> := <STRING> | <K REG> := <T CELL> | <K REG> := <T NUMBER> | <K REG> := <UNARY CELL> | <K REG> := <UNARY NUM> | <K REG> := <UNARY REG> <K REG> ::= <ID> <LABEL DEF> ::= <ID> : <LIMIT> ::= <FORUNTIL> <K REG> | <FORUNTIL> <T CELL> | <FORUNTIL> <T NUMBER> <LOG OP> ::= AND | OR | XOR <LONG NUM> ::= <INT NUM> L | <FRAC NUM> ' <SCALE> L | <INTEGER> ' <SCALE> L | <FRAC NUM> L <NUMBER> ::= <LONG NUM> | <REAL NUM> | <SHORT NUM> | <BYTE NUM> | <INT NUM> <PRIM COND> ::= <COMP COND> <PROC HD1> ::= PROCEDURE <ID> <PROC HD2> ::= <PROC HD1> ( <PROC HD3> ::= <PROC HD2> <K REG> <PROC HD4> ::= <PROC HD3> ) <PROC HD5> ::= EXTERNAL <PROC HD4> | GLOBAL <PROC HD4> | SEGMENT <PROC HD4> <PROC HD6> ::= <PROC HD4> | <PROC HD5> | <PROC HD5> BASE <K REG> | COMMON <PROC HD4> <PROC HD7> ::= <PROC HD6> ; <PROC ID> ::= <ID> <PROC SYN> ::= PROCEDURE <ID> SYN <PROC1> ::= <PROC ID> ( <PROC2> ::= <PROC1> <K REG> <PROGRAM*> ::= <PROGRAM+> <STATEMENT*> <PROGRAM+> ::= . | . <GLOB HD> ; <PROGRAM> ::= <PROGRAM*> . <REAL NUM> ::= <INT NUM> R | <FRAC NUM> ' <SCALE> | <INTEGER> ' <SCALE> | <FRAC NUM> <REL OP> ::= = | ~= | >= | <= | > | < <REP LIST1> ::= <REP LIST2> , | <T NUMBER> ( | ( <REP LIST2> ::= <REP LIST1> <FILL> <SCALE> ::= _ <INTEGER> | <INTEGER> <SHIFT OP> ::= SHLA | SHLL | SHRA | SHRL <SHORT NUM> ::= <INT NUM> S <REPEAT> ::= <REPEAT> <LABEL DEF> | <REPEAT> <STATEMENT> ; | REPEAT <REPUNTIL> ::= UNTIL <RP> ::= <RP> <STATEMENT> ; | ( <SI T TYPE> ::= BYTE | CHARACTER | INTEGER | LOGICAL | LONG REAL | REAL | SHORT INTEGER <SIMPLE ST> ::= <BLOCKBODY> END | <CASE SEQ> END | <CELL ST> | <FUNC ID> | <FUNC3> | <K REG ASS> | <PROC ID> | <PROC2> ) | NULL <STATEMENT*> ::= <GOTO ST> | <STATEMENT+> <STATEMENT+> ::= <IF> <COND THEN> <GOTO ST> | <IF> <COND THEN> <STATEMENT+> | <IF> <COND THEN> <TRUE PART> <GOTO ST> | <IF> <COND THEN> <TRUE PART> <STATEMENT+> | <REPEAT> <REPUNTIL> <PRIM COND> | <SIMPLE ST> | <WHILE> <COND DO> <STATEMENT*> | FOR <ASS STEP> <LIMIT> <DO> <STATEMENT*> <STATEMENT> ::= <STATEMENT*> <STRING> ::= <HEX VAL> X | " <CHARS> " <SYN DC1> ::= <SI T TYPE> REGISTER <ID> SYN | <SYN DC3> <ID> SYN | <T TYPE> <ID> SYN <SYN DC2> ::= <SYN DC1> <K REG> | <SYN DC1> <T CELL> | <SYN DC1> <T NUMBER> <SYN DC3> ::= <SYN DC2> , <T CELL ID> ::= <ID> <T CELL> ::= <T CELL ID> | <T CELL1> ) | <T CELL2> ) <T CELL1> ::= <T CELL1> <ARITH OP> <STRING> | <T CELL1> <ARITH OP> <T NUMBER> | <T CELL2> <ARITH OP> <K REG> | <T CELL2> <ARITH OP> <T NUMBER> | <T CELL3> <STRING> | <T CELL3> <T NUMBER> <T CELL2> ::= <T CELL3> <K REG> <T CELL3> ::= <T CELL ID> ( <T DECL1> ::= <T DECL2> | <T DECL2> <ID> | <T TYPE> | <T TYPE> <ID> <T DECL2> ::= <T DECL4> , <T DECL3> ::= <T DECL1> = <T DECL4> ::= <T DECL1> | <T DECL3> <FILL> <T NUMBER> ::= <ID> | _ <NUMBER> | <NUMBER> <T TYPE> ::= <SI T TYPE> | ARRAY <T NUMBER> <SI T TYPE> <TRUE PART> ::= <GOTO ST*> ELSE | <SIMPLE ST> ELSE <UNARY CELL> ::= <UNARY OP> <T CELL> | <UNARY OP> <UNARY CELL> <UNARY NUM> ::= <UNARY OP> <T NUMBER> | <UNARY OP> <UNARY NUM> <UNARY OP> ::= ABS | DEC | HALF | NEG <UNARY REG> ::= <UNARY OP> <K REG> | <UNARY OP> <UNARY REG> <WHILE> ::= <WHILE> <STATEMENT> ; | WHILE
The remainder of this appendix contains a summary of all the PL360 declarations, statements, and definitions in general form. The heading on each category indicates the section numbers where the category is discussed (cross-reference to table of contents).
Blocks: 3.4 BEGIN declarations; statements; labels: END Data Segment Declarations: 4.1 COMMON DATA name BASE Rn; COMMON BASE Rn; SEGMENT BASE Rn; GLOBAL DATA name BASE Rn; EXTERNAL DATA name BASE Rn; DUMMY BASE Rn; CLOSE BASE; Cell Declarations: 4.3 |-- Type = BYTE, SHORT INTEGER, INTEGER, REAL, LONG REAL --| Type id1, id2, ... , idn = (initialization); ARRAY n Type id1, id2, ... , idn; Cell Synonym Declarations: 4.5 |-- Type = BYTE, SHORT INTEGER, INTEGER, REAL, LONG REAL --| Type id1 SYN cell1, id2 SYN cell2, ... , idn SYN celln; Register Synonym Declarations: 4.6 |-- Type = INTEGER, REAL, LONG REAL --| Type REGISTER id1 SYN reg1, id2 SYN reg2, ... , idn SYN regn; Integer Value Synonym Declarations: 4.7 EQUATE id1 SYN exp1, id2 SYN exp2, ... , idn SYN expn; Register Assignment Statements: 5.1 register := term1 op term2 ... op termn Cell Assignment Statements: 5.2 cell := term1 op term2 ... op termn |-- unindexed cell --| cell := register |-- cell may be indexed --| GOTO Statements: 6.1 GOTO label Label Definitions: 6.1 label: IF Statements: 6.3 IF conditions THEN statement; IF conditions THEN statement ELSE statement; WHILE Statements: 6.4 WHILE conditions DO statement; REPEAT/UNTIL Statements: 6.5 REPEAT statement; ... label: ... UNTIL conditions; FOR Statements: 6.6 FOR Rn := expression STEP increment UNTIL limit DO statement; CASE Statements: 6.7 CASE Rn OF BEGIN statement; | 1st subordinate | statement; | 2nd subordinate | : : : : statement; | i-th subordinate | : : : : statement; | m-th subordinate | END Function Declarations: 7.1 |-- type = 0 - 15, code = #nnnn --| FUNCTION id1(type,code), id2( ... ); Function Statements: 7.2 id |-- no parameters --| id(p1) |-- 1 parameter --| id(p1,p2) |-- 2 parameters --| id(p1,p2,p3) |-- 3 parameters --| id(p1,p2,p3,p4) |-- 4 parameters --| NULL statements: 6.3, 8.3 NULL Procedure Declarations: 8.1, 8.2, 8.3, 8.5 PROCEDURE name (Ra); statement; | Simple procedure | COMMON PROCEDURE name (Ra); statement; | COMMON procedure | SEGMENT PROCEDURE name (Ra); statement; | SEGMENT procedure | SEGMENT PROCEDURE name (Ra) BASE Rp; statement; GLOBAL PROCEDURE name (Ra); statement; | GLOBAL procedure | GLOBAL PROCEDURE name (Ra) BASE Rp; statement; PROCEDURE name SYN prevname; | SYNonym procedure | Procedure Statements: 8.1, 8.2 name name (Rm) Programs: Chapter 3 and Chapter 8 BEGIN declarations; statements; labels: END. GLOBAL PROCEDURE name (Ra); statement. | GLOBAL program | GLOBAL PROCEDURE name (Ra) BASE Rp; statement.
During the development of the ALGOL-W compiler, Nicklus Wirth proposed a set of guidelines for producing uniformly readable PL360 programs. These rules with some minor modifications were reprinted in "PL360, A Language for the IBM 360" by Michael Malcolm of the Computer Science Department at Stanford University. I have elected to include these rules here because they have proven helpful and effective in both programming and debugging. These rules are only 'guidelines', not strict rules to be followed under any circumstances.
(a) Indent lines contained between BEGIN and END by three spaces: BEGIN ... R1 := R2; ... BEGIN ... PAGE; R0 := @LINE; ... END; R6 := R5; END (b) Do not indent after IF, FOR, WHILE clauses, but reserve a separate line for the clause, if it is followed by a lengthy statement: FOR R1 := 1 STEP 1 UNTIL 100 DO BEGIN ... ... END; However: IF R0 = 1 THEN R1 := R1 + 1; (c) In the case of IF-THEN-ELSE, the statements following THEN and ELSE should be shown to be of equal 'importance'; that is: IF R0 = 0 THEN R1 := 1 ELSE R2 := R1; or IF R0 = 0 THEN R1 := R1 * R2 + R3 ELSE R1 := R1 * R3 + R2; or IF R0 = 0 THEN BEGIN ... ... END ELSE BEGIN ... ... END; (d) A program usually contains a few very large blocks, each being more than a page long. In such cases, indentation does not make sense because the reader cannot see that the page being read is related to preceding or following pages. It is preferable to accompany the BEGIN and END of such major blocks with a comment linking them together with a common name or number.
Spacing is a powerful tool in grouping things together which should be read together, and to display the structure of a statement or declaration. If spaces are used in the same amount everywhere, they are useless and may as well be omitted with the benefit of saving paper. For example:
R1 := TEMP / 4 + SIAB9 * C ; TEMP := R1 ;
is as bad as:
The following rules may seem a bit absurd, but have proven useful:
R1 := X(R2+3) / Y(4) + R1; or R1 := TEMP / 4 + SIAB9 * C; TEMP := R1; However: R1 := R1--R1; R2 := R2+R3;
INTEGER FLAG; BYTE FULLWORD;
INTEGER X SYN B3(R2); It is hard to realize that the statement: R1 := X uses R2 as an index register!
This Appendix contains a brief narrative description of how one uses the interactive version of PL360 which runs under the ORVYL time-sharing monitor. This version is made possible through a special ORVYL-PL360 interface module written in Assembly Language using the ORVYL macro instructions.
This Appendix assumes that the ORVYL system is being used at Stanford where the ORVYL-PL360 compiler is saved as an ORVYL loadfile. To use it, just type:
? CALL PL360
You will then receive the message:
-WELCOME TO PL360 DECK?
If your account has been activated for ORVYL files, then you can type "YES" and PL360 will respond with:
You should then type the name of an ORVYL file in which PL360 should place the object modules from the subsequent compilation. This file can be either new or old. Appending " SCR" to the file name will cause an old file to be scratched for reuse; otherwise, you will be prompted:
A "NO" response will cause the file naming process to be repeated. The next thing PL360 asks is:
If you respond "YES", then you will again be asked to supply an ORVYL file to receive the PL360 Compiler list output. PL360 then asks:
You can now type WYLBUR commands which will be passed to and executed by WYLBUR. You can continue to pass commands to WYLBUR (for example, collect lines, edit lines, use files, copy files, etc.) until your WYLBUR working data set contains the PL360 program(s). You then type "COMPILE" immediately after a -? prompt and PL360 will begin compiling the program(s) contained in your WYLBUR working data set.
Any error messages and the line on which they occur are typed at the terminal as the compilation proceeds. Each time a segment is closed a message is typed at the terminal. When compiling from a WYLBUR working data set, the compiler terminates at the end of the data set and types:
You can type in a program directly by responding "COMPILE X" to a -? prompt, where X represents any non-blank character. PL360 responds:
BEGIN TYPING PL360 PROGRAM -?
You can now type in a PL360 program and each line will be compiled as you go. Unfortunately, if you make a mistake, you must start over since the old lines are not saved. Also, leading blanks are stripped from each input line. For these reasons, it is usually best to compile from a WYLBUR working data set. When typing the program in directly, you can leave PL360 at any time by typing "/*" or by simply hitting the ATTN button at the terminal.
As you are leaving PL360, ORVYL core memory is automatically cleared. The WYLBUR working data set is not cleared. If the program you are compiling has numerous errors and you wish to suppress the typing of error messages at the terminal, then simply hit the ATTN button at the terminal (except in response to a prompt). PL360 will then ask:
DO YOU WANT FURTHER ERROR MESSAGES TYPED?
A "NO" will cause the compilation to continue with no further error messages typed at the terminal. A "YES" will cause compilation to continue as before. In either case, the listing produced in the ORVYL file (if any) will be unaffected.
After leaving PL360, you can retrieve the object deck by typing:
GET <file name> CARD CLEAR
You can retrieve the listing by typing:
GET <file name> PRINT CLEAR
The listing has 133-byte records, the first byte of which is a carriage control character. Thus, when the listing is printed offline, the following WYLBUR command should be used:
PRINT UNN CC
The CC part of the PRINT command causes the first byte to be treated as a carriage control character. The resulting line printer listing looks like a batch PL360 compilation listing. The ORVYL version of PL360 has several advantages: Waiting for the batch queue is completely eliminated. Errors are printed at the terminal, and thus can usually be fixed immediately and another compilation can be made in a minute or two. Paper is saved since listings with errors are seldom listed offline. Finally, the ORVYL version of the runtime library can be used to run and test the program immediately at the terminal. In this way, ORVYL's debugging tools can be used and debugging takes far less time. Most short compilations can be done in about a second or two of ORVYL compute time. This is a significant savings over batch compilations.
The standard input-output subroutines using the same linkage conventions as the READ and WRITE subroutines described in Section 8.4 are available for input-output operations directly at the terminal when running a PL360 program under the ORVYL monitor. A description of the parameter passing conventions of these subroutines follows:
READ The address of a 132 byte input area should be provided in R0 prior to calling READ. Upon return, all registers are preserved except R15 which contains the number of non-blank characters typed by the user (counting imbedded blanks). All details such as error messages for illegal use of tabs or waiting too long to respond are taken care of by the READ subroutine. If the attention key is typed with no preceding characters, the condition code is set to 2, otherwise it is set to 0. . WRITE This subroutine works exactly like the subroutine described in Section 8.4; i.e., the address of a 132 byte output area is passed through register R0 and all registers are preserved upon return. The output area is typed at the terminal.
The following discussion assumes that the ORVYL system is being used at Stanford where the ORVYL READ and WRITE subroutines and the library subroutines listed in Section 8.4 are stored in object module form in the ORVYL file ORV.GG.PUB.PL360.RUNLIB. To run a PL360 program in ORVYL, just follow this simple process. First, compile the program. This may be achieved either in batch or with the ORVYL version of the PL360 compiler. The program must be a statement with segment name SEGN001 (cf. Section 4). Place the object module output of the PL360 compiler into an ORVYL file, if not already compiled directly as the DECK output using the ORVYL version of the PL360 compiler. Place in your active file something like this:
INCLUDE ORV.GG.PUB.PL360.RUNLIB INCLUDE <YourObjectFileName> ENTRY PL360 LOADFILE <YourLoadFileName> NAME <YourProgamName>
Issue the "LINK ACTIVE" command, and <YourProgramName> will be created in <YourLoadFileName> as a member of that loadfile library.
Your program is now ready to be executed.
CALL <YourProgramName> LIB <YourLoadFileName>
1. 1FFFFFFF 2. (a) 510030 (b) 72552 (c) 536200 (d) 509950 (e) 100 3. AND OR XOR 0F/0F 0F 0F 00 0F/F0 00 FF FF 0F/18 08 1F 17 F0/F0 F0 F0 00 F0/18 10 F8 E8 18/18 18 18 00 4. (a) 41880000 because 0.5 = 8/16; so 8.0 + 8/16 = 8.5 (b) 24.0 because 1x16 + 8 = 24 (c) 128.0 because 8x16 + 0 = 128
1. (b) Begins with a digit (c) Contains illegal character (period) (d) Contains lowercase characters (g) Definition of a constant 2. (b) Contains illegal characters (commas) (d) Must begin with a digit, such as 0.2L (f) Contains an imbedded blank (g) Definition of an identifier 3. (a) STRING (one byte) (c) STRING (19 bytes) (e) LONG REAL (h) SHORT INTEGER
1. |01| and |23| |15| and |22| |18| and |19| 2. |08| and |10| 3. (a) ARRAY, BEGIN, BYTE, COMMENT, DO, END, FUNCTION, SYN, WHILE (b) BCDTOVAL, READ, R0, R1, R2, R3, R5, VALTOBCD, WRITE (c) ANSWER, CARD, INPUT, OUTPUT, REDUCE
1. (a) ALPHA line |18| BETA line |07| DELTA line |16| (b) ALPHA - TEST, TOPS, EXTRA BETA - EXAM, FLAGS DELTA - LAST (c) group 1 - TEST, EXAM group 2 - TEST, EXAM, FLAGS, TOPS group 3 - TEST, EXAM group 4 - TEST, EXAM, EXTRA, LAST group 5 - TEST, EXAM 2. (a) LINE at line |01| takes 133 bytes. XX at line |02| takes 1024 bytes (256*4). SIZE at line |03| takes 4 bytes. LIMIT at line |03| takes 4 bytes. NSQR at line |05| takes 2 bytes. (b) Lines |06| and |07| (c) No, because the synonyms are declared within a block which ends at line |21|. All declared quantities within a block are forgotten when the block ends.
1. (a) Can't shift floating-point register. (b) Can't do logical operation with SHORT INTEGER operand. (c) Requires an odd-numbered assignment register. (d) Arithmetic operation invalid in cell assignment. (e) Correct! (f) Monadic and special operators must follow := operator only. (g) Requires an odd-numbered assignment register. (h) Final displacement of B2(_4) is negative. 2. (e) In a cell assignment statement, a register operand may only follow the := operator, as in (e) of problem 1. 3. R1 := #0110; |Notice that the original contents| R2 := #1100; |of R1 and R2 are reversed when we| R1 := #1010; |finish the sequence of statements| 4. (a) IJ := I * 16S + J * 4S; |-- Note that INTEGER constants can't be used because IJ is an even-numbered register --| (b) IJ=144 when I=2 and J=4 IJ=204 when I=3 and J=3 IJ=260 when I=4 and J=1 (c) I=1 and J=1, because (1*16+1)*4 = 68
1. (a) R1 <= R2 (b) F01 < 0L (c) X = Y (d) ~OVERFLOW 2. Yes. |2| with |5| |3| with |4| |4| with |3| 3. No, all GOTO's refer to the same X at line |2|. 4. (a) R1 = 0 (for s1) (b) R1 < 0 (for s2) (c) R1 > 0 (for s3) 5. (a) either R1 > R2 and R4 > R5 or R1 <= R2 and R2 > R3 and R4 > R5 (b) either R1 > R2 and R4 <= R5 or R1 <= R2 and R2 > R3 and R4 <= R5 (c) only R1 <= R2 and R2 <= R3 6. (a) MID := 14 SHLL 1 + R1; |-- MID = 28 + R1 --| (b) five times (c) LO + HI SHRA 1 may result in an address which is not an even multiple of the TABLE size (four bytes). For example, R1 = #07000 and R2 = #07004 will yield #07002 which must be rounded down to #07000.
1. (a) PAUSE (b) SETZONE(C) (c) STCK(C) (d) AP(N1,N2,C,CL) 2. True for all 3. (a) "& -*" (b) VALUES(76) := R4 or X(#5C) := R4 (c) #000001346396252CX (d) Yes; CVB yields #4CE9DD; UNPK yields "000000000504060E"
One possible answer is the last sample program in Chapter 9.
Eve, James. "PL360 Language Extensions." Newcastle upon Tyne, England: University of Newcastle upon Tyne, Computing Laboratory.
"IBM OS/VS Assembler Language." IBM GC33-4010.
"IBM OS/VS JCL Reference Manual." IBM G328-0618.
"IBM System/370 Principles of Operation." IBM GA22-7000.
"IBM System/370 Reference Data Card." IBM GX20-1850.
"IBM System/360 Linkage Editor and Loader Reference Card." IBM GX20-1739.
"IBM System/360 OS Assembler Language." IBM GC28-6514.
"IBM System/360 Principles of Operation." IBM GA22-6821.
"IBM System/360 Reference Data Card." IBM GX20-1703.
Kerningham, Brian, and P. J. Plauger. The Elements of Programming Style. New York: McGraw-Hill, 1974.
Michigan Terminal System (MTS). Vol. 1, rev. ed. Ann Arbor, Mich.: University of Michigan Computation Center, April 1976.
"ORVYL User's Guide." Stanford, Calif.: Stanford University, Center for Information Processing, Academic Computing Services, May 1975.
"PL360 Programming Manual." Newcastle upon Tyne, England: University of Newcastle upon Tyne, Computing Laboratory, 1970.
"PL360 Reference Manual." Stanford, Calif.: Stanford University, Center for Information Processing, Academic Computing Services, July 1975.
Wirth, Nicklus. "PL360, a Programming Language for the 360 Computers." Journal of American Computing Machinery (JACM) 15 (1968): 37. Describes the original PL360 language in Bacus-Naur syntactic form.
Wirth, Nicklus. "Format of PL360 Programs." ALGOL-W project memo. In Michael A. Malcolm, "PL360, a Programming Language for the IBM 360." Stanford, Calif.: Stanford University, Computer Science Department, May 1971. STAN-CS-71-215.