The Meta-Machine Code Targeted at MIPS: The Operating System Infrastructure As this writing is a collection of my ideas that have not been written down in full before now, this page will be gradually updated and refined whenever I recall or conceive more and more of my ideas concerning the topic and feel the want. In particular, this article will necessarily become more detailed and perhaps changed in other ways as the program advances and the smaller interfaces solidify. As this is the first entry of what is currently titled ``The Meta-Machine Code Targeted at MIPS'', along with being a large reason for the existence of this website, I find it reasonable that the title warrants explanation. As described in my 2017-07-07 article, I intend to design an interactive machine code development environment, my Meta-Machine Code (MMC). This version will be targeted at MIPS machines, due to my already owning a Lemote Yeeloong, and is a Meta-Machine Code in which the target machine code is also the meta-machine code, that is MIPS will be used to target MIPS. The use of MIPS poses some interesting design restrictions, such as alignment requirements and branch delay slots, but these will necessarily be worked around satisfactorily, and these considerations shouldn't be present in different targetings, and could be changed by anyone else customizing the system with the want. The intent is to have the system begin on OpenBSD 6.3, as this is the most convenient option for my Lemote Yeeloong, using the system call interface and others; later, it's intended that this infrastructure, the topic of much of this article, will be discarded for other infrastructure which permits booting into this MMC directly from the PMON2000 boot firmware of the Lemote Yeeloong. The simplification of this infrastructure and seperating it from the idealized MMC was a great concern; the idealized version of this MMC is intended to be a twenty kilobyte program, with the first four kilobytes being the Meta-Machine Code itself and the remaining sixteen kilobytes being alloted for the meta-machine code routines and keyboard tables that compose the interface. The four kilobytes will mainly provide the meta-machine code routines with necessary services, such as showing output or disassembling an instruction, arrange for each meta-machine code routine to operate in a consistent environment, manage the names, and interact with the underlying environment to achieve this; that is, the Meta-Machine Code itself will collect input, reference the routine table, configure the registers according to the interface, and then give control to the corresponding meta-machine code routine, which will then use this interface along with the services to, as an example, assemble and then place an instruction in the correct location. The specifics of this interface are still vague, incomplete, and under heavy consideration, necessarily; a meta-machine code routine will know the location it was called for, will be able to interact with the services in a consistent fashion, will be able to communicate the length of the changes made. It was thought that, by default, a meta-machine code routine would be aborted when a question is not wanted to be answered, such as if the wrong routine is called, but would be able to change this at the flip of a bit; it was realized that this would be better served as a parameter to asking services, which removes a bit that need be worried about and that, perhaps, simply offering a different variation of the routine would be better; other such global bits have similarly been disregarded and removed. Regardless, this is necessarily something that is decided and changed as problems are noticed and as routines are written. Now, to the OpenBSD infrastructure which comprises the other two parts, there will be a UNIX sh program which handles typical UNIXisms, such as command line arguments, and arranges for the machine code program to easily operate; this program will display help and version information, permit information already existing to be loaded, and arrange for the machine code program to take control with easy access to this information; the two observed limitations of this approach are that the loading of data already existing can only happen at the beginning of the program and that the saving of data can only be directed at the beginning of the program, as well, but is otherwise unhindered. The information will be made easily available by conforming to the intended internal structure of the machine code program, which may then read it in at the beginning. A UNIX program has access to three ``file descriptors'', ``standard input'', ``standard output'', and ``standard error''; the initial data will be dumped to standard input and standard error will be used to redirect output to be saved; standard output will be sent to the terminal to compose the interface. This choice removes the need for the open and close system calls and all of their unnecessary complications. Follows is the current state of the sh program, which may also be found from the menu linking this file. set -e trap 'stty "${s:--g}" > /dev/null; rm -f "$i"' EXIT name=$(echo "$0" | sed 's/.*\///') case ${1:--h} in -v|--version) echo 'Meta-Machine Code targeted at MIPS for OpenBSD 6.3'; exit ;; -h|--help) echo "$name [[-h|--help][-v|--version] [save [data [metadata]]]]"; exit ;; *) i=$(mktemp '/tmp/mmc-in.XXXXXX') o=$(mktemp '/tmp/mmc-out.XXXXXX') if [ "$2" ]; then cat "$2" > "$i"; fi truncate -s 20480 "$i" if [ "$3" ]; then cat "$3" >> "$i"; fi truncate -s 122880 "$i" ;; esac; s=$(stty -g); stty raw -echo cat "$i" - | mmc-mips-loader 2>| "$o" || true clear; sync; w=$(wc -c < "$o"); n=$(($w-122880)) m="Unable to write output files; something is wrong; find raw file at $o." if [ $w = 0 ]; then { echo 'No output desired.'; rm -f "$o"; exit; }; fi if [ $((w%122880)) != 0 ]; then { echo "$m"; exit; }; fi dd if="$o" of="$1.mips" skip="$n" bs=1024 count=20 || { echo "$m"; exit; } dd if="$o" of="$1.mmc" skip=$(($n+20480)) bs=1024 count=100 || { echo "$m"; exit; } rm -f "$o" This sh program handles the typical -v, --version, -h, and --help options; if not, it prepares a 120K input file by padding out with zeroes any MIPS machine code to 20K and then padding out the last 100K with the metadata, which is the range, name and routine information for the machine code followed by data for one thousand names; it then saves the terminal settings and changes them to suit the program; it then launches the launcher program, handling all redirections appropriately and ensuring that the return status is ignored; it then validates the output, if any was written, and writes the last save, the last 120K chunk, to the designated output files; lastly, it restores the settings and, in the case of no issues, destroys the temporary files used. This program is very ugly and any single issue will immediately destroy it, but that is the nature of sh. Since this was horrible enough in sh, attempting to do it in machine code would easily be far worse. The nonstandard truncate command is used. The choice of 20K as the base is due to the Meta-Machine Code itself having a maximum size of 20K; the metadata is four times as large; and each name is twenty bytes, permitting one thousand in an additional 20K. The second part of the infrastructure will be a small machine code program written to: call the pledge system call to restrict what the program can do, as I find it amusing and am glad to only use the read, write, and exit system calls in the tool; to use the mmap system call to read in the Meta-Machine Code program into a suitably large buffer and so bypass any limitations on program self-modification and whatnot; to provide suitable routines the Meta-Machine Code program can use for the read, write, and exit system calls; and to use the ioctl system call to obtain the terminal dimensions, writing the number of rows to two locations in two forms for use by the program. This program is not yet written, but already, by description, is reasonably simple and straightforward. It was thought there would need be code in the Meta-Machine Code to access these three system calls, but this idea was discarded so as to not taint the tool; it will instead use abstract routines provided by its loader. Care was taken to minimize the size of this system call code; the read and write system calls can fail for any number of reasons and the best solution was seen to be a potentially infinite loop; this loop can be used for both of these system calls and careful arrangement of register loading permits multiple entry points for further minimization; it is unlikely that this loop will truly resemble anything nearing infinite. Simplicity is further aided by all but the initial read call only reading one character per call and a common write count also being one, permitting these to be hardcoded. The exit system call is entirely trivial, consisting of two instructions, with its sole argument, the UNIX return value, using whatever is in the appropriate location at the time. This program fragment is not yet entirely solidified in the sense that it's not yet committed as numerical machine instructions, but rather a description of the routine that disregards branch delay slots; it is also unfinished; a handdrawn diagram using beautiful notation was used to write this and is intended to later be included as an image; follows is the routine in a more familiar notation; the N64 ABI is what is used here: start Load number (122,880) into the count register, six. Load location of the prime buffer into the buffer register, five. Jump to reader. file Load standard error into the file descriptor register, four. Load the number (122,880) into the count register, six. Load location of the prime buffer into the buffer register, five. Jump to writer. shower Load the number found in register one into the count register, six. Load the number found in register two into the buffer register, five. Jump to nwrite. write Load one into the count register. nwrite Load standard input into the file descriptor register, four. writer Load the write system call number, four, into the system call number register, two. Load a scratch location with the contents of register two. Jump to sys; the buffer register is already set here. read Load one into the count register, six; the buffer register is already set here. reader Load standard input into the file descriptor register, four. Load the read system call number, three, into the system call number register, two. Load a scratch location with the contents of register two. sys Perform the system call. Is register seven not zero, meaning the result is in error? If so, jump to sys. Does the result, in register two, equal the count, in register six? If so, return. Add the result, in register two, to the location, in register five. Subtract the result from the count, in register six. Load register two with the contents of the scratch location. Jump to sys. exit Load the exit system call number, one, into the system call number register, two. Perform the system call. As you can clearly see, this is a very poorly designed ABI, designed to avoid using registers that are trashed anyway, by overwriting registers that must be written to again in the case of failure, among other details. The Meta-Machine Code program itself will contain code to save and restore all registers, as the designers of the N64 ABI did not find it important to save all of the registers, instead requiring them to be saved in many places, many times, by each caller. Lastly, the interfacing with the terminal is an important function that is required to build and make usable the interface of the tool. This code will be in the Meta-Machine Code itself. The sh program already places the terminal in a mode in which input is direct and input echoing doesn't occur automatically. To achieve the interface, the tool uses ECMA-48 control functions; the way this is achieved is by placing all of the control functions in a contiguous area and providing a routine to call them by number; this saves space by permitting contiguous such control functions to be called at once, rather than by repeatedly calling the routine with the different numbers. The precise ordering and contents of this contiguous region is rather solidified, but still subject to improvements and other change. A caret denotes the escape character; a ``b'' denotes the backspace character; ``yyy'' denotes a dynamically generated row position; and the ``xx'' is the home location of the row, which is not yet solidified: ^[2J^[H^[xxG^[K^[A^[B^[T^[Sb b^[xx;yyyH These control functions erase the screen; reposition the cursor at the origin; reposition the cursor at the home position in the line; erase the remainder of the line from the current position to the end; move the cursor up and down; move the screen up and down; implement backspace, by backspacing, spacing, and then backspacing again; and reposition the cursor to the home position of a dynamically selected row. The code to make use of this region will accept an argument in one of the result registers, one, and use this to index into a table describing the control function(s) to be emitted: Load the table position to result register two. Add result register one to result register two. Load byte into register two using register two. Load the former five bits of this byte into register one. Load the latter three bits of this byte into register two. Add the location of the contiguous region to register one. Incerment register two by three. Call the shower routine. The table is merely a collection of bytes, the upper five bits denoting the area within the contiguous region of control functions and the lower three denoting the length of the control function, incremented by three to permit the third and fourth control functions to be emitted together in one step and because three is the length of the smallest control function used. The precise contents of the table are to designate each single control function, in order, followed by pairs of the first two and second two. .