Subject: Asides in PAL code

From: [email-masked] (Douglas W. Jones,201H MLH,3193350740,3193382879)
Date: 23 Jun 1994 20:15:22 GMT

Before I write code that depends on it, I want to check to see if one
of my favorite assembly language coding tricks is generally effective
under most PAL implementations for the PDP-8.  Here's an assembly
listing that illustrates the trick and shows a correct assembly of it:

   1                    PAGE
   2            THUMB=.;*POOL           / begin aside
   3 00600 0001 A,      1                 / eg, text that goes with
   4 00601 0002 B,      2                 / stuff on this page
   5            POOL=.;*THUMB           / end aside
   6 00200 0600         A                 / this is assembled normally
   7 00201 0601         B                 / and references the aside
   8                    PAGE
   9            THUMB=.;*POOL           / begin aside
  10 00602 0003 C,      3                 / stuff referenced by
  11 00603 0004 D,      4                 / a different page
  12            POOL=.;*THUMB           / end aside
  13 00400 0602         C                 / this is assembled normally
  14 00401 0603         D                 / and references its stuff
  15                    PAGE
  16            POOL=   .               / asides assemble after here
  17            $

If you've used an assembler with different csects (in the MACRO-11
sense of the word), the uses of this should be apparent.  I think the
first place I saw it done this was was under the assembler on the
DDP-516, which was paged in the same way as the PDP-8.

The idea is that you want things that are related to each other to be
nearby in the source listing, but if something is never directly
referenced as an operand (that is, full-word pointers are always used),
it has no business on the same page as the code that references it.
So, put the stuff that belongs elsewhere in the aside for the page
holding the code that references it.  TEXT directives are obvious
material for such asides.  So are most arrays.

The problem is, some assemblers die when they hit an undefined assembly
origin, even if, as in the above example, everything works out perfectly
by the time it matters (during the second pass).  At least one assembler
I used this trick with assembled the code correctly, but it did so with
a huge belch of error messages, a "fatal" pass one error for each
undefined origin, plus a "multiply defined label" error for each label
defined in an aside.

Programming with this approach does introduce one little stumbling
block, you can't make a forward reference to code in an aside because
those symbols are still undefined at the end of pass 1 (well, they might
be defined, but they got their values from an undefined location
counter).

So, does the above assembler as above under OS/8 PAL, how about P?S/8.

					Doug Jones
					[email-masked]

---

From: [email-masked] (Charles Lasner)
Date: 24 Jun 1994 18:27:16 GMT

In article <[email-masked]>,
Douglas W. Jones,201H MLH,3193350740,3193382879 <[email-masked]> wrote:
[wants to use pet tricks]

Your assembly is not "correct" by PDP-8 standards.  Both PAL8 and P?S/8 
will not tolerate symbols defined by comma being redefined unless the 
redefinition is to precisely the same value as a special case.  During 
pass one, "POOL" is undefined the first time, thus giving an undefined 
expression to lead to the argument to the "*".  P?S/8 and PAL8 differ on 
this one slightly.  PAL8 will make the entire expression 0000 since it's 
undefined.  P?S/8 PAL will attempt to continue the expression scan with 
just the local symbol value being 0000.  (Your example is too trivial to 
see a difference; try something like POOL+1.)

If you can change the program so that POOL starts out at its final value 
after pass one ends, but at the start of pass 1, then it will assemble 
without errors.  Note also that equated symbols can be redefined without 
error.  BTW, a quirk of P?S/8 PAL is that since there is a pass-1-end 
summary of undefined symbols whose values are listed as the first 
occurrence's address, you may have code that yields an error-free 
two-pass assembly even though there is a one-pass error.  (Since PAL8 can 
do neither a one-pass assembly nor a pass-1-end status report, you don't 
see the problem, etc.)  IF PAL8 were upgraded to include a between-passes 
summary, the error would pop up, etc. since the table shows undefined 
symbols.

>be defined, but they got their values from an undefined location

Generally not true.  It can be done, as long as the second pass starts 
with the finally correct value.  The exception case is when literals are 
being referenced, since the address of a literal is dependent on all 
references to all symbols that contribute to prior entries in the literal 
tables.  (At higher addresses since literals start at the end of the page 
and work back towards the beginning.)

>So, does the above assembler as above under OS/8 PAL, how about P?S/8.

Your assembler needs to be made meaner, so that you get ID or DT errors 
attempting to redefine code labels.  You can always use = to get what you 
want though.

cjl

---

From: [email-masked] (Douglas W. Jones,201H MLH,3193350740,3193382879)
Date: 24 Jun 1994 20:36:26 GMT

>From article <[email-masked]>,
by [email-masked] (Charles Lasner):

> without errors.

That's no fun, because you have to know what page your pool starts
on before you can assemble things correctly.  Of course, you can
assemble it, with errors, check the outcome, define POOL to match
it's eventual value, then assemble it again without errors, but
that's no fun.

> Note also that equated symbols can be redefined without 
> error.

That means that the following is safe code, merely by substituting, for
example, "A=.; 1" for "A, 1".  It's ugly, but sometimes, ugly is OK.

   1                    PAGE
   2            THUMB=.;*POOL           / begin aside
   3 00600 0001 A=.;    1                 / eg, text that goes with
   4 00601 0002 B=.;    2                 / stuff on this page
   5            POOL=.;*THUMB           / end aside
   6 00200 0600         A                 / this is assembled normally
   7 00201 0601         B                 / and references the aside
   8                    PAGE
   9            THUMB=.;*POOL           / begin aside
  10 00602 0003 C=.;    3                 / stuff referenced by
  11 00603 0004 D=.;    4                 / a different page
  12            POOL=.;*THUMB           / end aside
  13 00400 0602         C                 / this is assembled normally
  14 00401 0603         D                 / and references its stuff
  15                    PAGE
  16            POOL=   .               / asides assemble after here
  17            $

> BTW, a quirk of P?S/8 PAL is that since there is a pass-1-end 
> two-pass assembly even though there is a one-pass error.

That's what happened with MODCOMP's assembler all the time.  It assembled
correctly, but it only did it while kicking and screaming.  I ended up
living with the error messages, much to the disgust of the people in
MODCOMP's software support department.  I was using essentially the above
trick to generate the jump table for a Pascal case statement.  The compiler
worked like a charm, but programmers had to learn to ignore all the pass
one errors out of the assembler (you'd get one such error for every
case label).
> attempting to redefine code labels.

You're right about that, but I've always used the following algorithm to
generate such error messages:  When assigning a value to a label (as
defined with a comma in PAL, or with a colon in MACRO-11), require either
that the previous value of the label be undefined or that the previous
value be the same as the new one.  During pass 1, A, B, C and D in the
above example get undefined values because the location counter was
undefined.  Of course, I had the luxury of room for code to distinguish
undefined from other values, and shoehorning an assembler into 4K doesn't
guarantee room for this.
					Doug Jones
					[email-masked]

---

From: [email-masked] (Charles Lasner)
Date: 25 Jun 1994 19:14:03 GMT

In article <[email-masked]>,
>it's eventual value, then assemble it again without errors, but

True, but the alternatives are fun-breakers or error messages.  And 
worse, the errors are real, i.e., the redefinition doesn't take, so it 
stays being wrong at the initial, albeit useless, values.  DT means 
duplicate tag error, it gets ignored.  Sometimes it's known as an illegal 
definition or ID error, again it gets ignored.

>  16            POOL=   .               / asides assemble after here

Yes, that works fine, because you have relieved the assembler of address 
label responsibility; equates are always the final authority, as well 
they should be.  (It's saying to the assembler that I know what this 
should be whether you think you did know or not!)

>> two-pass assembly even though there is a one-pass error.

This feature emanates from the original paper-tape PAL III and was never
added to PAL8.  The problems over the years with PAL8 stem from the fact
that we bitched about it for years to a deaf-eared Richard Lary who
assumed that Mark Bramhall knew how to write non-quirky assemblers.  (The
earliest PAL8 versions were quite brain-dead.  My Kermit-12 distribution
includes one file, DECODE.PAL, which is written in a subset of what I'd
normally expect from PAL8, so that it can be locally assembled on a user
site.  The resulting binary can then be used to decode the rest of the
Kermit-12 distribution, which includes the PAL8 Version B0 from OS/278,
which is a much better assembler than its ancestors, etc.) Eventually,
Richie apologized to me when he saw just how piss-poor it was.  He did
some fast fixes to it to make it passible, but then had no further time to
improve it.

No significent work has ever been done on it since then, other than Jim
Roth's hashing symbol routines.  (Jim is the original architect of P?S/8
PAL.) Jim spent a lot of time developing PDP-8 quick-sort and hashing
routines using a dummy "null assembler" that basically scanned for symbols
and placed them in the table.  Real source files were used for timing
tests, etc.  The results of his tests were the basis for routines added to
both P?S/8 PAL and also PAL8 at different times, etc.  Jim was quite
encouraged by the way the optimized routines ran faster than the older
methods both assemblers used (binary searches, etc.), and he proceeded to
add live code to PAL8 while we upgraded P?S/8 PAL.  (I was adding many
features to P?S/8 PAL at the time to support LINC mode, literals, obscure
syntax such as "JMP.-1", etc., so it was a good time to upgrade the symbol
stuff as well.) However, he was greatly disappointed by the dismal
performance of PAL8, in that only insignificent improvement resulted in
PAL8 due to its overall crummy design and poor scanning performance; the
symbol table probes aren't the largest factor in terms of time spent, so
improving those routines doesn't contribute that much!  This is part of
why P?S/8 PAL today is so much faster an assembler than PAL8, more a
matter of how slow PAL8 is, than how fast P?S/8 PAL is, i.e., PAL8 could
be rewritten from the ground up and would be much faster, etc. 

At one point, DEC was negotiating with me for using P?S/8 PAL as the 
CAPS-8 assembler.  I had been disappointed with them too many times by 
then, and I demanded a credible discussion before proceeding.  (Too many 
free demos to idiot managers who had attitude problems, etc.)  When they 
saw I was serious, they abandoned me.  The net result is that CAPS-8 PALC 
is just a warmed-over variant on PAL8 of that era, etc.

This was the infamous "showdown" incident where I ran P?S/8 on two 
DECtapes versus them running OS/8 on an RK8E.  They chose the task, which 
turned out to be to assemble CCL.PA on both systems.  After leveling the 
playing field on considerations such as line-printing, etc., we ran the 
"race".  OS/8 and PAL8 was indeed twice as fast as P?S/8 PAL!  But 
considering that an RK8E-based system ought to be 10-20 times as fast as 
a DECtape made them scared!  (The coup-de-grace was when I binary loaded 
the results from the DECtape *faster* than OS/8 could from the RK8E.)

>one errors out of the assembler (you'd get one such error for every

Programmers have always attempted to do sane things that were disallowed 
by stupid assemblers.  Try on an x86 machine to generate a short jump 
instruction at the beginning of your code around all of the data to the 
real program start address.  MASM assumes the need for a long jump so you 
waste a NOP anyway.  (The data comes first because otherwise all of the 
other references to the data will also become long; it's too stupid to 
figure it out unless the references come after the definition.)  There is 
an x86 disassembler called Master Key that creates code with comments 
explaining that the syntax will cause errors in MASM.  Apparently the 
programmers used data bytes to create the values directly!

>undefined from other values, and shoehorning an assembler into 4K doesn't

No, the symbol does have the ability to be considered undefined.  As I 
said, there is a pass-one-end summary of all symbols still undefined 
using the first usage occurrence's address as the "value" for the benefit 
of the printout, etc.  However, there is no provision for the concept 
that the current location counter is itself in an undefined state.  Thus 
each statement is an island unto itself, contrary to your algorithm.  
Additionally, PAL8 voids the entire statement returning 0000 should any 
part of it be undefined, while P?S/8 PAL merely voids the undefined 
item.  Thus, code with errors flagged can generate different output:

PAL8:

0000	UNDEF+1
1000	TAD	UNDEF+1

P?S/8:

0001	UNDEF+1
1001	TAD	UNDEF+1

I tend to think the P?S/8 PAL method is more consistent, since PAL8 
doesn't void the entire expression should the statement be an MRI, while 
P?S/8 PAL never voids anything but the undefined element.

BTW, P?S/8 PAL has some extensions to the syntax that is reminiscent of 
the -11:

FOO:	TAD	#3

The : is equivalent to the , and the # is equivalent to the (.

cjl