The pymdl book

Appendix E. Measured differences and discoveries

Hand-written, from the "Found along the way", "Measured, MDL 55" and "pymdl's own" boxes in the chapters, and from docs/pymdl/harvest.md (the working file that gathered the raw material out of code comments, test docstrings, CHANGES.md and commit messages).

This appendix exists because the same two questions come up on every page of this book and are answered a paragraph at a time: where does pymdl deliberately differ from MDL 55, and what did this project learn about MDL 55 that the manuals do not say? The chapters answer them in place, where the reader meets them. This is the only place they are listed together.

Three things this appendix is not. It is not a bug list -- a genuine defect is a thing to fix, and the ones found while writing this book were fixed, with tests, and are recorded below only as discoveries. It is not a feature comparison -- pymdl is not trying to beat MDL 55 at anything. And it is not exhaustive about the machine: chapter 36 documents the emulator's own divergences from real hardware at their own sites, because they are answerable only against a specific CPU.

E.1. What "measured" means

A claim in this book marked Measured, MDL 55 was obtained by asking the question of a real MDL 55 running under an emulated PDP-10 and asking the same question of pymdl, and comparing the answers. Chapter 37 is how that is done; the short version is that tools/era_session.py drives one long-lived MDL 55 through its own terminal, and tools/mdl55_errhandler.py installs an "ERROR" interrupt handler (chapter 23, section 21.8.11) so that a question with a wrong answer does not end the session.

Three batteries of such questions are kept:

what size where
the oracle corpus -- expressions asked of both 710 forms tests/data/oracle/corpus*.mdl
the error battery -- generated forms chosen to provoke banners about 720 forms from five generators tests/era_battery/
the compiler shape oracle -- FUNCTIONs compiled both ways 640 shapes tools/era_oracle.tsv

Those sizes are checkable: the golden file has one line per corpus form, the shape oracle one non-comment line per shape, and tests/test_comprehensive_bars.py runs the battery's generators.

The oracle corpus is compared against a golden file of pymdl's own answers, not the era's: it is a regression net proving pymdl has not silently changed, which is a weaker and more honest claim than proving it right. Re-deriving the era side needs the emulator, and tests/test_mdl55_oracle.py is what does it.

E.2. Deliberate differences

Each row is a place where pymdl answers differently from MDL 55 on purpose, because the era's answer depended on a machine or an operating system that is not here. Each names the chapter where it is explained.

E.2.1. The substrate

difference pymdl MDL 55 chapter
Compiled code Python functions (PYCODE), or era PDP-10 code under the emulator PDP-10 machine code 33, 34
A SUBR a Python function assembled PDP-10 code in the interpreter 6
Recursion depth Python's stack, raised at start-up, with an evaluator guard that reports runaway recursion as an MDL error the control stack, which GROWs 13
Arithmetic width 36-bit FIXes carried through +, -, *, /, so <OVERFLOW <>> still wraps the hardware 19, 21
FLOAT a Python float, except that CHTYPE between FIX and FLOAT reinterprets a genuine 36-bit word PDP-10 single precision 9

The FLOAT row is the one with a seam in it, and chapter 9 has why: <MIN> and <MAX> must CHTYPE to the extreme FIXes, because Zork's "biggest number" idiom depends on it.

E.2.2. Storage

difference pymdl MDL 55 chapter
The garbage collector Python's; GC, BLOAT, BLOAT-STAT and GC-MON all exist and answer the documented shapes, about pymdl's own storage a copying or mark-sweep collector over five address spaces 24
GROW's collection not reproduced; the 32/64 rounding is "requires a garbage collection every time" 10
Associations held strongly, so one survives until removed by hand collected with their item and indicator 16
An escaped TUPLE still readable, but LEGAL? answers #FALSE () and printing refuses with ILLEGAL-ARGUMENT-BLOCK read whatever later frames pushed into those stack words 12
PURIFY, SUBSTITUTE fully reproduced -- chapters 31 and 33 depend on them -- 24

The TUPLE row is the pattern the whole section follows: pymdl reproduces every honest observable and declines to reproduce undefined behaviour. A program that obeys the manual cannot tell the difference; one that does not gets a definite answer instead of the era's arbitrary one.

E.2.3. Files, terminals and other processes

difference pymdl MDL 55 chapter
File names ITS syntax understood and reported ("DSK:name second"), resolved against a host file root real ITS directories 14
SAVE files pymdl's own format, restoring the same observables; an era SAVE file is refused rather than misread an image of the address space 14
GC-DUMP files pymdl's own format; sharing and self-reference preserved, as promised an image of storage words 14
Terminal SUBRs present and sensible; a pymdl session is as often a pipe, and TYI reads without waiting for ESC a real terminal with ITS interrupt characters 14, 23
The "NET" device OPEN answers a FALSE ARPANET sockets 14
IPC (SEND, IPC-ON, DEMSIG) present; answer the documented "nobody is listening" FALSE ITS inter-process messages 25
ACTIVATE-CHARS absent (the manual marks it Tenex-only) Tenex and Tops-20 only 23
"Dangerous" interrupts do not occur; pymdl's faults arrive as ERROR PDP-10 hardware traps 23
LOGOUT, VALRET answer #FALSE (), the documented "if it does return" case talk to the superior process 25

An era SAVE file is not lost, only read elsewhere: chapter 33 reads one, because the emulator has an address space to read it into.

E.2.4. Numbers and defaults that differ

difference pymdl MDL 55 chapter
.OBLIST in a fresh interpreter four entries -- INITIAL, ROOT, and the private oblists of the preloaded PPRINT and EDIT packages two, plus whatever was preloaded (section 15.5 allows exactly this) 18
Channel numbers pymdl's own ITS channel numbers, so the manual's <FOO ,INCHAN> is 1 17
RANDOM MDL's own generator, ported instruction for instruction, so the default sequence matches the same generator 6
GVAL/LVAL interference (section 24.4) absent; alternating costs nothing a cache in the atom block 26
TIME this process's CPU seconds the MDL job's 25

E.2.5. Known gaps

These are not departures; they are places where pymdl is simply not finished. Each is recorded at its own site in the chapter as well.

gap effect chapter
The "READ"/"WRITE" monitor's third argument a FRAME by TYPE that prints as #FRAME PUT, so the manual's own example works; but FUNCT and ARGS reject it as FIRST-ARG-WRONG-TYPE 23
SETLOC through a LOCD does not fire a "WRITE" monitor set on that locative 23
Native compilation coverage 413 of 439 library FUNCTIONs (94.1%); the refusals are honest FALSEs naming the reason 34

E.3. Discoveries

Things this project measured or worked out that neither manual states. They are grouped by where they live, and each names its chapter; the chapter has the evidence.

E.3.1. About the language, from MDL 55 itself

discovery chapter
An ATOM's value cell belongs to the atom, not its PNAME: FOO and FOO!-INTERRUPTS are different variables with different values 7, 18
T and ELSE have no value at all -- they are unbound ATOMs that evaluate to themselves, which is the whole of why COND clauses can use them 11
SET, LVAL, VALUE, UNASSIGN and LLOC reject a non-ENVIRONMENT in their final argument rather than ignoring it, and SET's banner says SECOND though the argument is its third 7
UNASSIGN leaves the binding and removes only the value, so BOUND? still answers T 7
EQVB folds left rather than complementing the exclusive-or of all its arguments; the two agree at two arguments and part company at three 21
<ASCII -1> is ARGUMENT-OUT-OF-RANGE, not the appendix-4 name BAD-ASCII-CHARACTER 6
A CLOSURE prints its captured bindings: #CLOSURE (#FUNCTION (() .A) ((A 1))) 12
INITIAL has 151 hash buckets and ROOT has 317 18
PICTURE is a release-55 built-in neither manual documents; <TYPEPRIM PICTURE> is STORAGE 9
^O is ^S only off ITS; under ITS a typed ^O does nothing, which section 16.8's parenthesis says and nobody reads 19
The era's initial ACTIVATE-CHARS set is ordered ^G ^S ^O, not sorted 23
The argument checking is irregular per-SUBR in ways no rule predicts; the table of 720 measured forms is the only description 6, 19
mudsys/primit carries ILLEGAL-ARGUMENT-BLOCK, ILLEGAL-FRAME and ILLEGAL-LOCATIVE as a matched trio, one for each thing that dies when a Function returns 15
A FUNCTION application has no FRAME of its own; the EVAL of its calling form stands for it 19
MDL 55's own <AND> and <OR> with no arguments compile to code that will not run 11, 34

E.3.2. The manuals' own errors

Roughly thirty places in the two manuals state something the interpreter contradicts -- arithmetic that does not add up, brackets that do not balance, a DECL that would stop its own example from running. They are corrected in place with ~~strikethrough~~ and the evidence, not gathered here, because a reader meets them while reading the passage and not while consulting a table. Chapter 12's INC1, chapter 13's LNUM, chapter 16's PERCENT association and chapter 18's INC are the four where the manual's own example does not run until the correction is applied.

E.3.3. About the era's own programs

discovery chapter
MIT's newlf.mud ends FIND-FILE with (#FALSE ("File not found")) -- a deliberate reason carried by a one-element COND clause, which is what caught pymdl's COND discarding reasons 14
% and %.INCHAN are used by era sources to compute parts of a file as it is read, so the reader must evaluate with a live interpreter and the real input CHANNEL 20
The <AND <OPEN ...> ...> idiom of section 8.4.1 appears in the corpus as OR: mat.mud turns a FALSE into its own error, inquire.mud falls back to a second directory 11
EXP-MAC, the compiler's peephole optimizer, is itself a MACRO expander 20, 34
Two analyzers in symana.mud carry bugs their sibling copies lack 34
The compiler's FILE-COMPILE is a different thing in an era compiler image than in its source 34
The assembler moves the atoms *INSERT, ARG and others between oblists, which is what destroyed pymdl's SQUOZE shell until the era's own MDL package replaced it 35
Nine of the ten copies of one file on the tape have rotted; the tenth is intact 38
MDL's own mudsys makes 23 distinct .CALLs 36

E.3.4. Bugs this book found

Writing a chapter means running its examples, and running the manual's examples found defects no test had. Each was fixed with a test; the chapter carries the story.

defect found by chapter
The printer chose brackets from the Python class rather than the TYPE, so one value printed two ways depending on how it was made <CHUTYPE .LOST LIST> in section 7.6.5.2 10
The #TYPE prefix path had the same defect, so an "ARGS" LIST CHTYPEd to FUNCTION printed #FUNCTION <...> FUNCT1 in section 9.5 12
COND returned a fresh empty FALSE instead of the last FALSE it saw, discarding every reason a failing test carried COUNT-CHAR in section 11.3, and its own comment 14
SET never consulted a DECL placed on a top-level binding by PUT-DECL, which is the situation PUT-DECL is documented for section 14.7.3 17
A dead locative and an out-of-range AT answered pymdl's own English rather than era banners section 12.1.1 15
The native COMPILE leaked a Python exception on a QUOTEd parameter instead of answering a FALSE section 9.4 34
FILE-ASSEMBLE died TYPE-MISMATCH because MESSAGE-CHANNEL was set at the wrong time chapter 35's E7 35
ALLTYPES was not in MDL 55's measured type-code order, so the unassembler named FIX's code FLOAT chapter 35 35

E.4. Where to look next

  • Chapter 37 is how the measuring is done, and is the chapter to read before adding a row to any of these tables.
  • docs/pymdl/harvest.md is the working file: 341 hits from code comments and docstrings, 19 CHANGES.md sections and 59 commit messages, each tagged with the manual section it belongs to. It is raw material, not prose, and it is where a claim's original wording lives.
  • src/pymdl/mdl/compil/CHANGES.md records every change made to MIT's vendored compiler sources, with the diff and the reason; appendix G has the rest of the provenance.