% This file is part of the MMIXware package (c) Donald E Knuth 1999 @i boilerplate.w %<< legal stuff: PLEASE READ IT BEFORE MAKING ANY CHANGES! \def\title{MMIX-SIM} \def\MMIX{\.{MMIX}} \def\NNIX{\hbox{\mc NNIX}} \def\Hex#1{\hbox{$^{\scriptscriptstyle\#}$\tt#1}} % experimental hex constant \def\<#1>{\hbox{$\langle\,$#1$\,\rangle$}}\let\is=\longrightarrow \def\dts{\mathinner{\ldotp\ldotp}} \def\bull{\smallskip\textindent{$\bullet$}} @s xor normal @q unreserve a C++ keyword @> @s bool normal @q unreserve a C++ keyword @> @*Introduction. This program simulates a simplified version of the \MMIX\ computer. Its main goal is to help people create and test \MMIX\ programs for {\sl The Art of Computer Programming\/} and related publications. It provides only a rudimentary terminal-oriented interface, but it has enough infrastructure to support a cool graphical user interface --- which could be added by a motivated reader. (Hint, hint.) \MMIX\ is simplified in the following ways: \bull There is no pipeline, and there are no caches. Thus, commands like \.{SYNC} and \.{SYNCD} and \.{PREGO} do nothing. \bull Simulation applies only to user programs, not to an operating system kernel. Thus, all addresses must be nonnegative; ``privileged'' commands such as \.{PUT}~\.{rK,z} or \.{RESUME}~\.1 or \.{LDVTS}~\.{x,y,z} are not allowed; instructions should be executed only from addresses in segment~0 (addresses less than \Hex{2000000000000000}). Certain special registers remain constant: $\rm rF=0$, $\rm rK=\Hex{ffffffffffffffff}$, $\rm rQ=0$; $\rm rT=\Hex{8000000500000000}$, $\rm rTT=\Hex{8000000600000000}$, $\rm rV=\Hex{369c200400000000}$. \bull No trap interrupts are implemented, except for a few special cases of \.{TRAP} that provide rudimentary input-output. @^interrupts@> \bull All instructions take a fixed amount of time, given by the rough estimates stated in the \MMIX\ documentation. For example, \.{MUL} takes $10\upsilon$, \.{LDB} takes $\mu+\upsilon\mkern1mu$; all times are expressed in terms of $\mu$ and~$\upsilon$, ``mems'' and ``oops.'' The clock register~rC increases by @^mems@> @^oops@> $2^{32}$ for each~$\mu$ and 1~for each~$\upsilon$. But the interval counter~rI decreases by~1 for each instruction, and the usage counter~rU increases by~1 for each instruction. @^rC@> @^rI@> @^rU@> @ To run this simulator, assuming \UNIX/ conventions, you say `\.{mmix} \ \.{progfile} \.{args...}', where \.{progfile} is an output of the \.{MMIXAL} assembler, \.{args...} is a sequence of optional command-line arguments passed to the simulated program, and \ is any subset of the following: @^command line arguments@> \bull \.{-t}\quad Trace each instruction the first $n$ times it is executed. (The notation \.{} in this option, and in several other options and interactive commands below, stands for a decimal integer.) \bull \.{-e}\quad Trace each instruction that raises an arithmetic exception belonging to the given bit pattern. (The notation \.{} in this option, and in several other commands below, stands for a hexadecimal integer.) The exception bits are DVWIOUZX as they appear in rA, namely \Hex{80} for~D (integer divide check), \Hex{40} for~V (integer overflow), \dots, \Hex{01} for~X (floating inexact). The option \.{-e} by itself is equivalent to \.{-eff}, tracing all eight exceptions. \bull \.{-r}\quad Trace details of the register stack. This option shows all the ``hidden'' loads and stores that occur when octabytes are written from the ring of local registers into memory, or read from memory into that ring. It also shows the full details of \.{SAVE} and \.{UNSAVE} operations. \bull \.{-l}\quad List the source line corresponding to each traced instruction, filling gaps of length $n$ or less. For example, if one instruction came from line 10 of the source file and the next instruction to be traced came from line 12, line 11 would be shown also, provided that $n\ge1$. If \.{} is omitted it is assumed to be~3. \bull \.{-s}\quad Show statistics of running time with each traced instruction. \bull \.{-P}\quad Show the program profile (that is, the frequency counts of each instruction that was executed) when the simulation ends. \bull \.{-L}\quad List the source lines corresponding to each instruction that appears in the program profile, filling gaps of length $n$ or less. This option implies \.{-P}. If \.{} is omitted it is assumed to be~3. \bull \.{-v}\quad Be verbose: \kern-2.5ptTurn on all options. (More precisely, the \.{-v} option is shorthand for \.{-t9999999999}~\.{-e} \.{-r} \.{-s} \.{-l10}~\.{-L10}.) \bull \.{-q}\quad Be quiet: Cancel all previously specified options. \bull \.{-i}\quad Go into interactive mode before starting the simulation. \bull \.{-I}\quad Go into interactive mode when the simulated program halts or pauses for a breakpoint. \bull \.{-b}\quad Set the buffer size of source lines to $\max(72,n)$. \bull \.{-c}\quad Set the capacity of the local register ring to $\max(256,n)$; this number must be a power of~2. \bull \.{-f}\quad Use the named file for standard input to the simulated program. This option should be used whenever the simulator is not being used interactively, because the simulator will not recognize end of file when standard input has been defined in any other way. \bull \.{-D}\quad Prepare the named file for use by other simulators, instead of actually doing a simulation. \bull \.{-?}\quad Print the ``\.{Usage}'' message, which summarizes the command-line options. \smallskip\noindent The author recommends \.{-t2} \.{-l} \.{-L} for initial offline debugging. While the program is being simulated, an {\it interrupt\/} signal (usually control-C) will cause the simulator to @^interrupts@> break and go into interactive mode after tracing the current instruction, even if \.{-i} and \.{-I} were not specified on the command line. @ In interactive mode, the user is prompted `\.{mmix>}' and a variety of @.mmix>@> commands can be typed online. Any command-line option can be given in response to such a prompt (including the `\.-' that begins the option), and the following operations are also available: \bull Simply typing \ or \.n\ to the \.{mmix>} prompt causes one \MMIX\ instruction to be executed and traced; then the user is prompted again. \bull \.c continues simulation until the program halts or reaches a breakpoint. (Actually the command is `\.c\', but we won't bother to mention the \ in the following description.) \bull \.q quits (terminates the simulation), after printing the profile (if it was requested) and the final statistics. \bull \.s prints out the current statistics (the clock times and the current instruction location). We have already discussed the \.{-s} option on the command line, which causes these statistics to be printed automatically; but a lot of statistics can fill up a lot of file space, so users may prefer to see the statistics only on demand. \bull \.{l}, \.{g}, \.{\$}, \.{rA}, \.{rB}, \dots, \.{rZZ}, and \.{M} will show the current value of a local register, global register, dynamically numbered register, special register, or memory location. Here \.{} specifies the type of value to be displayed; if \.{} is `\.!', the value will be given in decimal notation; if \.{} is `\..' it will be given in floating point notation; if \.{} is `\.\#' it will be given in hexadecimal, and if \.{} is `\."' it will be given as a string of eight one-byte characters. Just typing \.{} by itself will repeat the most recently shown value, perhaps in another format; for example, the command `\.{l10\#}' will show local register 10 in hexadecimal notation, then the command `\.!' will show it in decimal and `\..' will show it as a floating point number. If \.{} is empty, the previous type will be repeated; the default type is decimal. Register \.{rA} is equivalent to \.{g22}, according to the numbering used in \.{GET} and \.{PUT} commands. The `\.{}' in any of these commands can also have the form `\.{=}', where the value is a decimal or floating point or hexadecimal or string constant. (The syntax rules for floating point constants appear in {\mc MMIX-ARITH}. A string constant is treated as in the \.{BYTE} command of \.{MMIXAL}, but padded at the left with zeros if fewer than eight characters are specified.) This assigns a new value before displaying it. For example, `\.{l10=.1e3}' sets local register 10 equal to 100; `\.{g250="ABCD",\#a}' sets global register 250 equal to \Hex{000000414243440a}; `\.{M1000=-Inf}' sets M$[\Hex{1000}]_8=\Hex{fff0000000000000}$, the representation of $-\infty$. Special registers other than~rI cannot be set to values disallowed by~\.{PUT}. Marginal registers cannot be set to nonzero values. The command `\.{rI=250}' sets the interval counter to 250; this will cause a break in simulation after 250 instructions have been executed. \bull \.{+} shows the next $n$ octabytes following the one most recently shown, in format \.{}. For example, after `\.{l10\#}' a subsequent `\.{+30}' will show \.{l11}, \.{l12}, \dots, \.{l40} in hexadecimal notation. After `\.{g200=3}' a subsequent `\.{+30}' will set \.{g201}, \.{g202}, \dots, \.{g230} equal to~3, but a subsequent `\.{+30!}' would merely display \.{g201} through~\.{g230} in decimal notation. Memory addresses will advance by~8 instead of by~1. If \.{} is empty, the default value $n=1$ is used. \bull \.{@@} sets the address of the next tetrabyte to be simulated, sort of like a \.{GO} command. \bull \.{t} says that the instruction in tetrabyte location $x$ should always be traced, regardless of its frequency count. \bull \.{u} undoes the effect of \.{t}. \bull \.{b[rwx]} sets breakpoints at tetrabyte $x$; here \.{[rwx]} stands for any subset of the letters \.r, \.w, and/or~\.x, meaning to break when the tetrabyte is read, written, and/or executed. For example, `\.{bx1000}' causes a break in the simulation just after the tetrabyte in \Hex{1000} is executed; `\.{b1000}' undoes this breakpoint; `\.{brwx1000}' causes a break just after any simulated instruction loads, stores, or appears in tetrabyte number \Hex{1000}. \bull \.{T}, \.{D}, \.{P}, \.{S} sets the ``current segment'' to \.{Text\_Segment}, \.{Data\_Segment}, \.{Pool\_Segment}, or \.{Stack\_Segment}, respectively, namely to \Hex{0}, \Hex{2000000000000000}, \Hex{4000000000000000}, or \Hex{6000000000000000}. The current segment, initially \Hex{0}, is added to all memory addresses in \.{M}, \.{@@}, \.{t}, \.{u}, and \.{b} commands. @:Text_Segment}\.{Text\_Segment@> @:Data_Segment}\.{Data\_Segment@> @:Pool_Segment}\.{Pool\_Segment@> @:Stack_Segment}\.{Stack\_Segment@> \bull \.{B} lists all current breakpoints and tracepoints. \bull \.{i} reads a sequence of interactive commands from the specified file, one command per line, ignoring blank lines. This feature can be used to set many breakpoints or to display a number of key registers, etc. Included lines that begin with \.\% or \.i are ignored; therefore an included file cannot include {\it another\/} file. Included lines that begin with a blank space are reproduced in the standard output, otherwise ignored. \bull \.h (help) reminds the user of the available interactive commands. @* Rudimentary I/O. Input and output are provided by the following ten primitive system calls: @^I/O@> @^input/output@> \bull \.{Fopen}|(handle,name,mode)|. Here |handle| is a one-byte integer, |name| is a string, and |mode| is one of the values \.{TextRead}, \.{TextWrite}, \.{BinaryRead}, \.{BinaryWrite}, \.{BinaryReadWrite}. An \.{Fopen} call associates |handle| with the external file called |name| and prepares to do input and/or output on that file. It returns 0 if the file was opened successfully; otherwise returns the value~$-1$. If |mode| is \.{TextWrite} or \.{BinaryWrite}, any previous contents of the named file are discarded. If |mode| is \.{TextRead} or \.{TextWrite}, the file consists of ``lines'' terminated by ``newline'' characters, and it is said to be a text file; otherwise the file consists of uninterpreted bytes, and it is said to be a binary file. @.Fopen@> @.TextRead@> @.TextWrite@> @.BinaryRead@> @.BinaryWrite@> @.BinaryReadWrite@> Text files and binary files are essentially equivalent in cases where this simulator is hosted by an operating system derived from \UNIX/; in such cases files can be written as text and read as binary or vice versa. But with other operating systems, text files and binary files often have quite different representations, and certain characters with byte codes less than~|' '| are forbidden in text. Within any \MMIX\ program, the newline character has byte code $\Hex{0a}=10$. At the beginning of a program three handles have already been opened: The ``standard input'' file \.{StdIn} (handle~0) has mode \.{TextRead}, the ``standard output'' file \.{StdOut} (handle~1) has mode \.{TextWrite}, and the ``standard error'' file \.{StdErr} (handle~2) also has mode \.{TextWrite}. @.StdIn@> @.StdOut@> @.StdErr@> When this simulator is being run interactively, lines of standard input should be typed following a prompt that says `\.{StdIn>\ }', unless the \.{-f} option has been used. The standard output and standard error files of the simulated program are intermixed with the output of the simulator~itself. The input/output operations supported by this simulator can perhaps be understood most easily with reference to the standard library \.{stdio} that comes with the \CEE/ language, because the conventions of~\CEE/ have been explained in hundreds of books. If we declare an array |FILE *file[256]| and set |file[0]=stdin|, |file[1]=stdout|, and |file[2]=stderr|, then the simulated system call \.{Fopen}|(handle,name,mode)| is essentially equivalent to the \CEE/ expression $$\displaylines{ \hskip5em\hbox{(|file[handle]|? |(file[handle]=freopen(name,mode_string[mode],file[handle]))|:}\hfill\cr \hfill\hbox{|(file[handle]=fopen(name,mode_string[mode]))|)? 0: $-1$},% \hskip5em\cr}$$ if we set |mode_string|[\.{TextRead}]~=~|"r"|, |mode_string|[\.{TextWrite}]~=~|"w"|, |mode_string|[\.{BinaryRead}]~=~|"rb"|, |mode_string|[\.{BinaryWrite}]~=~|"wb"|, and |mode_string|[\.{BinaryReadWrite}]~=~|"wb+"|. \bull \.{Fclose}|(handle)|. If the given file handle has been opened, it is closed---no longer associated with any file. Again the result is 0 if successful, or $-1$ if the file was already closed or unclosable. The \CEE/ equivalent is $$\hbox{|fclose(file[handle])? -1: 0|}$$ with the additional side effect of setting |file[handle]=NULL|. \bull \.{Fread}|(handle,buffer,size)|. The file handle should have been opened with mode \.{TextRead}, \.{BinaryRead}, or \.{BinaryReadWrite}. @.Fread@> The next |size| characters are read into \MMIX's memory starting at address |buffer|. If an error occurs, the value |-1-size| is returned; otherwise, if the end of file does not intervene, 0~is returned; otherwise the negative value |n-size| is returned, where |n|~is the number of characters successfully read and stored. The statement $$\hbox{|fread(buffer,1,size,file[handle])-size|}$$ has the equivalent effect in \CEE/, in the absence of file errors. \bull \.{Fgets}|(handle,buffer,size)|. The file handle should have been opened with mode \.{TextRead}, \.{BinaryRead}, or \.{BinaryReadWrite}. @.Fgets@> Characters are read into \MMIX's memory starting at address |buffer|, until either |size-1| characters have been read and stored or a newline character has been read and stored; the next byte in memory is then set to zero. If an error or end of file occurs before reading is complete, the memory contents are undefined and the value $-1$ is returned; otherwise the number of characters successfully read and stored is returned. The equivalent in \CEE/ is $$\hbox{|fgets(buffer,size,file[handle])? strlen(buffer): -1|}$$ if we assume that no null characters were read in; null characters may, however, precede a newline, and they are counted just like other characters. \bull \.{Fgetws}|(handle,buffer,size)|. @.Fgetws@> This command is the same as \.{Fgets}, except that it applies to wyde characters instead of one-byte characters. Up to |size-1| wyde characters are read; a wyde newline is $\Hex{000a}$. The \CEE/~version, using conventions of the ISO multibyte string extension (MSE), is @^MSE@> approximately $$\hbox{|fgetws(buffer,size,file[handle])? wcslen(buffer): -1|}$$ where |buffer| now has type |wchar_t*|. \bull \.{Fwrite}|(handle,buffer,size)|. The file handle should have been opened with one of the modes \.{TextWrite}, \.{BinaryWrite}, or \.{BinaryReadWrite}. @.Fwrite@> The next |size| characters are written from \MMIX's memory starting at address |buffer|. If no error occurs, 0~is returned; otherwise the negative value |n-size| is returned, where |n|~is the number of characters successfully written. The statement $$\hbox{|fwrite(buffer,1,size,file[handle])-size|}$$ together with |fflush(file[handle])| has the equivalent effect in \CEE/. \bull \.{Fputs}|(handle,string)|. The file handle should have been opened with mode \.{TextWrite}, \.{BinaryWrite}, or \.{BinaryReadWrite}. @.Fputs@> One-byte characters are written from \MMIX's memory to the file, starting at address |string|, up to but not including the first byte equal to~zero. The number of bytes written is returned, or $-1$ on error. The \CEE/ version is $$\hbox{|fputs(string,file[handle])>=0? strlen(string): -1|,}$$ together with |fflush(file[handle])|. \bull \.{Fputws}|(handle,string)|. The file handle should have been opened with mode \.{TextWrite}, \.{BinaryWrite}, or \.{BinaryReadWrite}. @.Fputws@> Wyde characters are written from \MMIX's memory to the file, starting at address |string|, up to but not including the first wyde equal to~zero. The number of wydes written is returned, or $-1$ on error. The \CEE/+MSE version is $$\hbox{|fputws(string,file[handle])>=0? wcslen(string): -1|}$$ together with |fflush(file[handle])|, where |string| now has type |wchar_t*|. \bull \.{Fseek}|(handle,offset)|. The file handle should have been opened with mode \.{BinaryRead}, \.{BinaryWrite}, or \.{BinaryReadWrite}. @.Fseek@> This operation causes the next input or output operation to begin at |offset| bytes from the beginning of the file, if |offset>=0|, or at |-offset-1| bytes before the end of the file, if |offset<0|. (For example, |offset=0| ``rewinds'' the file to its very beginning; |offset=-1| moves forward all the way to the end.) The result is 0 if successful, or $-1$ if the stated positioning could not be done. The \CEE/ version is $$\hbox{|fseek(file[handle],@,offset<0? offset+1: offset,@, offset<0? SEEK_END: SEEK_SET)|? $-1$: 0.}$$ If a file in mode \.{BinaryReadWrite} is used for both reading and writing, an \.{Fseek} command must be given when switching from input to output or from output to input. \bull \.{Ftell}|(handle)|. The file handle should have been opened with mode \.{BinaryRead}, \.{BinaryWrite}, or \.{BinaryReadWrite}. @.Ftell@> This operation returns the current file position, measured in bytes from the beginning, or $-1$ if an error has occurred. In this case the \CEE/ function $$\hbox{|ftell(file[handle])|}$$ has exactly the same meaning. \smallskip Although these ten operations are quite primitive, they provide the necessary functionality for extremely complex input/output behavior. For example, every function in the \.{stdio} library of \CEE/, with the exception of the two administrative operations \\{remove} and \\{rename}, can be implemented as a subroutine in terms of the six basic operations \.{Fopen}, \.{Fclose}, \.{Fread}, \.{Fwrite}, \.{Fseek}, and \.{Ftell}. Notice that the \MMIX\ function calls are much more consistent than those in the \CEE/ library. The first argument is always a handle; the second, if present, is always an address; the third, if present, is always a size. {\it The result returned is always nonnegative if the operation was successful, negative if an anomaly arose.} These common features make the functions reasonably easy to remember. @ The ten input/output operations of the previous section are invoked by \.{TRAP} commands with $\rm X=0$, $\rm Y=\.{Fopen}$ or \.{Fclose} or \dots~or \.{Ftell}, and $\rm Z=\.{Handle}$. If~there are two arguments, the second argument is placed in \$255. If there are three arguments, the address of the second is placed in~\$255; the second argument is M$[\$255]_8$ and the third argument is M$[\$255+8]_8$. The returned value will be in \$255 when the system call is finished. (See the example below.) @ The user program starts at symbolic location \.{Main}. At this time @.Main@> @:Pool_Segment}\.{Pool\_Segment@> the global registers are initialized according to the \.{GREG} statements in the \.{MMIXAL} program, and \$255 is set to the numeric equivalent of~\.{Main}. Local register~\$0 is initially set to the number of {\it command-line arguments\/}; and @^command line arguments@> local register~\$1 points to the first such argument, which is always a pointer to the program name. Each command-line argument is a pointer to a string; the last such pointer is M$[\$0\ll3+\$1]_8$, and M$[\$0\ll3+\$1+8]_8$ is zero. (Register~\$1 will point to an octabyte in \.{Pool\_Segment}, and the command-line strings will be in that segment too.) Location M[\.{Pool\_Segment}] will be the address of the first unused octabyte of the pool segment. Registers rA, rB, rD, rE, rF, rH, rI, rJ, rM, rP, rQ, and rR are initially zero, and $\rm rL=2$. A subroutine library loaded with the user program might need to initialize itself. If an instruction has been loaded into tetrabyte M$[\Hex{90}]_4$, the simulator actually begins execution at \Hex{90} instead of at~\.{Main}; in this case \$255 holds the location of~\.{Main}. @^subroutine library initialization@> @^initialization of a user program@> (The routine at \Hex{90} can pass control to \.{Main} without increasing~rL, if it starts with the slightly tricky sequence $$\.{PUT rW, \$255;{ } PUT rB, \$255;{ } SETML \$255,\#F700;{ } % PUTI rB,0! PUT rX,\$255}$$ and eventually says \.{RESUME}; this \.{RESUME} command will restore \$255 and~rB. But the user program should {\it not\/} really count on the fact that rL is initially~2.) @ The main program ends when \MMIX\ executes the system call \.{TRAP}~\.{0}, which is often symbolically written `\.{TRAP}~\.{0,Halt,0}' to make its intention clear. The contents of \$255 at that time are considered to be the value ``returned'' by the main program, as in the |exit| statement of~\CEE/; a nonzero value indicates an anomalous exit. All open files are closed @.Halt@> when the program ends. @ Here, for example, is a complete program that copies a text file to the standard output, given the name of the file to be copied. It includes all necessary error checking. \vskip-14pt $$\baselineskip=10pt \obeyspaces\halign{\qquad\.{#}\hfil\cr * SAMPLE PROGRAM: COPY A GIVEN FILE TO STANDARD OUTPUT\cr \noalign{\smallskip} t IS \$255\cr argc IS \$0\cr argv IS \$1\cr s IS \$2\cr Buf\_Size IS 1000\cr {} LOC Data\_Segment\cr Buffer LOC @@+Buf\_Size\cr {} GREG @@\cr Arg0 OCTA 0,TextRead\cr Arg1 OCTA Buffer,Buf\_Size\cr \noalign{\smallskip} {} LOC \#200 main(argc,argv) \{\cr Main CMP t,argc,2 if (argc==2) goto openit\cr {} PBZ t,OpenIt\cr {} GETA t,1F fputs("Usage: ",stderr)\cr {} TRAP 0,Fputs,StdErr\cr {} LDOU t,argv,0 fputs(argv[0],stderr)\cr {} TRAP 0,Fputs,StdErr\cr {} GETA t,2F fputs(" filename\\n",stderr)\cr Quit TRAP 0,Fputs,StdErr \cr {} NEG t,0,1 quit: exit(-1)\cr {} TRAP 0,Halt,0\cr 1H BYTE "Usage: ",0\cr {} LOC (@@+3)\&-4 align to tetrabyte\cr 2H BYTE " filename",\#a,0\cr \noalign{\smallskip} OpenIt LDOU s,argv,8 openit: s=argv[1]\cr {} STOU s,Arg0\cr {} LDA t,Arg0 fopen(argv[1],"r",file[3])\cr {} TRAP 0,Fopen,3\cr {} PBNN t,CopyIt if (no error) goto copyit\cr {} GETA t,1F fputs("Can't open file ",stderr)\cr {} TRAP 0,Fputs,StdErr\cr {} SET t,s fputs(argv[1],stderr)\cr {} TRAP 0,Fputs,StdErr\cr {} GETA t,2F fputs("!\\n",stderr)\cr {} JMP Quit goto quit\cr 1H BYTE "Can't open file ",0\cr {} LOC (@@+3)\&-4 align to tetrabyte\cr 2H BYTE "!",\#a,0\cr \noalign{\smallskip} CopyIt LDA t,Arg1 copyit:\cr {} TRAP 0,Fread,3 items=fread(buffer,1,buf\_size,file[3])\cr {} BN t,EndIt if (items < buf\_size) goto endit\cr {} LDA t,Arg1 items=fwrite(buffer,1,buf\_size,stdout)\cr {} TRAP 0,Fwrite,StdOut\cr {} PBNN t,CopyIt if (items >= buf\_size) goto copyit\cr Trouble GETA t,1F trouble: fputs("Trouble w...!",stderr)\cr {} JMP Quit goto quit\cr 1H BYTE "Trouble writing StdOut!",\#a,0\cr \noalign{\smallskip} EndIt INCL t,Buf\_Size\cr {} BN t,ReadErr if (ferror(file[3])) goto readerr\cr {} STO t,Arg1+8\cr {} LDA t,Arg1 n=fwrite(buffer,1,items,stdout)\cr {} TRAP 0,Fwrite,StdOut\cr {} BN t,Trouble if (n < items) goto trouble\cr {} TRAP 0,Halt,0 exit(0)\cr ReadErr GETA t,1F readerr: fputs("Trouble r...!",stderr)\cr {} JMP Quit goto quit \}\cr 1H BYTE "Trouble reading!",\#a,0\cr }$$ @* Basics. To get started, we define a type that provides semantic sugar. @= typedef enum {@!false,@!true}@+@!bool; @ This program for the 64-bit \MMIX\ architecture is based on 32-bit integer arithmetic, because nearly every computer available to the author at the time of writing (1999) was limited in that way. It uses subroutines from the {\mc MMIX-ARITH} module, assuming only that type \&{tetra} represents unsigned 32-bit integers. The definition of \&{tetra} given here should be changed, if necessary, to agree with the definition in that module. @^system dependencies@> @= typedef unsigned int tetra; /* for systems conforming to the LP-64 data model */ typedef struct {tetra h,l;} octa; /* two tetrabytes make one octabyte */ typedef unsigned char byte; /* a monobyte */ @ We declare subroutines twice, once with a prototype and once with the old-style~\CEE/ conventions. The following hack makes this work with new compilers as well as the old standbys. @= #ifdef __STDC__ #define ARGS(list) list #else #define ARGS(list) () #endif @ @= void print_hex @,@,@[ARGS((octa))@];@+@t}\6{@> void print_hex(o) octa o; { if (o.h) printf("%x%08x",o.h,o.l); else printf("%x",o.l); } @ Most of the subroutines in {\mc MMIX-ARITH} return an octabyte as a function of two octabytes; for example, |oplus(y,z)| returns the sum of octabytes |y| and~|z|. Division inputs the high half of a dividend in the global variable~|aux| and returns the remainder in~|aux|. @= extern octa zero_octa; /* |zero_octa.h=zero_octa.l=0| */ extern octa neg_one; /* |neg_one.h=neg_one.l=-1| */ extern octa aux,val; /* auxiliary data */ extern bool overflow; /* flag set by signed multiplication and division */ extern int exceptions; /* bits set by floating point operations */ extern int cur_round; /* the current rounding mode */ extern char *next_char; /* where a scanned constant ended */ extern octa oplus @,@,@[ARGS((octa y,octa z))@]; /* unsigned $y+z$ */ extern octa ominus @,@,@[ARGS((octa y,octa z))@]; /* unsigned $y-z$ */ extern octa incr @,@,@[ARGS((octa y,int delta))@]; /* unsigned $y+\delta$ ($\delta$ is signed) */ extern octa oand @,@,@[ARGS((octa y,octa z))@]; /* $y\land z$ */ extern octa shift_left @,@,@[ARGS((octa y,int s))@]; /* $y\LL s$, $0\le s\le64$ */ extern octa shift_right @,@,@[ARGS((octa y,int s,int uns))@]; /* $y\GG s$, signed if |!uns| */ extern octa omult @,@,@[ARGS((octa y,octa z))@]; /* unsigned $(|aux|,x)=y\times z$ */ extern octa signed_omult @,@,@[ARGS((octa y,octa z))@]; /* signed $x=y\times z$ */ extern octa odiv @,@,@[ARGS((octa x,octa y,octa z))@]; /* unsigned $(x,y)/z$; $|aux|=(x,y)\bmod z$ */ extern octa signed_odiv @,@,@[ARGS((octa y,octa z))@]; /* signed $x=y/z$ */ extern int count_bits @,@,@[ARGS((tetra z))@]; /* $x=\nu(z)$ */ extern tetra byte_diff @,@,@[ARGS((tetra y,tetra z))@]; /* half of \.{BDIF} */ extern tetra wyde_diff @,@,@[ARGS((tetra y,tetra z))@]; /* half of \.{WDIF} */ extern octa bool_mult @,@,@[ARGS((octa y,octa z,bool xor))@]; /* \.{MOR} or \.{MXOR} */ extern octa load_sf @,@,@[ARGS((tetra z))@]; /* load short float */ extern tetra store_sf @,@,@[ARGS((octa x))@]; /* store short float */ extern octa fplus @,@,@[ARGS((octa y,octa z))@]; /* floating point $x=y\oplus z$ */ extern octa fmult @,@,@[ARGS((octa y ,octa z))@]; /* floating point $x=y\otimes z$ */ extern octa fdivide @,@,@[ARGS((octa y,octa z))@]; /* floating point $x=y\oslash z$ */ extern octa froot @,@,@[ARGS((octa,int))@]; /* floating point $x=\sqrt z$ */ extern octa fremstep @,@,@[ARGS((octa y,octa z,int delta))@]; /* floating point $x\,{\rm rem}\,z=y\,{\rm rem}\,z$ */ extern octa fintegerize @,@,@[ARGS((octa z,int mode))@]; /* floating point $x={\rm round}(z)$ */ extern int fcomp @,@,@[ARGS((octa y,octa z))@]; /* $-1$, 0, 1, or 2 if $yz$, $y\parallel z$ */ extern int fepscomp @,@,@[ARGS((octa y,octa z,octa eps,int sim))@]; /* $x=|sim|?\ [y\sim z\ (\epsilon)]:\ [y\approx z\ (\epsilon)]$ */ extern octa floatit @,@,@[ARGS((octa z,int mode,int unsgnd,int shrt))@]; /* fix to float */ extern octa fixit @,@,@[ARGS((octa z,int mode))@]; /* float to fix */ extern void print_float @,@,@[ARGS((octa z))@]; /* print octabyte as floating decimal */ extern int scan_const @,@,@[ARGS((char* buf))@]; /* |val| = floating or integer constant; returns the type */ @ Here's a quick check to see if arithmetic is in trouble. @d panic(m) {@+fprintf(stderr,"Panic: %s!\n",m);@+exit(-2);@+} @= if (shift_left(neg_one,1).h!=0xffffffff) panic("Incorrect implementation of type tetra"); @.Incorrect implementation...@> @ Binary-to-decimal conversion is used when we want to see an octabyte as a signed integer. The identity $\lfloor(an+b)/10\rfloor= \lfloor a/10\rfloor n+\lfloor((a\bmod 10)n+b)/10\rfloor$ is helpful here. @d sign_bit ((unsigned)0x80000000) @= void print_int @,@,@[ARGS((octa))@];@+@t}\6{@> void print_int(o) octa o; { register tetra hi=o.h, lo=o.l, r, t; register int j; char dig[20]; if (lo==0 && hi==0) printf("0"); else { if (hi&sign_bit) { printf("-"); if (lo==0) hi=-hi; else lo=-lo, hi=~hi; } for (j=0;hi;j++) { /* 64-bit division by 10 */ r=((hi%10)<<16)+(lo>>16); hi=hi/10; t=((r%10)<<16)+(lo&0xffff); lo=((r/10)<<16)+(t/10); dig[j]=t%10; } for (;lo;j++) { dig[j]=lo%10; lo=lo/10; } for (j--;j>=0;j--) printf("%c",dig[j]+'0'); } } @* Simulated memory. Chunks of simulated memory, 2K bytes each, are kept in a tree structure organized as a {\it treap}, following ideas of Vuillemin, Aragon, and Seidel @^Vuillemin, Jean Etienne@> @^Aragon, Cecilia Rodriguez@> @^Seidel, Raimund@> [{\sl Communications of the ACM\/ \bf23} (1980), 229--239; {\sl IEEE Symp.\ on Foundations of Computer Science\/ \bf30} (1989), 540--546]. Each node of the treap has two keys: One, called |loc|, is the base address of 512 simulated tetrabytes; it follows the conventions of an ordinary binary search tree, with all locations in the left subtree less than the |loc| of a node and all locations in the right subtree greater than that~|loc|. The other, called |stamp|, can be thought of as the time the node was inserted into the tree; all subnodes of a given node have a larger~|stamp|. By assigning time stamps at random, we maintain a tree structure that almost always is fairly well balanced. Each simulated tetrabyte has an associated frequency count and source file reference. @= typedef struct { tetra tet; /* the tetrabyte of simulated memory */ tetra freq; /* the number of times it was obeyed as an instruction */ unsigned char bkpt; /* breakpoint information for this tetrabyte */ unsigned char file_no; /* source file number, if known */ unsigned short line_no; /* source line number, if known */ } mem_tetra; @# typedef struct mem_node_struct { octa loc; /* location of the first of 512 simulated tetrabytes */ tetra stamp; /* time stamp for treap balancing */ struct mem_node_struct *left, *right; /* pointers to subtrees */ mem_tetra dat[512]; /* the chunk of simulated tetrabytes */ } mem_node; @ The |stamp| value is actually only pseudorandom, based on the idea of Fibonacci hashing [see {\sl Sorting and Searching}, Section~6.4]. This is good enough for our purposes, and it guarantees that no two stamps will be identical. @= mem_node* new_mem @,@,@[ARGS((void))@];@+@t}\6{@> mem_node* new_mem() { register mem_node *p; p=(mem_node*)calloc(1,sizeof(mem_node)); if (!p) panic("Can't allocate any more memory"); @.Can't allocate...@> p->stamp=priority; priority+=0x9e3779b9; /* $\lfloor2^{32}(\phi-1)\rfloor$ */ return p; } @ Initially we start with a chunk for the pool segment, since the simulator will be putting command-line information there before it runs the program. @= mem_root=new_mem(); mem_root->loc.h=0x40000000; last_mem=mem_root; @ @= tetra priority=314159265; /* pseudorandom time stamp counter */ mem_node *mem_root; /* root of the treap */ mem_node *last_mem; /* the memory node most recently read or written */ @ The |mem_find| routine finds a given tetrabyte in the simulated memory, inserting a new node into the treap if necessary. @= mem_tetra* mem_find @,@,@[ARGS((octa))@];@+@t}\6{@> mem_tetra* mem_find(addr) octa addr; { octa key; register int offset; register mem_node *p=last_mem; key.h=addr.h; key.l=addr.l&0xfffff800; offset=addr.l&0x7fc; if (p->loc.l!=key.l || p->loc.h!=key.h) @; return &p->dat[offset>>2]; } @ @= {@+register mem_node **q; for (p=mem_root; p; ) { if (key.l==p->loc.l && key.h==p->loc.h) goto found; if ((key.lloc.l && key.h<=p->loc.h) || key.hloc.h) p=p->left; else p=p->right; } for (p=mem_root,q=&mem_root; p && p->stamploc.l && key.h<=p->loc.h) || key.hloc.h) q=&p->left; else q=&p->right; } *q=new_mem(); (*q)->loc=key; @; p=*q; found: last_mem=p; } @ At this point we want to split the binary search tree |p| into two parts based on the given |key|, forming the left and right subtrees of the new node~|q|. The effect will be as if |key| had been inserted before all of |p|'s nodes. @= { register mem_node **l=&(*q)->left,**r=&(*q)->right; while (p) { if ((key.lloc.l && key.h<=p->loc.h) || key.hloc.h) *r=p, r=&p->left, p=*r; else *l=p, l=&p->right, p=*l; } *l=*r=NULL; } @* Loading an object file. To get the user's program into memory, we read in an \MMIX\ object, using modifications of the routines in the utility program \.{MMOtype}. Complete details of \.{mmo} format appear in the program for {\mc MMIXAL}; a reader who hopes to understand this section ought to at least skim that documentation. Here we need to define only the basic constants used for interpretation. @d mm 0x98 /* the escape code of \.{mmo} format */ @d lop_quote 0x0 /* the quotation lopcode */ @d lop_loc 0x1 /* the location lopcode */ @d lop_skip 0x2 /* the skip lopcode */ @d lop_fixo 0x3 /* the octabyte-fix lopcode */ @d lop_fixr 0x4 /* the relative-fix lopcode */ @d lop_fixrx 0x5 /* extended relative-fix lopcode */ @d lop_file 0x6 /* the file name lopcode */ @d lop_line 0x7 /* the file position lopcode */ @d lop_spec 0x8 /* the special hook lopcode */ @d lop_pre 0x9 /* the preamble lopcode */ @d lop_post 0xa /* the postamble lopcode */ @d lop_stab 0xb /* the symbol table lopcode */ @d lop_end 0xc /* the end-it-all lopcode */ @ We do not load the symbol table. (A more ambitious simulator could implement \.{MMIXAL}-style expressions for interactive debugging, but such enhancements are left to the interested reader.) @= mmo_file=fopen(mmo_file_name,"rb"); if (!mmo_file) { register char *alt_name=(char*)calloc(strlen(mmo_file_name)+5,sizeof(char)); sprintf(alt_name,"%s.mmo",mmo_file_name); mmo_file=fopen(alt_name,"rb"); if (!mmo_file) { fprintf(stderr,"Can't open the object file %s or %s!\n", @.Can't open...@> mmo_file_name,alt_name); exit(-3); } free(alt_name); } byte_count=0; @ @= FILE *mmo_file; /* the input file */ int postamble; /* have we encountered |lop_post|? */ int byte_count; /* index of the next-to-be-read byte */ byte buf[4]; /* the most recently read bytes */ int yzbytes; /* the two least significant bytes */ int delta; /* difference for relative fixup */ tetra tet; /* |buf| bytes packed big-endianwise */ @ The tetrabytes of an \.{mmo} file are stored in friendly big-endian fashion, but this program is supposed to work also on computers that are little-endian. Therefore we read four successive bytes and pack them into a tetrabyte, instead of reading a single tetrabyte. @d mmo_err { fprintf(stderr,"Bad object file! (Try running MMOtype.)\n"); @.Bad object file@> exit(-4); } @= void read_tet @,@,@[ARGS((void))@];@+@t}\6{@> void read_tet() { if (fread(buf,1,4,mmo_file)!=4) mmo_err; yzbytes=(buf[2]<<8)+buf[3]; tet=(((buf[0]<<8)+buf[1])<<16)+yzbytes; } @ @= byte read_byte @,@,@[ARGS((void))@];@+@t}\6{@> byte read_byte() { register byte b; if (!byte_count) read_tet(); b=buf[byte_count]; byte_count=(byte_count+1)&3; return b; } @ @= read_tet(); /* read the first tetrabyte of input */ if (buf[0]!=mm || buf[1]!=lop_pre) mmo_err; if (ybyte!=1) mmo_err; if (zbyte==0) obj_time=0xffffffff; else { j=zbyte-1; read_tet();@+ obj_time=tet; /* file creation time */ for (;j>0;j--) read_tet(); } @ @= { read_tet(); loop:@+if (buf[0]==mm) switch (buf[1]) { case lop_quote:@+if (yzbytes!=1) mmo_err; read_tet();@+break; @t\4@>@@; case lop_post: postamble=1; if (ybyte || zbyte<32) mmo_err; continue; default: mmo_err; } @; } @ In a normal situation, the newly read tetrabyte is simply supposed to be loaded into the current location. We load not only the current location but also the current file position, if |cur_line| is nonzero and |cur_loc| belongs to segment~0. @d mmo_load(loc,val) ll=mem_find(loc), ll->tet^=val @= { mmo_load(cur_loc,tet); if (cur_line) { ll->file_no=cur_file; ll->line_no=cur_line; cur_line++; } cur_loc=incr(cur_loc,4);@+ cur_loc.l &=-4; } @ @= octa cur_loc; /* the current location */ int cur_file=-1; /* the most recently selected file number */ int cur_line; /* the current position in |cur_file|, if nonzero */ octa tmp; /* an octabyte of temporary interest */ tetra obj_time; /* when the object file was created */ @ @= cur_loc.h=cur_loc.l=0; cur_file=-1; cur_line=0; @; do @@;@+while (!postamble); @; fclose(mmo_file); cur_line=0; @ We have already implemented |lop_quote|, which falls through to the normal case after reading an extra tetrabyte. Now let's consider the other lopcodes in turn. @d ybyte buf[2] /* the next-to-least significant byte */ @d zbyte buf[3] /* the least significant byte */ @= case lop_loc:@+if (zbyte==2) { read_tet();@+ cur_loc.h=(ybyte<<24)+tet; }@+else if (zbyte==1) cur_loc.h=ybyte<<24; else mmo_err; read_tet();@+ cur_loc.l=tet; continue; case lop_skip: cur_loc=incr(cur_loc,yzbytes);@+continue; @ Fixups load information out of order, when future references have been resolved. The current file name and line number are not considered relevant. @= case lop_fixo:@+if (zbyte==2) { read_tet();@+ tmp.h=(ybyte<<24)+tet; }@+else if (zbyte==1) tmp.h=ybyte<<24; else mmo_err; read_tet();@+ tmp.l=tet; mmo_load(tmp,cur_loc.h); mmo_load(incr(tmp,4),cur_loc.l); continue; case lop_fixr: delta=yzbytes; goto fixr; case lop_fixrx:j=yzbytes;@+if (j!=16 && j!=24) mmo_err; read_tet(); delta=tet; if (delta&0xfe000000) mmo_err; fixr: tmp=incr(cur_loc,-(delta>=0x1000000? (delta&0xffffff)-(1<= case lop_file:@+if (file_info[ybyte].name) { if (zbyte) mmo_err; cur_file=ybyte; }@+else { if (!zbyte) mmo_err; file_info[ybyte].name=(char*)calloc(4*zbyte+1,1); if (!file_info[ybyte].name) { fprintf(stderr,"No room to store the file name!\n");@+exit(-5); @.No room...@> } cur_file=ybyte; for (j=zbyte,p=file_info[ybyte].name; j>0; j--,p+=4) { read_tet(); *p=buf[0];@+*(p+1)=buf[1];@+*(p+2)=buf[2];@+*(p+3)=buf[3]; } } cur_line=0;@+continue; case lop_line:@+if (cur_file<0) mmo_err; cur_line=yzbytes;@+continue; @ Special bytes are ignored (at least for now). @= case lop_spec:@+ while(1) { read_tet(); if (buf[0]==mm) { if (buf[1]!=lop_quote || yzbytes!=1) goto loop; /* end of special data */ read_tet(); } } @ Since a chunk of memory holds 512 tetrabytes, the |ll| pointer in the following loop stays in the same chunk (namely, the first chunk of segment~3, also known as \.{Stack\_Segment}). @:Stack_Segment}\.{Stack\_Segment@> @:Pool_Segment}\.{Pool\_Segment@> @= aux.h=0x60000000;@+ aux.l=0x18; ll=mem_find(aux); (ll-1)->tet=2; /* this will ultimately set |rL=2| */ (ll-5)->tet=argc; /* and $\$0=|argc|$ */ (ll-4)->tet=0x40000000; (ll-3)->tet=0x8; /* and $\$1=\.{Pool\_Segment}+8$ */ G=zbyte;@+ L=0; for (j=G+G;j<256+256;j++,ll++,aux.l+=4) read_tet(), ll->tet=tet; inst_ptr.h=(ll-2)->tet, inst_ptr.l=(ll-1)->tet; /* \.{Main} */ (ll+2*12)->tet=G<<24; g[255]=incr(aux,12*8); /* we will |UNSAVE| from here, to get going */ @* Loading and printing source lines. The loaded program generally contains cross references to the lines of symbolic source files, so that the context of each instruction can be understood. The following sections of this program make such information available when it is desired. Source file data is kept in a \&{file\_node} structure: @= typedef struct { char *name; /* name of source file */ int line_count; /* number of lines in the file */ long *map; /* pointer to map of file positions */ } file_node; @ In partial preparation for the day when source files are in Unicode, we define a type \&{Char} for the source characters. @= typedef char Char; /* bytes that will become wydes some day */ @ @= file_node file_info[256]; /* data about each source file */ int buf_size; /* size of buffer for source lines */ Char *buffer; @ As in \.{MMIXAL}, we prefer source lines of length 72 characters or less, but the user is allowed to increase the limit. (Longer lines will silently be truncated to the buffer size when the simulator lists them.) @= if (buf_size<72) buf_size=72; buffer=(Char*)calloc(buf_size+1,sizeof(Char)); @ The first time we are called upon to list a line from a given source file, we make a map of starting locations for each line. Source files should contain at most 65535 lines. We assume that they contain no null characters. @= void make_map @,@,@[ARGS((void))@];@+@t}\6{@> void make_map() { long map[65536]; register int k,l; register long*p; @; for (l=1;l<65536 && !feof(src_file);l++) { map[l]=ftell(src_file); loop:@+if (!fgets(buffer,buf_size,src_file)) break; if (buffer[strlen(buffer)-1]!='\n') goto loop; } file_info[cur_file].line_count=l; file_info[cur_file].map=p=(long*)calloc(l,sizeof(long)); if (!p) panic("No room for a source-line map"); @.No room...@> for (k=1;k @= #include #include @ @= @^system dependencies@> { struct stat stat_buf; if (stat(file_info[cur_file].name,&stat_buf)>=0) if ((tetra)stat_buf.st_mtime > obj_time) fprintf(stderr, "Warning: File %s was modified; it may not match the program!\n", @.File...was modified@> file_info[cur_file].name); } @ Source lines are listed by the |print_line| routine, preceded by 12 characters containing the line number. If a file error occurs, nothing is printed---not even an error message; the absence of listed data is itself a message. @= void print_line @,@,@[ARGS((int))@];@+@t}\6{@> void print_line(k) int k; { char buf[11]; if (k>=file_info[cur_file].line_count) return; if (fseek(src_file,file_info[cur_file].map[k],SEEK_SET)!=0) return; if (!fgets(buffer,buf_size,src_file)) return; sprintf(buf,"%d: ",k); printf("line %.6s %s",buf,buffer); if (buffer[strlen(buffer)-1]!='\n') printf("\n"); line_shown=true; } @ @= #ifndef SEEK_SET #define SEEK_SET 0 /* code for setting the file pointer to a given offset */ #endif @ The |show_line| routine is called when we want to output line |cur_line| of source file number |cur_file|, assuming that |cur_line!=0|. Its job is primarily to maintain continuity, by opening or reopening the |src_file| if the source file changes, and by connecting the previously output lines to the new one. Sometimes no output is necessary, because the desired line has already been printed. @= void show_line @,@,@[ARGS((void))@];@+@t}\6{@> void show_line() { register int k; if (shown_file!=cur_file) @@; else if (shown_line==cur_line) return; /* already shown */ if (cur_line>shown_line+gap+1 || cur_line0) if (cur_line= FILE *src_file; /* the currently open source file */ int shown_file=-1; /* index of the most recently listed file */ int shown_line; /* the line most recently listed in |shown_file| */ int gap; /* minimum gap between consecutively listed source lines */ bool line_shown; /* did we list anything recently? */ bool showing_source; /* are we listing source lines? */ int profile_gap; /* the |gap| when printing final frequencies */ bool profile_showing_source; /* |showing_source| within final frequencies */ @ @= { if (!src_file) src_file=fopen(file_info[cur_file].name,"r"); else freopen(file_info[cur_file].name,"r",src_file); if (!src_file) { fprintf(stderr,"Warning: I can't open file %s; source listing omitted.\n", @.I can't open...@> file_info[cur_file].name); showing_source=false; return; } printf("\"%s\"\n",file_info[cur_file].name); shown_file=cur_file; shown_line=0; if (!file_info[cur_file].map) make_map(); } @ Here is a simple application of |show_line|. It is a recursive routine that prints the frequency counts of all instructions that occur in a given subtree of the simulated memory and that were executed at least once. The subtree is traversed in symmetric order; therefore the frequencies appear in increasing order of the instruction locations. @= void print_freqs @,@,@[ARGS((mem_node*))@];@+@t}\6{@> void print_freqs(p) mem_node *p; { register int j; octa cur_loc; if (p->left) print_freqs(p->left); for (j=0;j<512;j++) if (p->dat[j].freq) @loc+4*j|@>; if (p->right) print_freqs(p->right); } @ An ellipsis (\.{...}) is printed between frequency data for nonconsecutive instructions, unless source line information intervenes. @= { cur_loc=incr(p->loc,4*j); if (showing_source && p->dat[j].line_no) { cur_file=p->dat[j].file_no, cur_line=p->dat[j].line_no; line_shown=false; show_line(); if (line_shown) goto loc_implied; } if (cur_loc.l!=implied_loc.l || cur_loc.h!=implied_loc.h) if (profile_started) printf(" 0. ...\n"); loc_implied: printf("%10d. %08x%08x: %08x (%s)\n", p->dat[j].freq, cur_loc.h, cur_loc.l, p->dat[j].tet, info[p->dat[j].tet>>24].name); implied_loc=incr(cur_loc,4);@+ profile_started=true; } @ @= octa implied_loc; /* location following the last shown frequency data */ bool profile_started; /* have we printed at least one frequency count? */ @ @= { printf("\nProgram profile:\n"); shown_file=cur_file=-1;@+ shown_line=cur_line=0; gap=profile_gap; showing_source=profile_showing_source; implied_loc=neg_one; print_freqs(mem_root); } @* Lists. This simulator needs to deal with 256 different opcodes, so we might as well enumerate them~now. @= typedef enum{@/ @!TRAP,@!FCMP,@!FUN,@!FEQL,@!FADD,@!FIX,@!FSUB,@!FIXU,@/ @!FLOT,@!FLOTI,@!FLOTU,@!FLOTUI,@!SFLOT,@!SFLOTI,@!SFLOTU,@!SFLOTUI,@/ @!FMUL,@!FCMPE,@!FUNE,@!FEQLE,@!FDIV,@!FSQRT,@!FREM,@!FINT,@/ @!MUL,@!MULI,@!MULU,@!MULUI,@!DIV,@!DIVI,@!DIVU,@!DIVUI,@/ @!ADD,@!ADDI,@!ADDU,@!ADDUI,@!SUB,@!SUBI,@!SUBU,@!SUBUI,@/ @!IIADDU,@!IIADDUI,@!IVADDU,@!IVADDUI,@!VIIIADDU,@!VIIIADDUI,@!XVIADDU,@!XVIADDUI,@/ @!CMP,@!CMPI,@!CMPU,@!CMPUI,@!NEG,@!NEGI,@!NEGU,@!NEGUI,@/ @!SL,@!SLI,@!SLU,@!SLUI,@!SR,@!SRI,@!SRU,@!SRUI,@/ @!BN,@!BNB,@!BZ,@!BZB,@!BP,@!BPB,@!BOD,@!BODB,@/ @!BNN,@!BNNB,@!BNZ,@!BNZB,@!BNP,@!BNPB,@!BEV,@!BEVB,@/ @!PBN,@!PBNB,@!PBZ,@!PBZB,@!PBP,@!PBPB,@!PBOD,@!PBODB,@/ @!PBNN,@!PBNNB,@!PBNZ,@!PBNZB,@!PBNP,@!PBNPB,@!PBEV,@!PBEVB,@/ @!CSN,@!CSNI,@!CSZ,@!CSZI,@!CSP,@!CSPI,@!CSOD,@!CSODI,@/ @!CSNN,@!CSNNI,@!CSNZ,@!CSNZI,@!CSNP,@!CSNPI,@!CSEV,@!CSEVI,@/ @!ZSN,@!ZSNI,@!ZSZ,@!ZSZI,@!ZSP,@!ZSPI,@!ZSOD,@!ZSODI,@/ @!ZSNN,@!ZSNNI,@!ZSNZ,@!ZSNZI,@!ZSNP,@!ZSNPI,@!ZSEV,@!ZSEVI,@/ @!LDB,@!LDBI,@!LDBU,@!LDBUI,@!LDW,@!LDWI,@!LDWU,@!LDWUI,@/ @!LDT,@!LDTI,@!LDTU,@!LDTUI,@!LDO,@!LDOI,@!LDOU,@!LDOUI,@/ @!LDSF,@!LDSFI,@!LDHT,@!LDHTI,@!CSWAP,@!CSWAPI,@!LDUNC,@!LDUNCI,@/ @!LDVTS,@!LDVTSI,@!PRELD,@!PRELDI,@!PREGO,@!PREGOI,@!GO,@!GOI,@/ @!STB,@!STBI,@!STBU,@!STBUI,@!STW,@!STWI,@!STWU,@!STWUI,@/ @!STT,@!STTI,@!STTU,@!STTUI,@!STO,@!STOI,@!STOU,@!STOUI,@/ @!STSF,@!STSFI,@!STHT,@!STHTI,@!STCO,@!STCOI,@!STUNC,@!STUNCI,@/ @!SYNCD,@!SYNCDI,@!PREST,@!PRESTI,@!SYNCID,@!SYNCIDI,@!PUSHGO,@!PUSHGOI,@/ @!OR,@!ORI,@!ORN,@!ORNI,@!NOR,@!NORI,@!XOR,@!XORI,@/ @!AND,@!ANDI,@!ANDN,@!ANDNI,@!NAND,@!NANDI,@!NXOR,@!NXORI,@/ @!BDIF,@!BDIFI,@!WDIF,@!WDIFI,@!TDIF,@!TDIFI,@!ODIF,@!ODIFI,@/ @!MUX,@!MUXI,@!SADD,@!SADDI,@!MOR,@!MORI,@!MXOR,@!MXORI,@/ @!SETH,@!SETMH,@!SETML,@!SETL,@!INCH,@!INCMH,@!INCML,@!INCL,@/ @!ORH,@!ORMH,@!ORML,@!ORL,@!ANDNH,@!ANDNMH,@!ANDNML,@!ANDNL,@/ @!JMP,@!JMPB,@!PUSHJ,@!PUSHJB,@!GETA,@!GETAB,@!PUT,@!PUTI,@/ @!POP,@!RESUME,@!SAVE,@!UNSAVE,@!SYNC,@!SWYM,@!GET,@!TRIP}@+@!mmix_opcode; @ We also need to enumerate the special names for special registers. @= typedef enum{ @!rB,@!rD,@!rE,@!rH,@!rJ,@!rM,@!rR,@!rBB, @!rC,@!rN,@!rO,@!rS,@!rI,@!rT,@!rTT,@!rK,@!rQ,@!rU,@!rV,@!rG,@!rL, @!rA,@!rF,@!rP,@!rW,@!rX,@!rY,@!rZ,@!rWW,@!rXX,@!rYY,@!rZZ} @!special_reg; @ @= char *special_name[32]={"rB","rD","rE","rH","rJ","rM","rR","rBB", "rC","rN","rO","rS","rI","rT","rTT","rK","rQ","rU","rV","rG","rL", "rA","rF","rP","rW","rX","rY","rZ","rWW","rXX","rYY","rZZ"}; @ Here are the bit codes for arithmetic exceptions. These codes, except |H_BIT|, are defined also in {\mc MMIX-ARITH}. @d X_BIT (1<<8) /* floating inexact */ @d Z_BIT (1<<9) /* floating division by zero */ @d U_BIT (1<<10) /* floating underflow */ @d O_BIT (1<<11) /* floating overflow */ @d I_BIT (1<<12) /* floating invalid operation */ @d W_BIT (1<<13) /* float-to-fix overflow */ @d V_BIT (1<<14) /* integer overflow */ @d D_BIT (1<<15) /* integer divide check */ @d H_BIT (1<<16) /* trip */ @ The |bkpt| field associated with each tetrabyte of memory has bits associated with forced tracing and/or breaking for reading, writing, and/or execution. @d trace_bit (1<<3) @d read_bit (1<<2) @d write_bit (1<<1) @d exec_bit (1<<0) @ To complete our lists of lists, we enumerate the rudimentary operating system calls that are built in to \.{MMIXAL}. @d max_sys_call Ftell @= typedef enum{ @!Halt,@!Fopen,@!Fclose,@!Fread,@!Fgets,@!Fgetws, @!Fwrite,@!Fputs,@!Fputws,@!Fseek,@!Ftell} @!sys_call; @* The main loop. Now let's plunge in to the guts of the simulator, the master switch that controls most of the action. @= { if (resuming) loc=incr(inst_ptr,-4), inst=g[rX].l; else @; op=inst>>24;@+xx=(inst>>16)&0xff;@+yy=(inst>>8)&0xff;@+zz=inst&0xff; f=info[op].flags;@+yz=inst&0xffff; x=y=z=a=b=zero_octa;@+ exc=0;@+ old_L=L; if (f&rel_addr_bit) @; @; if (f&X_is_dest_bit) @; w=oplus(y,z); if (loc.h>=0x20000000) goto privileged_inst; switch(op) { @t\4@>@; } @; @; @; if (resuming && op!=RESUME) resuming=false; } @ Operands |x| and |a| are usually destinations (results), computed from the source operands |y|, |z|, and/or~|b|. @= octa w,x,y,z,a,b,ma,mb; /* operands */ octa *x_ptr; /* destination */ octa loc; /* location of the current instruction */ octa inst_ptr; /* location of the next instruction */ tetra inst; /* the current instruction */ int old_L; /* value of |L| before the current instruction */ int exc; /* exceptions raised by the current instruction */ int tracing_exceptions; /* exception bits that cause tracing */ int rop; /* ropcode of a resumed instruction */ int round_mode; /* the style of floating point rounding just used */ bool resuming; /* are we resuming an interrupted instruction? */ bool halted; /* did the program come to a halt? */ bool breakpoint; /* should we pause after the current instruction? */ bool tracing; /* should we trace the current instruction? */ bool stack_tracing; /* should we trace details of the register stack? */ bool interacting; /* are we in interactive mode? */ bool interact_after_break; /* should we go into interactive mode? */ bool tripping; /* are we about to go to a trip handler? */ bool good; /* did the last branch instruction guess correctly? */ tetra trace_threshold; /* each instruction should be traced this many times */ @ @= register mmix_opcode op; /* operation code of the current instruction */ register int xx,yy,zz,yz; /* operand fields of the current instruction */ register tetra f; /* properties of the current |op| */ register int i,j,k; /* miscellaneous indices */ register mem_tetra *ll; /* current place in the simulated memory */ register char *p; /* current place in a string */ @ @= { loc=inst_ptr; ll=mem_find(loc); inst=ll->tet; cur_file=ll->file_no; cur_line=ll->line_no; ll->freq++; if (ll->bkpt&exec_bit) breakpoint=true; tracing=breakpoint||(ll->bkpt&trace_bit)||(ll->freq<=trace_threshold); inst_ptr=incr(inst_ptr,4); } @ Much of the simulation is table-driven, based on a static data structure called the \&{op\_info} for each operation code. @= typedef struct { char *name; /* symbolic name of an opcode */ unsigned char flags; /* its instruction format */ unsigned char third_operand; /* its special register input */ unsigned char mems; /* how many $\mu$ it costs */ unsigned char oops; /* how many $\upsilon$ it costs */ char *trace_format; /* how it appears when traced */ } op_info; @ For example, the |flags| field of |info[op]| tells us how to obtain the operands from the X, Y, and~Z fields of the current instruction. Each entry records special properties of an operation code, in binary notation: \Hex{1}~means Z~is an immediate value, \Hex{2}~means rZ is a source operand, \Hex{4}~means Y~is an immediate value, \Hex{8}~means rY is a source operand, \Hex{10}~means rX is a source operand, \Hex{20}~means rX is a destination, \Hex{40}~means YZ is part of a relative address, \Hex{80}~means a push or pop or unsave instruction. The |trace_format| field will be explained later. @d Z_is_immed_bit 0x1 @d Z_is_source_bit 0x2 @d Y_is_immed_bit 0x4 @d Y_is_source_bit 0x8 @d X_is_source_bit 0x10 @d X_is_dest_bit 0x20 @d rel_addr_bit 0x40 @d push_pop_bit 0x80 @= op_info info[256]={ @, @, @, @}; @ @= {"TRAP",0x0a,255,0,5,"%r"},@| {"FCMP",0x2a,0,0,1,"%l = %.y cmp %.z = %x"},@| {"FUN",0x2a,0,0,1,"%l = [%.y(||)%.z] = %x"},@| {"FEQL",0x2a,0,0,1,"%l = [%.y(==)%.z] = %x"},@| {"FADD",0x2a,0,0,4,"%l = %.y %(+%) %.z = %.x"},@| {"FIX",0x26,0,0,4,"%l = %(fix%) %.z = %x"},@| {"FSUB",0x2a,0,0,4,"%l = %.y %(-%) %.z = %.x"},@| {"FIXU",0x26,0,0,4,"%l = %(fix%) %.z = %#x"},@| {"FLOT",0x26,0,0,4,"%l = %(flot%) %z = %.x"},@| {"FLOTI",0x25,0,0,4,"%l = %(flot%) %z = %.x"},@| {"FLOTU",0x26,0,0,4,"%l = %(flot%) %#z = %.x"},@| {"FLOTUI",0x25,0,0,4,"%l = %(flot%) %z = %.x"},@| {"SFLOT",0x26,0,0,4,"%l = %(sflot%) %z = %.x"},@| {"SFLOTI",0x25,0,0,4,"%l = %(sflot%) %z = %.x"},@| {"SFLOTU",0x26,0,0,4,"%l = %(sflot%) %#z = %.x"},@| {"SFLOTUI",0x25,0,0,4,"%l = %(sflot%) %z = %.x"},@| {"FMUL",0x2a,0,0,4,"%l = %.y %(*%) %.z = %.x"},@| {"FCMPE",0x2a,rE,0,4,"%l = %.y cmp %.z (%.b)) = %x"},@| {"FUNE",0x2a,rE,0,1,"%l = [%.y(||)%.z (%.b)] = %x"},@| {"FEQLE",0x2a,rE,0,4,"%l = [%.y(==)%.z (%.b)] = %x"},@| {"FDIV",0x2a,0,0,40,"%l = %.y %(/%) %.z = %.x"},@| {"FSQRT",0x26,0,0,40,"%l = %(sqrt%) %.z = %.x"},@| {"FREM",0x2a,0,0,4,"%l = %.y %(rem%) %.z = %.x"},@| {"FINT",0x26,0,0,4,"%l = %(int%) %.z = %.x"},@| {"MUL",0x2a,0,0,10,"%l = %y * %z = %x"},@| {"MULI",0x29,0,0,10,"%l = %y * %z = %x"},@| {"MULU",0x2a,0,0,10,"%l = %#y * %#z = %#x, rH=%#a"},@| {"MULUI",0x29,0,0,10,"%l = %#y * %z = %#x, rH=%#a"},@| {"DIV",0x2a,0,0,60,"%l = %y / %z = %x, rR=%a"},@| {"DIVI",0x29,0,0,60,"%l = %y / %z = %x, rR=%a"},@| {"DIVU",0x2a,rD,0,60,"%l = %#b%0y / %#z = %#x, rR=%#a"},@| {"DIVUI",0x29,rD,0,60,"%l = %#b%0y / %z = %#x, rR=%#a"},@| {"ADD",0x2a,0,0,1,"%l = %y + %z = %x"},@| {"ADDI",0x29,0,0,1,"%l = %y + %z = %x"},@| {"ADDU",0x2a,0,0,1,"%l = %#y + %#z = %#x"},@| {"ADDUI",0x29,0,0,1,"%l = %#y + %z = %#x"},@| {"SUB",0x2a,0,0,1,"%l = %y - %z = %x"},@| {"SUBI",0x29,0,0,1,"%l = %y - %z = %x"},@| {"SUBU",0x2a,0,0,1,"%l = %#y - %#z = %#x"},@| {"SUBUI",0x29,0,0,1,"%l = %#y - %z = %#x"},@| {"2ADDU",0x2a,0,0,1,"%l = %#y <<1+ %#z = %#x"},@| {"2ADDUI",0x29,0,0,1,"%l = %#y <<1+ %z = %#x"},@| {"4ADDU",0x2a,0,0,1,"%l = %#y <<2+ %#z = %#x"},@| {"4ADDUI",0x29,0,0,1,"%l = %#y <<2+ %z = %#x"},@| {"8ADDU",0x2a,0,0,1,"%l = %#y <<3+ %#z = %#x"},@| {"8ADDUI",0x29,0,0,1,"%l = %#y <<3+ %z = %#x"},@| {"16ADDU",0x2a,0,0,1,"%l = %#y <<4+ %#z = %#x"},@| {"16ADDUI",0x29,0,0,1,"%l = %#y <<4+ %z = %#x"},@| {"CMP",0x2a,0,0,1,"%l = %y cmp %z = %x"},@| {"CMPI",0x29,0,0,1,"%l = %y cmp %z = %x"},@| {"CMPU",0x2a,0,0,1,"%l = %#y cmp %#z = %x"},@| {"CMPUI",0x29,0,0,1,"%l = %#y cmp %z = %x"},@| {"NEG",0x26,0,0,1,"%l = %y - %z = %x"},@| {"NEGI",0x25,0,0,1,"%l = %y - %z = %x"},@| {"NEGU",0x26,0,0,1,"%l = %y - %#z = %#x"},@| {"NEGUI",0x25,0,0,1,"%l = %y - %z = %#x"},@| {"SL",0x2a,0,0,1,"%l = %y << %#z = %x"},@| {"SLI",0x29,0,0,1,"%l = %y << %z = %x"},@| {"SLU",0x2a,0,0,1,"%l = %#y << %#z = %#x"},@| {"SLUI",0x29,0,0,1,"%l = %#y << %z = %#x"},@| {"SR",0x2a,0,0,1,"%l = %y >> %#z = %x"},@| {"SRI",0x29,0,0,1,"%l = %y >> %z = %x"},@| {"SRU",0x2a,0,0,1,"%l = %#y >> %#z = %#x"},@| {"SRUI",0x29,0,0,1,"%l = %#y >> %z = %#x"} @ @= {"BN",0x50,0,0,1,"%b<0? %t%g"},@| {"BNB",0x50,0,0,1,"%b<0? %t%g"},@| {"BZ",0x50,0,0,1,"%b==0? %t%g"},@| {"BZB",0x50,0,0,1,"%b==0? %t%g"},@| {"BP",0x50,0,0,1,"%b>0? %t%g"},@| {"BPB",0x50,0,0,1,"%b>0? %t%g"},@| {"BOD",0x50,0,0,1,"%b odd? %t%g"},@| {"BODB",0x50,0,0,1,"%b odd? %t%g"},@| {"BNN",0x50,0,0,1,"%b>=0? %t%g"},@| {"BNNB",0x50,0,0,1,"%b>=0? %t%g"},@| {"BNZ",0x50,0,0,1,"%b!=0? %t%g"},@| {"BNZB",0x50,0,0,1,"%b!=0? %t%g"},@| {"BNP",0x50,0,0,1,"%b<=0? %t%g"},@| {"BNPB",0x50,0,0,1,"%b<=0? %t%g"},@| {"BEV",0x50,0,0,1,"%b even? %t%g"},@| {"BEVB",0x50,0,0,1,"%b even? %t%g"},@| {"PBN",0x50,0,0,1,"%b<0? %t%g"},@| {"PBNB",0x50,0,0,1,"%b<0? %t%g"},@| {"PBZ",0x50,0,0,1,"%b==0? %t%g"},@| {"PBZB",0x50,0,0,1,"%b==0? %t%g"},@| {"PBP",0x50,0,0,1,"%b>0? %t%g"},@| {"PBPB",0x50,0,0,1,"%b>0? %t%g"},@| {"PBOD",0x50,0,0,1,"%b odd? %t%g"},@| {"PBODB",0x50,0,0,1,"%b odd? %t%g"},@| {"PBNN",0x50,0,0,1,"%b>=0? %t%g"},@| {"PBNNB",0x50,0,0,1,"%b>=0? %t%g"},@| {"PBNZ",0x50,0,0,1,"%b!=0? %t%g"},@| {"PBNZB",0x50,0,0,1,"%b!=0? %t%g"},@| {"PBNP",0x50,0,0,1,"%b<=0? %t%g"},@| {"PBNPB",0x50,0,0,1,"%b<=0? %t%g"},@| {"PBEV",0x50,0,0,1,"%b even? %t%g"},@| {"PBEVB",0x50,0,0,1,"%b even? %t%g"},@| {"CSN",0x3a,0,0,1,"%l = %y<0? %z: %b = %x"},@| {"CSNI",0x39,0,0,1,"%l = %y<0? %z: %b = %x"},@| {"CSZ",0x3a,0,0,1,"%l = %y==0? %z: %b = %x"},@| {"CSZI",0x39,0,0,1,"%l = %y==0? %z: %b = %x"},@| {"CSP",0x3a,0,0,1,"%l = %y>0? %z: %b = %x"},@| {"CSPI",0x39,0,0,1,"%l = %y>0? %z: %b = %x"},@| {"CSOD",0x3a,0,0,1,"%l = %y odd? %z: %b = %x"},@| {"CSODI",0x39,0,0,1,"%l = %y odd? %z: %b = %x"},@| {"CSNN",0x3a,0,0,1,"%l = %y>=0? %z: %b = %x"},@| {"CSNNI",0x39,0,0,1,"%l = %y>=0? %z: %b = %x"},@| {"CSNZ",0x3a,0,0,1,"%l = %y!=0? %z: %b = %x"},@| {"CSNZI",0x39,0,0,1,"%l = %y!=0? %z: %b = %x"},@| {"CSNP",0x3a,0,0,1,"%l = %y<=0? %z: %b = %x"},@| {"CSNPI",0x39,0,0,1,"%l = %y<=0? %z: %b = %x"},@| {"CSEV",0x3a,0,0,1,"%l = %y even? %z: %b = %x"},@| {"CSEVI",0x39,0,0,1,"%l = %y even? %z: %b = %x"},@| {"ZSN",0x2a,0,0,1,"%l = %y<0? %z: 0 = %x"},@| {"ZSNI",0x29,0,0,1,"%l = %y<0? %z: 0 = %x"},@| {"ZSZ",0x2a,0,0,1,"%l = %y==0? %z: 0 = %x"},@| {"ZSZI",0x29,0,0,1,"%l = %y==0? %z: 0 = %x"},@| {"ZSP",0x2a,0,0,1,"%l = %y>0? %z: 0 = %x"},@| {"ZSPI",0x29,0,0,1,"%l = %y>0? %z: 0 = %x"},@| {"ZSOD",0x2a,0,0,1,"%l = %y odd? %z: 0 = %x"},@| {"ZSODI",0x29,0,0,1,"%l = %y odd? %z: 0 = %x"},@| {"ZSNN",0x2a,0,0,1,"%l = %y>=0? %z: 0 = %x"},@| {"ZSNNI",0x29,0,0,1,"%l = %y>=0? %z: 0 = %x"},@| {"ZSNZ",0x2a,0,0,1,"%l = %y!=0? %z: 0 = %x"},@| {"ZSNZI",0x29,0,0,1,"%l = %y!=0? %z: 0 = %x"},@| {"ZSNP",0x2a,0,0,1,"%l = %y<=0? %z: 0 = %x"},@| {"ZSNPI",0x29,0,0,1,"%l = %y<=0? %z: 0 = %x"},@| {"ZSEV",0x2a,0,0,1,"%l = %y even? %z: 0 = %x"},@| {"ZSEVI",0x29,0,0,1,"%l = %y even? %z: 0 = %x"} @ @= {"LDB",0x2a,0,1,1,"%l = M1[%#y+%#z] = %x"},@| {"LDBI",0x29,0,1,1,"%l = M1[%#y%?+] = %x"},@| {"LDBU",0x2a,0,1,1,"%l = M1[%#y+%#z] = %#x"},@| {"LDBUI",0x29,0,1,1,"%l = M1[%#y%?+] = %#x"},@| {"LDW",0x2a,0,1,1,"%l = M2[%#y+%#z] = %x"},@| {"LDWI",0x29,0,1,1,"%l = M2[%#y%?+] = %x"},@| {"LDWU",0x2a,0,1,1,"%l = M2[%#y+%#z] = %#x"},@| {"LDWUI",0x29,0,1,1,"%l = M2[%#y%?+] = %#x"},@| {"LDT",0x2a,0,1,1,"%l = M4[%#y+%#z] = %x"},@| {"LDTI",0x29,0,1,1,"%l = M4[%#y%?+] = %x"},@| {"LDTU",0x2a,0,1,1,"%l = M4[%#y+%#z] = %#x"},@| {"LDTUI",0x29,0,1,1,"%l = M4[%#y%?+] = %#x"},@| {"LDO",0x2a,0,1,1,"%l = M8[%#y+%#z] = %x"},@| {"LDOI",0x29,0,1,1,"%l = M8[%#y%?+] = %x"},@| {"LDOU",0x2a,0,1,1,"%l = M8[%#y+%#z] = %#x"},@| {"LDOUI",0x29,0,1,1,"%l = M8[%#y%?+] = %#x"},@| {"LDSF",0x2a,0,1,1,"%l = (M4[%#y+%#z]) = %.x"},@| {"LDSFI",0x29,0,1,1,"%l = (M4[%#y%?+]) = %.x"},@| {"LDHT",0x2a,0,1,1,"%l = M4[%#y+%#z]<<32 = %#x"},@| {"LDHTI",0x29,0,1,1,"%l = M4[%#y%?+]<<32 = %#x"},@| {"CSWAP",0x3a,0,2,2,"%l = [M8[%#y+%#z]==%a] = %x, %r"},@| {"CSWAPI",0x39,0,2,2,"%l = [M8[%#y%?+]==%a] = %x, %r"},@| {"LDUNC",0x2a,0,1,1,"%l = M8[%#y+%#z] = %#x"},@| {"LDUNCI",0x29,0,1,1,"%l = M8[%#y%?+] = %#x"},@| {"LDVTS",0x2a,0,0,1,""},@| {"LDVTSI",0x29,0,0,1,""},@| {"PRELD",0x0a,0,0,1,"[%#y+%#z .. %#x]"},@| {"PRELDI",0x09,0,0,1,"[%#y%?+ .. %#x]"},@| {"PREGO",0x0a,0,0,1,"[%#y+%#z .. %#x]"},@| {"PREGOI",0x09,0,0,1,"[%#y%?+ .. %#x]"},@| {"GO",0x2a,0,0,3,"%l = %#x, -> %#y+%#z"},@| {"GOI",0x29,0,0,3,"%l = %#x, -> %#y%?+"},@| {"STB",0x1a,0,1,1,"M1[%#y+%#z] = %b, M8[%#w]=%#a"},@| {"STBI",0x19,0,1,1,"M1[%#y%?+] = %b, M8[%#w]=%#a"},@| {"STBU",0x1a,0,1,1,"M1[%#y+%#z] = %#b, M8[%#w]=%#a"},@| {"STBUI",0x19,0,1,1,"M1[%#y%?+] = %#b, M8[%#w]=%#a"},@| {"STW",0x1a,0,1,1,"M2[%#y+%#z] = %b, M8[%#w]=%#a"},@| {"STWI",0x19,0,1,1,"M2[%#y%?+] = %b, M8[%#w]=%#a"},@| {"STWU",0x1a,0,1,1,"M2[%#y+%#z] = %#b, M8[%#w]=%#a"},@| {"STWUI",0x19,0,1,1,"M2[%#y%?+] = %#b, M8[%#w]=%#a"},@| {"STT",0x1a,0,1,1,"M4[%#y+%#z] = %b, M8[%#w]=%#a"},@| {"STTI",0x19,0,1,1,"M4[%#y%?+] = %b, M8[%#w]=%#a"},@| {"STTU",0x1a,0,1,1,"M4[%#y+%#z] = %#b, M8[%#w]=%#a"},@| {"STTUI",0x19,0,1,1,"M4[%#y%?+] = %#b, M8[%#w]=%#a"},@| {"STO",0x1a,0,1,1,"M8[%#y+%#z] = %b"},@| {"STOI",0x19,0,1,1,"M8[%#y%?+] = %b"},@| {"STOU",0x1a,0,1,1,"M8[%#y+%#z] = %#b"},@| {"STOUI",0x19,0,1,1,"M8[%#y%?+] = %#b"},@| {"STSF",0x1a,0,1,1,"%(M4[%#y+%#z]%) = %.b, M8[%#w]=%#a"},@| {"STSFI",0x19,0,1,1,"%(M4[%#y%?+]%) = %.b, M8[%#w]=%#a"},@| {"STHT",0x1a,0,1,1,"M4[%#y+%#z] = %#b>>32, M8[%#w]=%#a"},@| {"STHTI",0x19,0,1,1,"M4[%#y%?+] = %#b>>32, M8[%#w]=%#a"},@| {"STCO",0x0a,0,1,1,"M8[%#y+%#z] = %b"},@| {"STCOI",0x09,0,1,1,"M8[%#y%?+] = %b"},@| {"STUNC",0x1a,0,1,1,"M8[%#y+%#z] = %#b"},@| {"STUNCI",0x19,0,1,1,"M8[%#y%?+] = %#b"},@| {"SYNCD",0x0a,0,0,1,"[%#y+%#z .. %#x]"},@| {"SYNCDI",0x09,0,0,1,"[%#y%?+ .. %#x]"},@| {"PREST",0x0a,0,0,1,"[%#y+%#z .. %#x]"},@| {"PRESTI",0x09,0,0,1,"[%#y%?+ .. %#x]"},@| {"SYNCID",0x0a,0,0,1,"[%#y+%#z .. %#x]"},@| {"SYNCIDI",0x09,0,0,1,"[%#y%?+ .. %#x]"},@| {"PUSHGO",0x8a,0,0,3,"%lrO=%#b, rL=%a, rJ=%#x, -> %#y+%#z"},@| {"PUSHGOI",0x89,0,0,3,"%lrO=%#b, rL=%a, rJ=%#x, -> %#y%?+"} @ @= {"OR",0x2a,0,0,1,"%l = %#y | %#z = %#x"},@| {"ORI",0x29,0,0,1,"%l = %#y | %z = %#x"},@| {"ORN",0x2a,0,0,1,"%l = %#y |~ %#z = %#x"},@| {"ORNI",0x29,0,0,1,"%l = %#y |~ %z = %#x"},@| {"NOR",0x2a,0,0,1,"%l = %#y ~| %#z = %#x"},@| {"NORI",0x29,0,0,1,"%l = %#y ~| %z = %#x"},@| {"XOR",0x2a,0,0,1,"%l = %#y ^ %#z = %#x"},@| {"XORI",0x29,0,0,1,"%l = %#y ^ %z = %#x"},@| {"AND",0x2a,0,0,1,"%l = %#y & %#z = %#x"},@| {"ANDI",0x29,0,0,1,"%l = %#y & %z = %#x"},@| {"ANDN",0x2a,0,0,1,"%l = %#y \\ %#z = %#x"},@| {"ANDNI",0x29,0,0,1,"%l = %#y \\ %z = %#x"},@| {"NAND",0x2a,0,0,1,"%l = %#y ~& %#z = %#x"},@| {"NANDI",0x29,0,0,1,"%l = %#y ~& %z = %#x"},@| {"NXOR",0x2a,0,0,1,"%l = %#y ~^ %#z = %#x"},@| {"NXORI",0x29,0,0,1,"%l = %#y ~^ %z = %#x"},@| {"BDIF",0x2a,0,0,1,"%l = %#y bdif %#z = %#x"},@| {"BDIFI",0x29,0,0,1,"%l = %#y bdif %z = %#x"},@| {"WDIF",0x2a,0,0,1,"%l = %#y wdif %#z = %#x"},@| {"WDIFI",0x29,0,0,1,"%l = %#y wdif %z = %#x"},@| {"TDIF",0x2a,0,0,1,"%l = %#y tdif %#z = %#x"},@| {"TDIFI",0x29,0,0,1,"%l = %#y tdif %z = %#x"},@| {"ODIF",0x2a,0,0,1,"%l = %#y odif %#z = %#x"},@| {"ODIFI",0x29,0,0,1,"%l = %#y odif %z = %#x"},@| {"MUX",0x2a,rM,0,1,"%l = %#b? %#y: %#z = %#x"},@| {"MUXI",0x29,rM,0,1,"%l = %#b? %#y: %z = %#x"},@| {"SADD",0x2a,0,0,1,"%l = nu(%#y\\%#z) = %x"},@| {"SADDI",0x29,0,0,1,"%l = nu(%#y%?\\) = %x"},@| {"MOR",0x2a,0,0,1,"%l = %#y mor %#z = %#x"},@| {"MORI",0x29,0,0,1,"%l = %#y mor %z = %#x"},@| {"MXOR",0x2a,0,0,1,"%l = %#y mxor %#z = %#x"},@| {"MXORI",0x29,0,0,1,"%l = %#y mxor %z = %#x"},@| {"SETH",0x20,0,0,1,"%l = %#z"},@| {"SETMH",0x20,0,0,1,"%l = %#z"},@| {"SETML",0x20,0,0,1,"%l = %#z"},@| {"SETL",0x20,0,0,1,"%l = %#z"},@| {"INCH",0x30,0,0,1,"%l = %#y + %#z = %#x"},@| {"INCMH",0x30,0,0,1,"%l = %#y + %#z = %#x"},@| {"INCML",0x30,0,0,1,"%l = %#y + %#z = %#x"},@| {"INCL",0x30,0,0,1,"%l = %#y + %#z = %#x"},@| {"ORH",0x30,0,0,1,"%l = %#y | %#z = %#x"},@| {"ORMH",0x30,0,0,1,"%l = %#y | %#z = %#x"},@| {"ORML",0x30,0,0,1,"%l = %#y | %#z = %#x"},@| {"ORL",0x30,0,0,1,"%l = %#y | %#z = %#x"},@| {"ANDNH",0x30,0,0,1,"%l = %#y \\ %#z = %#x"},@| {"ANDNMH",0x30,0,0,1,"%l = %#y \\ %#z = %#x"},@| {"ANDNML",0x30,0,0,1,"%l = %#y \\ %#z = %#x"},@| {"ANDNL",0x30,0,0,1,"%l = %#y \\ %#z = %#x"},@| {"JMP",0x40,0,0,1,"-> %#z"},@| {"JMPB",0x40,0,0,1,"-> %#z"},@| {"PUSHJ",0xc0,0,0,1,"%lrO=%#b, rL=%a, rJ=%#x, -> %#z"},@| {"PUSHJB",0xc0,0,0,1,"%lrO=%#b, rL=%a, rJ=%#x, -> %#z"},@| {"GETA",0x60,0,0,1,"%l = %#z"},@| {"GETAB",0x60,0,0,1,"%l = %#z"},@| {"PUT",0x02,0,0,1,"%s = %r"},@| {"PUTI",0x01,0,0,1,"%s = %r"},@| {"POP",0x80,rJ,0,3,"%lrL=%a, rO=%#b, -> %#y%?+"},@| {"RESUME",0x00,0,0,5,"{%#b} -> %#z"},@| {"SAVE",0x20,0,20,1,"%l = %#x"},@| {"UNSAVE",0x82,0,20,1,"%#z: rG=%x, ..., rL=%a"},@| {"SYNC",0x01,0,0,1,""},@| {"SWYM",0x00,0,0,1,""},@| {"GET",0x20,0,0,1,"%l = %s = %#x"},@| {"TRIP",0x0a,255,0,5,"rY=%#y, rZ=%#z, rB=%#b, g[255]=0"} @ @= { if ((op&0xfe)==JMP) yz=inst&0xffffff; if (op&1) yz-=(op==JMPB? 0x1000000: 0x10000); y=inst_ptr;@+ z=incr(loc,yz<<2); } @ @= if (resuming && rop!=RESUME_AGAIN) @@; else { if (f&0x10) @; if (info[op].third_operand) @; if (f&0x1) z.l=zz; else if (f&0x2) @@; else if ((op&0xf0)==SETH) @; if (f&0x4) y.l=yy; else if (f&0x8) @; } @ There are 256 global registers, |g[0]| through |g[255]|; the first 32 of them are used for the special registers |rA|, |rB|, etc. There are |lring_mask+1| local registers, usually 256 but the user can increase this to a larger power of~2 if desired. The current values of rL, rG, rO, and rS are kept in separate variables called |L|, |G|, |O|, and |S| for convenience. (In fact, |O| and |S| actually hold the values rO/8 and rS/8, modulo |lring_size|.) @= { if (zz>=G) z=g[zz]; else if (zz= { if (yy>=G) y=g[yy]; else if (yy= { if (xx>=G) b=g[xx]; else if (xx= register int G,L,O; /* accessible copies of key registers */ @ @= octa g[256]; /* global registers */ octa *l; /* local registers */ int lring_size; /* the number of local registers (a power of 2) */ int lring_mask; /* one less than |lring_size| */ int S; /* congruent to $\rm rS\GG 3$ modulo |lring_size| */ @ Several of the global registers have constant values, because of the way \MMIX\ has been simplified in this simulator. Special register rN has a constant value identifying the time of compilation. (The macro \.{ABSTIME} is defined externally in the file \.{abstime.h}, which should have just been created by {\mc ABSTIME}\kern.05em; {\mc ABSTIME} is a trivial program that computes the value of the standard library function |time(NULL)|. We assume that this number, which is the number of seconds in the ``{\mc UNIX} epoch,'' is less than~$2^{32}$. Beware: Our assumption will fail in February of 2106.) @^system dependencies@> @d VERSION 1 /* version of the \MMIX\ architecture that we support */ @d SUBVERSION 0 /* secondary byte of version number */ @d SUBSUBVERSION 1 /* further qualification to version number */ @= g[rK]=neg_one; g[rN].h=(VERSION<<24)+(SUBVERSION<<16)+(SUBSUBVERSION<<8); g[rN].l=ABSTIME; /* see comment and warning above */ g[rT].h=0x80000005; g[rTT].h=0x80000006; g[rV].h=0x369c2004; if (lring_size<256) lring_size=256; lring_mask=lring_size-1; if (lring_size&lring_mask) panic("The number of local registers must be a power of 2"); @.The number of local...@> l=(octa*)calloc(lring_size,sizeof(octa)); if (!l) panic("No room for the local registers"); @.No room...@> cur_round=ROUND_NEAR; @ In operations like |INCH|, we want |z| to be the |yz| field, shifted left 48 bits. We also want |y| to be register~X, which has previously been placed in |b|; then |INCH| can be simulated as if it were |ADDU|. @= { switch (op&3) { case 0: z.h=yz<<16;@+break; case 1: z.h=yz;@+break; case 2: z.l=yz<<16;@+break; case 3: z.l=yz;@+break; } y=b; } @ @= b=g[info[op].third_operand]; @ @= if (xx>=G) { sprintf(lhs,"$%d=g[%d]",xx,xx); x_ptr=&g[xx]; }@+else { while (xx>=L) @; sprintf(lhs,"$%d=l[%d]",xx,(O+xx)&lring_mask); x_ptr=&l[(O+xx)&lring_mask]; } @ @= { if (((S-O)&lring_mask)==L && L!=0) stack_store(); l[(O+L)&lring_mask]=zero_octa; L=g[rL].l=L+1; } @ The |stack_store| routine advances the ``gamma'' pointer in the ring of local registers, by storing the oldest local register into memory location~rS and advancing rS. @d test_store_bkpt(ll) if ((ll)->bkpt&write_bit) breakpoint=tracing=true @= void stack_store @,@,@[ARGS((void))@];@+@t}\6{@> void stack_store() { register mem_tetra *ll=mem_find(g[rS]); register int k=S&lring_mask; ll->tet=l[k].h;@+test_store_bkpt(ll); (ll+1)->tet=l[k].l;@+test_store_bkpt(ll+1); if (stack_tracing) { tracing=true; if (cur_line) show_line(); printf(" M8[#%08x%08x]=l[%d]=#%08x%08x, rS+=8\n", g[rS].h,g[rS].l,k,l[k].h,l[k].l); } g[rS]=incr(g[rS],8), S++; } @ The |stack_load| routine is essentially the inverse of |stack_store|. @d test_load_bkpt(ll) if ((ll)->bkpt&read_bit) breakpoint=tracing=true @= void stack_load @,@,@[ARGS((void))@];@+@t}\6{@> void stack_load() { register mem_tetra *ll; register int k; S--, g[rS]=incr(g[rS],-8); ll=mem_find(g[rS]); k=S&lring_mask; l[k].h=ll->tet;@+test_load_bkpt(ll); l[k].l=(ll+1)->tet;@+test_load_bkpt(ll+1); if (stack_tracing) { tracing=true; if (cur_line) show_line(); printf(" rS-=8, l[%d]=M8[#%08x%08x]=#%08x%08x\n", k,g[rS].h,g[rS].l,l[k].h,l[k].l); } } @* Simulating the instructions. The master switch branches in 256 directions, one for each \MMIX\ instruction. Let's start with |ADD|, since it is somehow the most typical case---not too easy, and not too hard. The task is to compute |x=y+z|, and to signal overflow if the sum is out of range. Overflow occurs if and only if |y| and |z| have the same sign but the sum has a different sign. Overflow is one of the eight arithmetic exceptions. We record such exceptions in a variable called~|exc|, which is set to zero at the beginning of each cycle and used to update~rA at the end. The main control routine has put the input operands into octabytes |y| and~|z|. It has also made |x_ptr| point to the octabyte where the result should be placed. @= case ADD: case ADDI: x=w; /* |w=oplus(y,z)| */ if (((y.h^z.h)&sign_bit)==0 && ((y.h^x.h)&sign_bit)!=0) exc|=V_BIT; store_x: *x_ptr=x;@+break; @ Other cases of signed and unsigned addition and subtraction are, of course, similar. Overflow occurs in the calculation |x=y-z| if and only if it occurs in the calculation |y=x+z|. @= case SUB: case SUBI: case NEG: case NEGI: x=ominus(y,z); if (((x.h^z.h)&sign_bit)==0 && ((x.h^y.h)&sign_bit)!=0) exc|=V_BIT; goto store_x; case ADDU: case ADDUI: case INCH: case INCMH: case INCML: case INCL: x=w;@+goto store_x; case SUBU: case SUBUI: case NEGU: case NEGUI: x=ominus(y,z);@+goto store_x; case IIADDU: case IIADDUI: case IVADDU: case IVADDUI: case VIIIADDU: case VIIIADDUI: case XVIADDU: case XVIADDUI: x=oplus(shift_left(y,((op&0xf)>>1)-3),z);@+goto store_x; case SETH: case SETMH: case SETML: case SETL: case GETA: case GETAB: x=z;@+goto store_x; @ Let's get the simple bitwise operations out of the way too. @= case OR: case ORI: case ORH: case ORMH: case ORML: case ORL: x.h=y.h|z.h;@+ x.l=y.l|z.l;@+ goto store_x; case ORN: case ORNI: x.h=y.h|~z.h;@+ x.l=y.l|~z.l;@+ goto store_x; case NOR: case NORI: x.h=~(y.h|z.h);@+ x.l=~(y.l|z.l);@+ goto store_x; case XOR: case XORI: x.h=y.h^z.h;@+ x.l=y.l^z.l;@+ goto store_x; case AND: case ANDI: x.h=y.h&z.h;@+ x.l=y.l&z.l;@+ goto store_x; case ANDN: case ANDNI: case ANDNH: case ANDNMH: case ANDNML: case ANDNL: x.h=y.h&~z.h;@+ x.l=y.l&~z.l;@+ goto store_x; case NAND: case NANDI: x.h=~(y.h&z.h);@+ x.l=~(y.l&z.l);@+ goto store_x; case NXOR: case NXORI: x.h=~(y.h^z.h);@+ x.l=~(y.l^z.l);@+ goto store_x; @ The less simple bit manipulations are almost equally simple, given the subroutines of {\mc MMIX-ARITH}. The |MUX| operation has three inputs; in such cases the inputs appear in |y|, |z|, and~|b|. @d shift_amt (z.h || z.l>=64? 64: z.l) @= case SL: case SLI: x=shift_left(y,shift_amt); a=shift_right(x,shift_amt,0); if (a.h!=y.h || a.l!=y.l) exc|=V_BIT; goto store_x; case SLU: case SLUI: x=shift_left(y,shift_amt);@+goto store_x; case SR: case SRI: case SRU: case SRUI: x=shift_right(y,shift_amt,op&0x2);@+goto store_x; case MUX: case MUXI: x.h=(y.h&b.h)|(z.h&~b.h);@+ x.l=(y.l&b.l)|(z.l&~b.l); goto store_x; case SADD: case SADDI: x.l=count_bits(y.h&~z.h)+count_bits(y.l&~z.l);@+goto store_x; case MOR: case MORI: x=bool_mult(y,z,false);@+goto store_x; case MXOR: case MXORI: x=bool_mult(y,z,true);@+goto store_x; case BDIF: case BDIFI: x.h=byte_diff(y.h,z.h);@+x.l=byte_diff(y.l,z.l);@+goto store_x; case WDIF: case WDIFI: x.h=wyde_diff(y.h,z.h);@+x.l=wyde_diff(y.l,z.l);@+goto store_x; case TDIF: case TDIFI:@+ if (y.h>z.h) x.h=y.h-z.h; tdif_l:@+ if (y.l>z.l) x.l=y.l-z.l;@+ goto store_x; case ODIF: case ODIFI:@+if (y.h>z.h) x=ominus(y,z); else if (y.h==z.h) goto tdif_l; goto store_x; @ When an operation has two outputs, the primary output is placed in~|x| and the auxiliary output is placed in~|a|. @= case MUL: case MULI: x=signed_omult(y,z); test_overflow:@+if (overflow) exc|=V_BIT; goto store_x; case MULU: case MULUI: x=omult(y,z);@+a=g[rH]=aux;@+goto store_x; case DIV: case DIVI:@+if (!z.l && !z.h) aux=y, exc|=D_BIT; else x=signed_odiv(y,z); a=g[rR]=aux;@+goto test_overflow; case DIVU: case DIVUI: x=odiv(b,y,z);@+a=g[rR]=aux;@+goto store_x; @ The floating point routines of {\mc MMIX-ARITH} record exceptional events in a variable called |exceptions|. Here we simply merge those bits into the |exc| variable. The |U_BIT| is not exactly the same as ``underflow,'' but the true definition of underflow will be applied when |exc| is combined with~rA. @= case FADD: x=fplus(y,z); fin_float: round_mode=cur_round; store_fx: exc|=exceptions;@+ goto store_x; case FSUB: a=z;@+if (fcomp(a,zero_octa)!=2) a.h^=sign_bit; x=fplus(y,a);@+goto fin_float; case FMUL: x=fmult(y,z);@+goto fin_float; case FDIV: x=fdivide(y,z);@+goto fin_float; case FREM: x=fremstep(y,z,2500);@+goto fin_float; case FSQRT: x=froot(z,y.l); fin_unifloat:@+if (y.h || y.l>4) goto illegal_inst; round_mode=(y.l? y.l: cur_round);@+goto store_fx; case FINT: x=fintegerize(z,y.l);@+goto fin_unifloat; case FIX: x=fixit(z,y.l);@+goto fin_unifloat; case FIXU: x=fixit(z,y.l);@+exceptions&=~W_BIT;@+goto fin_unifloat; case FLOT: case FLOTI: case FLOTU: case FLOTUI: case SFLOT: case SFLOTI: case SFLOTU: case SFLOTUI: x=floatit(z,y.l,op&0x2,op&0x4);@+goto fin_unifloat; @ We have now done all of the arithmetic operations except for the cases that compare two registers and yield a value of $-1$~or~0~or~1. @d cmp_zero store_x /* |x| is 0 by default */ @= case CMP: case CMPI:@+if ((y.h&sign_bit)>(z.h&sign_bit)) goto cmp_neg; if ((y.h&sign_bit)<(z.h&sign_bit)) goto cmp_pos; case CMPU: case CMPUI:@+if (y.hz.h) goto cmp_pos; if (y.l= int register_truth @,@,@[ARGS((octa,mmix_opcode))@];@+@t}\6{@> int register_truth(o,op) octa o; mmix_opcode op; {@+register int b; switch ((op>>1) & 0x3) { case 0: b=o.h>>31;@+break; /* negative? */ case 1: b=(o.h==0 && o.l==0);@+break; /* zero? */ case 2: b=(o.h= case CSN: case CSNI: case CSZ: case CSZI:@/ case CSP: case CSPI: case CSOD: case CSODI:@/ case CSNN: case CSNNI: case CSNZ: case CSNZI:@/ case CSNP: case CSNPI: case CSEV: case CSEVI:@/ case ZSN: case ZSNI: case ZSZ: case ZSZI:@/ case ZSP: case ZSPI: case ZSOD: case ZSODI:@/ case ZSNN: case ZSNNI: case ZSNZ: case ZSNZI:@/ case ZSNP: case ZSNPI: case ZSEV: case ZSEVI:@/ x=register_truth(y,op)? z: b;@+goto store_x; @ Didn't that feel good, when 32 opcodes reduced to a single case? We get to do it one more time. Happiness! @= case BN: case BNB: case BZ: case BZB:@/ case BP: case BPB: case BOD: case BODB:@/ case BNN: case BNNB: case BNZ: case BNZB:@/ case BNP: case BNPB: case BEV: case BEVB:@/ case PBN: case PBNB: case PBZ: case PBZB:@/ case PBP: case PBPB: case PBOD: case PBODB:@/ case PBNN: case PBNNB: case PBNZ: case PBNZB:@/ case PBNP: case PBNPB: case PBEV: case PBEVB:@/ x.l=register_truth(b,op); if (x.l) { inst_ptr=z; good=(op>=PBN); }@+else good=(op= case LDB: case LDBI: case LDBU: case LDBUI:@/ i=56;@+j=(w.l&0x3)<<3; goto fin_ld; case LDW: case LDWI: case LDWU: case LDWUI:@/ i=48;@+j=(w.l&0x2)<<3; goto fin_ld; case LDT: case LDTI: case LDTU: case LDTUI:@/ i=32;@+j=0;@+ goto fin_ld; case LDHT: case LDHTI: i=j=0; fin_ld: ll=mem_find(w);@+test_load_bkpt(ll); x.h=ll->tet; x=shift_right(shift_left(x,j),i,op&0x2); check_ld:@+if (w.h&sign_bit) goto privileged_inst; goto store_x; case LDO: case LDOI: case LDOU: case LDOUI: case LDUNC: case LDUNCI: w.l&=-8;@+ ll=mem_find(w); test_load_bkpt(ll);@+test_load_bkpt(ll+1); x.h=ll->tet;@+ x.l=(ll+1)->tet; goto check_ld; case LDSF: case LDSFI: ll=mem_find(w);@+test_load_bkpt(ll); x=load_sf(ll->tet);@+ goto check_ld; @ @= case STB: case STBI: case STBU: case STBUI:@/ i=56;@+j=(w.l&0x3)<<3; goto fin_pst; case STW: case STWI: case STWU: case STWUI:@/ i=48;@+j=(w.l&0x2)<<3; goto fin_pst; case STT: case STTI: case STTU: case STTUI:@/ i=32;@+j=0; fin_pst: ll=mem_find(w); if ((op&0x2)==0) { a=shift_right(shift_left(b,i),i,0); if (a.h!=b.h || a.l!=b.l) exc|=V_BIT; } ll->tet^=(ll->tet^(b.l<<(i-32-j))) & ((((tetra)-1)<<(i-32))>>j); goto fin_st; case STSF: case STSFI: ll=mem_find(w); ll->tet=store_sf(b);@+exc=exceptions; goto fin_st; case STHT: case STHTI: ll=mem_find(w);@+ ll->tet=b.h; fin_st: test_store_bkpt(ll); w.l&=-8;@+ll=mem_find(w); a.h=ll->tet;@+ a.l=(ll+1)->tet; /* for trace output */ goto check_st; case STCO: case STCOI: b.l=xx; case STO: case STOI: case STOU: case STOUI: case STUNC: case STUNCI: w.l&=-8;@+ll=mem_find(w); test_store_bkpt(ll);@+ test_store_bkpt(ll+1); ll->tet=b.h;@+ (ll+1)->tet=b.l; check_st:@+if (w.h&sign_bit) goto privileged_inst; break; @ The |CSWAP| operation has elements of both loading and storing. We shuffle some of the operands around so that they will appear correctly in the trace output. @= case CSWAP: case CSWAPI: w.l&=-8;@+ll=mem_find(w); test_load_bkpt(ll);@+test_load_bkpt(ll+1); a=g[rP]; if (ll->tet==a.h && (ll+1)->tet==a.l) { x.h=0, x.l=1; test_store_bkpt(ll);@+test_store_bkpt(ll+1); ll->tet=b.h, (ll+1)->tet=b.l; strcpy(rhs,"M8[%#w]=%#b"); }@+else { b.h=ll->tet, b.l=(ll+1)->tet; g[rP]=b; strcpy(rhs,"rP=%#b"); } goto check_ld; @ The |GET| command is permissive, but |PUT| is restrictive. @= case GET:@+if (yy!=0 || zz>=32) goto illegal_inst; x=g[zz]; goto store_x; case PUT: case PUTI:@+ if (yy!=0 || xx>=32) goto illegal_inst; strcpy(rhs,"%z = %#z"); if (xx>=8) { if (xx<=11) goto illegal_inst; /* can't change rC, rN, rO, rS */ if (xx<=18) goto privileged_inst; if (xx==rA) @@; else if (xx==rL) @@; else if (xx==rG) @; } g[xx]=z;@+zz=xx;@+break; @ @= { x=z;@+ strcpy(rhs,z.h? "min(rL,%#x) = %z": "min(rL,%x) = %z"); if (z.l>L || z.h) z.h=0, z.l=L; else old_L=L=z.l; } @ @= { if (z.h!=0 || z.l>255 || z.l= { if (z.h!=0 || z.l>=0x40000) goto illegal_inst; cur_round=(z.l>=0x10000? z.l>>16: ROUND_NEAR); } @ Pushing and popping are rather delicate, because we want to trace them coherently. @= case PUSHGO: case PUSHGOI: inst_ptr=w;@+goto push; case PUSHJ: case PUSHJB: inst_ptr=z; push:@+if (xx>=L) { if (((S-O)&lring_mask)==L && L!=0) stack_store(); xx=L++; } x.l=xx;@+l[(O+xx)&lring_mask]=x; /* the ``hole'' records the amount pushed */ sprintf(lhs,"l[%d]=%d, ",(O+xx)&lring_mask,xx); x=g[rJ]=incr(loc,4); L-=xx+1;@+ O+=xx+1; b=g[rO]=incr(g[rO],(xx+1)<<3); sync_L: a.l=g[rL].l=L;@+break; case POP:@+if (xx!=0 && xx<=L) y=l[(O+xx-1)&lring_mask]; if (g[rS].l==g[rO].l) stack_load(); k=l[(O-1)&lring_mask].l&0xff; while ((tetra)(O-S)<=(tetra)k) stack_load(); L=k+(xx<=L? xx: L+1); if (L>G) L=G; if (L>k) { l[(O-1)&lring_mask]=y; if (y.h) sprintf(lhs,"l[%d]=#%x%08x, ",(O-1)&lring_mask,y.h,y.l); else sprintf(lhs,"l[%d]=#%x, ",(O-1)&lring_mask,y.l); }@+else lhs[0]='\0'; y=g[rJ];@+ z.l=yz<<2;@+ inst_ptr=oplus(y,z); O-=k+1;@+ b=g[rO]=incr(g[rO],-((k+1)<<3)); goto sync_L; @ To complete our simulation of \MMIX's register stack, we need to implement |SAVE| and |UNSAVE|. @= case SAVE:@+if (xx; if (k==255) k=rB; else if (k==rR) k=rP; else if (k==rZ+1) break; else k++; } O=S, g[rO]=g[rS]; x=incr(g[rO],-8);@+goto store_x; @ This part of the program naturally has a lot in common with the |stack_store| subroutine. (There's a little white lie in the section name; if |k|~is |rZ+1|, we store rG and~rA, not |g[k]|.) @= ll=mem_find(g[rS]); if (k==rZ+1) x.h=G<<24, x.l=g[rA].l; else x=g[k]; ll->tet=x.h;@+test_store_bkpt(ll); (ll+1)->tet=x.l;@+test_store_bkpt(ll+1); if (stack_tracing) { tracing=true; if (cur_line) show_line(); if (k>=32) printf(" M8[#%08x%08x]=g[%d]=#%08x%08x, rS+=8\n", g[rS].h,g[rS].l,k,x.h,x.l); else printf(" M8[#%08x%08x]=%s=#%08x%08x, rS+=8\n", g[rS].h,g[rS].l,k==rZ+1? "(rG,rA)": special_name[k],x.h,x.l); } S++, g[rS]=incr(g[rS],8); @ @= case UNSAVE:@+if (xx!=0 || yy!=0) goto illegal_inst; z.l&=-8;@+g[rS]=incr(z,8); for (k=rZ+1;;) { @; if (k==rP) k=rR; else if (k==rB) k=255; else if (k==G) break; else k--; } S=g[rS].l>>3; stack_load(); k=l[S&lring_mask].l&0xff; for (j=0;jG? G: k; g[rL].l=L;@+a=g[rL]; g[rG].l=G;@+break; @ @= g[rS]=incr(g[rS],-8); ll=mem_find(g[rS]); test_load_bkpt(ll);@+test_load_bkpt(ll+1); if (k==rZ+1) x.l=G=g[rG].l=ll->tet>>24, a.l=g[rA].l=(ll+1)->tet&0x3ffff; else g[k].h=ll->tet, g[k].l=(ll+1)->tet; if (stack_tracing) { tracing=true; if (cur_line) show_line(); if (k>=32) printf(" rS-=8, g[%d]=M8[#%08x%08x]=#%08x%08x\n", k,g[rS].h,g[rS].l,ll->tet,(ll+1)->tet); else if (k==rZ+1) printf(" (rG,rA)=M8[#%08x%08x]=#%08x%08x\n", g[rS].h,g[rS].l,ll->tet,(ll+1)->tet); else printf(" rS-=8, %s=M8[#%08x%08x]=#%08x%08x\n", special_name[k],g[rS].h,g[rS].l,ll->tet,(ll+1)->tet); } @ The cache maintenance instructions don't affect this simulation, because there are no caches. But if the user has invoked them, we do provide a bit of information when tracing, indicating the scope of the instruction. @= case SYNCID: case SYNCIDI: case PREST: case PRESTI: case SYNCD: case SYNCDI: case PREGO: case PREGOI: case PRELD: case PRELDI: x=incr(w,xx);@+break; @ Several loose ends remain to be nailed down. % (Incidentally, a ``loose end'' should never be confused with ``Lucent.'') @= case GO: case GOI: x=inst_ptr;@+inst_ptr=w;@+goto store_x; case JMP: case JMPB: inst_ptr=z;@+break; case SYNC:@+if (xx!=0 || yy!=0 || zz>6) goto illegal_inst; if (zz<=3) break; case LDVTS: case LDVTSI: privileged_inst: strcpy(lhs,"!privileged"); goto break_inst; illegal_inst: strcpy(lhs,"!illegal"); break_inst: breakpoint=tracing=true; if (!interacting && !interact_after_break) halted=true; break; @* Trips and traps. We have now implemented 253 of the 256 instructions: all but \.{TRIP}, \.{TRAP}, and \.{RESUME}. The |TRIP| instruction simply turns |H_BIT| on in the |exc| variable; this will trigger an interruption to location~0. @^interrupts@> The |TRAP| instruction is not simulated, except for the system calls mentioned in the introduction. @= case TRIP: exc|=H_BIT;@+break; case TRAP:@+if (xx!=0 || yy>max_sys_call) goto privileged_inst; strcpy(rhs,trap_format[yy]); g[rWW]=inst_ptr; g[rXX].h=sign_bit, g[rXX].l=inst; g[rYY]=y, g[rZZ]=z; z.h=0, z.l=zz; a=incr(b,8); @; switch (yy) { case Halt: @;@+g[rBB]=g[255];@+break; case Fopen: g[rBB]=mmix_fopen((unsigned char)zz,mb,ma);@+break; case Fclose: g[rBB]=mmix_fclose((unsigned char)zz);@+break; case Fread: g[rBB]=mmix_fread((unsigned char)zz,mb,ma);@+break; case Fgets: g[rBB]=mmix_fgets((unsigned char)zz,mb,ma);@+break; case Fgetws: g[rBB]=mmix_fgetws((unsigned char)zz,mb,ma);@+break; case Fwrite: g[rBB]=mmix_fwrite((unsigned char)zz,mb,ma);@+break; case Fputs: g[rBB]=mmix_fputs((unsigned char)zz,b);@+break; case Fputws: g[rBB]=mmix_fputws((unsigned char)zz,b);@+break; case Fseek: g[rBB]=mmix_fseek((unsigned char)zz,b);@+break; case Ftell: g[rBB]=mmix_ftell((unsigned char)zz);@+break; } x=g[255]=g[rBB];@+break; @ @= if (!zz) halted=breakpoint=true; else if (zz==1) { if (loc.h || loc.l>=0x90) goto privileged_inst; print_trip_warning(loc.l>>4,incr(g[rW],-4)); }@+else goto privileged_inst; @ @= char arg_count[]={1,3,1,3,3,3,3,2,2,2,1}; char *trap_format[]={ "Halt(%z)", "$255 = Fopen(%!z,M8[%#b]=%#q,M8[%#a]=%p) = %x", "$255 = Fclose(%!z) = %x", "$255 = Fread(%!z,M8[%#b]=%#q,M8[%#a]=%p) = %x", "$255 = Fgets(%!z,M8[%#b]=%#q,M8[%#a]=%p) = %x", "$255 = Fgetws(%!z,M8[%#b]=%#q,M8[%#a]=%p) = %x", "$255 = Fwrite(%!z,M8[%#b]=%#q,M8[%#a]=%p) = %x", "$255 = Fputs(%!z,%#b) = %x", "$255 = Fputws(%!z,%#b) = %x", "$255 = Fseek(%!z,%b) = %x", "$255 = Ftell(%!z) = %x"}; @ @= if (arg_count[yy]==3) { ll=mem_find(b);@+test_load_bkpt(ll);@+test_load_bkpt(ll+1); mb.h=ll->tet, mb.l=(ll+1)->tet; ll=mem_find(a);@+test_load_bkpt(ll);@+test_load_bkpt(ll+1); ma.h=ll->tet, ma.l=(ll+1)->tet; } @ The input/output operations invoked by \.{TRAP}s are done by subroutines in an auxiliary program module called {\mc MMIX-IO}. Here we need only declare those subroutines, and write three primitive interfaces on which they depend. @ @= extern void mmix_io_init @,@,@[ARGS((void))@]; extern octa mmix_fopen @,@,@[ARGS((unsigned char,octa,octa))@]; extern octa mmix_fclose @,@,@[ARGS((unsigned char))@]; extern octa mmix_fread @,@,@[ARGS((unsigned char,octa,octa))@]; extern octa mmix_fgets @,@,@[ARGS((unsigned char,octa,octa))@]; extern octa mmix_fgetws @,@,@[ARGS((unsigned char,octa,octa))@]; extern octa mmix_fwrite @,@,@[ARGS((unsigned char,octa,octa))@]; extern octa mmix_fputs @,@,@[ARGS((unsigned char,octa))@]; extern octa mmix_fputws @,@,@[ARGS((unsigned char,octa))@]; extern octa mmix_fseek @,@,@[ARGS((unsigned char,octa))@]; extern octa mmix_ftell @,@,@[ARGS((unsigned char))@]; extern void print_trip_warning @,@,@[ARGS((int,octa))@]; extern void mmix_fake_stdin @,@,@[ARGS((FILE*))@]; @ The subroutine |mmgetchars(buf,size,addr,stop)| reads characters starting at address |addr| in the simulated memory and stores them in |buf|, continuing until |size| characters have been read or some other stopping criterion has been met. If |stop<0| there is no other criterion; if |stop=0| a null character will also terminate the process; otherwise |addr| is even, and two consecutive null bytes starting at an even address will terminate the process. The number of bytes read and stored, exclusive of terminating nulls, is returned. @= int mmgetchars @,@,@[ARGS((char*,int,octa,int))@];@+@t}\6{@> int mmgetchars(buf,size,addr,stop) char *buf; int size; octa addr; int stop; { register char *p; register int m; register mem_tetra *ll; register tetra x; octa a; for (p=buf,m=0,a=addr; mtet; if ((a.l&0x3) || m>size-4) @@; else @@; } return size; } @ @= { *p=(x>>(8*((~a.l)&0x3)))&0xff; if (!*p && stop>=0) { if (stop==0) return m; if ((a.l&0x1) && *(p-1)=='\0') return m-1; } p++,m++,a=incr(a,1); } @ @= { *p=x>>24; if (!*p && (stop==0 || (stop>0 && x<0x10000))) return m; *(p+1)=(x>>16)&0xff; if (!*(p+1) && stop==0) return m+1; *(p+2)=(x>>8)&0xff; if (!*(p+2) && (stop==0 || (stop>0 && (x&0xffff)==0))) return m+2; *(p+3)=x&0xff; if (!*(p+3) && stop==0) return m+3; p+=4,m+=4,a=incr(a,4); } @ The subroutine |mmputchars(buf,size,addr)| puts |size| characters into the simulated memory starting at address |addr|. @= void mmputchars @,@,@[ARGS((unsigned char*,int,octa))@];@+@t}\6{@> void mmputchars(buf,size,addr) unsigned char *buf; int size; octa addr; { register unsigned char *p; register int m; register mem_tetra *ll; octa a; for (p=buf,m=0,a=addr; msize-4) @@; else @; } } @ @= { register int s=8*((~a.l)&0x3); ll->tet^=(((ll->tet>>s)^*p)&0xff)<= { ll->tet=(*p<<24)+(*(p+1)<<16)+(*(p+2)<<8)+*(p+3); p+=4,m+=4,a=incr(a,4); } @ When standard input is being read by the simulated program at the same time as it is being used for interaction, we try to keep the two uses separate by maintaining a private buffer for the simulated program's \.{StdIn}. Online input is usually transmitted from the keyboard to a \CEE/ program a line at a time; therefore an |fgets| operation works much better than |fread| when we prompt for new input. But there is a slight complication, because |fgets| might read a null character before coming to a newline character. We cannot deduce the number of characters read by |fgets| simply by looking at |strlen(stdin_buf)|. @= char stdin_chr @,@,@[ARGS((void))@];@+@t}\6{@> char stdin_chr() { register char* p; while (stdin_buf_start==stdin_buf_end) { if (interacting) { printf("StdIn> ");@+fflush(stdout); @.StdIn>@> } if (!fgets(stdin_buf,256,stdin)) panic("End of file on standard input; use the -f option, not <"); stdin_buf_start=stdin_buf; for (p=stdin_buf;p= char stdin_buf[256]; /* standard input to the simulated program */ char *stdin_buf_start; /* current position in that buffer */ char *stdin_buf_end; /* current end of that buffer */ @ Just after executing each instruction, we do the following. Underflow that is exact and not enabled is ignored. (This applies also to underflow that was triggered by |RESUME_SET|.) @= if ((exc&(U_BIT+X_BIT))==U_BIT && !(g[rA].l&U_BIT)) exc &=~U_BIT; if (exc) { if (exc&tracing_exceptions) tracing=true; j=exc&(g[rA].l|H_BIT); /* find all exceptions that have been enabled */ if (j) @; g[rA].l |= exc>>8; } @ @= { tripping=true; for (k=0; !(j&H_BIT); j<<=1, k++) ; exc&=~(H_BIT>>k); /* trips taken are not logged as events */ g[rW]=inst_ptr; inst_ptr.h=0, inst_ptr.l=k<<4; g[rX].h=sign_bit, g[rX].l=inst; if ((op&0xe0)==STB) g[rY]=w, g[rZ]=b; else g[rY]=y, g[rZ]=z; g[rB]=g[255]; g[255]=zero_octa; } @ We are finally ready for the last case. @= case RESUME:@+if (xx || yy || zz) goto illegal_inst; inst_ptr=z=g[rW]; b=g[rX]; if (!(b.h&sign_bit)) @; break; @ Here we check to see if the ropcode restrictions hold. If so, the ropcode will actually be obeyed on the next fetch phase. @d RESUME_AGAIN 0 /* repeat the command in rX as if in location $\rm rW-4$ */ @d RESUME_CONT 1 /* same, but substitute rY and rZ for operands */ @d RESUME_SET 2 /* set r[X] to rZ */ @= { rop=b.h>>24; /* the ropcode is the leading byte of rX */ switch (rop) { case RESUME_CONT:@+if ((1<<(b.l>>28))&0x8f30) goto illegal_inst; case RESUME_SET: k=(b.l>>16)&0xff; if (k>=L && k>24)==RESUME) goto illegal_inst; break; default: goto illegal_inst; } resuming=true; } @ @= if (rop==RESUME_SET) { op=ORI; y=g[rZ]; z=zero_octa; exc=g[rX].h&0xff00; f=X_is_dest_bit; }@+else { /* |RESUME_CONT| */ y=g[rY]; z=g[rZ]; } @ We don't want to count the |UNSAVE| that bootstraps the whole process. @= if (g[rU].l || g[rU].h || !resuming) { g[rC].h+=info[op].mems; /* clock goes up by $2^{32}$ for each $\mu$ */ g[rC]=incr(g[rC],info[op].oops); /* clock goes up by 1 for each $\upsilon$ */ g[rU]=incr(g[rU],1); /* usage counter counts total instructions simulated */ g[rI]=incr(g[rI],-1); /* interval timer counts down by 1 only */ if (g[rI].l==0 && g[rI].h==0) tracing=breakpoint=true; } @* Tracing. After an instruction has been executed, we often want to display its effect. This part of the program prints out a symbolic interpretation of what has just happened. @= if (tracing) { if (showing_source && cur_line) show_line(); @; @; if (showing_stats || breakpoint) show_stats(breakpoint); just_traced=true; }@+else if (just_traced) { printf(" ...............................................\n"); just_traced=false; shown_line=-gap-1; /* gap will not be filled */ } @ @= bool showing_stats; /* should traced instructions also show the statistics? */ bool just_traced; /* was the previous instruction traced? */ @ @= if (resuming && op!=RESUME) { switch (rop) { case RESUME_AGAIN: printf(" (%08x%08x: %08x (%s)) ", loc.h,loc.l,inst,info[op].name);@+break; case RESUME_CONT: printf(" (%08x%08x: %04xrYrZ (%s)) ", loc.h,loc.l,inst>>16,info[op].name);@+break; case RESUME_SET: printf(" (%08x%08x: ..%02x..rZ (SET)) ", loc.h,loc.l,(inst>>16)&0xff);@+break; } }@+else { ll=mem_find(loc); printf("%10d. %08x%08x: %08x (%s) ",ll->freq,loc.h,loc.l,inst,info[op].name); } @ This part of the simulator was inspired by ideas of E.~H. Satterthwaite, @^Satterthwaite, Edwin Hallowell, Jr.@> {\sl Software---Practice and Experience\/ \bf2} (1972), 197--217. Online debugging tools have improved significantly since Satterthwaite published his work, but good offline tools are still valuable; alas, today's algebraic programming languages do not provide tracing facilities that come anywhere close to the level of quality that Satterthwaite was able to demonstrate for {\mc ALGOL} in 1970. @= if (lhs[0]=='!') printf("%s instruction!\n",lhs+1); /* privileged or illegal */ else { @; if (z.l==0 && (op==ADDUI||op==ORI)) p="%l = %y = %#x"; /* \.{LDA}, \.{SET} */ else p=info[op].trace_format; for (;*p;p++) @; if (exc) printf(", rA=#%05x", g[rA].l); if (tripping) tripping=false, printf(", -> #%02x", inst_ptr.l); printf("\n"); } @ Push, pop, and \.{UNSAVE} instructions display changes to rL and rO explicitly; otherwise the change is implicit, if |L!=old_L|. @= if (L!=old_L && !(f&push_pop_bit)) printf("rL=%d, ",L); @ Each \MMIX\ instruction has a {\it trace format\/} string, which defines its symbolic representation. For example, the string for \.{ADD} is |"%l = %y + %z = %x"|; if the instruction is, say, \.{ADD}~\.{\$1,\$2,\$3} with $\$2=5$ and $\$3=8$, and if the stack offset is 100, the trace output will be |"$1=l[101] = 5 + 8 = 13"|. Percent signs (\.\%) induce special format conventions, as follows: \bull \.{\%a}, \.{\%b}, \.{\%p}, \.{\%q}, \.{\%w}, \.{\%x}, \.{\%y}, and \.{\%z} stand for the numeric contents of octabytes |a|, |b|, |ma|, |mb|, |w|, |x|, |y|, and~|z|, respectively; a ``style'' character may follow the percent sign in this case, as explained below. \bull \.{\%(} and \.{\%)} are brackets that indicate the mode of floating point rounding. If |round_mode=ROUND_NEAR|, |ROUND_OFF|, |ROUND_UP|, |ROUND_DOWN|, the corresponding brackets are \.(~and~\.), \.[~and~\.], \.\^~and~\.\^, \.\_~and~\.\_. Such brackets are placed around a floating point operator; for example, floating point addition is denoted by `\.{[+]}' when the current rounding mode is rounding-off. \bull \.{\%l} stands for the string |lhs|, which usually represents the ``left hand side'' of the instruction just performed, formatted as a register number and its equivalent in the ring of local registers (e.g., `\.{\$1=l[101]}') or as a register number and its equivalent in the array of global registers (e.g., `\.{\$255=g[255]}'). The \.{POP} instruction uses |lhs| to indicate how the ``hole'' in the register stack was plugged. \bull \.{\%r} means to switch to string |rhs| and continue formatting from there. This mechanism allows us to use variable formats for opcodes like \.{TRAP} that have several variants. \bull \.{\%t} means to print either `\.{Yes, ->loc}' (where \.{loc} is the location of the next instruction) or `\.{No}', depending on the value of~|x|. \bull \.{\%g} means to print `\.{ (bad guess)}' if |good| is |false|. \bull \.{\%s} stands for the name of special register |g[zz]|. \bull \.{\%?} stands for omission of the following operator if |z=0|. For example, the memory address of \.{LDBI} is described by `\.{\%\#y\%?+}'; this means to treat the address as simply `\.{\%\#y}' if |z=0|, otherwise as `\.{\%\#y+\%z}'. This case is used only when |z| is a relatively small number (|z.h=0|). @= { if (*p!='%') fputc(*p,stdout); else { style=decimal; char_switch: switch (*++p) { @t\4@>@; default: printf("BUG!!"); /* can't happen */ } } } @ Octabytes are printed as decimal numbers unless a ``style'' character intervenes between the percent sign and the name of the octabyte: `\.\#' denotes hexadecimal notation, prefixed by~\.\#; `\.0' denotes hexadecimal notation with no prefixed~\.\# and with leading zeros not suppressed; `\..' denotes floating decimal notation; and `\.!' means to use the names \.{StdIn}, \.{StdOut}, or \.{StdErr} if the value is 0, 1, or~2. @.StdIn@> @.StdOut@> @.StdErr@> @= case '#': style=hex;@+ goto char_switch; case '0': style=zhex;@+ goto char_switch; case '.': style=floating;@+ goto char_switch; case '!': style=handle;@+ goto char_switch; @ @= typedef enum {@!decimal,@!hex,@!zhex,@!floating,@!handle} fmt_style; @ @= case 'a': trace_print(a);@+break; case 'b': trace_print(b);@+break; case 'p': trace_print(ma);@+break; case 'q': trace_print(mb);@+break; case 'w': trace_print(w);@+break; case 'x': trace_print(x);@+break; case 'y': trace_print(y);@+break; case 'z': trace_print(z);@+break; @ @= fmt_style style; char *stream_name[]={"StdIn","StdOut","StdErr"}; @.StdIn@> @.StdOut@> @.StdErr@> @# void trace_print @,@,@[ARGS((octa))@];@+@t}\6{@> void trace_print(o) octa o; { switch (style) { case decimal: print_int(o);@+return; case hex: fputc('#',stdout);@+print_hex(o);@+return; case zhex: printf("%08x%08x",o.h,o.l);@+return; case floating: print_float(o);@+return; case handle:@+if (o.h==0 && o.l<3) printf(stream_name[o.l]); else print_int(o);@+return; } } @ @= case '(': fputc(left_paren[round_mode],stdout);@+break; case ')': fputc(right_paren[round_mode],stdout);@+break; case 't':@+if (x.l) printf(" Yes, -> #"),print_hex(inst_ptr); else printf(" No");@+break; case 'g':@+if (!good) printf(" (bad guess)");@+break; case 's': printf(special_name[zz]);@+break; case '?': p++;@+if (z.l) printf("%c%d",*p,z.l);@+break; case 'l': printf(lhs);@+break; case 'r': p=switchable_string;@+break; @ @d rhs &switchable_string[1] @= char left_paren[]={0,'[','^','_','('}; /* denotes the rounding mode */ char right_paren[]={0,']','^','_',')'}; /* denotes the rounding mode */ char switchable_string[48]; /* holds |rhs|; position 0 is ignored */ /* |switchable_string| must be able to hold any |trap_format| */ char lhs[32]; int good_guesses, bad_guesses; /* branch prediction statistics */ @ @= void show_stats @,@,@[ARGS((bool))@];@+@t}\6{@> void show_stats(verbose) bool verbose; { octa o; printf(" %d instruction%s, %d mem%s, %d oop%s; %d good guess%s, %d bad\n", g[rU].l,g[rU].l==1? "": "s",@| g[rC].h,g[rC].h==1? "": "s",@| g[rC].l,g[rC].l==1? "": "s",@| good_guesses,good_guesses==1? "": "es",bad_guesses); if (!verbose) return; o = halted? incr(inst_ptr,-4): inst_ptr; printf(" (%s at location #%08x%08x)\n", halted? "halted": "now", o.h, o.l); } @* Running the program. Now we are ready to fit the pieces together into a working simulator. @c #include #include #include #include #include #include "abstime.h" @@; @@; @@; @@; @# int main(argc,argv) int argc; char *argv[]; { @; mmix_io_init(); @; @; @; @; while (1) { if (interrupt && !breakpoint) breakpoint=interacting=true, interrupt=false; else { breakpoint=false; if (interacting) @; } if (halted) break; do @@; while ((!interrupt && !breakpoint) || resuming); if (interact_after_break) interacting=true, interact_after_break=false; } end_simulation:@+if (profiling) @; if (interacting || profiling || showing_stats) show_stats(true); return 0; } @ Here we process the command-line options; when we finish, |*cur_arg| should be the name of the object file to be loaded and simulated. @d mmo_file_name *cur_arg @= myself=argv[0]; for (cur_arg=argv+1;*cur_arg && (*cur_arg)[0]=='-'; cur_arg++) scan_option(*cur_arg+1,true); if (!*cur_arg) scan_option("?",true); /* exit with usage note */ argc -= cur_arg-argv; /* this is the |argc| of the user program */ @ Careful readers of the following subroutine will notice a little white bug: A tracing specification like \.{t1000000000} or even \.{t0000000000} or even \.{t!!!!!!!!!!} is silently converted to \.{t4294967295}. The \.{-b} and \.{-c} options are effective only on the command line, but they are harmless while interacting. @= void scan_option @,@,@[ARGS((char*,bool))@];@+@t}\6{@> void scan_option(arg,usage) char *arg; /* command-line argument (without the `\.-') */ bool usage; /* should we exit with usage note if unrecognized? */ { register int k; switch (*arg) { case 't':@+if (strlen(arg)>10) trace_threshold=0xffffffff; else if (sscanf(arg+1,"%d",&trace_threshold)!=1) trace_threshold=0; return; case 'e':@+if (!*(arg+1)) tracing_exceptions=0xff; else if (sscanf(arg+1,"%x",&tracing_exceptions)!=1) tracing_exceptions=0; return; case 'r': stack_tracing=true;@+return; case 's': showing_stats=true;@+return; case 'l':@+if (!*(arg+1)) gap=3; else if (sscanf(arg+1,"%d",&gap)!=1) gap=0; showing_source=true;@+return; case 'L':@+if (!*(arg+1)) profile_gap=3; else if (sscanf(arg+1,"%d",&profile_gap)!=1) profile_gap=0; profile_showing_source=true; case 'P': profiling=true;@+return; case 'v': trace_threshold=0xffffffff;@+ tracing_exceptions=0xff; stack_tracing=true; @+ showing_stats=true; gap=10, showing_source=true; profile_gap=10, profile_showing_source=true, profiling=true; return; case 'q': trace_threshold=tracing_exceptions=0; stack_tracing=showing_stats=showing_source=false; profiling=profile_showing_source=false; return; case 'i': interacting=true;@+return; case 'I': interact_after_break=true;@+return; case 'b':@+if (sscanf(arg+1,"%d",&buf_size)!=1) buf_size=0;@+return; case 'c':@+if (sscanf(arg+1,"%d",&lring_size)!=1) lring_size=0;@+return; case 'f': @;@+return; case 'D': @;@+return; default:@+if (usage) { fprintf(stderr, "Usage: %s progfile command-line-args...\n",myself); @.Usage: ...@> for (k=0;usage_help[k][0];k++) fprintf(stderr,usage_help[k]); exit(-1); }@+else@+ for (k=0;usage_help[k][1]!='b';k++) printf(usage_help[k]); return; } } @ @= char *myself; /* |argv[0]|, the name of this simulator */ char **cur_arg; /* pointer to current place in the argument vector */ bool interrupt; /* has the user interrupted the simulation recently? */ bool profiling; /* should we print the profile at the end? */ FILE *fake_stdin; /* file substituted for the simulated \.{StdIn} */ FILE *dump_file; /* file used for binary dumps */ char *usage_help[]={@/ " with these options: (=decimal number, =hex number)\n",@| "-t trace each instruction the first n times\n",@| "-e trace each instruction with an exception matching x\n",@| "-r trace hidden details of the register stack\n",@| "-l list source lines when tracing, filling gaps <= n\n",@| "-s show statistics after each traced instruction\n",@| "-P print a profile when simulation ends\n",@| "-L list source lines with the profile\n",@| "-v be verbose: show almost everything\n",@| "-q be quiet: show only the simulated standard output\n",@| "-i run interactively (prompt for online commands)\n",@| "-I interact, but only after the program halts\n",@| "-b change the buffer size for source lines\n",@| "-c change the cyclic local register ring size\n",@| "-f use given file to simulate standard input\n",@| "-D dump a file for use by other simulators\n",@| ""}; char *interactive_help[]={@/ "The interactive commands are:\n",@| " trace one instruction\n",@| "n trace one instruction\n",@| "c continue until halt or breakpoint\n",@| "q quit the simulation\n",@| "s show current statistics\n",@| "l set and/or show local register in format t\n",@| "g set and/or show global register in format t\n",@| "rA set and/or show register rA in format t\n",@| "$ set and/or show dynamic register in format t\n",@| "M set and/or show memory octabyte in format t\n",@| "+ set and/or show n additional octabytes in format t\n",@| " is ! (decimal) or . (floating) or # (hex) or \" (string)\n",@| " or (previous ) or = (change value)\n",@| "@@ go to location x\n",@| "b[rwx] set or reset breakpoint at location x\n",@| "t trace location x\n",@| "u untrace location x\n",@| "T set current segment to Text_Segment\n",@| "D set current segment to Data_Segment\n",@| "P set current segment to Pool_Segment\n",@| "S set current segment to Stack_Segment\n",@| "B show all current breakpoints and tracepoints\n",@| "i insert commands from file\n",@| "-