The following is a series of programming “tricks” that I have acquired or developed throughout my experience on the UNIVAC 1108. I hope that they will prove to be worthwhile here and there to others. Many of them have been contributed in response to earlier editions of this document. If any one has any similar contributions to make to this collection, we might be able to turn this into a regular publication which I, at least initially, would be glad to compile. Send them to:
Walter Gilbert
Obsolete address elided by editor.
Do it today and you'll feel better for it tomorrow.
Note: save this copy because the next edition (4) will not include anything that has been in the previous 3 editions: three “strikes” and out.
This is probably one of the most generally useful item herein contained and also one of the most obvious (once you have seen it). When manipulating a program file frequently in a run, especially from demand, it can be very convenient to attach the name “TPF$” to it so that it becomes your default file.
@USE TPF$,PROGRAM*FILE.
Many of the advantages are obvious. This technique can be particularly useful for MAP'ing because you can write your maps so that they work in any file by not having to point them at your specific program file. John Walker of ISD (Calif) has found a very good reason for @FREE'ing your original TPF$ before attaching the name to another file. It will only happen in an unusual sequence of events but it would do weird and wonderful things to both files.
(Corr. Ed. 2)
If you dislike writing ER's to COM$ because of the nuisance involved, here is a little PROC which allows you to do it all in one line. It generates the call in line, and the three-word packet and literal, if given, under the LIT counter. Examples:
C$$OM 12,('CONSOLE MSG.'). C$$OM 22,MSG1. C$$OM 6,MSG2,10,ANSWER.
The PROC:
P PROC 1,2. C$$OM* NAME. L,U A0,(($))+((P(1,1),P(1,2))); -((P(1,3),P(1,4)+(P(1,3)=0)*($+1)))+1. ER COM$. END.
Here is a trick that is useful in multiactivity programs where there is a possibility that an activity may become locked in a test-and-set loop. This is particularly valuable while debugging or if a test-and-set word is being shared between realtime and non-realtime activities. (What can happen is that if an RT activity hits a test-and-set word set by a non-realtime activity, the non-RT activity will never regain control again to clear the lock and your system will suddenly go into a blank stare.) The concept is instead of directly executing the TS instruction, execute it remotely via the “EX” instruction using index incrementation. If the lock is set, the interrupt occurs and the Exec requeues the activity at the “EX” instruction. However, when control is regained, the “EX” is no longer pointing at the “TS” so control can be recaptured by the program. The following is an example of how this could be used.
. . . . . . L,U A0,TSWORD. POINT A0 AT A TS WORD LMJ X11,TANDS. GO DO A TEST AND SET . . . . . . . . . . . . . THE TEST AND SET SUBROUTINE . TS TS 0,A0. EX POINTS HERE 1ST TIME JGD A2,TS2. EX POINTS HERE 2ND TIME TANDS* L,U A2,11. TEST THE LOCK 12 TIMES TS2 L A1,(1,0). EX TS,*A1. EX EITHER TS OR JGD JP A2,0,X11. JUMP IF LOCK PASSED . CODE GOES HERE TO DO . SOMETHING ABOUT THE . LOCK WHICH HAS BEEN HIT . 12 TIMES IN A ROW.
By use of the two DEFINE statements given below, FORTRAN can be convinced to address characters packed six per words as if each were in a word by itself. It makes character farbling almost nice.
INTEGER WORDS( ... ) DEFINE WWW(I) = WORDS(I) DEFINE CHARS(I) = FLD(6*MOD(I+5,6),6,WWW((I+5)/6))
There two DEFINEs, the first solely to pacify FORTRAN, enable you to use the CHAR(...) like any other dimensioned variable (almost) with the effect being
CHARS(1) => FLD( 0,6,WORDS(1)) CHARS(2) => FLD( 6,6,WORDS(1)) CHARS(3) => FLD(12,6,WORDS(1)) . . . . CHARS(6) => FLD(30,6,WORDS(1)) CHARS(7) => FLD( 0,6,WORDS(2)) . . . . ETC.
Example:
5 READ(5,10,END=999)(WORDS(I),I=1,12)) 10 FORMAT(12A6) DO 20 M=1,72 @FIND 1ST NONBLANK IN LINE 20 IF(CHARS(M).NE.1R ) GO TO 30 GO TO 5 @(BLANK LINE) 30 IF(M.EQ.1) GO TO 60 DO 40 I=M,72 @LEFT JUSTIFY THE LINE 40 CHARS(I-M+1) = CHARS(I) DO 50 I=2,M @SPACE FILL THE END 50 CHARS(74-I) = 1R 60 ...
This method can be adapted to other subdivisions of words besides characters, vis:
Quarterwords:
INTEGER WORDS( ... ) WWW(I) = WORDS(I) QTR(I) = FLD(9*MOD(I+3,4),9,WWW((I+3)/4))
Bits:
INTEGER WORDS( ... ) WWW(I) = WORDS(I) BITS(I) = FLD(MOD(I+35,36),1,WWW((I+35)/36))
Note that this algorithm has been changed from that in edition one because it would fail if all six digits were nines (due to the old +0 −0 problem of one's complement).
How many times have you struggled over the problem of determining whether or not all six FIELDATA characters in a word are numerals. Never again. With the following five instructions, all six can be validated in one deft stroke. Try it.
LN A0,<WORD>. THE WORD TO CHECK ANU A0,(060606060606). OR A0,A1. AND A1,(0606060606060). JNZ A2,<ERROR>. JUMP IF ANY NON-NUMERIC
The proof is left as an exercise for the reader. By changing the masks, any subset of characters in the word can be validated to be numeric. Also, other contiguous series of characters can be checked in a similar manner. The following will validate that the word being tested contains only FIELDATA letters of the alphabet and no others.
L A0,<WORD>. ANU A0,(060606060606). OR A0,A1. AND A1,(0404040404040). JNZ A2,<ERROR>.
The numeric test can be written in FORTRAN simply as follows:
DATA M0,M9/'000000','999999'/ . . . IF(AND(M0,OR(BOOL(-WD),M9+BOOL(-WD))))99,,99
They said it couldn't be done, but … the 1100 can do nines complement addition and subtraction directly in FIELDATA without conversion to binary and without messy loops to handle the characters (digits) one at a time. In the following examples, the values are assumed to be full words of six FIELDATA numerals, left filled with “0”s if necessary. The code in Example 5 is useful to guarantee that the characters being manipulated are indeed FIELDATA numerals, “0” to “9”.
L A0,<WORD1>. LOAD 6 FIELDATA NUMRALS A A0,<WORD2>. ADD ANOTHER WORD OF NUMERALS A A0,(0252525252525). GENERATE THE CARRIES AND A0,(0606060606060). PICK OUT CARRIES SSC A1,3. POSITION THE CARRIES AN A0,A1. EFFECT THE CARRIES OR A0,(0606060606060). RESULT IN A1
L A0,<WORD1>. AN A0,<WORD2>. AND A0,(0606060606060) GENERATE CARRIES SSC A1,3. AN A0,A1. EFFECT THE CARRIES OR A0,(0606060606060). RESULT IN A1
L A0,(0252525252525). AN A0,<WORD>. SIGN NOW CHANGED
DL A0,<WORDS1>. DA A0,<WORDS2>. DA A0,(0252525252525252525252525). DL A2,A0. OR A3,(0171717171717). OR A2,(0171717171717). DAN A0,A3. (TWO AN'S FASTER HERE) DSC A3,3. DAN A0,A3. (ALSO HERE) DAN A0,(0060606060606060606060606). RESULT IN A0 & A1.
(Ed. 2) Floyd Linn of Houston provided the improvements to the add and sub algorithms—they are each one line shorter than in the original version.
The following 10 instructions will convert a word of six FIELDATA numerals in A0 to their binary equivalent in A1. I believe that this is the fastest way to do this on an 1108.
This algorithm is applicable to many different computers, not just the UNIVAC 1108. I have known this concept for so long I am uncertain if it is original.
AND A0,(0171717171717). STRIP OFF 'ZONE' BITS AND A1,(-0,0,-0,0,-0,0). EXTRACT ALTERNATE DIGITS MF A2,(54*/29). AN A1,A2. 3 PAIRS OF DIGITS CONVERTED AND A1,(0,-0,0). MF A2,((1*/12-100)*/23). AN A1,A2. RIGHT 4 DIGITS NOW DONE AND A1,(-0,0,0). MF A2,((1*/24-10000)*/11). AN A1,A2. DONE. RESULT IN A1, ORIGINAL IN A0.
A frequent reason for dropping into assembly language is to scan a string of characters packed six per word. There are probably as many character-fetch methods as there are programmers, however, I feel that the following method is both the fastest and the most economical of registers—it requires only three, total. This code came from the Computer Science Center of the University of Maryland.
To initialize the scan:
L X11,(1,LOAD). L A1,(1,STRING).
To load a character into A0
EX 0,*X11.
The “subroutine”:
L,S6 A0,0,*A1. LMJ X11,0,X11. LOAD L,S1 A0,0,A1. L,S2 A0,0,A1. L,S3 A0,0,A1. L,S4 A0,0,A1. L,S5 A0,0,A1. LMJ X11,LOAD-2.
In the world of useless instructions, CDU, Characteristic Difference to Upper, ranks very high (and very rank). However, since the advent of the mighty quarterword, it has suddenly become marginally useful, if you go in for that sort of thing. Observe:
CDU A0,(0).
has the effect of peeling out the leftmost quarterword in A0 and putting it into A1, right justified and zero filled.
. . . . . . LOC1 L A0,<...>,*X1. LOAD WORD OF QUARTERWORDS L,U R3,3. LOC2 CDU A0,R3. QTRWD TO A1 . . FARBLE . . . . FARBLE . . LSSC A0,9. JGD R3,LOC2. JGD ...,LOC1.
If you write FORTRAN programs that make heavy use of halfwords, sixths, quarters and thirds, the following FORTRAN PROC is a great convenience and also makes the code much more readable.
AXR PROC DEFINE S1(I) = FLD( 0, 6,I) DEFINE S2(I) = FLD( 6, 6,I) DEFINE S3(I) = FLD(12, 6,I) DEFINE S4(I) = FLD(18, 6,I) DEFINE S5(I) = FLD(24, 6,I) DEFINE S6(I) = FLD(30, 6,I) DEFINE Q1(I) = FLD( 0, 9,I) DEFINE Q2(I) = FLD( 9, 9,I) DEFINE Q3(I) = FLD(18, 9,I) DEFINE Q4(I) = FLD(27, 9,I) DEFINE T1(I) = FLD( 0,12,I) DEFINE T2(I) = FLD(12,12,I) DEFINE T3(I) = FLD(24,12,I) DEFINE H1(I) = FLD( 0,18,I) DEFINE H2(I) = FLD(18,18,I) END
Now common partial words can be referenced easily:
INCLUDE AXR . . . . . . S3(VAL) = S2(PTR)+S6(TABLE(I)) IF(H1(ITEM).EQ.0) ...
Everyone who learns the assembly language of the 1100 after knowing the assembly language of another vendor's hardware is struck by the fact that the U-field in an instruction cannot be a negative offset. One frequently needs to write instructions like
L A0,-1,X10.
but has to accomplish his needs some other way. However, there is an occasion on the 1100 where instructions like the above are possible within limits. Since all shift instructions only interpret the rightmost seven bits of the computed shift count, it is possible to fake a negative offset by the following:
SSL A0,-1,A3. ==> SSL A0,0777,A3. LDSL A10,-2,X1. ==> LDSL A10,0776,X1. ETC. ETC.
as long as the computed shift count remains positive.
(John Walker, ISD)
Sometimes you write a subroutine which must be executed in quarter word mode, and you wish to make sure it's been called in the correct mode. This can be accomplished in one instruction without destroying any registers by:
TP,T1 $. IS QUARTER WORD MODE ON? J THIRDW. <QUARTER WORD MODE RETURN>
Obviously, a “TN” instruction will reverse the test.
(John Walker, ISD)
Sometimes the need arises to set a flag nonzero when register contents may not be assumed or changed. UNIVAC's PROC to turn on diagnostic dumps provides a three instruction sequence for this. Much better is the following:
SA,S1 A0,<CELL> SNA,S2 A0,<CELL>
This will always set T1 of the word nonzero. If in quarter word mode, replace S1 and S2 with Q1 and Q2 to set H1 nonzero. If speed is of the essence, H1 and H2 will be faster.
(John Walker and Walter J. Gilbert)
While the type of a run is passed in A4 at program initiation time, it is difficult for a reentrant subroutine to determine if it has been called from a demand run unless the user knows that Exec register R14 (octal 0136) always contains the type and level of the executing activity. The type will always be 4 for a demand run.
LA A0,0136 (TAL) LOAD TYPE AND LEVEL TOP,U A0,0300 IS IT A DEMAND RUN? J DEMAND YES. DO FUNNY THINGS <BATCH RUN> DO STRAIGHT-LACED THINGS
(Note: this will not work beyond Exec level 31!)
(John Walker, ISD)
It is frequently convenient to be able to address characters whose numbers are known at assembly time without the nuisance of counting words and sixths. This assembly FUNC provides such a facility in a manner parallel to Walt Gilbert's CHARS DEFINE for FORTRAN (Trick #4):
COLUMN* FUNC B EQU COLUMN(1)-1 A EQUF B/6,,13-(B-(B/6)*6) END A
Examples of use:
LA A0,IMAGE+COLUMN(10) LOAD COLUMN 10 OF IMAGE LA A3,COLUMN(4),X2 LOAD FOURTH COLUMN OF BUFFER
Note that this FUNC may be easily rewritten to address quarter words.
It is frequently desirable to compare two 36-bit quantities as unsigned values. The 1100 offers no direct test instructions to accomplish this (other than MASL, MASG, which are supererogatory for our purposes). This trick was discovered in the DOD COBOL library, but has spread very slowly. The following instructions will do it:
LA A0,<FIRST> LOAD FIRST VALUE ANA A0,<SECOND> SUBTRACT SECOND FROM FIRST JZ A0,EQUAL IF DIFFERENCE ZERO, THEY'RE EQUAL JC GREATER IF CARRY SET, FIRST > SECOND . <LESS> IF CARRY NOT SET, FIRST < SECOND
(John Walker)
(Ted Tenny, Lockheed)
Although FORTRAN subroutines are not recursive, the “ASSIGN” statement with the assigned “GO TO” statement can be used to implement a recursion scheme, by saving return addresses on a pushdown stack. Here's how:
INTEGER PSTACK, TOP, RTNADR COMMON TOP, PSTACK(256) . . . COMMENT: STATEMENT 102 BEGINS A RECURSIVE PROCEDURE; 102 CALL PUSH(RTNADR) . . . [PROCEDURE BODY] . . . CALL POP(RTNADR) GO TO RTNADR COMMENT: END OF PROCEDURE; . . . COMMENT: CALL TO THE RECURSIVE PROCEDURE AT STATEMENT 102; ASSIGN 104 TO RTNADR GO TO 102 104 CONTINUE . . . SUBROUTINE PUSH(ITEM) INTEGER PSTACK, TOP COMMON TOP, PSTACK(256) TOP = TOP + 1 IF (TOP.GT.256) CALL PSDUMP PSTACK(TOP) = ITEM RETURN SUBROUTINE POP(ITEM) INTEGER PSTACK, TOP COMMON TOP, PSTACK(256) IF(TOP.EQ.0) CALL PSDUMP ITEM = PSTACK(TOP) TOP = TOP - 1 RETURN
The “ASSIGN” statement and the unconditional “GO TO” behave like the machine instructions which the compiler generates for an ordinary subroutine call, i.e. they save the return address and transfer control to the subroutine. What's different is that the return address is then stored on a pushdown stack instead of in a fixed location. This makes recursion possible. Note that all of the recursive procedures must belong to the same FORTRAN compilation, since “RTNADR” must be recognized and treated as a local address by the FORTRAN compiler.
Procedures implemented in this fashion are without arguments. The subroutines “PUSH” and “POP” can be used to save and retrieve local variables within a procedure, and they could be extended to transmit the values of simple arguments.
(D. Zave, EXEC 8 Code)
BINOCT. BINARY TO OCTAL CONVERSION AND A0,(0707070707070). LSSC A1,3. A A1,('000000'). THESE 12 INSTRUCTION LR R2,(0700070007). CONVERT A BINARY WORD MLU A1,A0. IN A0 TO A 12-DIGIT LR R2,(0707770777077). FIELDATA OCTAL EQUIVALENT MLU A0,A1. IN A1 &A2. SSC A1,30. THE ORIGINAL WORD IN DSC A1,48. A0 IS NOT CHANGED SSC A2,12. DSC A1,12. SSC A1,24.
(Walter Colquitt, Sigma Corp.)
It is frequently required to execute some special initializing code the first time a routine is used, and only the first time. The following two lines of “self-destructing” code accomplish this. Note that the address fields of both instructions will be changed ultimately.
SLJ $. J INITCODE.
(Carlos Ballantyne, SIA)
The following PROC allows the easy building of entire subscripted lists with arbitrary names. In all cases, entry zero contains the number of entries in the list.
P PROC *1 EQTL* NAME I DO P(1),DFN(I) EQU P(1,I) DFN(0) EQU P(1) * EQU DFN END . A EQTL 2,4,6,5 000000 04 02 04 06 05 00 +A(0),A(1),A(2),A(3),A(4),A(5) END
(Ivan H. Mann, III EPA)
This is the shortest way to convert a positive binary integer less than 100 to FIELDATA.
DSL A0,36. ORIGINAL VALUE IN A0 DI,U A0,10. LSSL A0,6. POSITION LEFT DIGIT A,U A1,06060,A0. DONE
(Walter Gilbert, UMD)
Given a table of integers sorted low to high, this is the fastest way of finding a particular entry.
SZ X11. TOP OF TABLE L,U A0,N-1. N=TABLE LENGTH L,U A1,(N-1)/2. TABLE MIDPOINT L A2,VALUE. VALUE TO SEARCH FOR L A4,LSC((N-1)/16). # OF BINARY LEVELS TO GO . SEE NOTES BELOW J ANU. WE'RE OFF! LOOP S A1,X11,A3. EITHER (A1)=> X11 OR (A1)=> A0 AU A0,X11. COMPUTE NEW MIDPOINT SSL A1,1. ANU ANU A2,TABLE,A1. COMPARE SSL A3,35. ISOLATE SIGN BIT JGD A4,LOOP. S A1,X11,A3. END OF BINARY SECTION AN A0,X11. LXI,U X11,1. SETUP FOR SEARCH OF L,U R1,1,A0. 8 TO 16 WORDS SE A2,TABLE,*X11. J NOFIND. L,U A0,TABLE-1,X11. FOUND! ADDR IN A0
Note: for dynamic table sizes, the value on line 5 is computed:
LSC A3,A0. L,U A3,35. ANU A3,A4. SSL A4,4.
For fixed table sizes, the value on line 5 is computed by the following FUNC:
F FUNC. LSC* NAME. I(0) EQU 0. I DO 18, DO 1*/(I-1)-1<F(1) ,I(0) EQU I. END I(0).
There is a fast way to test if a character belongs to a particular set of characters without multiple tests or using a table. Since a FIELDATA character can have a value of 0 to 63, we can assign a bit in a double word to each character and build a mask which is used:
L A0,<CHAR> L A1,<MASK> DSC A1,0,A0 JNB A2,NOFIND
A few masks are:
CHARACTERS MASK COMMENTS ---------- ---- -------- A-Z,0-9,$- 017770010137777777700 FILE NAME CHARACTERS 0-9,!JKLMNOPQR 017771000000077700000 OVERPUNCHED SIGN
where the rightmost bit (bit 0) represents “@”, and the rest proceed to the left from there.
GlossaryIn the era in which this document was compiled, each major computer manufacturer had their own private nomenclature for objects for which generally accepted terms have emerged over the succeeding years. If your memory of UNIVAC-speak is tad rusty, the following table provides present-day equivalents for those used herein. When reading the assembly language examples, it may help to consult the instruction set reference. |
|
UNIVAC Term | Contemporary Equivalent |
---|---|
Activity | Process or Thread |
ER | Operating System Call |
Exec, EXEC-8 | Operating System |
Map | Linker / Link Instructions |
Multiactivity | Multi-threaded |
PROC or FUNC | Assembler Macro |
Program File | Directory |
Run | Job |
U-Field | Address Field (of instruction) |
/* | Left shift operator (Assembler) |
About this EditionThe original editions of this document circulated widely within the UNIVAC systems programming community in the mid 1970s. Walt Gilbert's original collection of “cute tricks” spurred a flurry of submissions by others, culminating in this edition which was originally distributed in March 1975. This electronic edition was prepared by John Walker in November 1999, and is based on an nth generation photocopy of the original kindly supplied by Kent Walker (no relation), a fellow UNIVAC systems programmer in the 1970s who is now an engineer at Space Systems/Loral in California. Having been originally printed on a gnarly 1970s UNIVAC line printer, then copied numerous times, I wasn't able to scan and OCR the paper document, so there was no alternative but to retype it. The original text was formatted by UNIVAC DOC and was, consequently, all upper case. Given the need to re-keyboard the text, I decided to convert the descriptive text to upper and lower case in the interest of readability; the code examples remain in upper case as they were originally presented. A couple of obvious typos in the text have been corrected; the code examples are verbatim. I have added links, where appropriate, to other documents related to topics discussed herein.
Back to UNIVAC MemoriesOther Computing History Resources at FourmilabFourmilab Home Page |