My VAXstations now have Vim 9.2 (patch level 491, February 2026 source): the character-cell build for OpenVMS VAX, installed for all users with a DCL installer and a DCL uninstaller. It also turns syntax highlighting on without crashing, which took a bug hunt.
The first kit (October 14) installed the prebuilt vim.exe that Zoltan Arpadffy’s
Polarhome service builds, exactly as supplied. That program had a defect: with syntax
highlighting or file-type detection on, it ended with “Caught deadly signal SEGV” on
.TXT files, on an empty buffer and in :help. The new kit, kit C, has a VIM.EXE
I compiled from the matching VMS source with a six-line fix in Vim’s C code. The
cause turned out to be neither the compiler nor the VAX. It was a C union read through a
different member than the one written, which every other platform happens to tolerate.
The story is below. A second small fix came along the way: Vim on VMS now reads a
.VIMRC made with EDIT or CREATE properly, instead of as one line.
The current kit, kit E2 (October 27), is the same VIM.EXE with a system-wide vimrc in
one of two modes, chosen at install time: COLOUR, syntax colouring on for every user (my
4000/90 and 4000/96 run that), or PLAIN, colour off unless you ask for it and no standard
plugins except matchparen, for the slow machines (my 25 MHz 4000 VLC, where a start with colour
took 36 seconds).
Claude Code did much of the hard work.
Downloads, with SHA-256 sums in SHA256SUMS.TXT:
- VIM_VAX_92_491E2.BCK
(48 MB, SHA-256
7c7c3a76aaccff0c47ee23996878dd7c8ad997484025a09b49e8bf369df54c29): kit E2, the one to install, and its file list VIM_VAX_92_491E2_LIST.LIS. (Kit E, VIM_VAX_92_491E.BCK, is kept for the record.) Its two system vimrc files are also here on their own: VIMRC_COLOUR.VIM and VIMRC_PLAIN.VIM; the installer copies one toVIM:VIMRC.. - VIM_INSTALL.COM and VIM_UNINSTALL.COM; the saveset also holds VIM_SETUP.COM, which each user runs.
- The fixes, VIM9CMDS_WHILE_END.DIFF and SCRIPTFILE_SOURCE_TEXT.DIFF, the build settings, KIT_SETTINGS.DIFF, and the regression test, WHILE_TEST.VIM. All four are also in the saveset.
- README.TXT and SOURCES.TXT
(the provenance: every archive’s hash, the patched files’ hashes, how
VIM.EXEwas built). - VIM_TEST.COM, the test a user runs.
- The source I built from, Polarhome’s unmodified
vim-92-src-vms.zip
(
af138e18afb121b67123adff24231e0434f5eab762c7da7f03b8b0da99d67d21), and the batch job that built it, BUILD_FIX.COM. - The licence: LICENSE.TXT (the Vim licence).
- Earlier kits stay here for the record: kit C,
VIM_VAX_92_491C.BCK (
3065c45c...0696a, the sameVIM.EXE, no system vimrc), and the first kit, VIM_VAX_92_491B.BCK (0bd490e7...a396bbf5dd), with the unmodified, crashingVIM.EXE.
The bug hunt
(M) Everything in this section was measured, on a simulated MicroVAX 3900 (SIMH, OpenVMS VAX 7.3, the same Compaq C 6.4) unless it says Linux.
Narrowing it down. The crash came when Vim’s filetype detection fell back to its Vim9
script autoload/dist/script.vim. I cut that script into pieces:
Compiling all of it (
:defcompile) worked. Running it crashed, so the fault was in execution, not in the compiler of Vim9 script.Its big function,
DetectFromText, is one longif/elseifchain. With all 46 branches it ran. With the finalelseblock added, it crashed.That
elseblock, split into one construct per test, gave the answer. Every function with a:whileloop crashed, and nothing else did, down todef F() var n = 5 while n < 3 n += 1 endwhile enddef
Vim told me why. :disassemble F printed 4 WHILE $1 -> 655360. The loop ends at
instruction 10, and 655,360 is 10 × 65,536. Another function said -> 720896 = 11 × 65,536
where 11 was right. The jump target was stored 16 bits too high. When the loop condition was
false, Vim jumped to instruction 655,360 of a 13-instruction function.
The cause. Vim compiles each instruction into a C struct whose arguments sit in a union.
A plain jump keeps its target in jump_T, { enum; int }. A :while keeps it in
whileloop_T, { short; int }. At :endwhile the compiler fills in the targets of all the
pending jumps, and it writes them through jump_T, the :while instruction included.
Execution then reads it back through whileloop_T.
With normal alignment both ints sit at offset 4, so this works on every other machine.
DEC C on VAX does not pad structures by default. I measured it with a ten-line C program:
| Compaq C qualifiers | sizeof(whileloop_T) | offset of while_end | offset of jump_where | while_end after writing 11 as jump_where |
|---|---|---|---|---|
| default | 6 | 2 | 4 | 720896 |
/MEMBER_ALIGNMENT | 8 | 4 | 4 | 11 |
So it was not the compiler and not the VAX. It was a C portability bug that only a compiler that packs structures could expose. My own rebuild from the unmodified source crashed in exactly the same way, so a rebuild alone could not have fixed it.
The fix is to write the member the instruction actually uses:
isn = ((isn_T *)instr->ga_data) + cur->el_end_label;
if (isn->isn_type == ISN_WHILE)
isn->isn_arg.whileloop.while_end = jump_where;
else
isn->isn_arg.jump.jump_where = jump_where;
Building the whole of Vim with /MEMBER_ALIGNMENT would also have hidden it. But that changes
the layout of every structure, including the ones that go to the VAX C library and system
services, and it would fix nothing for anyone else.
Are there more like it? (M, Linux) I built the same Vim source on Linux with Vim’s own
types packed to one byte (#pragma pack(1), the VAX layout) and without :terminal and
channels, as on the VAX. The packed build crashed on the same test. With the fix, all 14 of
Vim’s own test_vim9_* suites gave exactly the same results as a normal build. Without it,
three suites failed, the ones with :while loops. Every other place that patches an earlier
instruction writes the right member.
Installing
You need OpenVMS VAX 7.3, SYSTEM (or SYSPRV), about 180,000 free blocks while installing
(the 94,563-block saveset and the 80,000 blocks of the installed tree), and patience. The
restore of about 4,770 files takes 10 to 12 minutes on a VAXstation 4000/90 or 4000/96, and
34 minutes on a 25 MHz 4000 VLC.
- Put the saveset and the
.COMfiles into one directory..BCKin binary,.COMin ASCII. $ @VIM_INSTALLrestores the tree intoSYS$COMMON:[SYSLIB.VIM], verifies it, writes a manifest and smoke-tests it with a non-interactive edit.@VIM_INSTALL "" REPLACEreplaces an installed copy (kit B or C, for example; 15 to 19 minutes on a 4000/90 or 96). Nothing is changed outside that directory.- Each user:
@SYS$COMMON:[SYSLIB.VIM]VIM_SETUP. It definesVIM,VIMRUNTIMEand the commandsVI,VIM,VIEW,XXDandEXCTAGS. $ @VIM_UNINSTALLremoves exactly what the manifest recorded, and keeps any file that was changed.
Options are quoted, because the VMS C run-time makes unquoted command lines lower case:
-R is written -/r (that is how Vim’s own os_vms.txt spells it), and -u NONE must be
written -u "NONE". Unquoted, -u none names a file called none: Vim says E282 and
loads all the plugins anyway. My first kit’s README made exactly that mistake in its
“reproducers”.
The second fix: .VIMRC files made with EDIT
(M) The first kit C test run turned up an older defect. A file Vim sources (your
.VIMRC, :source) was read line by line only if it was Stream_LF. A file made by
CREATE, EDIT or FTP in ASCII mode has variable-length records, and Vim read all of it
as one line, with errors like E121: Undefined variable: echomsg.
The cause is one word. On VMS, Vim opens scripts in binary mode ("rb"), and in binary mode
the VAX C library returns a variable-length file’s records with nothing between them. Opening
scripts in text mode ("r") on VMS only fixes it. Variable-length, VFC and a 5,000-character
line all now read correctly, Stream_LF is unchanged, and startup time is the same within the
noise. Your .VIMRC can now be written with any editor.
One DCL trap remains: COPY VIM:VIMRC_EXAMPLE.VIM SYS$LOGIN:.VIMRC makes
VIMRC_EXAMPLE.VIMRC, because DCL fills the missing file name in from the input. To start
from the example, let Vim write it:
$ VIM -es -u "NONE" -i "NONE" -c "write sys$login:.vimrc" -c "qa!" VIM:VIMRC_EXAMPLE.VIM
Syntax colouring for everyone (kit D2)
(M) Would colour even work? The VAXen are used over Telnet, and VMS decides the terminal type at
login (SET TERMINAL/INQUIRE): from my Linux terminal it says VT200_Series, No Ansi_Color.
That looked bad. But VMS Vim takes its terminal name from the C library (vt200-80), and it matches
every vt1..vt5 name to its built-in vt320 description, which has 8 colours and ANSI colour
codes. VT100, VT400 and even UNKNOWN (which falls back to vt320) all came out as t_Co=8, and the
session logs are full of ESC[34m comments and ESC[33m keywords. A real monochrome DEC terminal
should simply ignore those codes; I have not tried one yet.
So the kit now installs a system vimrc, VIM:VIMRC. (what :version calls $VIM/vimrc):
filetype plugin indent on
if &t_Co > 2
syntax on
endif
It is read before your own SYS$LOGIN:.VIMRC, so syntax off there still wins. One subtlety:
Vim loads defaults.vim, which does the same two things, only when you have no vimrc of your
own. The first version (kit D) ran them anyway, so for users without a .VIMRC syntax was loaded
twice. Kit D2 first checks whether Vim will find a vimrc, exrc, VIMINIT or EXINIT of yours (the
same list, in the same order, as Vim’s main.c) and does nothing if not. One thing the test turned
up: on VMS, Vim’s “second user vimrc”, sys$login:vimfiles/vimrc, does not name
SYS$LOGIN:[.VIMFILES]VIMRC.; Vim never reads it.
What colour costs (VAXstation 4000/96, starting Vim on a two-line C file, 3 runs each; the “now” column is measured, the “before” column partly derived):
| Who | Before (kit C) | Now (kit D2, M) |
|---|---|---|
You have no .VIMRC (defaults.vim already turned syntax on) | the same as now (derived: the system vimrc does nothing for you) | 7.5 s |
You have a .VIMRC without syntax lines | more than 2.6 s (measured with -u, which leaves 'compatible' on and skips most plugins) | 7.4 s |
vim -u "NONE" (nothing at all) | 1.1 s (M) | 1.1 s |
By difference (derived, not profiled: this build has no --startuptime), filetype detection plus
C syntax is roughly 4 s of that and the standard plugins roughly 2 s. On the
VAXstation 4000 VLC (25 MHz) everything takes about 4.7 times as long: 35.8 s with no .VIMRC,
35.5 s with one (more than 9.3 s for the same user without colour, measured the same flawed
way), 3.3 s for vim -u "NONE". A user with no .VIMRC waits that long with any kit, because it
is Vim’s own defaults.vim that turns syntax on, which is why kit E (below) switches it off there.
I got the baselines wrong twice along the way, both times by trusting a command-line switch to
mean what I wanted. vim -u "DEFAULTS" also skips the plugins, so I first concluded that the
double syntax on cost 2.5 s; it cost about 0.45 s (the README in kit D2 still repeats the 2.5 s;
the next kit fixes the text). And vim -u myfile leaves 'compatible' on, which makes most
plugins quit at once, so my “without colour” figures for users with a .VIMRC are too low.
Colour off on the slow VAXen (kit E)
Thirty-six seconds to open a file is too long, so the slow machines now get colour off by
default, and anyone who wants it puts it in their own .VIMRC. Rather than two kits, kit E
carries both system vimrc files and the installer chooses:
$ @VIM_INSTALL VIM_VAX_92_491E2.BCK REPLACE PLAIN ! or COLOUR (kit E2, below, installs the same way)
Left out, the choice is made from F$GETSYI("HW_NAME"): PLAIN on a “VAXstation 4000-VLC” or a
3100, COLOUR anywhere else. The manifest records it, so the uninstaller and the next person know.
The PLAIN vimrc has one job for a user with no vimrc of their own: stop defaults.vim
(let g:skip_defaults_vim = 1) and do everything it does except filetype detection and syntax:
nocompatible (without defaults.vim nothing else turns Vi mode off), scrolloff=5, the
jump-back-to-last-position autocommand, and so on. For a user who has a vimrc it does nothing, so
one line in your .VIMRC gets the colour back:
source $VIMRUNTIME/defaults.vim
What it saves (M, VAXstation 4000 VLC, seconds per start on a two-line C file, 3 runs each, the real start-up path every time):
| Before (kit D2, COLOUR) | Now (kit E, PLAIN) | |
|---|---|---|
No .VIMRC | 36.4 | 13.0 |
Own .VIMRC without syntax lines | 35.5 | 12.4 |
Own .VIMRC with syntax on and filetype plugin indent on | 37.6 | 39.6 |
No .VIMRC, --noplugin | 27.1 | 4.3 |
vim -u "NONE" | 3.3 | 3.3 |
So a start without colour now takes about 13 seconds instead of 36, and colour costs about 27 seconds on this machine, whichever kit is installed (the third row). The standard plugins (matchparen, gzip, zip, tar and the rest) still cost about 9 seconds of the 13, which is the next section.
No plugins on the slow VAXen (kit E2)
This build has no --startuptime, so I timed each plugin by switching it off (its
g:loaded_... flag set on the command line, 3 starts each, on the VLC). No single plugin was
the problem. With every flag set, 10.2 of the 13 seconds remained: Vim still searches the
runtime path and opens all 14 plugin files, only to have each one stop at its first if. What
does help is the option that skips the search altogether, 'noloadplugins' (4.6 s), and then
loading the one plugin worth having by name (about 4.9 s):
set noloadplugins
runtime plugin/matchparen.vim
So kit E2’s PLAIN vimrc does that, for every user, and one line in your .VIMRC gets all the
plugins back (Vim loads them after every vimrc has been read):
set loadplugins
Two things I nearly got wrong. matchparen does nothing while 'compatible' is set, and the
system vimrc runs before Vim has found your .VIMRC and switched 'compatible' off. So,
as first written, the runtime line would have loaded nothing for exactly the users with a
.VIMRC. The vimrc now switches it off for them first, as Vim is about to anyway. And
netrw (:Explore, editing a directory) never loaded on VMS at all, in any kit. Its plugin
is only packadd netrw, and :packadd looks for plugin/**/*.vim; the VMS port cannot expand
** (Vim’s start-up code even says so and avoids it there). So :packadd finds the package,
adds it to the runtime path, and runs nothing. By hand it works:
set runtimepath+=$VIMRUNTIME/pack/dist/opt/netrw
runtime! plugin/netrwPlugin.vim
That one goes upstream with the other findings.
What it saves (M, VAXstation 4000 VLC, seconds per start, 3 runs each):
| Kit E (PLAIN) | Kit E2 (PLAIN) | |
|---|---|---|
No .VIMRC | 13.2 | 5.0 |
Own .VIMRC without syntax lines | 12.5 | 4.4 |
Own .VIMRC with syntax on and filetype plugin indent on | 39.5 | 31.3 |
No .VIMRC, --noplugin | 4.5 | 4.9 |
vim -u "NONE" | 3.3 | 3.3 |
From 36 seconds with colour on October 26 to 5 seconds on October 27, on a 25 MHz machine. Even colour,
switched on in your own .VIMRC, is 8 seconds quicker, because the plugins are gone from that
start too. The --noplugin row went up a little: the system vimrc loads matchparen even then.
The interesting parts
- ODS-2. Names are at most 39 characters. Eight of the 4,766 files in the runtime
archive have longer names (Vim’s own syntax-test inputs and expected screens);
UNZIPrefuses them, and they are not in the kit, which lists them inREADME.TXTwith sizes and hashes. 39 more, in the source archive’s test directory, could not be unpacked for the build either; nothing in the build needs them. - Eight directory levels. Seven runtime files lie deeper than that below
SYS$COMMON:[SYSLIB], and RMS refuses such names. They can only be reached through a rooted logical name (DEFINE/TRANSLATION=CONCEALED), so the installer restores through one. Restoring a saveset through a rooted name must sayROOT:[*...].ROOT:[000000...]makes a real subdirectory called000000; I did that once in this round. - Vim cannot use a rooted name as
VIMRUNTIME. What works is a rootedVIMROOTwith ordinary names pointing into it:VIM = VIMROOT:[000000],VIMRUNTIME = VIMROOT:[VIM92]. - E138 on first use. Vim 9.2 on VMS cannot create the viminfo file, so
VIM_SETUPcreates an empty.VIMINFOonce. - The maintainers’ own “modified by” setting does not build on VAX. Setting
MODIFIED_BYinMake_vms.mmsmakes the makefile append a#definetoconfig.hwith DCLOPEN/APPEND. DEC C then cannot read the file (%RMS-W-RTB, 76 byte record too large). I put the#defineinos_vms_conf.hinstead. - The test.
VIM_TEST.COMmakes 19 checks as an unprivileged user: banner, a non-interactive edit that keeps the record format and makes a new version, syntax highlighting,:helpfiles, the optional packages,XXD,CTAGSand the viminfo write. Kit C adds the four cases that used to crash, the regression test and two checks of variable-length scripts; kit D2 adds eight checks of the system vimrc (no vimrc, your own.VIMRC,syntax offwinning,.EXRC,VIMINIT,_VIMRC.): 27 in all. Kit E addssyntax onin your own.VIMRCwinning, and kit E2 checks which plugins each kind of user gets,set loadpluginsbringing them back andNoMatchParenswitching matchparen off: 30 in all.
Results (M = measured)
- (M, simulator) Building my
VIM.EXEfrom the source archive: 24 minutes 52 seconds (unpack 3 min 45 s, compile and link 21 min 5 s), on a SIMH MicroVAX 3900 sharing a busy Linux host (not a VAXstation; real-VAX times to follow). - (M, simulator) Before the fix, 8 of the 64 narrowing tests crashed: every one that contains a
:whileloop. With the fix all 64 run, and the regression testWHILE_TEST.VIMpasses. - (M, simulator)
VIM_TESTas a fresh, unprivilegedUSER1: 19 of 19 for kit C, in 18 seconds; 27 of 27 for kit D2, in 22 seconds. - (M, simulator) The lifecycle test (install, refuse a second install,
CHECK, a changed file and a user file survive the uninstall, uninstall, second uninstall says “not installed”, reinstall,REPLACE): 21 of 21 for kit C, in 35 and 47 minutes on two runs (the host was busy), and 23 of 23 for kit D2 (two more checks for the system vimrc), in 30 minutes; each restore of the 4,772 or 4,773 files took 7 to 16 minutes. (An earlier draft said 22 of 22 for kit C: I had counted the summary line.) - (M, Linux) Vim’s own Vim9 test suites with the VAX structure layout: the same results as a normal build, with the fix.
- (M, VAXstation 4000/90) Installing kit C over kit B with
REPLACE: 18 minutes 28 seconds (removing kit B 6 min 33 s, restoring 4,772 files 11 min 34 s).VIM_TESTas an unprivileged user: 19 of 19. Interactively, a.VIMRCtyped withCREATE, a.TXTfile with syntax on, and:helpall work – the program the first kit shipped crashed on the last two. - (M, VAXstation 4000/96 and 4000/90, October 26) Kit D2 over the installed kit with
REPLACE: 15 min 14 s and 18 min 34 s;VIM_TEST27 of 27 on both; colour checked on the screen. - (M, VAXstation 4000 VLC) A fresh install of kit D2: 33 min 58 s (the saveset
went over by RCP through an SSH tunnel in 2 min 25 s);
VIM_TEST27 of 27; colour checked. Then kit E in PLAIN mode over it: 58 min 12 s (removing D2 23 min, restoring 34 min);VIM_TEST28 of 28; the manifest recordsVIMRC_MODE PLAIN. - (M, simulator) Kit E: lifecycle 31 of 31;
VIM_TEST28 of 28 in PLAIN mode and again in COLOUR mode after aREPLACE. - (M, simulator) Kit E2: lifecycle 32 of 32;
VIM_TEST30 of 30 in PLAIN and in COLOUR mode; kit E2 installed withREPLACEover kit E, as on the real machine, keeps nothing of kit E and passes again. - (M, VAXstation 4000 VLC, October 27) Kit E2 in PLAIN mode over kit E with
REPLACE: 58 min 16 s (removing kit E 23 min, restoring 34 min; the saveset went over by RCP in 2 min 25 s);VIM_TEST30 of 30; a start with no.VIMRC5.0 s (it was 13.2 s). - The 4000/60 and the 3100/M38 are switched off until November; they get kit E2 then (the 3100 in PLAIN mode), and the 4000/90 and 96 move from D2 to E2 in COLOUR mode.
Things this does not do
:helpneeds a window. Run from a batch job (no terminal), Vim sees a screen of 2 lines by 12 columns and saysE36: Not enough room. Interactively it works.:packaddruns no plugin file on VMS (the**pattern above), sonetrwandmatchitdo not load unless you load them by hand.- No GUI (the VAXen are headless), no Python/Perl/Lua, no clipboard.
- The fixes have not gone upstream yet.
Licence
Vim is distributed under the Vim licence, which is charityware (:help uganda).
Unmodified parts may be passed on with the licence text. The runtime files, XXD.EXE and
CTAGS.EXE in the kit are unmodified. My VIM.EXE is a modified Vim, which the
licence also allows on conditions:
- the licence text goes with it, unmodified;
- the changes go with every copy (the three diffs above, which are also in the saveset);
- there is a contact for the modified version (me);
:versionand the intro screen say it was modified.
Mine say Modified by Douglas P. Fields, Jr. <symbolics@lisp.engineer>. My changes are
under the Vim licence. The installer, uninstaller, setup procedure and tests are mine.
What the package is
Polarhome’s vim-92-runtime.zip (the runtime files) and vim-92-exe-vax-term.zip (from which
the kit keeps XXD.EXE and CTAGS.EXE), and my VIM.EXE. I built it from Polarhome’s
vim-92-src-vms.zip (vim92-491) with Compaq C V6.4-005 on OpenVMS VAX V7.3 and MMS, with
the same compiler options as theirs (/optimize /prefix=all, MODEL = HUGE, terminal only).
It is a terminal-only build (+syntax, +acl, +vim9script; no GUI, no Python, no job or
channel). The archives and their hashes are listed in SOURCES.TXT.
