Code samples for paravirtualized input and output
Introduction
The PVIO
Simulation instruction, give Dauug|36 programs a temporary filesystem for experimentation and testing until the architecture has an I/O subsystem that can attach real filesystems. It also has a mechanism for determining the host system’s time and date. PVIO
also allows the superuser to sleep the simulation for up to 68.719 seconds.
The temporary filesystem is minimalist. There are no directories or devices, only 36-bit filenames that are usually written as a tetrasexagesimal constant. Files can be queried for their existence and length, erased, appended to sequentially, and read sequentially. There is no seek or truncate functionality. Rewinds can be done by closing and reopening. Files can be treated as a either a sequence of bytes or a sequence of 36-bit words at the time they are opened.
I/O errors are considered to never occur, therefore no error indicators are provided. Be careful typing command names, because unrecognized names won’t tell you they’re no-ops.
The paravirtualized I/O features have no concept of permissions. The PVIO
instructions are regular user instructions, and any program can unconditionally do everything these instructions support.
Security
The security semantics of the PVIO
instruction are horrific, but they do not undermine the architecture’s security. Why?
- The
PVIO
instruction is a temporary measure for early testing. - A physical board cannot implement or emulate
PVIO
. Too much “magic” occurs.
Things PVIO
can do that violate separation between programs in simulation, but can’t happen on a real machine, are these:
- Read any file within the simulation, no questions asked.
- Delete any file within the simulation, no questions asked.
- Append to or create any file within the simulation, no questions asked.
- Determine the time and date, no questions asked.
Although humans don’t ordinarily view knowing the time as a security concern, a program that can read the time can infer whether and when something else is running on the same machine.
Examples
The below examples incorporate all of the paravirtualized I/O features, and can be used as templates for using these features in other programs.
Reading the number of seconds since the Epoch
This shows how to obtain the current date and time in register now
.
unsigned now now = 0 pvio clock`t ; The left operand is ignored.
Getting a file’s length in bytes or words
Paravirtualized files come in either of two formats: a stream of bytes, or a stream of 36-bit words. Which of the two is not stored with the file in any way, so the program needs to know how a file is going to be processed. Byte files are stored in the usual manner. See the Simulation instruction documentation for how word files are stored.
This example shows how to determine a file’s length in bytes or words, assuming you know correctly which of the two applies. A length of all ones is returned if the file does not exist, so if you use a signed length, the file doesn’t exist if the length is negative. This is also the intended mechanism for testing for a file’s existence.
signed len ; Suppose MyFile`t is a text file. Here's its length in bytes: len = MyFile`t pvio len8`t ; Suppose MyData`t is a data file of 36-bit words. ; Here's its length in words: len = MyData`t pvio len36`t ; Suppose NoFile`t is the name of a file that isn't here. ; Here's how to check. Note that the PVIO instruction doesn't ; touch the ALU and therefore can't set any flags. To update ; the N(egative) flag, we place an ALU instruction after PVIO. len = NoFile`t pvio len8`t len = len jump < FileNotFound ; We get here if the file exists. FileNotFound: ; We get here if the file doesn't exist.
If you’re just going to call in8`t
or in36`t
, these finish with a len8`t
or len36`t
operation for you. See “Copying a word file to another word file” below for an example.
Erasing a file
This removes a file if it is present in the filesystem, and does nothing if the file is not present. Either way, the file will not exist afterward.
; Delete a file named CORE if it exists. unsigned ig(nored) ig = CORE`t pvio unlink`t
Appending to a byte file
This appends the text “Hi.” and a newline to an existing file of bytes. If the file does not already exist, a new empty file is created before the text is added.
unsigned f(ilename) ig(nored) ; Filenames are simply 36-bit numbers. Here is one such name. f = Marc's`t ; Uncomment this line if you need a new file instead of appending. ;ig = f pvio unlink`t ig = f pvio out8`t ; The file is now open for writing bytes. ig = f pvio demux`t ; See below. ig = 72 pvio write`t ; ASCII: H i . newline ig = 105 pvio write`t ig = 46 pvio write`t ig = 10 pvio write`t ig = f pvio close`t ; The file is no longer open.
Because write`t
is a ternary operation (file to write to, what to write, and the fact we’re writing), we have to overcome the problem that Dauug|36 only supports two operands. This is the purpose of demux`t
above, which specifies which filename subsequent write`t
operations are sent to. The default is to write to whatever was last opened by an out8`t
or out36`t
, so strictly speaking we didn’t need the demux`t
here.
Formatted register output to a byte file
Sometimes we need to write a register’s contents in a common number base. This example separates numbers written with newlines.
unsigned f(ilename) ig(nored) n(umber) ; I symlinked this file to /dev/pts/0 to provide console output. ; lrwxrwxrwx 1 me me 10 Jul 11 11:55 file-000pts -> /dev/pts/0 f = pts`t ig = f pvio out8`t ; The file is now open for writing bytes. ; Let's print this number in a few radices. n = 53_316_291_173 ig = n pvio oct`t ; 615171405145`o ig = 10 pvio write`t ; newline ig = n pvio dec`t ; 53,316,291,173 ig = 10 pvio write`t ; newline ig = n pvio sdec`t ; -15,403,185,563 (signed decimal) ig = 10 pvio write`t ; newline ig = n pvio hex`t ; c69e60a65`h ig = 10 pvio write`t ; newline ig = n pvio tet`t ; NFVwFB`t ig = 10 pvio write`t ; newline ig = f pvio close`t ; The file is no longer open.
Writing a new word file
This writes the two 36-bit words zeroth`t iguana`t to a file. If the file already exists, it is replaced with a new empty file before writing.
unsigned f(ilename) ig(nored) ; Filenames are simply 36-bit numbers. Here is one such name. f = LOG`t ; Comment out this line to append to the file if it already exists. ig = f pvio unlink`t ig = f pvio out36`t ; The file is now open for writing words. ig = f pvio demux`t ; Only needed if more than one output file open. ig = zeroth`t pvio write`t ig = iguana`t pvio write`t ig = f pvio close`t ; The file is no longer open.
Reading from a byte file
This example sums the bytes in a file. The seteof`t
command lets us specify what value appears to be read after the end of file is reached.
unsigned f(ilename) ig(nored) len sum signed char ; Filenames are simply 36-bit numbers. Here is one such name. f = Bytes`t ; This needs to happen *before* a file is opened for reading: ; determine what will be read to indicate end-of-file. The ; default if we don't use seteof`t is ~1, so we don't strictly ; need to do this for this example. Whatever is chosen applies ; to all files opened for reading until another seteof`t happens. ig = ~1 pvio seteof`t ; Open the file for reading bytes. len = f pvio in8`t ; We don't use the file's length here, but in8`t and in36`t return ; the length in bytes and words in case we can use it. ; Sum the bytes in the file. sum = 0 loop: char = f pvio read`t ; Read a byte. Normal range is 0-255. cmp char - ~1 ; See if 'char' is negative one. jump == eof ; If yes, the end of file was reached. sum = sum + char jump loop eof: ig f pvio close`t ; The file is no longer open.
Warning about using filenames
Several places on this page, including the above example, make it appear that read`t
and close`t
take a filename as an argument, as did in8`t
before it. This is to say, operations that come after a file being opened are mapped onto the open file by their name. This assumption is perhaps 90% true, but can fail in baffling ways.
The real link between an open and a subsequent file operation (say, in8`t
and read`t
) is by global register number, which is a number between 0 and 131,071. This is necessary because the architecture supports multitasking, and more than one program may need to read from the same file. Programs do not share registers, so a file that two different programs are reading won’t get confused as to its position.
You will get into trouble if you refer to a file via two different registers, like this:
unsigned b(yte) f(ilename) ig(nored) f = MyFile`t ig = f pvio in8`t b = MyFile`t pvio read`t
The problem with this example is that in8`t
maps the file it opens onto register f
, which is a variable, but read`t
refers to a completely different register where the constant MyFile`t
is stored. It’s true that the two registers have the same contents, but what PVIO
requires is that they are the same registers.
On the other hand, this counterintuitive example works:
unsigned b(yte) f(ilename) ig(nored) f = MyFile`t ig = f pvio in8`t f = XXXXXX`t ; The filename has been overwritten. b = f pvio read`t ; The read succeeds, because it's the same register.
Copying a word file to another word file
Here is a trickier example that uses two open files named From`t
and To`t
. Because a file can contain any word, including whatever we designate to mean “end of file,” we use the length of the file to determine when to stop.
unsigned ig(nored) len word len = From`t pvio in36`t ; open file to read, get its length ig = To`t pvio out36`t ; open file to write continue.copy: len = len ; update Z(ero) flag from len jump == done.copy ; we're done with len is 0 word = From`t pvio read`t ; read a word ig = word pvio write`t ; write that word len = len - 1 ; we have one less word remaining jump continue.copy done.copy: ig = From`t pvio close`t ; close both files ig = To`t pvio close`t
Causing the simulation to sleep
Here is how to get the simulator to sleep for at least a billion nanoseconds, which is one second.
unsigned ig(nored) ig = 1_000_000_000 pvio nsleep`t ; note: nsleep`t, NOT sleep`t
For user programs nsleep`t
does nothing, because the purpose of nsleep`t
is to allow a simulated operating system to lighten its burden on the simulator’s host OS when no user programs need the CPU. PVIO
ensures that it left operand came from a superuser register (system register between 0 and 511 inclusive) before it suspends the simulation. User programs shouldn’t be and aren’t allowed to do that.
An interactive program
This is the first-ever interactive program for Dauug|36. Written on 11 July 2023, all it does is echo what the user types. The host OS is line-buffered.
signed c ; character read from/written to terminal window unsigned ig(nored) unsigned inreg ; register that maps /dev/pts/0 for input unsigned outreg ; register that maps /dev/pts/0 for output inreg = pts`t ; file-000pts is symlinked to /dev/pts/0 on host. outreg = pts`t ig = ~999 pvio seteof`t ; Reads will return ~999 if no input. ig = ~666 pvio setidl`t ; Reads will return ~666 if at end of file. ig = outreg pvio out8`t ig = inreg pvio in8`t sleepy: ; Sleep a billion nanoseconds if there was no input. ig = 1_000_000_000 pvio nsleep`t tty: ; This is the main character read/write loop. c = inreg pvio read`t cmp c - 0 jump >= have.char cmp c - ~666 jump == sleepy ; We jump if no input was ready. cmp c - ~999 jump == close'm ; We jump if the user pressed Ctrl-D (EOF). have.char: ig = c pvio write`t jump tty close'm: ; We get here if we're done. ig = outreg pvio close`t ig = inreg pvio close`t