Modern PDP-11 C Compilers

I’ve extended the build script now to include the PCC compiler followed by the Unix V7 assembler (running with apout). You can see the new section here.

The Unix V7 assembler allows the JBR and JCC pseudo instruction to be treated correctly - something which is not currently possible when using the GNU assembler.

I’m getting identical binaries with both assemblers, apart from where those instructions need the long form, which is encouraging.

I’ve very impressed with apout - it’s working very well.

Dave

3 Likes

I do think you are better off with your twist-a-modern-compiler approach. Sounds like you are only one hurdle away from success. And… Maybe I’m thinking to much like a “functional programmer”, but can’t you insert (or “pipe” together) a simple translator that converts the synthetic instructions to real ones?

How does it fail? Does it work if you add .word before the data?

Possibly, but there were other problems as well that affected PCC output.

  1. the default number base is decimal not octal
  2. extended branch istructions (JBR/JCC) are not supported

I was able to address (1) by updating the code generator in PCC

Unfortunately I was not able to address (2) - even with macros - so I stopped at that point.

In the end I was able to get code from all four compilers running.

I still need to update the wiki page with the details of the 4th option: ACK (Amsterdam Compiler Kit) but this seemed to be the best option.

There is a comparison here:

Dave

3 Likes

3 small remarks:

  1. There is a higher rate maintenance fork of ack (mainly focused on MS-DOS) at https://github.com/tkchia/ack. I did not have time to do a regression for PDP-11 but worth looking at.
  2. Also, For new generation macOS compilation, it is necessary to trick the build with a something like a gcc wrapper to have this option -Wno-error=implicit-function-declaration.
  3. It is with noting that the compiler focuses on unix v7 but and not bsd 2.x
1 Like

Not to sidetrack the thread away from the PDP-11, but I thought I’d
mention some recent entertainment I’ve had with the B-em emulator with
another coprocessor emulator – the NS32000 “Tube” running
PanOS 1.4.

There was a VAX Fortran port of the University of Texas text
game “Super Star Trek” (itself an expansion of Mike Mayfield’s
original BASIC game) floating around on the Web a few years
back (with attributions “PRODUCED FOR YOUR ENJOYMENT BY
DAVID MATUSZEK AND PAUL REYNOLDS WITH MODIFICATIONS BY
DON SMITH AND M. KELLOGG”). I got this code back in 2016
from a link on a Web page authored by one Oleg Uryutin of
“Allwards Laboratory”, though that page doesn’t seem to exist
anymore (and was not archived by the Wayback Machine).

Anyway, this code builds and runs on a SimH VAX-11/780
(with VMS 4.7 and VAX Fortran V4.8-276) with no modifications
or difficulties, as might be expected. But after I got
PanOS running I thought it might be fun to try to port the
code using Acorn/PanOS Fortran 77. And – it worked, with
a few gotchas concerning the inter-operability of old-style
Hollerith constants and F77-style “CHARACTER” strings,
which I overcame by “cheating” a bit and linking to some
“helper” conversion routines written in C (with a small
assembly-language interface – the PanOS F77 manual describes the
subroutine calling convention and how to do inter-language calls,
though the examples they give are in Pascal).

You mentioned you found (and fixed) two bugs in the GCC back end. Did you report these? As the pdp11 back end maintainer, I’d like to get the fixes applies to GCC. You could create an issue for GCC, or if you prefer post the description and diffs here.
As for test suite, GCC comes with a very large test suite. All the “compile” tests should work since they don’t need a target system to run on. “execute” tests take more work, as you pointed out. I’ve been able to run a fair number of them using a quick & dirty “bare metal” pdp11 flavor of Newlib, but that isn’t fit to publish, it needs to be refined.

1 Like

Sorry @pkoning, I didn’t get around to reporting these.

There is a decent write-up on this wiki page:

Here are the two changes:

The first fix is in: gcc/config/pdp11/pdp11.c

--- pdp11.cc	2022-06-22 18:23:09.856751320 +0100
+++ pdp11_fixed.cc	2022-06-22 18:34:08.685577564 +0100
@@ -517,6 +517,27 @@
 	  && REGNO (XEXP (XEXP (operands[0], 0), 0)) == STACK_POINTER_REGNUM
 	  && reg_overlap_mentioned_p (stack_pointer_rtx, operands[1]))
 	sameoff = true;
+
+      /* DMB - catch for source [1] is an indirect access via a register that
+         is also used as the destination [0] */
+      if (GET_CODE (operands[0]) == REG && GET_CODE (operands[1]) == MEM) {
+        int dstreg = REGNO (operands[0]);
+        int srcreg = -1;
+        if (GET_CODE (XEXP (operands[1], 0)) == REG) {
+          srcreg = REGNO (XEXP (operands[1], 0));
+        } else if (GET_CODE (XEXP (operands[1], 0)) == PLUS) {
+          if (GET_CODE (XEXP (XEXP (operands[1], 0), 0)) == REG) {
+            srcreg = REGNO (XEXP (XEXP (operands[1], 0), 0));
+          } else if (GET_CODE (XEXP (XEXP (operands[1], 0), 1)) == REG) {
+            srcreg = REGNO (XEXP (XEXP (operands[1], 0), 1));
+          }
+        }
+        /* If the source and destination registers are the same, force
+           little endian instruction order */
+        if (srcreg == dstreg) {
+          useorder = little;
+        }
+      }
     }
 
   /* If the caller didn't specify order, use the one we computed,


The second fix is in: gcc/config/pdp11/pdp11.md

--- pdp11.md	2022-06-22 18:23:14.736815708 +0100
+++ pdp11_fixed.md	2022-06-22 18:26:21.043289924 +0100
@@ -271,6 +271,14 @@
    output_asm_insn ("tst\t%0", exops[1]);
   else
    output_asm_insn ("cmp\t%0,%1", exops[1]);
+
+  // Correct V/N flags so signed comparisons work
+  output_asm_insn ("cln", NULL);
+  if (!CONST_INT_P (exops[1][1]) || INTVAL (exops[1][1]) != 0) {
+   output_asm_insn ("clv", NULL);
+   output_asm_insn ("bcc\t%l0", lb);
+   output_asm_insn ("sen", NULL);
+  }
   output_asm_label (lb[0]);
   fputs (":\n", asm_out_file);

Dave

Thanks, found it. I can fine tune those, I can see some further optimizations. A likely route is to handle signed and unsigned compares separately, that can be done.
Question for you: I would like to log a gcc defect for this, can I credit you for the report? Along the same lines, I’d credit you for contributing to the solution, unless you’d prefer not to be mentioned.

2 Likes

Question for you: I would like to log a gcc defect for this, can I credit you for the report? Along the same lines, I’d credit you for contributing to the solution, unless you’d prefer not to be mentioned.

Yes, I’d be happy to be credited in this way.

You show up with an alias “hoglet” here, but the email notification seems to have a real name. Which name should I use?
Also: can I use your spigot program as a test case for the GCC test suite to use?

I tend to use both: David Banks (hoglet)

Please go ahead and use the Pi Spigot as a test case.

Dave

1 Like

In the current development stream compiler (V13.0) the divide at line 93 (“p /= 10;”) generates a div instruction rather than a library call to unsigned 16 bit divide. The “unsigned” bit makes no sense, the current code does a signed divide as it should.
The long divides do generate library calls, as they must since they produce 32 bit quotients which isn’t what “div” does.
The register issue is still there, in -O0 only.

These changes were originally against the GCC 11.2 source tree, whch I think was the latest stable release the time.

This was the command I was using to build:

pdp11-aout-gcc -nostdlib -Ttext $ADDR src/crt0_gcc.s src/$i -lgcc -o $name

As far as I recall, I was working without optimization because there were more issues when optimization was enabled.

I’m not sure I’m following here; line 93 of which file?

spigot.c line 93, in function “print”.

In the version I built 4 months ago, that particular division in print() also generated a DIV instruction.

We are possibly talking at cross-purposes here.

Are you saying that one or other of the patches either is no longer needed, or just plain doesn’t make sense?

I have the latest GCC sources (from the git mirror) now checked out, so I’m able to fairly quickly test things.

It seems only the second patch (to pdp11.md) is needed for the spigot test case to run.

Here’s my test case for the first patch:

#include "tube.h"

void outhex(uint8_t i);
void outhex32(uint32_t i);
void outnl();

int program() {
   uint32_t a = 0x12345678;
   uint32_t *ap = &a;
   outhex32(*ap);
   outnl();
}

void outhex(uint8_t i) {
   i &= 15;
   if (i > 9) {
      outc(i + ('A' - 10));
   } else {
      outc(i + '0');
   }
}

void outhex32(uint32_t i) {
   int d;
   for (d = 0; d < 8; d++) {
      // Note: this uses ASHC which is a signed shift
      outhex((uint8_t)(i >> 28));
      i <<= 4;
   }
}

void outnl() {
   outc(10);
   outc(13);
}

This fails with the latest GCC, which generates the following code:

00000128 <_program>:
 128:	1166           	mov	r5, -(sp)
 12a:	1185           	mov	sp, r5
 12c:	65c6 fffa      	add	$-6, sp
 130:	15f5 1234 fffa 	mov	$11064, -6(r5)
 136:	15f5 5678 fffc 	mov	$53170, -4(r5)
 13c:	15c0 fffa      	mov	$-6, r0
 140:	6140           	add	r5, r0
 142:	1035 fffe      	mov	r0, -2(r5)
 146:	1d40 fffe      	mov	-2(r5), r0
 14a:	1200           	mov	(r0), r0     <<<<<  clobbers r0
 14c:	1c01 0002      	mov	2(r0), r1
 150:	1066           	mov	r1, -(sp)
 152:	1026           	mov	r0, -(sp)
 154:	09f7 004e      	jsr	pc, 1a6 <_outhex32>
 158:	65c6 0004      	add	$4, sp
 15c:	09f7 009c      	jsr	pc, 1fc <_outnl>
 160:	00a0           	nop
 162:	1146           	mov	r5, sp
 164:	1585           	mov	(sp)+, r5
 166:	0087           	rts	pc

With the patch (to pdp11.cc), the code is:

00000128 <_program>:
 128:	1166           	mov	r5, -(sp)
 12a:	1185           	mov	sp, r5
 12c:	65c6 fffa      	add	$-6, sp
 130:	15f5 1234 fffa 	mov	$11064, -6(r5)
 136:	15f5 5678 fffc 	mov	$53170, -4(r5)
 13c:	15c0 fffa      	mov	$-6, r0
 140:	6140           	add	r5, r0
 142:	1035 fffe      	mov	r0, -2(r5)
 146:	1d40 fffe      	mov	-2(r5), r0
 14a:	1c01 0002      	mov	2(r0), r1    <<<< swapped
 14e:	1200           	mov	(r0), r0     <<<< swapped
 150:	1066           	mov	r1, -(sp)
 152:	1026           	mov	r0, -(sp)
 154:	09f7 004e      	jsr	pc, 1a6 <_outhex32>
 158:	65c6 0004      	add	$4, sp
 15c:	09f7 009c      	jsr	pc, 1fc <_outnl>
 160:	00a0           	nop
 162:	1146           	mov	r5, sp
 164:	1585           	mov	(sp)+, r5
 166:	0087           	rts	pc

This is all with -O0

When we finally get our Imp77 compilers resurrected fully, remind me to build the old Pdp11
Imp compiler for you. It did rather well in Brian Wichmann’s Ackermann benchmark*, a little more info on which is mentioned here - we should have the ability to generate pdp11 assembler text interspersed with Imp source text so you can compare it to GCC et al. Due to some limitations in the current compilers, the pdp11 compiler isn’t ready to be rebuilt, but an older version of the source with some bits not yet recreated is temporarily at https://gtoal.com/tmp/imp11/ if you like looking at that sort of thing (ie unfamiliar languages, compiler code, historical software, etc).
[*: https://history.dcs.ed.ac.uk/archive/docs/Imp_Benchmarks/acklt.pdf
https://history.dcs.ed.ac.uk/archive/docs/Imp_Benchmarks/ackpe.pdf
https://history.dcs.ed.ac.uk/archive/docs/Imp_Benchmarks/ack.pdf ]

1 Like

Excellent articles! Recommended reading for lovers of the older architectures.