Perl 5.8.4 for VAX OpenVMS 7.3
My VAXstations now have Perl: Perl 5.8.4, as a binary kit for OpenVMS
VAX 7.3 that needs no compiler on the machine it is installed on. It
comes as a BACKUP saveset with a DCL installer and a DCL uninstaller, it
passes all but 8 of the 871 files of Perl’s own test suite, and it no
longer dies when a number gets too big – which the Perl 5.8.4 release does on a
VAX, for something as small as print 1.7e38.
Claude Code assisted, which is to say, did much of the hard work.
Downloads, with SHA-256 sums in SHA256SUMS.TXT:
- PERL584_VAX_20260930.BCK
(31 MB): the kit to install. Perl without
DEBUGGING. - PERL584_VAX_DEBUG_20260930.BCK
(31 MB): the same built with
DEBUGGING, only needed forperl -D. - PERL584_INSTALL.COM and PERL584_UNINSTALL.COM.
- README.TXT, which says everything below about installing in more detail, and SHA256SUMS.TXT.
- Source kit (13 MB): the unmodified Perl 5.8.4 release, every patch, the licence texts, the configure answers, and the build, test and kit scripts.
- Examples and tests: the programs shown below, the overflow tests, and their expected output.
- The file list of each saveset: default, DEBUGGING.
Perl is under the Artistic License or the GPL, at your choice; the source kit is the corresponding source.
Why 5.8.4
Three reasons, none of them deep:
- Its
readme.vmsstill describes building on a VAX with DEC C. - Craig A. Berry’s VMS build kit for it,
perlbuild584.zipfrom May 2004, is on the DECUS/OpenVMS freeware mirror with the whole release inside, so there was a known source to start from. - The ready-made alternatives turned out to be for other machines: the “Perl” on the HP freeware CD is an Alpha kit, and the 5.8.4 binaries on the mirror are for Alpha and Itanium.
I have not tried to build anything newer on the VAX.
The machines
- A VAXstation 4000/60 (80 MB) and a VAXstation 4000/96 (128 MB), both OpenVMS VAX 7.3 on ZuluSCSI disks, both headless.
- Simulated MicroVAX 3900s (SIMH) with the same VMS, for anything that might go wrong, and because they are many times faster.
- Compaq C V6.4-005 and MMS V3.7.
The first build was on the 4000/60. The kits you can download were built on the simulator, then installed and tested on both real machines.
Building and testing, measured
On the 4000/60, the unpatched release:
| Step | Time |
|---|---|
| The whole build, all standard extensions and every Encode table | about 6 h 40 min |
| Perl’s test suite, 871 test files, 73,592 tests | about 8 h 46 min of test time |
| Result | 766 files passed, 96 skipped, 9 failed |
Perl needs a page file quota above 100,000 pages to build, according to
readme.vms; I built with 131,072.
On the simulator, a pair of clean builds with and without DEBUGGING,
timed one after the other on an otherwise idle machine:
DEBUGGING | no DEBUGGING | |
|---|---|---|
| Configure | 1 min 10 s | 1 min 4 s |
Build (MMS) | 38 min 31 s | 34 min 36 s |
| Test suite | 43 min | 40 min |
That pair had only the first fix described below. The two builds that
became the kits were made the next day with the whole patch (the
DEBUGGING one took 37 min 34 s; the other shared the machine with a
test run, so its time means nothing). Their results:
DEBUGGING kit | default kit | |
|---|---|---|
| Test suite | 767 passed, 96 skipped, 8 failed | the same, file for file |
PERLSHR.EXE | 2131 blocks | 1916 blocks |
Simulator times say nothing about a real VAX except the ratio between the two columns.
The eight failures that remain are all explained. One is a DCL
command-line length limit in the test for perl -P. One is a test with a
two-digit year that now means 2070. The other six are big-number tests
(one of them passes all its tests and is counted as failed only because
it now prints a warning), and they are the subject of the next section.
The bug hunt: print 1.7e38
A VAX double (D_FLOAT) stops at about 1.7 x 10^38, and there is no infinity. A result that does not fit is a hardware fault. Perl 5.8.4 as released, on the 4000/60:
$ perl -e "print 1.36514538e37, qq(\n)" ! 1.36514538e+37
$ perl -e "print 1.7e38, qq(\n)"
%SYSTEM-F-FLTOVF_F, arithmetic fault, floating overflow at PC=0016D2D1, PSL=03C00000
Improperly handled condition, image exit forced.
and then a register dump, and Perl is gone. Not even eval helps: the
process image has been killed. 1.7e38 is a perfectly good D_FLOAT number,
and readme.vms promises that overflow while converting a string to a
number is handled silently. That is how t/op/pack fails: one constant
in the test file, 1.36514538e67, kills Perl before the first test.
Where. No debugger was needed. The PC in the crash, a link map of
PERLSHR, and a machine-code listing of numeric.c put it on one
addd3 instruction in Perl_my_atof2. Perl reads the digits before and
after the decimal point separately, scales each by a power of ten, and
adds the two. The scaling has a VMS-only guard against overflow. The
addition has none. For 1.7e38 the integer part has already been pushed
to the largest number by that guard, and adding the fraction to it
overflows.
Upstream. I looked at numeric.c in later Perls (5.8.9, 5.10.1,
5.14.4, 5.20.3, 5.28.3 and 5.38.0): the guard is there in all of those,
and so is the unguarded addition. There was nothing to backport.
The first fix was small: saturate that addition. The obvious test,
“is hi greater than MAX - lo”, turned out to be wrong in a rare case,
a rounding tie that hides the overflow. The test that is exact
is hi/2 + lo/2 > MAX/2, because halving a binary floating-point number
loses nothing.
Then everything else. A literal was only the most embarrassing way to
overflow. $x * 10, $x + $x, 2 ** 200, hex of a long string,
List::Util::sum: each one killed Perl the same way. The C library, it
turned out, does not: pow, exp and strtod quietly return the largest
number. So I made Perl’s own arithmetic do what the C library already
does: give the largest number, and warn about it under use warnings.
On the simulator, with the patched DEBUGGING build:
$ PERL "-w" -e "use B::Deparse; my $x = 9**9**9; my $y = 1.7e38; my $z = $y * 10; print qq(loaded\n)"
Floating point overflow in multiplication (*), result set to 1.70141e+38 at -e line 1.
loaded
(9**9**9 is there for a reason. My first version also warned when the C
library’s pow saturated, and Perl’s test suite promptly failed two tests
it had been passing: B::Deparse writes 9**9**9 when it means infinity,
so every program that loaded it got a warning. The warning now appears
only where Perl used to crash.)
A safety net. Perl’s own arithmetic is not the only code that can
overflow; any compiled extension can. So there is also a VMS condition
handler that turns a floating fault into an ordinary Perl error. This was
harder than it sounds. A handler may only unwind to a stack frame that
has itself established a handler, so one handler in main crashed with
an access violation; the handler had to go into the macro Perl uses
everywhere it sets up an eval. On the 4000/60:
$ PERL SAFETYNET.PL 1000
fault inside eval is caught ok (Reserved floating point operand in unpack)
1000 faults in a loop, all caught ok (1000 caught)
nested eval: inner catches ok
$SIG{__DIE__} is called ok (Reserved floating point operand in unpack)
fault inside a sort block is caught ok (Reserved floating point operand in unpack)
fault inside an overloaded + is caught ok (Reserved floating point operand in unpack)
ordinary arithmetic afterwards ok (5050)
SAFETYNET: 7 ok, 0 not ok
SAFETYNET: now a fault outside eval (the script should die here)
Reserved floating point operand in unpack at safetynet.pl line 15.
%SYSTEM-F-ABORT, abort
And a hang that went away. In the release, a script that sets
$SIG{FPE} and then overflows never finishes: VMS restarts the faulting
instruction after the handler returns, it faults again, forever. With the
safety net the script gets a Perl error in under a second of CPU (on the
simulator; the unpatched Perl ran into the 30-second CPU limit I gave it).
Before and after, on the 4000/60. The test in the examples archive has 33 cases, each run as a separate Perl. With the release, 6 ran and 27 killed Perl. With the kit, all 33 do what they should:
case 11: MAX + MAX => MAX [warning: Floating point overflow in addition (+), result set to MAX]
case 13: MAX/2 * 4 => MAX [warning: Floating point overflow in multiplication (*), result set to MAX]
case 31: XS overflow inside eval (Time::HiRes::alarm) => caught: Floating point overflow in subroutine entry
case 32: reserved operand inside eval (unpack d) => caught: Reserved floating point operand in unpack
The whole patch changes eight source files and is conditional on “VMS
without IEEE floating point”. It is in the source kit as
VAXFP_OVERFLOW.DIFF.
DEBUGGING or not
On VMS, Perl’s Configure builds with DEBUGGING unless told otherwise,
so that is what my first kit had. The kit without it is smaller and
faster, and gives the same test results, so it is now the default. Five
small workloads, CPU seconds, three runs each, on the simulator:
| Workload | no DEBUGGING | DEBUGGING | Cost |
|---|---|---|---|
| hash | 14.63 | 15.31 | +4.7% |
| sort | 14.20 | 15.11 | +6.4% |
| regex | 8.54 | 9.49 | +11.0% |
| string | 4.44 | 4.83 | +8.8% |
| numeric | 8.11 | 9.60 | +18.4% |
| total | 49.92 | 54.34 | +8.8% |
I have not run this on a real VAX; I would expect the ratio to hold and the absolute times not to.
Installing
You need OpenVMS VAX 7.3, SYSTEM (or SYSPRV), and about 122,000 free
blocks on the system disk. No compiler.
Get one saveset and the two
.COMfiles into one directory on the VAX. Transfer modes matter: the.BCKin binary, the.COMfiles in ASCII.A binary transfer loses the saveset’s record attributes. Put them back:
$ SET FILE/ATTRIBUTE=(RFM:FIX,LRL:32256,MRS:32256,RAT:NONE) - PERL584_VAX_20260930.BCK(The installer refuses the kit and prints this command if you forget.)
As
SYSTEM:$ @PERL584_INSTALLIt checks privilege, space and the saveset, restores the tree to
SYS$COMMON:[SYSLIB.PERL584...], writes a manifest of every file it installed, and runs a smoke test. It took 35 seconds on the simulator, 3 minutes on the 4000/96 and 6 minutes on the 4000/60. It changes nothing else on the system: no startup file, noSYLOGIN, noDCLTABLES, no system logical name.Each user, at the prompt or in
LOGIN.COM:$ @SYS$COMMON:[SYSLIB.PERL584]PERL_SETUP.COM $ PERL -v
@PERL584_UNINSTALL CHECK says what an uninstall would remove, and
@PERL584_UNINSTALL does it. It deletes only files the manifest lists
and that are still exactly as installed, one explicit
DELETE file;version at a time, and keeps and lists anything you changed
or added. VMS file versions make that easy to get right. Besides many
runs on simulators, I uninstalled it once on the 4000/96: CHECK found
all 1487 files unchanged, the uninstall took 4 minutes 13 seconds and
removed the whole tree, nothing else on the system changed (I compared
directory listings of SYS$COMMON:[SYSLIB], SYS$MANAGER and
SYS$STARTUP before and after), and a fresh install afterwards took
3 minutes and passed the same tests as before.
Perl that knows it is on VMS
The kit has the VMS extensions, and they are the fun part. Three small programs from the examples archive, with their output on the 4000/60:
DCL symbols, read and set from Perl (VMS::DCLsym):
DCLSYM: PEX_INPUT (LOCAL table) has 6 numbers; sum 90, max 31, sorted 4,5,9,15,26,31
RMS record files, fixed and variable length (VMS::Stdio::vmsopen):
RECORDS: PEX_FIXED.DAT: 5 records read back, RMS says FIX 32
RECORDS: [record 1: 1 squared is 1 ]
RECORDS: [record 2: 2 squared is 4 ]
...
RECORDS: PEX_VAR.DAT: 5 records read back, RMS says VAR 0
RECORDS: [record 1: 1 squared is 1]
File versions. On VMS glob("*.*;*") returns every version of every
file:
my %v;
for my $path (glob("${dir}*.*;*")) {
my ($name, $ver) = $path =~ /([^\]>]+);(\d+)$/ or next;
push @{ $v{uc $name} }, [$ver, $path];
}
VERSIONS: A.TXT 3 version(s), highest ;3, 36 bytes; oldest 'first A', newest 'third A'
VERSIONS: B.TXT 1 version(s), highest ;1, 10 bytes; oldest 'only B', newest 'only B'
VERSIONS: unixify gives /SYS$SYSDEVICE/HOME/USER1/LOGIN.COM; vmsify gives it back as SYS$SYSDEVICE:[HOME.USER1]LOGIN.COM
(My first RECORDS.PL wrote its five variable-length records as one,
because Perl buffers print. The first test passed anyway: it checked the
file’s record format, not its records.)
Known limits
- Numbers still stop at 1.7e38. An overflow now gives the largest
number instead of killing Perl, but the largest number is not the right
answer. Six of the eight test files that still fail are big-number
tests. The clearest case: Math::BigRat’s
bsqrtof1/3turns a value of about 40 digits into a native number on the way, which now saturates instead of crashing, and the result is a wrong fraction. Keep big values as objects or strings. $SIG{FPE}is never called. The fault becomes a Perl error first; useeval.Time::HiRes: a sub-second alarm does not interruptselect().%ENV: assigning to it creates a process logical name that is still there after Perl exits.localtimeuses the C library’s time zone rules, which in VMS 7.3 are the US rules from before 2007, so it is an hour off for a few weeks each spring and autumn.- No G_FLOAT kit.
readme.vmssays Perl can be built with/FLOAT=G_FLOAT, which reaches about 9 x 10^307. I have not built it. The patch was written so that it should be right for G_FLOAT too: it never mentions a D_FLOAT constant. - Core Perl only: no CPAN modules, no threads.
- The kit installs to
SYS$COMMON:[SYSLIB.PERL584]and nowhere else.
Rebuilding
The source kit’s README.TXT has the steps: unzip the release, copy the
eight patched files over it (they are there in full, in case your VAX
has no patch), and submit one batch job that runs Configure and MMS. Perl’s
source tree is deeper than the eight directory levels ODS-2 allows in one
file specification, so everything goes through concealed logical names.
That also caught me once at install time: the prefix Configure wrote,
SYS$COMMON:[SYSLIB.PERL584.], is a concealed root inside a concealed
root, and RMS will not have it. One line of the generated PERL_SETUP.COM
is changed for that, and that diff is in the source kit too.
How this was done
As with Emacs: Claude sessions with written briefs, on two VAXstations and a simulator, with the rule that nothing counts until a test fails without the fix and passes with it, and the transcripts are kept. Every number above comes from one of those transcripts. Every kit install was tried on a simulator first.
Credits: Larry Wall and the Perl 5 Porters for Perl; Craig A. Berry and
the other maintainers of the VMS port, whose readme.vms and build kit
made this possible twenty-two years later; and Claude Code (Anthropic’s
Claude models) for much of the building, debugging, testing and writing.
