Poplog 15.53 for VAX OpenVMS 7.3: Pop-11, Prolog, Lisp and ML

Poplog 15.53 for VAX OpenVMS 7.3: Pop-11, Prolog, Lisp and ML

My VAXstations now run Poplog, the University of Sussex’s multi-language system: Pop-11 with its editor VED, Prolog, Common Lisp and Standard ML, all in one process. It comes as a binary kit for OpenVMS VAX 7.3, a BACKUP saveset with a DCL installer and uninstaller, and needs no compiler on the machine it is installed on.

The VAX/VMS sources of Poplog 15.53 survive. A VAX Poplog to compile them with does not, and Poplog is written in Poplog. So the first compiler had to be recreated on Linux. The rest of this post is about how that went.

Claude Code assisted, which is to say, did much of the hard work.

Downloads, with SHA-256 sums in SHA256SUMS.TXT:

  • POPLOG1553_VAX_20260929.BCK (30 MB): the kit, 3315 files.
  • POPLOG_INSTALL.COM, POPLOG_UNINSTALL.COM and POPLOG_STARTUP.COM, and the two that are also inside the kit, POPLOG_SETUP.COM and POPLOG_IMAGE_CHECK.COM.
  • README.TXT, which says everything below about installing in more detail, and SHA256SUMS.TXT.
  • The Poplog Starter Manual: PDF (383 pages) or HTML. It is Poplog’s own documentation, converted from VED format: Aaron Sloman’s Pop-11 primer, the VED tutorials and user guide, and introductions to the Prolog, Lisp and ML subsystems, with a first chapter of mine on starting each language with this kit.
  • Examples (66 KB): the 16 programs described below, with DCL tests and their expected output.
  • Source kit (9 MB): the unmodified Sussex VAX/VMS sources, the two build-only patches, the recreated Linux bootstrap, the VAX MACRO it generated, and step-by-step rebuild instructions with expected hashes and timings.
  • The file list of the saveset.

Poplog is Copyright (C) 1981-1999 The University of Sussex, under an XFree86-style licence; the licence file is in the source kit.

Why Poplog

Poplog was a teaching and research system for AI at Sussex from the early 1980s, and it ran on VAX/VMS for much of that time. It is four languages on one virtual machine: they share the garbage collector, the compiler back end and the editor, and Prolog and Lisp programs can call Pop-11 directly. It is also a period piece in the best sense: VED is an editor and an IDE at once, and the documentation is full of TEACH files written for students.

It is Lisp-adjacent enough for this blog, and a Symbolics owner can hardly resist a VAX running four AI languages from the 1980s and 1990s.

The machines

  • A VAXstation 4000/60 (80 MB) and a VAXstation 4000/96 (128 MB), both OpenVMS VAX 7.3, both headless.
  • Simulated MicroVAX 3900s (SIMH) with the same VMS, for the build and for everything that might go wrong.
  • On the Linux side, an x86-64 laptop running GetPoplog Seed, today’s 64-bit Poplog.

The kit was built on a simulator, then installed and tested on both real VAXstations and on a fresh simulator.

The bootstrap problem

Poplog’s compiler, POPC, its linker POPLINK and its librarian POPLIBR are themselves Pop-11 programs. To build a VAX Poplog you run them on a working VAX Poplog, and I could not find one anywhere.

What does exist:

  • The Poplog 15.53 VAX/VMS sources, a tar file of 8 December 2001 (S.vaxvms.tar.gz, 7.4 MB), on the GetPoplog archive and the University of Birmingham mirror.
  • GetPoplog Seed, a maintained Poplog for 64-bit Linux.

So the cross-compiler was made out of the two: the original VAX/VMS sources of POPC, POPLINK and POPLIBR, loaded into Seed’s Pop-11 on Linux, with their target set to VAX. They compile the core system to VAX MACRO (.a files) and link data (.w files). The VAX assembles those with its own MACRO, LIBRARY and LINK, and the result is a VAX Pop-11. From there, everything else was built natively on the VAX with Sussex’s own build procedures (mksyscomp, mknewpop, mkstartup, mkplog, mkclisp, mkpml): the VAX POPC, POPLINK and POPLIBR, then the saved images for Pop-11, Prolog, Common Lisp and ML.

The shape of it, with measured times:

StepWhereTime
Compile the 303 core modules to VAX MACROLinux5.2 s
Compile the 39 VED modulesLinux1.1 s
Assemble the coresimulated VAX26 CPU-s
Build the object libraries; assemble VED; linksimulated VAX19 CPU-s
mksyscomp … mkpml: everything else, nativelysimulated VAXabout 33 CPU-s

A second rebuild from scratch, done to check the instructions, gave the same modules, the same image sizes and the same test results.

What went wrong on the way

Measured from the transcripts, in the order they happened.

An undeclared variable in the 2001 sources. POPC compiled 302 of the 303 core modules and stopped:

;;; UNDECLARED IDENTIFIER $-Sys$-Extern$-lang (IN Convert_symbols)

in vmsextern.p, the module that talks to VMS’s linker. One word added to an lvars declaration fixed it. It is one of two patches, both only for building; the source in the kit is the original.

The first full image printed its banner and exited. POPLINK had been given a bare library, decided that setpop_reset was not in the image, and so setpop, Pop-11’s top level, went straight to sysexit(). My first theory blamed VED. The real fix was to link the core the way Sussex’s own pglink -core does, with every module of the library forced in. After that, 1+1 => printed ** 2, which was a good day.

%nort became a file search. VMS Poplog expands % and * in its command-line arguments as RMS wildcards, so pop11 %nort looked for files. Sussex’s own mkstartup.com writes \%nort. So do my tests and the README now.

ODS-2 is eight directories deep; Poplog is nine. Unpacking the tree gave 114 %RMS-F-SYN errors until it went under a concealed rooted logical name, USEPOP, which is how Poplog expects to be found anyway.

A saveset that would only restore to where it came from. The first kit saveset was made with full paths. Saving it through the concealed root made it restore anywhere.

The host tools are not byte-deterministic. Two runs on Linux give identical VAX MACRO for every module but list some link tables in a different order. The second native rebuild showed it does not matter.

Changing a file’s protection changes its revision date. The uninstaller only deletes files that are unchanged since installation, and it decides that by size and revision date. The installer now compares before it sets protections.

A Pop-11 “decimal” on a VAX is single precision. It is a 32-bit F_float there; true -> popdprecision keeps results double.

A taste of each language

Each snippet is in the examples download (taste/) and printed this on the VAXstation 4000/96. To start:

$ @SYS$COMMON:[SYSLIB.POPLOG]POPLOG_SETUP.COM
Sussex Poplog Version 15.53
$ POP11

Sussex Poplog (Version 15.53 29-SEP-2026)
Copyright (c) 1982-1999 University of Sussex. All rights reserved.

Setpop
:

Pop-11 (POP11 \%nort \%noinit TASTE.P):

define fact(n); if n < 2 then 1 else n * fact(n - 1) endif enddefine;
fact(30) =>
lvars i;
[% for i from 1 to 10 do i * i endfor %] =>
1/3 + 1/6 =>                        ;;; exact fractions
2 ** 100 =>                         ;;; big integers
vars verb;                           ;;; the matcher binds vars
if [the cat sat on the mat] matches [== cat ?verb ==] then verb => endif;
maplist([apple fig pear], length) =>
define compose(f, g); procedure(x); f(g(x)) endprocedure enddefine;
compose(fact, length)([a b c d]) =>
systranslate('SYS$NODE') =>         ;;; a VMS logical name
sysexit();
** 265252859812191058636308480000000
** [1 4 9 16 25 36 49 64 81 100]
** 1_/2
** 1267650600228229401496703205376
** sat
** [5 3 4]
** 24
** vax96::

(1_/2 is how Pop-11 prints the ratio one half.)

Prolog (PROLOG TASTE.PL):

:- library(useful).
parent(tom, bob).  parent(bob, ann).  parent(bob, pat).  parent(pat, jim).
ancestor(X, Y) :- parent(X, Y).
ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
show(G) :- write(G), nl.
:- findall(Y, ancestor(tom, Y), L), show(descendants_of_tom = L).
:- findall(X+Y, append(X, Y, [a,b,c]), L), show(splits = L).
:- X is 1 << 100, show(two_to_the_100 = X).
greeting --> [hello], who.
who --> [world].  who --> [vax].
:- phrase(greeting, [hello, vax]) -> show(parsed) ; show(not_parsed).
:- ancestor(jim, _) -> show(jim_has_children) ; show(jim_has_no_children).
:- halt.
descendants_of_tom = [bob, ann, pat, jim]
splits = [[] + [a, b, c], [a] + [b, c], [a, b] + [c], [a, b, c] + []]
two_to_the_100 = 1267650600228229401496703205376
parsed
jim_has_no_children

Common Lisp (CLISP TASTE.LSP):

(defun fact (n) (if (< n 2) 1 (* n (fact (- n 1)))))
(print (fact 30))
(print (mapcar #'(lambda (x) (* x x)) '(1 2 3 4 5)))
(print (/ 1 3))                                ; a ratio
(defstruct vax name model (mb 16))
(let ((v (make-vax :name "VAX60" :model "4000/60" :mb 80)))
  (print (list (vax-name v) (vax-model v) (vax-mb v))))
(let ((h (make-hash-table)))
  (dolist (w '(pop prolog lisp ml pop lisp pop)) (incf (gethash w h 0)))
  (print (list (gethash 'pop h) (gethash 'lisp h) (gethash 'ml h))))
(print (reduce #'+ (loop for i from 1 to 100 collect i)))
(format t "~%~R~%" 1990)
(terpri)
265252859812191058636308480000000
(1 4 9 16 25)
1/3
("VAX60" "4000/60" 80)
(3 2 1)
5050
one thousand, nine hundred and ninety

Standard ML (PML TASTE.ML):

Compile.quiet := true;
fun say s = output (std_out, s ^ "\n");
fun fact 0 = 1 | fact n = n * fact (n - 1);
say (makestring (fact 30));
datatype shape = Circle of int | Rect of int * int;
fun area (Circle r) = 3 * r * r
  | area (Rect (w, h)) = w * h;
say (makestring (map area [Circle 2, Rect (3, 4)]));
fun upto (i, j) = if i > j then [] else i :: upto (i + 1, j);
say (makestring (List.foldl (fn (a, x) => a + x) 0 (upto (1, 100))));
val compose = fn (f, g) => fn x => f (g x);
say (makestring ((compose (fact, fn x => x + 1)) 4));
say (makestring (1 div 0) handle Div => "1 div 0 raises Div");
val _ = System.exit ();
265252859812191058636308480000000
[12, 12]
5050
120
1 div 0 raises Div

VED. From the Pop-11 prompt, ved vax96.p opens the editor. I typed two lines and compiled each with keypad ENTER then l1 (“load this line”). The output goes to its own buffer, output.p, at the top of the screen. This is the 80x24 terminal at that moment:

───┤   3├ l1  (editing: output.p) ──────────────────────────────────────────────
 ** Hello from VED on a VAXstation 4000/96
 ** vax96::
─
...
───┤   2├ l1  (editing: vax96.p) ───────────────────────────────────────────────
 define hello(); 'Hello from VED on a VAXstation 4000/96' => enddefine;
 hello(); sys_host_name() =>

Sixteen example programs

The examples download has programs in all four languages. Each language has a DCL procedure, RUN_EXAMPLES.COM, that runs its programs and compares the output with the expected output, 32 checks in all:

  • Pop-11: Tetris for a VT100 or VT220 (DEC cursor addressing, the DEC special graphics character set for the well, raw keys); a bc-style calculator with big integers and exact fractions; a VMS explorer that reads logical names, every version of every file, and the output of SHOW SYSTEM; and the backup-log checker from the Perl post, same rules, for a side-by-side.
  • Prolog: Sudoku by plain backtracking; Conway’s Life drawn on a VT terminal; N-queens; the zebra puzzle; a DCG that parses VMS file specifications; and a program where Pop-11 asks RMS and DCL about files and Prolog rules answer what PURGE/KEEP=2 would delete.
  • Common Lisp: A* search on the 8-puzzle; a file-version report; and a program written in portable 1984 Common Lisp so that it can run on Poplog Common Lisp and on DEC’s VAX LISP V3.1, which is also installed on the 4000/60.
  • Standard ML: a small typed language with a parser, type checker and evaluator; Dijkstra’s shortest paths over my lab’s DECnet node registry; and a red-black tree with property checks.

All 32 checks pass on the simulator, the 4000/60 and the 4000/96. The 13 programs that can also run on Linux Poplog give the same output there, line for line, and Life’s stream of VT escape sequences was the same, byte for byte (88,626 bytes), on the VAX and on Linux.

Measured, in seconds:

ProgramSimulatorVAXstation 4000/60VAXstation 4000/96
Sudoku, Inkala’s “hardest” (13,810 placements)110-115621188
Sudoku, Norvig’s hard17.1-7.541.112.4
Life, 200 generations of the r-pentomino31.0156.550.9
8-puzzle, a 31-move position (A*)32.0169.157.2
1000! and other bignum work (Lisp)0.695.302.07
N-queens, all 92 solutions for N = 80.251.240.39

The 4000/96 runs these about three times as fast as the 4000/60, and the simulator about five or six times. The simulator figures are from 29-SEP and say nothing about a real VAX except the ratios.

Two Lisps on one VAX

DEC’s VAX LISP V3.1 and Poplog Common Lisp 2.0 ran the same portable program on the 4000/60. Every result line was identical. The times differ, in milliseconds:

PartPoplog CLVAX LISP, compiledVAX LISP, interpreted
bignums: 1000!, its digit sum, 2^200, a gcd5,3009,56012,090
symbolic differentiation22070300
state machines made by a macro5502601,420

Poplog is faster at big numbers; DEC’s compiler is faster at list work.

Shared images

Poplog’s saved images (the Prolog, Lisp and ML systems, and Pop-11’s own startup image) can be made into shared, permanent global sections, so that every user maps the same pages. The original build procedures did that with an -install option. The kit’s installer does it as SYSTEM with Poplog’s own sys_install_image, through POPLOG_STARTUP.COM. It costs 2,458 global pages and 4 global sections. A reboot removes them, so the installer prints a line to add to the site startup to get them back each boot. It does not change any startup file itself.

I expected this to make Poplog start faster. Measured, it does, a little. These are elapsed times in hundredths of a second for Prolog to start and halt, on the same files with and without sharing:

sharednot shared
VAXstation 4000/9619-2223-24
VAXstation 4000/6053-6061-62
simulator44

The bigger surprise was that the first kit’s installation was slower still (47 on the 4000/96, 80-97 on the 4000/60), and reinstalling the same files removed the difference whether they were shared or not. I do not know why. One guess is that the first installation’s files were fragmented on the disk. I cannot check it now, because the reinstall replaced those files.

Installing

As SYSTEM:

$ SET FILE/ATTRIBUTE=(RFM:FIX,LRL:32256,MRS:32256,RAT:NONE) -
     POPLOG1553_VAX_20260929.BCK      ! after a binary transfer
$ @POPLOG_INSTALL

It installs into SYS$COMMON:[SYSLIB.POPLOG...], checks the eleven executables and saved images against the checksums recorded when they were built, runs a test, shares the saved images, and tests again. It took 1.5 minutes on a simulator, 6 on the 4000/96 and 12 on the 4000/60. Then any user:

$ @SYS$COMMON:[SYSLIB.POPLOG]POPLOG_SETUP.COM
$ PROLOG

POPLOG_UNINSTALL removes exactly the files the install made, one explicit file name and version at a time, and keeps and reports any file that was changed or added since. Nothing else on the system is changed: no startup file, SYLOGIN, DCL tables, system logical names or UAF records.

Known limits

  • Not everything is there. The kit has Pop-11, VED, Prolog, Common Lisp, ML and the compiler tools, and the HELP files. It leaves out the REF and TEACH trees and the X Window System parts; the Starter Manual has the most useful teaching material instead.
  • Old dialects. Poplog Prolog 3.2 has no // and no 0'c. Poplog ML 2.1 follows the 1990 Definition, with no option or substring.
  • Single-precision decimals in Pop-11, as above.
  • % and * in arguments are RMS wildcards: write \%nort.
  • Shared images need a startup line to survive a reboot, and adding it is up to you.
  • It installs to SYS$COMMON:[SYSLIB.POPLOG] and nowhere else.

Rebuilding

The source kit’s README-FIRST.TXT lists what is where; POPLOG_VAX_REBUILD.md has every command, in order, from the Sussex tar file to a running kit, with the expected hash at each step and the time it took. You need a Linux x86-64 machine with GetPoplog Seed v0.3.2 (the exact files and hashes are given) and a VAX or SIMH with OpenVMS 7.3, VAX MACRO, and a C compiler (I used Compaq C V6.4) for a small run-time library of six objects.

How this was done

As with Emacs and Perl: Claude sessions with written briefs, working on two VAXstations and several simulators, with the rule that nothing counts until a test shows it, and with the transcripts kept. Every measured number above comes from one of those transcripts; the history in “Why Poplog” is background, not measurement.

Credits: Aaron Sloman and the Poplog teams at Sussex and Birmingham, who wrote Poplog and kept its sources available for twenty-five years; the GetPoplog project, whose Seed made the bootstrap possible; and Claude Code (Anthropic’s Claude models) for much of the building, debugging, testing and writing.