25 Years of Loud, Expensive Space Heaters.



A half-written draft of this piece has been living on my HDD for nearly half a year. Recent discussions re: the subject of “aging and death of PC irons” prompted me to dust it off and quickly post. Regularly-scheduled programming will resume shortly…



“The tools, the means and the methods disappear under the overwhelming dead weight of simulacra and cvasi-originals nobody knows how to distinguish from the genuine article anymore long before the activities they used to support are altogether renounced as such. That’s be why you’re still pretending to be engaging in computing long after the last computers became unavailable to you. That’s why you can’t buy silk anything, that’s also why I have to argue with the clerks over cotton socks (”oh, it only says 80% cotton 20% synthetic” “right” “that’s the elastic, you don’t want socks without elastic like for diabethics do you ? the kind that crumple around the ankle and don’t stay up ?” “…” They’re always shocked when they proclaim so and so article “no longer exists” and I point out I’m wearing it.”

Mircea Popescu, “Domestic casting”


“…why are we even _thinking_ about home computer equipment when we wish to attract professional programmers? in _every_ field I know, the difference between the professional and the mass market is so large that Joe Blow wouldn’t believe the two could coexist. more often than not, you can’t even get the professional quality unless you sign a major agreement with the vendor — such is the investment on both sides of the table…. …they don’t make poles long enough for me want to touch Microsoft products, and I don’t want any mass-marketed game-playing device or Windows appliance _near_ my desk or on my network. this is my _workbench_, dammit, it’s not a pretty box to impress people with graphics and sounds. when I work at this system up to 12 hours a day, I’m profoundly uninterested in what user interface a novice user would prefer.”

Erik Naggum


As a boy, I had one of these “breadboxes”:

Commodore 64.

This small but nearly perfect “micro” came with full electronic schematics, and was a joy to use. But, more pertinently to the subject at hand, it ate less than 10 watts of current at peak load, and consequently needed no forced air cooling:

Commodore 64 Internals.

You could hear the birds sing while using the Commodore.

After this, came the time of the ubiquitous 486 “AT” towers, where already the power supply blower could be heard from across the room…

486 with turbo

… but at least the CPU needed no fan of its own:

Cyrix 486


And then, came the “Pentiums” etc., and suddenly it was considered acceptable for a desktop computer to sound like a vacuum cleaner and draw in similar quantities of dust.

I have always despised the CPU fan. Possibly because — in spite of living for many years in the “country of the deaf” — I did not partake in their peculiar heathen ritual whereby they blow away their high-frequency hearing (this thing they call “concerts”.)

When I first began to work for serious pay, soon enough I was able to build a “workstation” (in my conception, a workstation is a machine that you expect to work with, daily, and consequently ought to consist of the best components that your money can buy.) But the idea that such a machine ought to loudly suck in several liters of air and dust every second, I found quite offputting. And so, went and purchased a Toyota radiator, and ended up with this:

Water AMD Front

There was nothing “overclocked” inside the pictured dual-”Athlon” box. The experiment with piped water was intended strictly for noise-deadening and “uptime” (i.e. avoid disassembly for cleaning) :

Water AMD Rear

After a few days of temperature measurements, I proceeded to remove the two fans which came with the chassis. But there still remained the one in the power supply. Initially I thought to fill the latter with transformer oil and seal a copper heat exchanger coil inside; but at the time did not have a welder, and so it stood as it were.

Water AMD

Prefab “water blocks” for various CPU had just appeared on the market, so it was not necessary to make them by hand:

Water CPU Block

Pumps specifically built for this purpose, however, were not available, so the chosen pump was of the sealed magnetic type used in fish ponds. It circulated about 4 litres of distilled water + antifungal:

Water AMD Side

HDD water block:

Water HDD

And video card…

Water GPU

… which, some five years later, developed a leak and drowned the machine. Rubber hoses with compression fittings are not a reliable component.

The reader is invited to point and laugh at the author’s folly.

After this, and to this day, I live with the whine of fans, just like everyone else. And yes it is possible to buy “quiet” fans. But when there are six or seven of them, and you run your machine 24/7 (with, remember, two — or even four — CPUs, a full “server” board’s worth of RAM, etc., it’s after all a workstation, you work on it…) , and at full CPU load in the night (why would you want to have idle cycles in your workstation? you paid for it, let it work!) and dust slowly accumulates…

Eventually, the airflow slows, the two (or if you’re “loaded”, four, or more…) 200 watt processors begin to warm the “oven”, and capacitors — pop.

And if you, like me, use strictly pre-NSA-Fritzchip “egg boiler” AMD irons, you will be in search of a costly and potentially-unobtainable set of replacement parts.


It is here that I must point out that there is nothing physically impossible, or even especially difficult, about constructing a hermetically sealed electrical machine which disposes of multiple kilowatts of waste heat via adequate passive cooling.

Your local power company fields thousands of them:

Mains Transfomer

In the early 2000s, Korean company “Zalman” marketed a kit for constructing a dust-sucker-free PC:

Zalman TNN

This was a pretty solid item, 40kg or so of “hedgehog” thermal mass, at several thousand $. Unfortunately, “Zalman” folded before it could mass-produce a multi-CPU variant of this system.

A roughly similar item is apparently still sold today:

HdPlex

… but it is of course a “consumer” piece of rubbish, limited to a single (and underpowered) CPU, on account of a laughably-inadequate “hedgehog”.

If anyone were to market a “mains transformer”-style 100%-passively-cooled chassis for serious workstations — I would buy it. I do not care if it were to have the size, weight, and cost of an upright piano. (I expect, on purely physical grounds, that it must — especially if built with a 2-3x engineering margin.)

A PC workstation that could be radiosnoop-shielded, filled with inert gas, and welded shut — and reliably expected to work for decades, would IMHO be a quite serious advance — in a market where serious advances have been conspicuously absent for many years.

“Finite Field Arithmetic.” Chapter 18B: “Cutouts” in Peh.

This article is part of a series of hands-on tutorials introducing FFA, or the Finite Field Arithmetic library. FFA differs from the typical “Open Sores” abomination, in that — rather than trusting the author blindly with their lives — prospective users are expected to read and fully understand every single line. In exactly the same manner that you would understand and pack your own parachute. The reader will assemble and test a working FFA with his own hands, and at the same time grasp the purpose of each moving part therein.


Chapter 18 consists of three parts, of which only the first (18A) includes a vpatch; the
second (18B) — completes the discussion of the mechanisms; while the third (18C) contains worked examples of practical use.


You are presently reading 18B.

You will need:

  • A Keccak-based VTron (for this and all subsequent chapters.)
  • All of the materials from Chapters 1 – 18A.

There is no vpatch in Chapter 18B. To run the examples given in this Chapter, please use the Peh you built in Chapter 18A, and refer to the Peh 251K Instruction Set Table as you read on.


The time has come to discuss the intended applications of Peh.


Peh is constructed as a sane — maximally-programmable, maximally built-for-smart-people, and maximally rodenticidal — replacement for such broken-at-birth abominations as GPG, SSL, etc.

Unlike all currently-popular braindamaged cryptoware (e.g. Koch’s GPG), Peh deliberately refrains from imposing any particular cryptographic algorithm on the operator.

Specifically, you are free to use any form — or combination of forms — of public or secret-key cryptography which can be expressed using Peh opcodes, interpreted in whatever agreed-upon sequence, to transform particular inputs into the necessary outputs. (At the most extreme minimum: only you and your counterparty need to agree! Let the enemy manually sweat over every stolen transmission, to determine what your algorithm and data representation happen to be — why give him anything for free? Let him work to the bone.)


A well-crafted Peh Tape is a human-readable (to the trained eye) document, rather than “machine barf”.

The particular method for the generation and demarcation of keys (public, private, secret), ciphertexts, plaintexts, signatures, etc. is deliberately left open to the operator. Working examples of Peh cryptosystems will, however, be offered in forthcoming chapters.

In typical usage, a Peh Tape (which, recall, may produce new Peh Tapes when executed) will represent one or more of the following mathematical objects:

  1. A Generator, which from some quantity of TRNG bits produces a new Private Key.
  2. A Private Key produced by a Generator (I).
  3. A Transformer, which, from a given Private Key (II), derives a distributable Public Key.
  4. A distributable Public Key produced by a Transformer (III).
  5. A distributable Encryptor, which produces a Ciphertext from an arbitrary Plaintext input (and some quantity of TRNG bits) that, in turn, can only be deciphered by a Decryptor (VII) derived from the associated Private Key (II). An Encryptor may be identical to a Public Key (IV), or be derived from same using a mechanical process.
  6. A distributable Ciphertext produced by an Encryptor (V).
  7. A Decryptor, derived from a Private Key (II), which, when given a Ciphertext (VI) that had been produced by a corresponding Encryptor (V), emits the corresponding Plaintext.
  8. A Signet Mold, which produces a Signet from a given Private Key (II).
  9. A Signet produced by a Signet Mold (VIII).
  10. A distributable Seal produced by invoking a Signet (IX) on a particular arbitrary Payload, making use of some quantity of TRNG bits.
  11. A distributable Litmus, derived from a given Public Key (IV); takes a Seal (X), and a given Payload, and determines whether said Seal was legitimately produced by invoking a Signet born from that Public Key’s originating Private Key on that particular Payload.

However, given the freedom offered by Peh to the operator, some patterns of usage may involve moving parts different from those described above. In particular, the operator may choose to use entirely different private (and corresponding public) keys for signing and encryption; or some combination of “genuine” and “decoy” components; or, in certain exotic scenarios, sets of multiple private keys; or even some yet-unforeseen system that does not fit into the suggested outline at all!

The operator must remember that he alone is responsible for formulating correct cryptosystems for use with Peh, and/or enlisting trusted friends to help in doing so. The primitive opcodes offered in Peh provide for a foundation of fits-in-head and sidechannel-free arithmetic, but it is certainly possible to kill yourself and your counterparty by inept cryptosystem design ! You have been warned, and this warning — which states a fact that ought to be obvious to the intelligent reader — will not be repeated again. A sapper errs once!

We will return to discussing the cryptographic usage of Peh in subsequent Chapters.


Now, let’s wrap up our discussion of the mechanisms offered in Chapter 18A.

The reader was introduced to the subroutine syntax of Peh, where a definition of the form:

@ Name @ Body ;

… permits the later invocation of the defined subroutine, in the form of:

@ Name !

… or, where a shorthand notation is desired:

!

… which invokes the most recently-defined (i.e. nearest to the left of the ! symbol) subroutine on the Tape.

Now we will discuss the restrictions imposed by Peh on the definition and invocation of subroutines, and the rationale for these restrictions.

Let’s review again the last example set of erroneous Peh Tapes from 18A:

# Peh Tape Eggogology
1
@foo@[foo]@bar!; @bar@[bar]; @foo!

Cannot invoke Subroutine 'bar' before the position where it is defined!
2
@foo@[foo]!; @foo!

No Subroutines were defined prior to this position!
3
@foo@[foo]@foo!; @foo!

Recursive invocation in Subroutine 'foo' is prohibited!
4
@foo@@bar@[bar]; !

Attempted to define Subroutine 'bar' while inside a Loop or Subroutine!
5
: @foo@[foo];

Attempted to define Subroutine 'foo' while inside a Loop or Subroutine!

In Tape 1, the subroutine bar is invoked inside foo; but its definition resides to the right of the invocation point, which is prohibited:

ffa_calc.adb:

      -- Attempt to invoke a Subroutine with the supplied name:
      procedure Invoke_Named_Subroutine(Name : in Sub_Names) is
         -- Position of the current Proposed Sub in Sub Table:
         Index  : Subroutine_Table_Range := Lookup_Subroutine(Name);
         -- To invoke a Sub, it MUST exist in the Sub Table.
 
         -- Name of the Proposed Sub (for eggogs) :
         S_Name : String := String(Tape(Name.L .. Name.R));
      begin
         -- If no defined Subroutine has this Name, eggog:
         if Index = Subs'First then
            E("Invoked Undefined Subroutine '" & S_Name & "' !");
         end if;
         -- Otherwise, proceed to the invocation:
         declare
            -- The Sub Table Entry we successfully looked up:
            Sub : Sub_Def := Subs(Index);
         begin
            -- Recursion is prohibited in Peh Tapes. Detect it:
            if IP in Sub.Payload.L .. Sub.Payload.R then
               E("Recursive invocation in Subroutine '" 
                   & S_Name & "' is prohibited!");
            end if;
            -- Prohibit Subroutines whose definitions end AFTER the current IP:
            if IP < Sub.Payload.R then
               E("Cannot invoke Subroutine '" & S_Name &
                   "' before the position where it is defined!");
            end if;
            -- Proceed to invoke the Subroutine:
            Invoke_Subroutine(Sub);
         end;
      end Invoke_Named_Subroutine;

Observe that the recursive invocation in Tape 3 is likewise prohibited.

Re-definition of a subroutine, or the placement of a subroutine definition inside of a loop or subroutine (as seen in e.g. as in Tapes 4 and 5) is also prohibited:

ffa_calc.adb:

      -- Attempt to intern the given Subroutine into the Subroutines Table:
      procedure Intern_Subroutine(Sub : in Sub_Def) is
         -- Position of the current Proposed Sub in Sub Table:
         Index  : Subroutine_Table_Range := Lookup_Subroutine(Sub.Name);
         -- To DEFINE a Sub, it must NOT have existed in Sub Table.
 
         -- Name of the Proposed Sub (for eggogs) :
         S_Name : String := String(Tape(Sub.Name.L .. Sub.Name.R));
      begin
         -- If a Sub with this Name already exists, eggog:
         if Index /= Subs'First then
            E("Attempted to redefine Subroutine '" & S_Name & "' !");
         end if;
         -- Definitions are prohibited inside Loops or Sub calls:
         if Control_Stack_Not_Empty then
            E("Attempted to define Subroutine '" 
                & S_Name & "' while inside a Loop or Subroutine!");
         end if;
         -- If the Subroutine Table is full, eggog:
         if STP = Subs'Last then
            E("Cannot define the Subroutine '" & S_Name
                & ": the Subroutine Table is Full!");
         end if;
         -- Finally, intern the Proposed Subroutine into the Sub Table:
         STP       := STP + 1;
         Subs(STP) := Sub;
      end Intern_Subroutine;

There are two fundamental reasons for these restrictions.

The first of these is that every Peh Tape is a text meant to be maximally understandable to the human reader, i.e. a fits-in-head artifact. The intended reader, is, naturally, one who is in possession of whatever components (public and private) which may be required to carry out the computation represented by the given Tape. Point being, convoluted control flow is undesirable in Peh Tapes. Strong ciphers may rely on number-theoretical conjectures which offer "trapdoor transformation"; but never on "obfuscated program contests".

The second reason is that uniform left-to-right control flow (where the machine may "visit" an earlier location by invoking a subroutine, but may not transfer control to an earlier Tape position permanently a la "GOTO") permits the implementation of a Cutout mechanism, which allows safe sequential processing of multiple Peh Tapes -- in order to compute functions having both "public" and "private" inputs. This mechanism is the main subject of this Chapter.


Now we will discuss what a Cutout is, and how it is used.

There are only two Cutout control commands. Let's reproduce them here from the Peh 251K Instruction Set given in the previous Chapter:

Peh Instruction Set Ver. 251K
Op Description # Ins # Outs Notes
LC Mark Left Side of Cutout 0 0 Mark the start of the optional Cutout Tape segment. When the Cutout is armed by the closing RC instruction, Subroutines defined after the LC Position, but prior to the RC, may be invoked from Positions after the RC. Subroutines defined prior to the LC Position may be invoked from Positions prior to the RC, but not from those after RC. Fatal if a RC to mark the end of the Cutout does not occur at some later point. Fatal if executed more than once in a Peh run.
RC Mark Right Side of Cutout, and Arm 0 0 Mark the end of the Cutout, and arm it. Once armed, the Cutout remains in force until Peh halts, and may not be disabled or redefined. All subsequent instructions must obey the Cutout (see LC) or will trigger fatal error. Register-access instructions, including ZR and QD, executed prior to this Position will access only the segregated Cutout Register Set. Fatal if a LC has not executed prior. Fatal if executed more than once in a Peh run.

A Cutout is an optional feature of Peh which may be activated only once in a given Peh run. When proclaimed using a matching pair of LC and RC opcodes, it divides the Peh Tapespace into the following three logical segments:

Guarded L C Cutout R C Public

The Guarded segment is understood to span from the first position in the Tapespace to the one immediately before the LC opcode; the Cutout segment spans from the LC position to that of the RC; and the Public segment spans from the position immediately after the RC, to the very last position in the Tapespace.

If LC executes inside a particular Peh run, denoting the start of the Cutout, a RC must also execute to denote its end: or the run will produce an Eggog verdict regardless of what else happens.

A Cutout is intended for use in a Peh session where the Tapespace is filled with two or more Tapes intended to be executed sequentially. It denotes a constrained subset of subroutines on the first Tape which may be invoked from the subsequent Tapes. Subroutines which do not fall into this denoted subset, may not be directly invoked from the later Tapes.

After RC executes, the Cutout is armed in the particular Peh run; it may not be disarmed or redefined, and the following restrictions will remain in place until Peh halts:

  1. The Cutout is unaffected by the Zap commands.
  2. Any further execution of an LC or RC command triggers an immediate halt with a verdict of Eggog..
  3. The Cutout may not bisect a Subroutine definition or a Loop structure. Attempting to do this results in halting with a verdict of Eggog.
  4. A Subroutine defined inside the Guarded segment may be invoked from inside the Guarded segment. (The restriction discussed earlier, where Subroutines may only be invoked when they have been defined to the left of the invocation point -- remains in force, here and elsewhere below.)
  5. A Subroutine defined in either the Guarded or the Cutout segments may be called from inside the Cutout segment.
  6. A Subroutine defined inside the Cutout segment may be called from inside the Public segment.
  7. A Subroutine defined inside the Guarded segment may not be called from inside the Public segment. Any attempt to do so results in halting with a verdict of Eggog.
  8. All Register-accessing opcodes (including Zaps and Debug Print) which execute inside either the Guarded or the Cutout segments, will make use exclusively of the segregated Cutout Register Set.
  9. All Register-accessing opcodes (including Zaps and Debug Print) which execute inside the Public segment, will make use exclusively of the Ordinary Register Set.

It should be apparent to the alert reader that only the Data Stack may be used to move information between the Public segment and the other two, while Registers cannot be so used. (Peh Tapes which do not arm the Cutout may pass data to/from subroutines using Registers; as may Peh Tapes which do make use of it, but strictly within either of the two sides of the Tapespace that are separated by RC.)


The alert reader will also have guessed that the term "Cutout" is lifted from that of the traditional spycraft concept, where it refers to a trusted courier who carries information between two parties that are unable to speak directly to one another.


Now, let's review some examples of valid Tapes demonstrating the use of a Cutout:

Peh Tape Output of Tape

@aa@[a]; LC @bb@[b]@aa!; RC @bb! QY

ba

@aa@[bar]; LC @bb@[foo]@aa!; RC @bb! QY

foobar

@aa@[bar]; LC @bb@[foo]@aa!; RC @baz@[baz]; @baz! QY

baz

@yy@[yy]; @xx@[xx]@yy!; LC @foo@[foo]@xx!; RC @foo! QY

fooxxyy

And now, some invalid (i.e. leading mercilessly to an Eggog Verdict) examples:

Peh Tape Eggogology

@aa@[a]; LC @bb@[b]@aa!; RC @aa! QY

FATAL: Tick: 32, IP: 32, Symbol: '!' : Attempted movement to IP: 5 violates the Cutout!

@yy@[yy]; @xx@[xx]@yy!; LC @foo@[foo]@xx!; RC @yy! QY

FATAL: Tick: 50, IP: 50, Symbol: '!' : Attempted movement to IP: 5 violates the Cutout!

@yy@[yy]; @xx@[xx]@yy!; LC RC ! QY

FATAL: Tick: 31, IP: 31, Symbol: '!' : Attempted movement to IP: 15 violates the Cutout

Why would you want to use a Cutout in practice? Let's say you have a subroutine which puts a "private" constant on the Data Stack, e.g.:

@PrivConst@.DEADF00D;

... that you can later invoke as:

@PrivConst!

This is the standard method for the use of constants in Peh Tapes.

Now, suppose that you have decided to use a cryptosystem where the private component is fed into Peh immediately prior to a Ciphertext Tape provided by a third party. In order to emit its Plaintext payload, the Ciphertext Tape is permitted to invoke a specific set of private operations defined in the first Tape; but it should not be able to invoke any other subroutines which may have been defined there.

In particular, you do not want the third-party Ciphertext to be able to cause a copy of an arbitrary private constant to be emitted into the output of the Peh run (which could undesirably litter an "uncleanable" solid-state disk with long-term secrets; or cause them to be displayed on a monitor subject to peeking; or attempt to transmit them by radio using a crafted CPU-grind loop; etc.) Consequently, you will want a Cutout of the following form:

Tapespace
(Contains two concatenated Tapes)
Left Tape, Executed First Right Tape
@PrivConst@.DEADF00D; LC @DoPrivOp@@PrivConst! R* ; RC .3 @DoPrivOp!
Guarded Cutout Public

Hence, the subroutine DoPrivOp may be invoked in the Public segment (i.e. the entire remainder of the Tapespace to the right of RC), but PrivConst may not, even if the author of the second Tape knew its name. And any Register operations which happen on the left side of the RC, will use a segregated register set, which cannot be accessed from the portion of the Tapespace which resides to the right of the RC. This segregated register set may hold temporary values that are used by the "private" operations, for instance.

If you append a QD command to the end of the above Tape, and feed it to a Peh invoked with adequate Tapespace and a 256-bit FZ width, you will get the following debug output:

Data Stack:
    1 : 000000000000000000000000000000000000000000000000000000029C09D027
Control Stack:
Registers:
    g : 0000000000000000000000000000000000000000000000000000000000000000
    h : 0000000000000000000000000000000000000000000000000000000000000000
    i : 0000000000000000000000000000000000000000000000000000000000000000
    j : 0000000000000000000000000000000000000000000000000000000000000000
    k : 0000000000000000000000000000000000000000000000000000000000000000
    l : 0000000000000000000000000000000000000000000000000000000000000000
    m : 0000000000000000000000000000000000000000000000000000000000000000
    n : 0000000000000000000000000000000000000000000000000000000000000000
    o : 0000000000000000000000000000000000000000000000000000000000000000
    p : 0000000000000000000000000000000000000000000000000000000000000000
    q : 0000000000000000000000000000000000000000000000000000000000000000
    r : 0000000000000000000000000000000000000000000000000000000000000000
    s : 0000000000000000000000000000000000000000000000000000000000000000
    t : 0000000000000000000000000000000000000000000000000000000000000000
    u : 0000000000000000000000000000000000000000000000000000000000000000
    v : 0000000000000000000000000000000000000000000000000000000000000000
    w : 0000000000000000000000000000000000000000000000000000000000000000
    x : 0000000000000000000000000000000000000000000000000000000000000000
    y : 0000000000000000000000000000000000000000000000000000000000000000
    z : 0000000000000000000000000000000000000000000000000000000000000000
Subroutines:
    1 : 'PrivP' ( 8, 17 ) (Guarded)
    2 : 'DoPrivOp' ( 32, 43 ) (Cutout)
Cutout: Armed: ( 20, 46 )
Flag  : 0
Ticks : 85
IP    : 63
WARNING: Tape terminated with a non-empty Data Stack!
WARNING: Tape terminated without a Verdict.

Admittedly this is a simplistic example, but it will have to suffice for now.


Now let's look at the implementation mechanism, which is uncomplicated. First, the data structures which represent the Cutout and the two Register Sets:

ffa_calc.adb:

   -- Start a Peh Machine with the given Dimensions and Tape; return a Verdict.
   function Peh_Machine(Dimensions : in Peh_Dimensions;
                        Tape       : in Peh_Tapes;
                        RNG        : in RNG_Device) return Peh_Verdicts is
 
   ............
 
      subtype Cutouts    is Segment; -- Cutout (see Ch.18 discussion)
 
   ............
 
      -- Registers:
      subtype RegNames is Character range 'g' .. 'z';
      type RegTables is array(RegNames range <>) of FZ(1 .. Wordness);
 
      -- Ordinary Register Set (accessed if no Cutout, or when ABOVE it)
      Registers     : RegTables(RegNames'Range);
 
      -- 'Cutout' Register Set (accessed only if IP is IN or BELOW the Cutout)
      CO_Registers  : RegTables(RegNames'Range);
 
   ............
 
      -- 'Cutout' Tape Segment. (See Ch.18 discussion re: when and how to use.)
      -- If the Cutout is armed, it stays armed until Peh halts.
      Cutout_Begun  : Boolean      := False;
      Cutout_Armed  : Boolean      := False;
      Cutout        : Cutouts;
 
   ............


The opcodes which declare and arm the Cutout:

ffa_calc.adb:

 
   ............
 
            ---------------------------------------------------------
            -- Left...
            when 'L' =>
 
               -- Which L-op?
               case O is
 
                  ............
                  ............
 
                  -- ... 'Cutout' :
                  -- Mark the LEFT SIDE of the 'Cutout' Tape segment;
                  -- The Tape IN OR PRIOR to it will retain the ability to
                  -- move directly into points PRIOR to THIS position
                  -- on the Tape (i.e. where THIS Op had executed).
                  -- Ops on Tape AFTER 'RC' mark can move INTO Cutout,
                  -- but NOT directly into any position PRIOR to it.
                  -- If 'LC' is executed, a 'RC' MUST occur before Tape end.
                  -- FATAL if a 'LC' or 'RC' Op had previously executed.
                  when 'C' =>
                     -- Eggog if we have ALREADY begun the Cutout somewhere:
                     if Cutout_Begun then
                        E("'LC' Op may only execute ONCE on a Tape!");
                     end if;
                     -- Cutout defs are prohibited inside loops or Sub calls:
                     if Control_Stack_Not_Empty then
                        E("Attempted to execute 'LC' (Left-Cutout)" &
                            " inside a Loop or Subroutine!");
                     end if;
                     -- Set the START of the Cutout, and mark it 'begun':
                     Cutout_Begun := True;
                     Cutout.L     := IP;
 
                  -- ... Unknown (Eggog):
                  when others =>
                     Undefined_Prefix_Op;
 
               end case;
            ---------------------------------------------------------
            -- Right...
            when 'R' =>
 
               -- Which R-op?
               case O is
 
                  ............
                  ............
 
                  -- ... 'Cutout' :
                  -- Mark the RIGHT SIDE of the 'Cutout' Tape segment that
                  -- began with 'LC', and permanently arms the Cutout.
                  -- After THIS position, no IP_Next may be set which
                  -- directly transfers control to a point PRIOR to 'LC'.
                  -- FATAL if no 'LC' had executed to mark the LEFT SIDE.
                  when 'C' =>
                     -- Eggog if we never marked the beginning with 'LC':
                     if not Cutout_Begun then
                        E("'RC' Op found, but no there was no prior 'LC' !");
                     end if;
                     -- Eggog if we have already armed the Cutout:
                     if Cutout_Armed then
                        E("'RC' Op found, but the Cutout is already armed!");
                     end if;
                     -- Cutout defs are prohibited inside loops or Sub calls:
                     if Control_Stack_Not_Empty then
                        E("Attempted to execute 'RC' (Right-Cutout)" &
                            " inside a Loop or Subroutine!");
                     end if;
                     -- Otherwise proceed to complete and arm the Cutout:
                     Cutout.R     := IP;
                     Cutout_Armed := True;
 
                  -- ... Unknown (Eggog):
                  when others =>
                     Undefined_Prefix_Op;
 
               end case;


The predicates which answer questions concerning a given Tape Position's relation vis-a-vis the Cutout:

ffa_calc.adb:

 
   ............
 
      ------------
      -- Cutout --
      ------------
 
      -- Find whether Cutout would prohibit move from current IP to the given :
      function Cutout_Prohibits(Position : in Tape_Positions) return Boolean is
      begin
         return Cutout_Armed and IP > Cutout.R and Position < Cutout.L;
      end Cutout_Prohibits;
 
 
      -- Find whether given a Tape Position lies inside an armed Cutout:
      function In_Cutout(Position : in Tape_Positions) return Boolean is
      begin
         return Cutout_Armed and Position in Cutout.L .. Cutout.R;
      end In_Cutout;
 
 
      -- Determine whether to use the Cutout Registers at the current position:
      function Use_CO_Registers return Boolean is
      begin
         -- If we are either BELOW or INSIDE armed Cutout : we use only the
         -- CO_Registers alternative register file. Otherwise: use Registers.
         return Cutout_Armed and IP <= Cutout.R;
      end Use_CO_Registers;
 
   ............

The behaviour of the Zap operations, as they concern the Cutout:

ffa_calc.adb:

 
   ............
 
      ----------
      -- Zaps --
      ----------
 
      -- Zero the Data Stack and reset the SP:
      procedure Zap_Data_Stack is
      begin
         -- Clear the Data Stack:
         for i in Stack'Range loop
            FFA_FZ_Clear(Stack(i));
         end loop;
         -- Set SP to bottom:
         SP := Stack_Positions'First;
      end Zap_Data_Stack;
 
 
      -- Zero all Registers (Ordinary set) :
      procedure Zap_Ordinary_Registers is
      begin
         for r in RegNames'Range loop
            FFA_FZ_Clear(Registers(r));
         end loop;
      end Zap_Ordinary_Registers;
 
 
      -- Zero all Registers (Cutout set) :
      procedure Zap_Cutout_Registers is
      begin
         for r in RegNames'Range loop
            FFA_FZ_Clear(CO_Registers(r));
         end loop;
      end Zap_Cutout_Registers;
 
 
      -- Zero all Registers in the currently-active Register Set:
      procedure Zap_Registers is
      begin
         if Use_CO_Registers then
            Zap_Cutout_Registers;
         else
            Zap_Ordinary_Registers;
         end if;
      end Zap_Registers;
 
   ............


The behaviour of the Register access operations, as they concern the Cutout:

ffa_calc.adb:

 
   ............
 
               -------------------------
               -- Fetch from Register --
               -------------------------
            when 'g' .. 'z' =>
               -- Put value of Register on stack
               Push;
               if Use_CO_Registers then
                  Stack(SP) := CO_Registers(C); -- use Cutout Register set
               else
                  Stack(SP) := Registers(C);    -- use ordinary set
               end if;
 
   ............
   ............
 
            ---------------------------------------------------------
            -- Zap...
            when 'Z' =>
 
               -- .. Zap what?
               case O is
 
                  -- ... Registers:
                  when 'R' =>
                     -- If in Cutout, will zap only Cutout set of regs
                     Zap_Registers;
 
                  -- ... Data Stack:
                  when 'D' =>
                     Zap_Data_Stack;
 
                  -- ... Overflow Flag:
                  when 'F' =>
                     Zap_Flag;
 
                  -- ... All Zappable State:
                  when 'A' =>
                     Zap_Master;
 
                  when others =>
                     Undefined_Prefix_Op;
 
               end case;
 
            ---------------------------------------------------------
            -- Write into Register...
            when '$' =>
 
               -- Eggog if operator gave us a garbage Register name:
               if O not in RegNames then
                  E("There is no Register '" & O & "' !");
               end if;
 
               -- Selected Register exists; move top FZ on stack into it:
               Want(1);
               if Use_CO_Registers then
                  CO_Registers(O) := Stack(SP); -- use Cutout Register set
               else
                  Registers(O)    := Stack(SP); -- use ordinary set
               end if;
               Drop;


And, finally, the execution mechanism of Ch. 18A, where the Cutout is actually enforced:

ffa_calc.adb:

 
   ............
 
      -----------------------------
      -- Start of Tape Execution --
      -----------------------------
 
   begin
      -- Reset all resettable state:
      Zap_Master;
      Zap_Cutout_Registers;
 
      -- Execution begins with the first Op on the Tape:
      IP := Tape_Positions'First;
 
      loop
 
         -- If current Op is NOT the last Op on the Tape:
         if not Last_Tape_Symbol then
 
            -- ... then default successor of the current Op is the next one:
            IP_Next := IP + 1;
 
         else
 
            -- ... but if no 'next' Op exists, or quit-with-Mu, we stay put:
            IP_Next := IP; -- ... this will trigger an exit from the loop.
 
         end if;
 
         -- Advance Odometer for every Op (incl. prefixes, in comments, etc) :
         Ticks := Ticks + 1;
 
         -- Execute the Op at the current IP:
         Op(Tape(IP));
 
         -- Halt when...
         exit when
           Verdict /= Mu or -- Got a Verdict, or...
           IP_Next  = IP or -- Reached the end of the Tape, or...
           Exhausted_Life;  -- Exhausted Life.
 
         -- If the Cutout has been armed on this Tape, then enforce it:
         if Cutout_Prohibits(IP_Next) then
            E("Attempted movement to IP:" & Tape_Positions'Image(IP_Next) &
                " violates the Cutout!");
         end if;
 
         -- We did not halt yet, so select the IP of the next Op to fetch:
         IP := IP_Next;
 
      end loop;
 
      -- At this point, the Tape has halted.
 
      ............
 
      -- Unclosed Cutout:
      if Cutout_Begun and not Cutout_Armed then
         E("The Cutout declaration 'LC' at IP:"
             & Tape_Positions'Image(Cutout.L) & " is Unterminated!");
      end if;
 
      ............

It is really quite simple -- any transfer of control flow which violates an armed Cutout, immediately triggers a halt with Eggog verdict.



In the next Chapter, 18C, we will walk through a set of practical examples which make use of the currently-defined Peh opcodes.


~To be continued!~

“Finite Field Arithmetic.” Chapter 18A: Subroutines in Peh.

This article is part of a series of hands-on tutorials introducing FFA, or the Finite Field Arithmetic library. FFA differs from the typical “Open Sores” abomination, in that — rather than trusting the author blindly with their lives — prospective users are expected to read and fully understand every single line. In exactly the same manner that you would understand and pack your own parachute. The reader will assemble and test a working FFA with his own hands, and at the same time grasp the purpose of each moving part therein.


Chapter 18 consists of three parts, of which only the
first (18A) includes a vpatch; the second (18B) — completes the discussion of the mechanisms; while the third (18C) contains worked examples of practical use. You are presently reading 18A.

You will need:

Add the above vpatches and seals to your V-set, and press to ffa_ch18_subroutines.kv.vpatch.

You should end up with the same directory structure as previously.

As of Chapter 18A, the versions of Peh and FFA are 251 and 253, respectively.

Now compile Peh:

cd ffacalc
gprbuild

But do not run it quite yet.


LibFFA per se is unchanged from the previous Chapter’s, as reflected in the version numbers.

A number of significant changes have been made to the Peh instruction set of Chapter 17.

We will begin by summarizing the entire instruction set, as it currently stands in this Chapter:

Peh Instruction Set Ver. 251K
Op Description # Ins # Outs Notes
Blocks
( Enter Comment Block 0 0 All further symbols are ignored until comment block is exited; supports nesting. Results in a Warning if the Block is not closed by the end of the Tape.
) Exit Comment Block 0 0 Fatal if not currently in a comment block.
[ Enter Quote Block 0 0 All further symbols are not executed, but instead echoed verbatim until quote block is exited; supports nesting. Results in a Warning if the Block is not closed by the end of the Tape.
] Exit Quote Block 0 0 Fatal if not in a quote block.
{ Enter Conditional Block 1 0 Pop top item from data stack; if it was non-zero, execute all symbols until a matching } exits the conditional block; otherwise ignore them until same; supports nesting. Results in a Warning if the Block is not closed by the end of the Tape. Fatal if data stack is empty. The Subroutine terminator ; is prohibited in a Conditional Block.
} Exit Conditional Block 0 1 Pushes a 1 to data stack if the branch being exited had been taken, otherwise pushes a 0.
Data Stack Motion
" Dup 1 2 Push a copy of the top item to the data stack.
_ Drop 1 0 Discard the item on top of data stack
' Swap 2 2 Exchange top and second item on data stack
` Over 2 3 Push a copy of second item to data stack
Constants
. Push Zero 0 1 Push a brand-new zero to data stack
0..9, A..F, a..f Insert Hexadecimal Digit 1 1 Insert the given hexadecimal digit as the junior-most nibble into the top item on data stack. Fatal if data stack is empty. Equivalent to top := (16 × top) + Digit.
Predicates
= Equals 2 1 Push a 1 on data stack if the top and second items are bitwise-equal; otherwise 0
< Less-Than 2 1 Push a 1 on data stack if the second item is less than the top item
> Greater-Than 2 1 Push a 1 on data stack if the second item is greater than the top item
Bitwise
& Bitwise-AND 2 1 Compute bitwise-AND of the top and second item, push result on data stack.
| Bitwise-OR 2 1 Compute bitwise-OR of the top and second item, push result on data stack.
^ Bitwise-XOR 2 1 Compute bitwise-XOR of the top and second item, push result on data stack.
~ Bitwise-Complement 1 1 Compute 1s-complement negation of the top item on data stack, i.e. flip all bits of it.
U Bitwise-MUX 3 1 If top item is nonzero, a copy of the second item will be pushed to the data stack; otherwise – of the third item.
W Width-Measure 1 1 Calculate the position of the senior-most bit in the top item that equals 1 (or return 0 if there are none) and push this number to data stack.
RS Right-Shift 2 1 Shift the second item right by the number of bits given in the top item modulo the FZ bitness.
LS Left-Shift 2 1 Shift the second item left by the number of bits given in the top item modulo the FZ bitness.
Arithmetic: Basic
- Subtract 2 1 Subtract top item from second item, push result on data stack, and save borrow bit into Flag
+ Add 2 1 Add top and second item, push result to data stack, and save carry bit into Flag
O Push Overflow Flag 0 1 Push a copy of Flag, the register containing carry or borrow from the most recent arithmetic op, to the data stack
Arithmetic: Division
\ Divide with Remainder 2 2 Divide second item by top item, push quotient and then remainder on data stack; division by zero is fatal
/ Divide without Remainder 2 1 Divide second item by top item, push only quotient on data stack; division by zero is fatal
% Modulus 2 1 Divide second item by top item, push only remainder on data stack; division by zero is fatal
G Greatest Common Divisor 2 1 Find the Greatest Common Divisor of the top and second item, and push to the data stack. GCD(0,0) is conventionally defined as 0.
Arithmetic: Multiplication
* Multiply 2 2 Multiply second item and top item, push the junior half of the result on data stack, and then the senior half
R* Right-Multiply 2 1 Multiply top and second item, and push only the junior half of the product to the data stack. The “Low-Multiply” from Ch. 14B.
S Square 1 2 Square the top item, push the junior half of the result on data stack, and then the senior half
Arithmetic: Modular
MS Modular Square 2 1 Square the second item modulo the top item and push the result (necessarily fits in one FZ) to the data stack. division by zero is fatal.
M* Modular Multiplication 3 1 Multiply third item and second item, modulo the top item; push the result to data stack (necessarily fits in one FZ); division by zero is fatal
MX Modular Exponentiation 3 1 Raise third item to the power of the second item, modulo the top item, push the result to data stack (necessarily fits in one FZ); division by zero is fatal.
Arithmetic: Primes
P Perform a single shot of the Miller-Rabin Monte Carlo Primality Test on N, the second item on the data stack, using the top item as the Witness parameter for the test. Push a 1 to the data stack if N was found to be composite; or a 0, if N was not found to be composite. 2 1 If the supplied Witness does not satisfy the inequality 2 ≤ Witness ≤ N - 2 , it will be mapped via modular arithmetic to a value which satisfies it.
N ∈ {0, 1} will be pronounced composite under any Witness; N ∈ {2, 3} will be judged not composite under any Witness.
Any N which was found to be composite under any particular Witness, is in fact composite. The converse, is however, not true; see Ch. 16 discussion.
Registers
$g, $h, ... $z Data Stack to Register 1 0 Pop top item from the data stack, and assign to one of registers gz. The previous value of the register is discarded. Uses the currently-active Register Set.
g, h, ... z Register to Data Stack 0 1 Push the current value of selected register: gz to the data stack. Register retains its current value. Uses the currently-active Register Set.
I/O
# Print FZ 1 0 Pop and output top item from data stack to the standard output, in hexadecimal representation.
? Random 0 1 Fill a FZ from the active RNG and push on data stack. Takes potentially-unbounded time, depending on the machine RNG.
Control
LC Mark Left Side of Cutout 0 0 Mark the start of the optional Cutout Tape segment. When the Cutout is armed by the closing RC instruction, Subroutines defined after the LC Position, but prior to the RC, may be invoked from Positions after the RC. Subroutines defined prior to the LC Position may be invoked from Positions prior to the RC, but not from those after RC. Fatal if a RC to mark the end of the Cutout does not occur at some later point. Fatal if executed more than once in a Peh run.
RC Mark Right Side of Cutout, and Arm 0 0 Mark the end of the Cutout, and arm it. Once armed, the Cutout remains in force until Peh halts, and may not be disabled or redefined. All subsequent instructions must obey the Cutout (see LC) or will trigger fatal error. Register-access instructions, including ZR and QD, executed prior to this Position will access only the segregated Cutout Register Set. Fatal if a LC has not executed prior. Fatal if executed more than once in a Peh run.
: Enter Loop 0 0 Push a Call of type Loop, with return Position consisting of the current Tape Position, to the control stack. Overflowing the control stack is fatal.
, Conditional End Loop 1 0 Pop a Call with expected type Loop from the control stack; pop top item from data stack, and if it is non-zero then transfer control to that Call’s return Position upon the next tick, i.e. performing another iteration of the Loop. Underflowing the control stack or data stack is fatal. Attempting to return from a Call of type Subroutine via this instruction is also fatal.
@ Begin Name For Subroutine Invocation / Begin or End Name for Subroutine Definition 0 0 If part of a @Name! form : Invoke a previously-defined Subroutine with given Name (see ! below.) If part of a @Name@body; form : Define a new Subroutine with given Name. Permitted characters in Name are A-Z, a-z, 0-9, - and _ . Names must be 2 or more Symbols in length. Failure to terminate the Name (in a @Name! form) or Subroutine Body (in a @Name@body; form) is fatal. Attempting to define a Subroutine inside a Subroutine or Loop is fatal. Attempting to redefine a Subroutine is fatal. The placement of the terminating ; inside a Conditional Block is fatal. The presence of any unbalanced Block operators inside a Subroutine definition is fatal. Fatal if the Subroutine Table (256 entries) is full. Fatal if occurs as the last instruction on the Tape.
! Subroutine Invocation 0 0 Push a Call with type Subroutine to the control stack, with return Position equal to the next instruction following this one. If a Name has been set via the @Name notation: attempt to look up a Subroutine with that Name in the Subroutine Table, and invoke it. If not found, fatal. If no name has been set, attempt to invoke the nearest Subroutine defined to the left of this IP and invoke it. If none exist, fatal. If this instruction is the last instruction on the Tape, fatal. Attempting to invoke a Subroutine recursively, or to invoke any Subroutine defined to the right of the current Position, is fatal. If the Invocation violates an armed Cutout, fatal. Overflowing the control stack is fatal.
; Subroutine Termination / Return 0 0 During Subroutine definition: terminates the definition. Use inside a Conditional Block is fatal. During Subroutine execution: pops a Call with expected type Subroutine from the control stack, and transfer control to that Call’s return Position upon the next tick. Underflowing the control stack is fatal. Attempting to return from a Call of type Loop via this instruction is also fatal, i.e. all Loops inside a Subroutine must terminate before the Subroutine may terminate.
Halting
QY Quit with ‘Yes’ Verdict 0 0 Halt Peh with a Verdict of Yes. Fatal if executed inside a Loop or a Subroutine.
QN Quit with ‘No’ Verdict 0 0 Halt Peh with a Verdict of No.
QM Quit with ‘Mu’ Verdict 0 0 Halt Peh with a Verdict of Mu.
QD Quit with ‘Mu’ Verdict and Debug Trace 0 0 Halt Peh with a Verdict of Mu, and print debug trace, which includes the contents of the control and data stacks, the Subroutines Table, and Cutout definition if the Cutout is armed. The Register set printed will be the active one (i.e. the segregated Cutout Register Set if current Position is to the left of the end of the Cutout; otherwise the ordinary Register set.)
QE Quit with Eggog 0 0 Halt Peh and signal a catastrophic error.
Zaps
ZR Zap Registers 0 0 Reset the currently-active Register Set.
ZD Zap Data Stack 0 0 Reset data stack.
ZF Zap Overflow Flag 0 0 Reset Overflow Flag.
ZA Zap All 0 0 Reset data stack, Flag, and the currently-active Register Set.
Other
V Push the Peh and FFA version numbers to the data stack. 0 2 Kelvin Versioning is in use.
Not Yet Defined:
H Undefined 0 0 Prohibited
I Undefined 0 0 Prohibited
J Undefined 0 0 Prohibited
K Undefined 0 0 Prohibited
N Undefined 0 0 Prohibited
T Undefined 0 0 Prohibited
X Undefined 0 0 Prohibited (outside of MX)
Y Undefined 0 0 Prohibited

Please refer to this table as you read on.


The primary new feature introduced to Peh in this Chapter is the Subroutine. A Subroutine Table is provided, and holds up to 256 entries:

limits.ads:

package Limits is
 
   ............
 
   -- The exact size of the Peh Subroutine Table. This is an invariant.
   Subroutine_Table_Size  : constant Positive := 256;
 
   -- The minimum number of Symbols in a Subroutine Name. This is an invariant.
   Subr_Min_Name_Length   : constant Positive := 2;
 
end Limits;

ffa_calc.ads:

package FFA_Calc is
 
   ............
 
   -- Valid indices into the Subroutine Table:
   subtype Subroutine_Table_Range is Natural range 0 .. Subroutine_Table_Size;
   -- The 'zero' position indicates 'emptiness', as in the above.
 
   ............
 
end FFA_Calc;

Each Subroutine entry consists of a Tape Segment (see below) to represent the name of the Subroutine, and a second segment to represent its body. Prior to describing the syntax for defining or invoking Subroutines, let’s review the required moving parts, including all of the necessary changes to the Chapter 17 control stack mechanism:

ffa_calc.adb:

   -- Start a Peh Machine with the given Dimensions and Tape; return a Verdict.
   function Peh_Machine(Dimensions : in Peh_Dimensions;
                        Tape       : in Peh_Tapes;
                        RNG        : in RNG_Device) return Peh_Verdicts is
 
   ............
   ............
 
      -- Types of Entry for the Control Stack:
      type Call_Types is (Invalid, Subroutines, Loops);
 
      -- Control Stack Entries:
      type Call is
         record
            Why : Call_Types := Invalid; -- Which call type?
            Ret : Tape_Positions;        -- The IP we must return to after it
         end record;
 
      -- Control Stack; permits bidirectional motion across the Tape:
      Control_Stack : array(ControlStack_Range) of Call;
 
      -- Current top of the Control Stack:
      CSP           : ControlStack_Range := ControlStack_Range'First;
 
      -- A Segment represents a particular section of Tape, for certain uses.
      type Segment is
         record
            -- The Tape Position of the FIRST Symbol on the Segment:
            L : Tape_Positions := Tape'First; -- Default: start of the Tape.
 
            -- The Tape Position of the LAST Symbol on the Segment:
            R : Tape_Positions := Tape'Last;  -- Default: end of the Tape.
         end record;
 
      -- Subtypes of Segment:
      subtype Sub_Names  is Segment; -- Subroutine Names
      subtype Sub_Bodies is Segment; -- Subroutine Bodies
      subtype Cutouts    is Segment; -- Cutout (see Ch.18 discussion)
 
      -- Represents a Subroutine defined on this Tape:
      type Sub_Def is
         record
            Name    : Sub_Names;  -- Name of the Subroutine.
            Payload : Sub_Bodies; -- Body of the Subroutine.
         end record;
 
      -- Subroutine Table. Once defined, Subs may not be erased or altered.
      Subs          : array(Subroutine_Table_Range) of Sub_Def;
 
      -- Position of the most recently-defined Subroutine in Subs :
      STP           : Subroutine_Table_Range := Subs'First;
 
   ............
 
      -- Whether we are currently inside a Proposed Subroutine Name:
      SubNameMode   : Boolean      := False;
 
      -- Whether we are currently inside a Proposed Subroutine Body:
      SubBodyMode   : Boolean      := False;
 
      -- Current levels of nestable Blocks when reading a Subroutine Body:
      SubQuoteLevel : Natural      := 0;
      SubCommLevel  : Natural      := 0;
      SubCondLevel  : Natural      := 0;
 
      -- Scratch for a Subroutine being proposed for lookup or internment:
      Proposed_Sub  : Sub_Def;
 
   ............

The control stack manipulators now look like this:

ffa_calc.adb:

      -------------------
      -- Control Stack --
      -------------------
 
      -- Determine whether the Control Stack is Not Empty:
      function Control_Stack_Not_Empty return Boolean is
      begin
         return CSP /= Control_Stack'First;
      end Control_Stack_Not_Empty;
 
 
      -- Construct a Call and push it to the Control Stack:
      procedure Control_Push(Call_Type : in Call_Types;
                             Return_IP : in Tape_Positions) is
      begin
         -- First, test for Overflow of Control Stack:
         if CSP = Control_Stack'Last then
            E("Control Stack Overflow!");
         end if;
         -- Push a Call with given parameters to the Control Stack:
         CSP                := CSP + 1;
         Control_Stack(CSP) := (Why => Call_Type, Ret => Return_IP);
      end Control_Push;
 
 
      -- Pop an IP from the Control Stack, and verify expected Call Type:
      function Control_Pop(Expected_Type : in Call_Types)
                          return Tape_Positions is
         C : Call;
      begin
         -- First, test for Underflow of Control Stack:
         if CSP = Control_Stack'First then
            E("Control Stack Underflow!");
         end if;
         -- Pop from Control Stack:
         C                      := Control_Stack(CSP);
         Control_Stack(CSP).Why := Invalid;
         CSP                    := CSP - 1;
         -- Now, see whether it was NOT the expected type. If so, eggog:
         if C.Why /= Expected_Type then
            declare
               CT : constant array(Call_Types) of String(1 .. 10)
                 := (" INVALID  ", "Subroutine", "Loop state");
            begin
               E("Currently in a " & CT(C.Why) & "; but this Op exits a "
                   & CT(Expected_Type) & " !");
            end;
         end if;
         -- ... The Call was of the expected type, so return it:
         return C.Ret;
      end Control_Pop;

Specifically, and unlike in Chapter 17, we store not only the return position when effecting a transfer of control, but also the nature of the operation which caused the transfer. Thereby we obtain the stricter syntax (reviewed in the instruction set table at the start of this Chapter), where the ; operator may only terminate a Subroutine, while the , operator may only terminate a Loop.

Let’s walk through precisely how a Subroutine comes into being. The starting point for this process is the @ operator:

ffa_calc.adb:

      -- Execute a Normal Op
      procedure Op_Normal(C : in Character) is
 
         -- Over/underflow output from certain ops
         F : Word;
 
      begin
 
         case C is
   ............
   ............
 
               -- Indicate the start of a Subroutine Name, e.g. @SubName
               -- ... if DEFINING  a NEW   Subroutine: is followed by @body;
               -- ... if INVOKING EXISTING Subroutine: is followed by !
            when '@' =>
               -- Save the NEXT IP as the first Symbol of the proposed Name:
               Proposed_Sub.Name.L := Next_IP_On_Tape;
               -- Enter the Name mode:
               SubNameMode         := True;
               -- We will remain in Name mode until we see a @ or ! .
   ............
   ............
 
         end case;
 
      end Op_Normal;

For now, let’s ignore invocation and focus on definition. The the @ operator, when first encountered, triggers Name Mode. This sets the “scratch” Subroutine definition tape segment tuple’s Name.L (”left of name”) to the next Position on the tape, which consequently must exist:

ffa_calc.adb:

      -- Certain Ops are NOT permitted to occur as the final Op on a Tape:
      function Next_IP_On_Tape return Tape_Positions is
      begin
         -- Check if we are in fact on the last Symbol of the Tape:
         if Last_Tape_Symbol then
            E("This Op requires a succeeding Tape Position, "
             & "but it is at the end of the Tape!");
         end if;
         -- ... Otherwise, return the immediate successor Tape Position:
         return IP + 1;
      end Next_IP_On_Tape;

Afterwards, it behaves much like the other block modes described in Chapter 4:

ffa_calc.adb:

      -- Process a Symbol
      procedure Op(C : in Character) is
      begin
 
         -- See whether we are inside a 'Block' :
   ............
   ............
         -- ... in a Comment block:
   ............
   ............
 
            -- ... in a Quote block:
   ............
   ............
 
            --- ... in a ~taken~ Conditional branch:
   ............
   ............
 
            --- ... in a proposed Subroutine Name:
         elsif SubNameMode then
            case C is
 
               -- Attempt to INVOKE the named Subroutine:
               when '!' =>
                  -- Detect attempt to invoke a Sub with no Name:
                  if IP = Proposed_Sub.Name.L then
                     E("Attempted to invoke a nameless Subroutine!");
                  end if;
                  -- Exit the Name mode:
                  SubNameMode := False;
                  -- Attempt to invoke the subroutine:
                  Invoke_Named_Subroutine(Proposed_Sub.Name);
 
               -- Attempt to read a body for a Subroutine Definition:
               when '@' =>
                  -- Detect attempt to define a Sub with no Name:
                  if IP = Proposed_Sub.Name.L then
                     E("Attempted to define a nameless Subroutine!");
                  end if;
                  -- Save the NEXT IP as the beginning of the proposed Body:
                  Proposed_Sub.Payload.L := Next_IP_On_Tape;
                  -- Exit the Name mode:
                  SubNameMode            := False;
                  -- Enter Sub Body mode:
                  SubBodyMode            := True;
 
               -- Any permissible Symbol in a Subroutine Name:
               when '0' .. '9' | 'A' .. 'Z' | 'a' .. 'z' | '-' | '_'  =>
                  -- Save IP as the potential end of the proposed Sub Name:
                  Proposed_Sub.Name.R := IP;
 
               when others =>
                  E("Symbol '" & C & "' is prohibited in a Subroutine Name !");
            end case;
   ............
   ............

Once Peh is in Name mode, all subsequent Symbols encountered on the Tape will be interpreted as part of a Name, until a Name Terminator — either @ (Subroutine Definition Name Terminator) or ! (Subroutine Invocation Name Terminator) is encountered, or a prohibited Symbol (i.e. not part of the permissible set A-Z, a-z, 0-9, - and _ ) triggers a fatal error.

Observe that null (i.e. containing no Symbols) Names are prohibited.

Once the Name mode is exited by one of the two specified Name Terminator symbols, what happens next depends on which of these two Symbols was encountered.

If the Name Mode was exited by the @ operator, a new Subroutine body is expected to come next. (See below.) If instead the ! operator is encountered, we proceed into a Subroutine invocation. (Described further below.)

Let’s examine the Body Mode used to complete a Subroutine definition:

ffa_calc.adb:

   ............
   ............
            --- ... in a proposed Subroutine Body:
         elsif SubBodyMode then
            declare
               -- Name of Proposed Subroutine (for eggogs) :
               Name : String
                 := String(Tape(Proposed_Sub.Name.L .. Proposed_Sub.Name.R));
            begin
               case C is
                  -- Subroutine Terminator:
                  when ';' =>
                     -- Only takes effect if NOT in a Comment or Quote Block:
                     if SubCommLevel = 0 and SubQuoteLevel = 0 then
                        if SubCondLevel /= 0 then
                           E("Conditional Return in Subroutine: '"
                               & Name & "' is Prohibited!" &
                               " (Please check for unbalanced '{'.)'");
                        end if;
                        -- Now, Sub-Comm, Quote, and Cond levels are 0.
                        -- The ';' becomes last Symbol of the new Sub's Body.
                        -- Test for attempt to define a Sub with a null Body:
                        if IP = Proposed_Sub.Payload.L then
                           E("Null Body in Subroutine: '" & Name 
                               & "' is prohibited!");
                        end if;
                        -- Exit Body mode, and intern this new Sub definition:
                        Proposed_Sub.Payload.R := IP;
                        -- Exit the Sub Body mode:
                        SubBodyMode            := False;
                        -- Attempt to intern the Proposed Subroutine:
                        Intern_Subroutine(Proposed_Sub);
                     end if;
 
                     -- Begin-Comment inside a Subroutine Body:
                  when '(' =>
                     SubCommLevel := SubCommLevel + 1;
 
                     -- End-Comment inside a Subroutine Body:
                  when ')' =>
                     -- If cannot drop Sub Comment level:
                     if SubCommLevel = 0 then
                        E("Unbalanced ')' in Body of Subroutine: '"
                            & Name & "' !");
                     end if;
                     SubCommLevel := SubCommLevel - 1;
 
                     -- Begin-Quote inside a Subroutine Body:
                  when '[' =>
                     -- Ignore if Commented:
                     if SubCommLevel = 0 then
                        SubQuoteLevel := SubQuoteLevel + 1;
                     end if;
 
                     -- End-Quote inside a Subroutine Body:
                  when ']' =>
                     -- Ignore if Commented:
                     if SubCommLevel = 0 then
                        -- If cannot drop Sub Quote level:
                        if SubQuoteLevel = 0 then
                           E("Unbalanced ']' in Body of Subroutine: '"
                               & Name & "' !");
                        end if;
                        SubQuoteLevel := SubQuoteLevel - 1;
                     end if;
 
                     -- Begin-Conditional inside a Subroutine Body:
                  when '{' =>
                     -- Ignore if Commented or Quoted:
                     if SubCommLevel = 0 and SubQuoteLevel = 0 then
                        SubCondLevel := SubCondLevel + 1;
                     end if;
 
                     -- End-Conditional inside a Subroutine Body:
                  when '}' =>
                     -- Ignore if Commented or Quoted:
                     if SubCommLevel = 0 and SubQuoteLevel = 0 then
                        -- If cannot drop Sub Conditional level:
                        if SubCondLevel = 0 then
                           E("Unbalanced '}' in Body of Subroutine: '"
                               & Name & "' !");
                        end if;
                        SubCondLevel := SubCondLevel - 1;
                     end if;
 
                     -- All other Symbols have no special effect in Sub Body :
                  when others =>
                     null; -- Stay in Body mode until we see the ';'.
               end case;
            end;
   ............
   ............

This piece may appear complicated, but is really quite simple: we advance on the Tape until a point where the ; Subroutine Terminator is executed in such a way that it does not lie inside a Comment or Quote Block. Following this, we verify that it did not occur inside a Conditional Block — this situation triggers a fatal error, as it is undesirable for Peh Tapes to include convoluted control flow. At that point, we verify that the body of the Subroutine actually contains at least one Symbol. Afterwards, we mark the “right” of the Subroutine Body in the “scratch” Subroutine definition tape segment tuple Proposed_Sub, and proceed to internment:

ffa_calc.adb:

      -- Attempt to intern the given Subroutine into the Subroutines Table:
      procedure Intern_Subroutine(Sub : in Sub_Def) is
         -- Position of the current Proposed Sub in Sub Table:
         Index  : Subroutine_Table_Range := Lookup_Subroutine(Sub.Name);
         -- To DEFINE a Sub, it must NOT have existed in Sub Table.
 
         -- Name of the Proposed Sub (for eggogs) :
         S_Name : String := String(Tape(Sub.Name.L .. Sub.Name.R));
      begin
         -- If a Sub with this Name already exists, eggog:
         if Index /= Subs'First then
            E("Attempted to redefine Subroutine '" & S_Name & "' !");
         end if;
         -- Definitions are prohibited inside Loops or Sub calls:
         if Control_Stack_Not_Empty then
            E("Attempted to define Subroutine '" 
                & S_Name & "' while inside a Loop or Subroutine!");
         end if;
         -- If the Subroutine Table is full, eggog:
         if STP = Subs'Last then
            E("Cannot define the Subroutine '" & S_Name
                & ": the Subroutine Table is Full!");
         end if;
         -- Finally, intern the Proposed Subroutine into the Sub Table:
         STP       := STP + 1;
         Subs(STP) := Sub;
      end Intern_Subroutine;

The mechanism is self-explanatory — we ensure that no Subroutine with the proposed Name was already defined in the Subroutine Table; afterwards — that the definition was not attempted inside of another Subroutine or a Loop; next, that the table is not filled to capacity; and finally we record Proposed_Sub into the Subroutine Table.

Here is how Lookup_Subroutine works:

ffa_calc.adb:

      -- Find Subroutine with supplied Name in Subroutine Table, if it exists:
      function Lookup_Subroutine(Name : in Sub_Names)
                                return Subroutine_Table_Range is
         -- Number of Symbols in the Name of the current Proposed Subroutine:
         Sub_Name_Length : Positive := 1 + Name.R - Name.L;
      begin
         -- Enforce minimum Subroutine Name length:
         if Sub_Name_Length < Subr_Min_Name_Length then
            E("Proposed Name is" & Positive'Image(Sub_Name_Length) &
                " Symbols long, but the shortest permitted Name length is" &
                Positive'Image(Subr_Min_Name_Length) & " !");
         end if;
         -- Walk the Subroutine Table from first to last valid entry:
         for i in Subs'First + 1 .. STP loop
            declare
               -- The current Sub in the Subroutine Table being examined:
               S             : Sub_Def := Subs(i);
               -- Number of Symbols in the Name of S:
               S_Name_Length : Positive := 1 + S.Name.R - S.Name.L;
            begin
               -- If the lengths of the Names match:
               if Sub_Name_Length = S_Name_Length then
                  -- If the two Names are actually equal:
                  if Tape(Name.L .. Name.R) = Tape(S.Name.L .. S.Name.R) then
                     return i; -- Return the table index of the located Sub
                  end if;
               end if;
            end;
         end loop;
         -- Name was not found in Subroutine Table; return the zero position:
         return Subs'First;
      end Lookup_Subroutine;

This is a quite simple routine, and will not be discussed in detail. It must be noted, however, that it is also used in the invocation mechanism. Let's now proceed to describe it:

ffa_calc.adb:

      -- Invoke a given Subroutine:
      procedure Invoke_Subroutine(Sub : in Sub_Def) is
      begin
         -- Push the Call to Control Stack:
         Control_Push(Call_Type => Subroutines, Return_IP => Next_IP_On_Tape);
         -- Next instruction will be the first Symbol of the Sub's Body:
         IP_Next := Sub.Payload.L;
      end Invoke_Subroutine;
 
 
      -- Attempt to invoke a Subroutine with the supplied name:
      procedure Invoke_Named_Subroutine(Name : in Sub_Names) is
         -- Position of the current Proposed Sub in Sub Table:
         Index  : Subroutine_Table_Range := Lookup_Subroutine(Name);
         -- To invoke a Sub, it MUST exist in the Sub Table.
 
         -- Name of the Proposed Sub (for eggogs) :
         S_Name : String := String(Tape(Name.L .. Name.R));
      begin
         -- If no defined Subroutine has this Name, eggog:
         if Index = Subs'First then
            E("Invoked Undefined Subroutine '" & S_Name & "' !");
         end if;
         -- Otherwise, proceed to the invocation:
         declare
            -- The Sub Table Entry we successfully looked up:
            Sub : Sub_Def := Subs(Index);
         begin
            -- Recursion is prohibited in Peh Tapes. Detect it:
            if IP in Sub.Payload.L .. Sub.Payload.R then
               E("Recursive invocation in Subroutine '" 
                   & S_Name & "' is prohibited!");
            end if;
            -- Prohibit Subroutines whose definitions end AFTER the current IP:
            if IP < Sub.Payload.R then
               E("Cannot invoke Subroutine '" & S_Name &
                   "' before the position where it is defined!");
            end if;
            -- Proceed to invoke the Subroutine:
            Invoke_Subroutine(Sub);
         end;
      end Invoke_Named_Subroutine;

The various prohibitions -- we will discuss in the next Chapter, 18B. First, the reader must fully understand the two permitted forms of Subroutine invocation. The first of these, we have already seen earlier. It is the @Name! form, where we exit the Name Mode with the ! operator:

ffa_calc.adb:

      -- Process a Symbol
      procedure Op(C : in Character) is
      begin
 
   ............
 
            --- ... in a proposed Subroutine Name:
         elsif SubNameMode then
            case C is
 
               -- Attempt to INVOKE the named Subroutine:
               when '!' =>
                  -- Detect attempt to invoke a Sub with no Name:
                  if IP = Proposed_Sub.Name.L then
                     E("Attempted to invoke a nameless Subroutine!");
                  end if;
                  -- Exit the Name mode:
                  SubNameMode := False;
                  -- Attempt to invoke the subroutine:
                  Invoke_Named_Subroutine(Proposed_Sub.Name);

In this form, we attempt to invoke a Subroutine having that particular Name. If it is not found in the Subroutine Table, the Peh run terminates with a Verdict of Eggog. Otherwise, we succeed in placing the succeeding (i.e. after the !) Tape Position on the control stack:

ffa_calc.adb:

         -- Push the Call to Control Stack:
         Control_Push(Call_Type => Subroutines, Return_IP => Next_IP_On_Tape);
         -- Next instruction will be the first Symbol of the Sub's Body:
         IP_Next := Sub.Payload.L;

... and the execution of the next instruction proceeds from the Sub.Payload.L Position, i.e. from the "left-most" (i.e. first) instruction in the body of the invoked Subroutine.

If the ! Symbol is encountered outside of the Name Mode state, we instead perform the Invoke_Left_Subroutine operation:

ffa_calc.adb:

      -- Execute a Normal Op
      procedure Op_Normal(C : in Character) is
 
         -- Over/underflow output from certain ops
         F : Word;
 
      begin
 
         case C is
   ............
   ............
 
               -- '!' invokes a previously-defined Subroutine:
               -- ... If found after @Name was given, the syntax is: @SubName!
               -- ... If found in THIS context, with no @Name , then invokes
               --     the nearest Subroutine defined to the LEFT of this IP.
               -- NO Sub defined to the RIGHT of the current IP may be invoked.
            when '!' =>
               Invoke_Left_Subroutine;

ffa_calc.adb:

      -- Invoke the nearest Subroutine defined to the LEFT of the current IP:
      procedure Invoke_Left_Subroutine is
         -- Position of the Subroutine to be invoked (Subs'First if none)
         Index : Subroutine_Table_Range := Subs'First;
      begin
         -- Find the nearest invocable Sub (i.e. to the LEFT of current IP) :
         -- Walk starting from the LAST Sub in Subs, down to the FIRST:
         for i in reverse Subs'First + 1 .. STP loop
            -- If a Sub's definition ended PRIOR TO the current IP:
            if Subs(i).Payload.R < IP then
               -- Save that Sub's table index:
               Index := i;
               -- If we found a Sub that met the condition, stop walking:
               exit when Index /= Subs'First;
            end if;
         end loop;
         -- If no Subs have been defined prior to current IP, then eggog:
         if Index = Subs'First then
            E("No Subroutines were defined prior to this position!");
         end if;
         -- Proceed to invoke the selected Sub:
         Invoke_Subroutine(Subs(Index));
      end Invoke_Left_Subroutine;

Here, we simply locate the closest Subroutine definition to the left of the current Position, and invoke it. This permits a shorthand notation where a particular Subroutine is used repeatedly inside of an immediately-neighbouring one.


We return from a Subroutine using the ; operator:

ffa_calc.adb:

               -- Return from a Subroutine:
            when ';' =>
               -- Next instruction will be at the saved Return Position:
               IP_Next := Control_Pop(Subroutines);

Execution then proceeds starting with the instruction that immediately followed the invoking ! Symbol on the Tape (i.e. the Symbol which initiated the Subroutine invocation which we are returning from.)

Observe that, in order to exit from a Subroutine, all Loop states inside that Subroutine must have terminated, and all Conditional Blocks closed. The ; operator may not occur inside of a Conditional Block.


The new, stricter semantics of the control stack require a reworked Loop mechanism, slightly different from that of Chapter 17:

ffa_calc.adb:

               -----------
               -- Loops --
               -----------
 
               -- Begin Loop: Push IP (i.e. of THIS Op) to Control Stack.
            when ':' =>
               Control_Push(Call_Type => Loops, Return_IP => IP);
 
               -- Conditional End Loop: Pop top of Stack, and...
               -- ... if ZERO:    simply discard the top of the Control Stack.
               -- ... if NONZERO: pop top of Control Stack and make it next IP.
            when ',' =>
               Want(1);
               declare
                  Loop_Position : Tape_Positions := Control_Pop(Loops);
                  Trigger       : WBool          := FFA_FZ_NZeroP(Stack(SP));
               begin
                  -- If Trigger is active, re-enter the Loop:
                  if Trigger = 1 then
                     IP_Next := Loop_Position;
                  end if;
               end;
               -- ... otherwise, continue normally.
               Drop;

... the exact functioning of which should at this point be clear to the reader.


Now, let's review some examples of valid Subroutine definition and invocation in Peh. Assume that all of these Tapes end with a whitespace Symbol, so that the trailing ! operations are valid :

Peh Tape Output of Tape

@foo@[foo]; @foo!

foo

@foo@[foo]; !

foo

@foo@[foo]; !!!

foofoofoo

@foo@[foo]; @bar@[bar]; !

bar

@foo@[foo]; @bar@[bar]!; !

barfoo

@foo@[foo]; @bar@[bar]@foo!; !

barfoo

And now, some invalid (i.e. leading mercilessly to an Eggog Verdict) examples:

Peh Tape Eggogology

!

No Subroutines were defined prior to this position!

@foo!

Invoked Undefined Subroutine 'foo' !

@foo

The Subroutine Name at IP: 2 is Unterminated!

@foo@

The Body of Subroutine: 'foo' is Unterminated!

@foo@;

Null Body in Subroutine: 'foo' is prohibited!

;

Control Stack Underflow!

@@[hello];

Attempted to define a nameless Subroutine!

@!

Attempted to invoke a nameless Subroutine!

@x@[x];

Proposed Name is 1 Symbols long, but the shortest permitted Name length is 2 !

And now, a few slightly more interesting invalid uses:

Peh Tape Eggogology

@foo@[foo]@bar!; @bar@[bar]; @foo!

Cannot invoke Subroutine 'bar' before the position where it is defined!

@foo@[foo]!; @foo!

No Subroutines were defined prior to this position!

@foo@[foo]@foo!; @foo!

Recursive invocation in Subroutine 'foo' is prohibited!

@foo@@bar@[bar]; !

Attempted to define Subroutine 'bar' while inside a Loop or Subroutine!

: @foo@[foo];

Attempted to define Subroutine 'foo' while inside a Loop or Subroutine!

In the next Chapter, 18B, we will discuss the rationale for the restrictive semantics of the Subroutine system; review the new Cutout mechanism; and cover the several remaining changes to Peh introduced in the 18A vpatch. Stay tuned!


~To be continued!~

“Finite Field Arithmetic.” Chapter 17: Introduction to Peh.

This article is part of a series of hands-on tutorials introducing FFA, or the Finite Field Arithmetic library. FFA differs from the typical “Open Sores” abomination, in that — rather than trusting the author blindly with their lives — prospective users are expected to read and fully understand every single line. In exactly the same manner that you would understand and pack your own parachute. The reader will assemble and test a working FFA with his own hands, and at the same time grasp the purpose of each moving part therein.

Chapter 16B, the M-R proof, has been postponed; it will appear towards the end of the series.

You will need:

Add the above vpatches and seals to your V-set, and press to ffa_ch17_peh.kv.vpatch.

You should end up with the same directory structure as previously.

FFACalc is gone! But it was reborn as… see below.

As of Chapter 17, the versions of Peh and FFA are 252 and 253, respectively.

Now compile Peh:

cd ffacalc
gprbuild

But do not run it quite yet.


First, the mail bag!


Reader bvt has given me to know that he has read and signed Chapters 7 – 9:

He also published a report of his interesting experiment with ASM-accelerated multiplication.

Thank you, reader bvt!


Now, let’s eat the meat of this Chapter.


LibFFA per se is unchanged (aside from the removal of a single “uniturd” from a single comment) from Chapter 16A, as reflected in the version numbers. No major changes to LibFFA per se are expected to be necessary from this point onward.

FFACalc, the “toy” demonstration program of FFA, is no more. With the addition of certain necessary (why — will become apparent to the reader…) features, it has pupated into a simple “FORTH-like” interpreter “machine”, titled: Peh. These features, and the mechanisms of their implementation, will be the subject of this Chapter.

We will begin by summarizing the Peh instruction set, as it stands in this Chapter:

Peh Instruction Set.
Op Description # Ins # Outs Notes
Blocks
( Enter Comment Block 0 0 All further symbols are ignored until comment block is exited; supports nesting.
) Exit Comment Block 0 0 Fatal if not currently in a comment block.
[ Enter Quote Block 0 0 All further symbols are not executed, but instead echoed verbatim until quote block is exited; supports nesting.
] Exit Quote Block 0 0 Fatal if not in a quote block.
{ Enter Conditional Branch 1 0 Pop top item from stack; if it was non-zero, execute all symbols until a matching } exits the conditional block; otherwise ignore them until same; supports nesting. Fatal if stack is empty.
} Exit Conditional Branch 0 1 Pushes a 1 to stack if the branch being exited had been taken, otherwise pushes a 0.
Stack Motion
" Dup 1 2 Push a copy of the top item to the stack.
_ Drop 1 0 Discard the item on top of stack
' Swap 2 2 Exchange top and second item on stack
` Over 2 3 Push a copy of second item to stack
Constants
. Push Zero 0 1 Push a brand-new zero to stack
0..9, A..F, a..f Insert Hexadecimal Digit 1 1 Insert the given hexadecimal digit as the junior-most nibble into the top item on stack. Fatal if stack is empty. Equivalent to top := (16 × top) + Digit.
Predicates
= Equals 2 1 Push a 1 on stack if the top and second items are bitwise-equal; otherwise 0
< Less-Than 2 1 Push a 1 on stack if the second item is less than the top item
> Greater-Than 2 1 Push a 1 on stack if the second item is greater than the top item
Bitwise
& Bitwise-AND 2 1 Compute bitwise-AND of the top and second item, push result on stack
| Bitwise-OR 2 1 Compute bitwise-OR of the top and second item, push result on stack
^ Bitwise-XOR 2 1 Compute bitwise-XOR of the top and second item, push result on stack
~ Bitwise-Complement 1 1 Compute 1s-complement negation of the top item on stack, i.e. flip all bits of it.
U Bitwise-MUX 3 1 If top item is nonzero, a copy of the second item will be pushed to the stack; otherwise – of the third item.
W Width-Measure 1 1 Calculate the position of the senior-most bit in the top item that equals 1 (or return 0 if there are none) and push this number to stack.
RS Right-Shift 2 1 Shift the second item right by the number of bits given in the top item modulo the FZ bitness.
LS Left-Shift 2 1 Shift the second item left by the number of bits given in the top item modulo the FZ bitness.
Arithmetic: Basic
- Subtract 2 1 Subtract top item from second item, push result on stack, and save borrow bit into Flag
+ Add 2 1 Add top and second item, push result to stack, and save carry bit into Flag
O Push Overflow Flag 0 1 Push a copy of Flag, the register containing carry or borrow from the most recent arithmetic op, to the stack
Arithmetic: Division
\ Divide with Remainder 2 2 Divide second item by top item, push quotient and then remainder on stack; division by zero is fatal
/ Divide without Remainder 2 1 Divide second item by top item, push only quotient on stack; division by zero is fatal
% Modulus 2 1 Divide second item by top item, push only remainder on stack; division by zero is fatal
G Greatest Common Divisor 2 1 Find the Greatest Common Divisor of the top and second item, and push to the stack. GCD(0,0) is conventionally defined as 0.
Arithmetic: Multiplication
* Multiply 2 2 Multiply second item and top item, push the junior half of the result on stack, and then the senior half
R* Right-Multiply 2 1 Multiply top and second item, and push only the junior half of the product to the stack. The “Low-Multiply” from Ch. 14B.
S Square 1 2 Square the top item, put the junior half of the result on stack, and then the senior half
Arithmetic: Modular
MS Modular Square 2 1 Square the second item modulo the top item and push the result (necessarily fits in one FZ) to the stack. division by zero is fatal.
M* Modular Multiplication 3 1 Multiply third item and second item, modulo the top item; push the result to stack (necessarily fits in one FZ); division by zero is fatal
MX Modular Exponentiation 3 1 Raise third item to the power of the second item, modulo the top item, push the result to stack (necessarily fits in one FZ); division by zero is fatal.
Arithmetic: Primes
P Perform a single shot of the Miller-Rabin Monte Carlo Primality Test on N, the second item on the stack, using the top item as the Witness parameter for the test. Push a 1 to the stack if N was found to be composite; or a 0, if N was not found to be composite. 2 1 If the supplied Witness does not satisfy the inequality 2 ≤ Witness ≤ N - 2 , it will be mapped via modular arithmetic to a value which satisfies it.
N ∈ {0, 1} will be pronounced composite under any Witness; N ∈ {2, 3} will be judged not composite under any Witness.
Any N which was found to be composite under any particular Witness, is in fact composite. The converse, is however, not true; see Ch. 16 discussion.
Registers
$g, $h, ... $z Stack to Register 1 0 Pop top item from the stack, and assign to one of registers gz. The previous value of the register is discarded.
g, h, ... z Register to Stack 0 1 Push the current value of selected register: gz to the stack. Register retains its current value.
I/O
# Print FZ 1 0 Output top item to the standard output, in hexadecimal representation.
? Random 0 1 Fill a FZ from the active RNG and put on stack. Takes potentially-unbounded time, depending on the machine RNG.
Control
: Push Tape Position 0 0 Push the current Tape Position to the control stack. Does not affect the data stack.
; Unconditional Return 0 0 Pop a Tape Position from the control stack, and transfer control there upon the next tick. Underflowing the control stack is fatal.
, Conditional Return 1 0 Pop a Tape Position from the control stack; pop top item from stack, and if it is non-zero then transfer control to the new Tape Position upon the next tick. Underflowing the control stack is fatal.
Halting
QY Quit with ‘Yes’ Verdict 0 0 Halt Peh with a Verdict of Yes.
QN Quit with ‘No’ Verdict 0 0 Halt Peh with a Verdict of No.
QM Quit with ‘Mu’ Verdict 0 0 Halt Peh with a Verdict of Mu.
QD Quit with ‘Mu’ Verdict and Debug Trace 0 0 Halt Peh with a Verdict of Mu, and print debug trace.
QE Quit with Eggog 0 0 Halt Peh and signal a catastrophic error.
Other
V Push the Peh and FFA version numbers to the stack. 0 2 Kelvin Versioning is in use.
Z Zap 0 0 Reset Data Stack, Registers, and Flag.
Not Yet Defined:
! Undefined 0 0 Prohibited
@ Undefined 0 0 Prohibited
H Undefined 0 0 Prohibited
I Undefined 0 0 Prohibited
J Undefined 0 0 Prohibited
K Undefined 0 0 Prohibited
N Undefined 0 0 Prohibited
T Undefined 0 0 Prohibited
X Undefined 0 0 Prohibited (outside of MX)
Y Undefined 0 0 Prohibited

Please refer to this table as you read on.


Previously, in FFACalc, command symbols were interpreted strictly sequentially. Thereby it was possible to use the system strictly interactively, and to enter a potentially-infinite sequence of commands. This is not possible if we wish (and for certain purposes, we do) to have nonlinear control flow. Therefore we trade away the “infinity”, in exchange for a “batch” model of command input.

The operator is to invoke Peh just like FFACalc before, i.e. with a Width and Height specification, but additionally with a specification of spatial and time bounds for the Peh Tape execution:

Usage: ./peh WIDTH HEIGHT TAPESPACE LIFE [/dev/rng]

… and from that point, Peh will accept a certain number of symbols on the “standard input”, until it reaches the TAPESPACE spatial bound; immediately after the specified TAPESPACE storage is filled (or an “end of file” marker is found on the “standard input”) the Peh Machine will execute a number of program steps bounded by LIFE (if LIFE is set to zero, the result is an “immortal” run — generally this mode of operation is useful strictly in Peh Tape development and novice exploration.)

To permit bidirectional motion across a Peh Tape, a Control Stack is implemented. But we will say more about this further below.

First, let’s review the data structure specifications and the “main” routine of Peh:

limits.ads:

package Limits is
 
   -- Maximum permitted length of a Peh Tape.
   -- Peh Tapes live on the iron stack, like everything else,
   -- so it is not possible to promise "infinite" storage space for them.
   Max_Peh_TapeSpace      : constant Positive := 1048576; -- 1MB
   -- Operator may enlarge this constant, but may have to adjust OS stack cap.
   -- On small/embedded systems, it can be made smaller, as appropriate.
 
   -- The exact height of the Peh Control Stack. This is an invariant.
   Peh_Control_Stack_Size : constant Positive := 256;
 
end Limits;

The internal mechanism of the interpreter is still titled “FFACalc”.

ffa_calc.ads:

package FFA_Calc is
 
   -- Peh Tapes:
   subtype Peh_Tape_Range is Positive range 1 .. Max_Peh_TapeSpace;
   type Peh_Tapes is array(Peh_Tape_Range range <>) of Character;
 
   -- Possible Verdicts of a non-erroneous Peh Tape run:
   type Peh_Verdicts is (Yes, No, Mu);
 
   -- Operator-Selectable Spatial and Time Dimensions of a Peh Machine:
   type Peh_Dimensions is
      record
         Width     : Positive;
         Height    : Positive;
         TapeSpace : Peh_Tape_Range;
         Life      : Natural;
      end record;
 
   -- Valid indices into the Control Stack:
   subtype ControlStack_Range is Natural range 0 .. Peh_Control_Stack_Size;
   -- The 'zero' position, as with the Data Stack, indicates 'emptiness'
   -- when pointed to by CSP ( see ffa_calc.adb ) and is never accessed.
 
   -- Ensure that requested Peh Dimensions are permissible. Terminate if not.
   procedure Validate_Peh_Dimensions(Dimensions : in Peh_Dimensions);
 
   -- Start a Peh Machine with the given Dimensions and Tape; return a Verdict.
   function Peh_Machine(Dimensions : in Peh_Dimensions;
                        Tape       : in Peh_Tapes;
                        RNG        : in RNG_Device) return Peh_Verdicts;
 
end FFA_Calc;

peh.adb:

-- This is the 'main' procedure of Peh for all Unixlike OS.
procedure Peh is
 
   PehDim  : Peh_Dimensions; -- Operator-specified spacetime footprint.
 
   RNG     : RNG_Device;     -- The selected RNG device. Peh requires a RNG.
 
begin
 
   -- If a valid number of command line params was NOT given, print a likbez :
   if Arg_Count < 5 or Arg_Count > 6 then
      Eggog("Usage: ./peh WIDTH HEIGHT TAPESPACE LIFE [/dev/rng]");
   end if;
 
   declare
      Arg1 : CmdLineArg;
      Arg2 : CmdLineArg;
      Arg3 : CmdLineArg;
      Arg4 : CmdLineArg;
   begin
 
      -- Get commandline args:
      Get_Argument(1, Arg1); -- First  mandatory arg : Width
      Get_Argument(2, Arg2); -- Second mandatory arg : Height
      Get_Argument(3, Arg3); -- Third  mandatory arg : TapeSpace
      Get_Argument(4, Arg4); -- Fourth mandatory arg : Life
 
      if Arg_Count = 6 then
 
         -- A RNG was specified (Arg_Count includes program name itself)
         declare
            Arg5 : CmdLineArg;
         begin
            Get_Argument(5, Arg5); -- Fifth arg (optional) : RNG device
 
            -- Ada.Sequential_IO chokes on paths with trailing whitespace!
            -- So we have to give it a trimmed path. But we can't use
            -- Ada.Strings.Fixed.Trim, because it suffers from
            -- SecondaryStackism-syphilis. Instead we are stuck doing this:
            Init_RNG(RNG, Arg5(Arg5'First .. Len_Arg(5)));
         end;
 
      else
 
         -- If RNG was NOT explicitly specified:
         Init_RNG(RNG); -- Use the machine default. The '?' Op requires a RNG.
 
         -- Warn the operator that we are going to use the default system RNG:
         Achtung("WARNING: The '?' command will use DEFAULT entropy source : "
                   & Default_RNG_Path & " !");
         -- Generally, you do NOT want this, outside of noob exploration/tests.
 
      end if;
 
      -- Parse the four mandatory arguments into Positives:
      PehDim.Width     := Positive'Value(       Arg1 );
      PehDim.Height    := Positive'Value(       Arg2 );
      PehDim.TapeSpace := Peh_Tape_Range'Value( Arg3 );
      PehDim.Life      := Natural'Value(        Arg4 );
 
   exception
 
      -- There was an attempt to parse garbage in the init parameters:
      when others =>
         Eggog("Invalid arguments!");
 
   end;
 
   -- Validate requested Peh Dimensions. If invalid, program will terminate.
   Validate_Peh_Dimensions(PehDim);
 
   -- Read, from Unix 'standard input' , and then execute, the Tape:
   declare
 
      -- The current Tape input symbol
      Tape_Read_Char  : Character;
 
      -- The TapeSpace
      TapeSpace       : Peh_Tapes(1 .. PehDim.TapeSpace) := (others => ' ');
 
      -- 'End of File' condition when reading :
      EOF             : Boolean := False;
 
      -- Will contain the Verdict produced by the Tape:
      Verdict         : Peh_Verdicts;
 
   begin
 
      -- Attempt to read the entire expected Tapespace length, and no more:
      for TapePosition in TapeSpace'Range loop
 
         -- Attempt to receive a symbol from the standard input:
         if Read_Char(Tape_Read_Char) then
 
            -- Save the successfully-read symbol to the TapeSpace:
            TapeSpace(TapePosition) := Tape_Read_Char;
 
         else
 
            -- Got an EOF instead of a symbol:
            EOF := True;
            if TapePosition /= TapeSpace'Length then
               Achtung("WARNING: Short Tape: Tapespace filled to position:" &
                         Peh_Tape_Range'Image(TapePosition) & " of" &
                         Peh_Tape_Range'Image(TapeSpace'Last) & ".");
            end if;
 
         end if;
 
         exit when EOF; -- When EOF, halt reading, and proceed to execution.
 
      end loop;
 
      -- Execute Peh over the given Tape, on Peh Machine with given dimensions:
      Verdict := Peh_Machine(Dimensions => PehDim,
                             Tape       => TapeSpace,
                             RNG        => RNG);
 
      -- A correctly-written Peh Tape is expected to produce a Verdict.
      -- On Unix, we will give it to the caller process via the usual means:
      case Verdict is
 
         -- Tape produced a Verdict of 'Yes' :
         when Yes =>
            Quit(Yes_Code);
 
         -- Tape produced a Verdict of 'No'  :
         when No =>
            Quit(No_Code);
 
            -- Tape ran to completion without producing any Verdict at all.
            -- Outside of simple test scenarios, noob explorations, etc.,
            -- this usually means that there is a logical mistake in the
            -- Tape somewhere, and we will warn the operator:
         when Mu =>
            Achtung("WARNING: Tape terminated without a Verdict.");
            Quit(Mu_Code);
 
      end case;
 
      -- If the Tape aborted on account of a fatal error condition (e.g. div0)
      -- Peh will Quit(Sad_Code) (see E(..) in ffa_calc.adb .)
      -- Therefore, Peh ALWAYS returns one of FOUR possible Unix return-codes:
      -- -2, -1, 0, 1. (see os.ads .)
 
   end;
 
end Peh;


Apart from the new “batch input” system, the reader will also observe the presence of a new concept: the Verdict. Virtually all Peh Tapes will set forth to perform a particular computation which may succeed or fail, depending on the inputs (given as Peh Tapes may be fed into Peh back-to-back, the earlier Tape constituting an “input” for a subsequent Tape.) Therefore it is necessary to have a mechanism for proclaiming this success or failure to the operator (on Unixlike OS — the calling process.) Failure is distinguished into two types, as we will see further below.

The four possible Verdicts of a Peh run are Yes, No, Mu, and Eggog. Let’s discuss each of these, to remove any possible doubt as to their correct usage.

A verdict of Yes indicates a positive answer to the question being put to the machine (e.g. “is this a valid signature of the given payload by the given public key?”)

A verdict of No is the logical opposite of the above.

A verdict of Mu is meant to indicate that a Tape was not able to produce a logically-valid Verdict, for any reason that does not entail an irrecoverable arithmetical error (e.g. division by zero, or abuse of the stacks) in the program on the Tape. For example, in the event that a given computational input violates a particular agreed-upon format, Mu may be proclaimed. An empty tape will, unsurprisingly, result in a Verdict of Mu; as will any Tape that does not explicitly proclaim a different Verdict prior to its termination.

A verdict of Eggog indicates a “coarse error of pilotage” (e.g. stack over/underflow, division by zero, or another logically-impermissible – i.e. compromising the logical validity of any further instructions — condition) on the input Tape; or, alternatively, an explicit invocation of the QE instruction; or, elementarily, an invocation of Peh with incompletely-specified or prohibited spatial dimensions.

Verdicts, including Eggog, may be explicitly proclaimed by a Tape, using the Q operator prefix:

ffa_calc.adb:

      -- Execute a Prefixed Op
      procedure Op_Prefixed(Prefix : in Character;
                            O      : in Character) is
 
 .........     
 
      begin
 
         -- Which Prefix Op?
         case Prefix is
 
            ---------------------------------------------------------
            -- Quit...
            when 'Q' =>
 
               -- .. Quit how?
               case O is
 
                  -- ... with a 'Yes' Verdict:
                  when 'Y' =>
                     Verdict := Yes;
 
                  -- ... with a 'No' Verdict:
                  when 'N' =>
                     Verdict := No;
 
                  -- ... with a 'Mu' Verdict: (permitted, but discouraged)
                  when 'M' =>
                     IP_Next := IP; -- Force a 'Mu' Termination
 
                  -- ... with Debug Trace, and a 'Mu' Verdict:
                  when 'D' =>
                     Print_Trace;
                     IP_Next := IP; -- Force a 'Mu' Termination
 
                     -- ... with an explicit Tape-triggered fatal EGGOG!
                     -- The 'QE' curtain call is intended strictly to signal
                     -- catastrophic (e.g. iron) failure from within a Tape
                     -- program ('cosmic ray' scenario) where a ~hardwired
                     -- mechanism~ of any kind appears to have done something
                     -- unexpected; or to abort on a failed test of the RNG;
                     -- or similar hard-stop scenarios, where either physical
                     -- iron, or basic FFA routine must be said to have failed,
                     -- and the continued use of the system itself - dangerous.
                     -- The use of 'QE' for any other purpose is discouraged;
                     -- please do not use it to indicate failed decryption etc.
                  when 'E' =>
                     -- Hard-stop with this eggog:
                     E("Tape-triggered CATASTROPHIC ERROR! " &
                         "Your iron and/or your build of Peh, " &
                         "may be defective! Please consult " & 
                         "the author of this Tape.");
 
                     -- ... Unknown (Eggog):
                  when others =>
                     Undefined_Prefix_Op;
 
               end case;

… i.e. QY (”Yes”), QN (”No”), QM (”Mu”), QD (”Mu” with Debug Trace), and QE (”Eggog”, and please read the comment to learn the expected usage thereof.)

Observe that the “Yes” and “No” Verdicts can only result from an explicit proclamation, using the respective command, QY or QN.


Now let’s discuss the Control Stack, the new mechanism which permits bidirectional motion along a Tape, e.g. loops:

ffa_calc.adb:

 
   -- Start a Peh Machine with the given Dimensions and Tape; return a Verdict.
   function Peh_Machine(Dimensions : in Peh_Dimensions;
                        Tape       : in Peh_Tapes;
                        RNG        : in RNG_Device) return Peh_Verdicts is
 
.........
 
      -- Valid indices into the Tape:
      subtype Tape_Positions is Peh_Tape_Range range Tape'First .. Tape'Last;
 
      -- Position of the CURRENT Op on the Tape:
      IP            : Tape_Positions;
 
      -- After an Op, will contain position of NEXT op (if = to IP -> halt)
      IP_Next       : Tape_Positions;
 
      -- Control Stack; permits bidirectional motion across the Tape:
      Control_Stack : array(ControlStack_Range) of Tape_Positions
        := (others => Tape_Positions'First);
 
      -- Current top of the Control Stack:
      CSP           : ControlStack_Range := ControlStack_Range'First;
 
.........
 
      -- Current Verdict. We run while 'Mu', tape remains, and Ticks under max.
      Verdict       : Peh_Verdicts := Mu;
 
.........
 
      -------------------
      -- Control Stack --
      -------------------
 
      -- Push a given Tape Position to the Control Stack:
      procedure Control_Push(Position : in Tape_Positions) is
      begin
         -- First, test for Overflow of Control Stack:
         if CSP = Control_Stack'Last then
            E("Control Stack Overflow!");
         end if;
 
         -- Push given Tape Position to Control Stack:
         CSP                := CSP + 1;
         Control_Stack(CSP) := Position;
      end Control_Push;
 
 
      -- Pop a Tape Position from the Control Stack:
      function Control_Pop return Tape_Positions is
         Position : Tape_Positions;
      begin
         -- First, test for Underflow of Control Stack:
         if CSP = Control_Stack'First then
            E("Control Stack Underflow!");
         end if;
 
         -- Pop a Tape Position from Control Stack:
         Position           := Control_Stack(CSP);
         Control_Stack(CSP) := Tape_Positions'First;
         CSP                := CSP - 1;
         return Position;
      end Control_Pop;
 
.........
 
   begin
      -- Reset all resettable state:
      Zap;
 
      -- Execution begins with the first Op on the Tape:
      IP := Tape_Positions'First;
 
      loop
 
         -- If current Op is NOT the last Op on the Tape:
         if IP /= Tape_Positions'Last then
 
            -- ... then default successor of the current Op is the next one:
            IP_Next := IP + 1;
 
         else
 
            -- ... but if no 'next' Op exists, or quit-with-Mu, we stay put:
            IP_Next := IP; -- ... this will trigger an exit from the loop.
 
         end if;
 
         -- Advance Odometer for every Op (incl. prefixes, in comments, etc) :
         Ticks := Ticks + 1;
 
         -- Execute the Op at the current IP:
         Op(Tape(IP));
 
         -- Halt when...
         exit when
           Verdict /= Mu or -- Got a Verdict, or...
           IP_Next  = IP or -- Reached the end of the Tape, or...
           Exhausted_Life;  -- Exhausted Life.
 
         -- We did not halt yet, so select the IP of the next Op to fetch:
         IP := IP_Next;
 
      end loop;
 
      -- Warn operator about any unclosed blocks:
      if CommLevel > 0 then
         Achtung("WARNING: Tape terminated with an unclosed Comment!");
      end if;
 
      if QuoteLevel > 0 then
         Achtung("WARNING: Tape terminated with an unclosed Quote!");
      end if;
 
      if CondLevel > 0 then
         Achtung("WARNING: Tape terminated with an unclosed Conditional!");
      end if;
 
      -- Warn operator if we terminated with a non-empty Control Stack.
      -- This situation ought to be considered poor style in a Peh Tape;
      -- for clarity, Verdicts should be returned from a place near
      -- the visually-apparent end of a Tape. However, this is not mandatory.
      if CSP /= Control_Stack'First then
         Achtung("WARNING: Tape terminated with a non-empty Control Stack!");
      end if;
 
      -- We're done with the Tape, so clear the state:
      Zap;
 
      -- Return the Verdict:
      return Verdict;
 
   end Peh_Machine;

Peh is an entirely conventional “dual-tape stack machine”. The Control Stack is not directly accessible to the operator, and is manipulated strictly by a small set of instruction symbols which facilitate “backwards” transfer of control along the Tape.

Let’s examine the three currently-defined instruction symbols which make use of the Control Stack:

ffa_calc.adb:

               -------------------
               -- Control Stack --
               -------------------
 
               -- Push current IP (i.e. of THIS Op) to Control Stack.
            when ':' =>
               Control_Push(IP);
 
               -- Conditional Return: Pop top of Stack, and...
               -- ... if ZERO:    simply discard the top of the Control Stack.
               -- ... if NONZERO: pop top of Control Stack and make it next IP.
            when ',' =>
               Want(1);
               declare
                  Position : Tape_Positions := Control_Pop;
               begin
                  if FFA_FZ_NZeroP(Stack(SP)) = 1 then
                     IP_Next := Position;
                  end if;
               end;
               Drop;
 
               -- UNconditional Return: Control Stack top popped into IP_Next.
            when ';' =>
               IP_Next := Control_Pop;

The : operator pushes the current (i.e. its own) position on the Tape to the Control Stack. (Observe that, just as with the Data Stack, overflow is prohibited and results in an Eggog Verdict.)

The ; operator pops a Tape Position from the Control Stack, and execution (as always, if the Life limit has not yet been reached) continues from that position. (Observe that, just as with the Data Stack, underflow is prohibited and results in an Eggog Verdict.)

The , operator is a conditional variant of the ; operation.

These three instructions can be used to implement loops. E.g.:

$ echo -n '.5:[foo].1-",_QY' | ./bin/peh 256 32 17 61 /dev/random

Results in the output:

foofoofoofoofoo

Nested loops are permitted, up to the limit imposed by the Control Stack depth. E.g.,

echo -n '.7:[a].5:[b].1-",_.1-",_QY' | ./bin/peh 256 32 27 405 /dev/random

… produces:

abbbbbabbbbbabbbbbabbbbbabbbbbabbbbbabbbbb

The ; operator (intended for returning from subroutine invocations, which will be introduced in the next Chapter!) can also be used to perform an “infinite” (in actuality, bounded by Life) loop. Generally you will not do this in a Peh Tape intended for distribution, but it is possible:

$ echo -n ':[a];' | ./bin/peh 256 32 6 13 /dev/random

… will output:

aaa
WARNING: Exhausted Life ( 13 ticks ) 
WARNING: Tape terminated with an unclosed Quote! 
WARNING: Tape terminated with a non-empty Control Stack! 
WARNING: Tape terminated without a Verdict.

The reader is invited to experiment with loops and their behaviour with respect to the Peh invocation parameters.

To aid in the development of Peh Tapes, the QD operator may be used to display a complete state dump and terminate (with verdict of Mu) a program under development. E.g.:

echo -n '.1.2.3:::QD' | ./bin/peh 256 32 27 405 /dev/random

… will yield:

WARNING: Short Tape: Tapespace filled to position: 12 of 27.
WARNING: Tape terminated with a non-empty Control Stack!
WARNING: Tape terminated without a Verdict.
Data Stack:
    3 : 0000000000000000000000000000000000000000000000000000000000000003
    2 : 0000000000000000000000000000000000000000000000000000000000000002
    1 : 0000000000000000000000000000000000000000000000000000000000000001
Control Stack:
    3 : 9
    2 : 8
    1 : 7
Registers:
    g : 0000000000000000000000000000000000000000000000000000000000000000
    h : 0000000000000000000000000000000000000000000000000000000000000000
    i : 0000000000000000000000000000000000000000000000000000000000000000
    j : 0000000000000000000000000000000000000000000000000000000000000000
    k : 0000000000000000000000000000000000000000000000000000000000000000
    l : 0000000000000000000000000000000000000000000000000000000000000000
    m : 0000000000000000000000000000000000000000000000000000000000000000
    n : 0000000000000000000000000000000000000000000000000000000000000000
    o : 0000000000000000000000000000000000000000000000000000000000000000
    p : 0000000000000000000000000000000000000000000000000000000000000000
    q : 0000000000000000000000000000000000000000000000000000000000000000
    r : 0000000000000000000000000000000000000000000000000000000000000000
    s : 0000000000000000000000000000000000000000000000000000000000000000
    t : 0000000000000000000000000000000000000000000000000000000000000000
    u : 0000000000000000000000000000000000000000000000000000000000000000
    v : 0000000000000000000000000000000000000000000000000000000000000000
    w : 0000000000000000000000000000000000000000000000000000000000000000
    x : 0000000000000000000000000000000000000000000000000000000000000000
    y : 0000000000000000000000000000000000000000000000000000000000000000
    z : 0000000000000000000000000000000000000000000000000000000000000000
Ticks : 11
IP    : 11

Warnings are sent to the “standard error” output stream under all Unixlike OS.


Now let’s discuss Registers. These are implemented quite simply:

ffa_calc.adb:

 
   -- Start a Peh Machine with the given Dimensions and Tape; return a Verdict.
   function Peh_Machine(Dimensions : in Peh_Dimensions;
                        Tape       : in Peh_Tapes;
                        RNG        : in RNG_Device) return Peh_Verdicts is
 
.........
 
      -- Registers:
      subtype RegNames is Character range 'g' .. 'z';
      type RegTables is array(RegNames range <>) of FZ(1 .. Wordness);
      Registers     : RegTables(RegNames'Range);
 
.........
 
      -- Execute a Normal Op
      procedure Op_Normal(C : in Character) is
 
         -- Over/underflow output from certain ops
         F : Word;
 
      begin
 
         case C is
 
.........
 
               -------------------------
               -- Fetch from Register --
               -------------------------
            when 'g' .. 'z' =>
               Push;
               Stack(SP) := Registers(C); -- Put value of Register on stack
 
.........
 
 
      -- Execute a Prefixed Op
      procedure Op_Prefixed(Prefix : in Character;
                            O      : in Character) is
.........
 
      begin
 
         -- Which Prefix Op?
         case Prefix is
 
.........
 
            ---------------------------------------------------------
            -- Write into Register...
            when '$' =>
 
               -- Eggog if operator gave us a garbage Register name:
               if O not in RegNames then
                  E("There is no Register '" & O & "' !");
               end if;
 
               -- Selected Register exists; move top FZ on stack into it:
               Want(1);
               Registers(O) := Stack(SP);
               Drop;
.........

There are precisely 20 Registers: g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z.

Remember that the Peh system is case-sensitive.

A Register may be assigned a value by invoking the $ operator, followed by the name of the Register, e.g. $r. This will pop a FZ from the Data Stack and assign its value to the selected register.

To retrieve a value from a Register, it suffices to invoke the name of that register as a Peh command, e.g. r. This will place a copy of the current value of that register on the Data Stack.



In Chapter 18, we will introduce a subroutine mechanism to Peh.


~To be continued!~

X-Ray Large-Format Kindergarten.

Below is the output of a new test-fire of the PCB radiography system, this time using FujiFilm “Super HR-T Medium Speed” (25×20 cm) film in place of the earlier self-developing dental Eco-30.

However, the time has not yet come to molest the MacIvory — first, we must characterize the film and the pertinent chemistry.

This time, the victims were a FG TRNG Mainboard and Analogue RNG Board, and additionally a vintage Adaptec 2940 SCSI card.

Exposure: 45 sec. @ 35kV, in “Fuji” scintillator cassette; developed and fixed in “SDX” B&W developer and fixer, respectively 1 min. and 2min. each; with water as stop bath.


Fuji: hang


Adaptec 2940.
Click for full resolution (Warning: 35MB!)

Fuji: Adaptec


Adaptec 2940 Detail: CPU.
Click for full resolution (Warning: 16MB!)

Fuji: Adaptec


FG TRNG Mainboard.
Click for full resolution (Warning: 26MB!)

Fuji: FG MB


FG TRNG Analogue RNG Unit.
Click for full resolution (Warning: 25MB!)

Fuji: FG Analogue


The shadow artifacts are accounted for by the off-center placement of the film inside the cassette: the dead center of the beamline ended up in the bottom right-hand corner of the film. Scratches are to be blamed on the plastic trays; I’ve been informed that one ought to place sheet glass at the bottom of each tray when developing radiographic emulsion, but did not bother for this test. The results of this mistake are visible on the upper left hand corner of the Adaptec scan.

No fogging was observed, it would appear that — contrary to some reports — a traditional red “safelight” is entirely adequate for this film.

The obvious loss of resolution, however, is to be blamed on the scintillator: these infamously introduce blur. I suspect that the film per se, and the development chemistry, are entirely good as they stand. Future shots will be taken with a direct, cassette-less exposure, as soon as I find what can be used for a properly opaque bag for the Fuji film (as it is, it ships “naked”, in crates of 100.)

Edit: how do we know it’s the scintillator? The alert reader will observe, via the full-res images, that the grain of the Fuji film is finer than that of the Eco-30. This suggests that the new film per se is not to blame for the blur.

X-Ray Microscopy of Symbolics “Ivory” CPUs.

I currently have three Symbolics MacIvory CPUs. (Do you, reader, have one to sell? Please leave a comment!)

One is currently installed in a working machine and was not yet molested; the two remaining chips, I obtained in 2017 by bartering away a Symbolics 3620.

At some point I will cut one or more of the “Ivories” open, and perform die microscopy. But prior to this, why not a bit of non-destructive imaging?


The first of the two samples is a “production” model, with a metallic cap:

Click for full resolution (1 MB):

Iron Ivory

Edit: the bottom side of this unit looks like this.

An attempted die shot at maximum voltage yielded:

Click for full resolution (Warning: 20 MB!):

Iron Ivory


It appears that 35kV (the maximum setting of the system) is unable to produce a usefully-contrasting image of the die and connections.

So we move on to the second sample, a “proto” model with a ceramic cap:


Click for full resolution (200 kB):

Ceramic Ivory

And with this one, we get:

Click for full resolution (Warning: 20 MB!):

Ceramic Ivory


Pretty interesting. What is the substance visible on the die? (glue holding the lid in place? or a “bird’s eye” view of the metallic layers of the die itself?)

Later, a magnified shot will be taken of this unit, with 35cm film and a scintillation cassette.

Exposure: 75 sec. @ 35kV, contact print, film: “Eco-30″.


X-Ray Spectrography Kindergarten.

This article is a continuation of “X-Ray Microscopy Kindergarten.”.


Preliminary experiment with multi-energy x-ray (colour channel combination), distinguishing materials by absorption at different wavelengths.

FG CPLD MultiEnergy Xray

The tube appears to exhibit some “heel effect” at the longer wavelengths.

Exposure: 75 sec. @ 35kV, 21kV, 20kV (R, G, B) film: “Eco-30″.


X-Ray Stereography Kindergarten.

This article is a continuation of “X-Ray Microscopy Kindergarten.”.


Here, we see a stereo pair (approx. +/- 25 deg. one-axis rotation of the object from the horizontal plane, while film is stationary) of the device from before.

FG CPLD Stereograph

The vias are clearly distinguishable.

Exposure: 70 sec. @ 34kV; 0.3mA + 2x beam spread; film: “Eco-30″.


X-Ray Microscopy Kindergarten.

This article is a continuation of “PCB Radiography Kindergarten, Continued.”.


The radiography system is equipped with a “microfocus” tube, and its beam has a 30 degree spread cone. So we can get a look at that very same FG TRNG XC9572 CPLD seen earlier, but with 2x magnification:

Click for full resolution (Warning: 7MB)

FG CPLD 2x

This time, we can clearly distinguish the gold bonding wires which connect the silicon die to the pins.

Unfortunately, it is not possible to take a shot of the die surface using this type of instrument… (or is it..?)

Exposure: 85 sec. @ 34kV; 0.3mA + 2x beam spread; film: “Eco-30″.


PCB Radiography Kindergarten, Continued.

This article is a continuation of “PCB Radiography Kindergarten”.


This was a second test-fire of that radiography system, using yet-another board where I already know where everything is (on account of having designed it.)

This time, the victim is a FG TRNG Mainboard:

Click for full resolution (Warning: 16MB)

FG Mainboard

For comparison, a visible-light photograph of the same section:


FG Mainboard (optical)

Detail of XC9572 CPLD:


FG CPLD

Detail of 14.7456MHz quartz resonator:


FG Resonator

Exposure: 100 sec. @ 32kV, 0.3mA @ 25cm; film: “Eco-30″.


// Script to allow anchoring of user-selected content on html pages. // Original idea deployed by http://archive.today // Packaged for WordPress on http://trilema.com/2015/that-spiffy-selection-thing/