123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589 |
- .\" Implementation details
- .\"
- .\" $Id$
- .bp
- .NH
- IMPLEMENTATION DETAILS.
- .PP
- The pertinent issues are addressed below, in arbitrary order.
- .NH 2
- Stack manipulation and start-up
- .PP
- It is not at all easy to start the EM machine with the stack in a reasonable
- and consistent state. One reason is the anomalous value of the ML register
- and another is the absence of a proper RSB. It may be argued that the initial
- stack does not have to be in a consistent state, since the first instruction
- proper is only executed after \fIargc\fP, \fIargv\fP and \fIenviron\fP
- have been stacked (which takes care of the empty stack) and the initial
- procedure has been called (which creates a RSB). We would, however, like to
- preform the stacking of these values and the calling of the initial procedure
- using the normal stack and call routines, which again require the stack to be
- in an acceptable state.
- .NH 3
- The anomalous value of the ML register
- .PP
- All registers in the EM machine point to word boundaries, and all of them,
- except ML, address the even-numbered byte at the boundary.
- The exception has a good reason: the even numbered byte at the ML boundary does
- not exist.
- This problem is not particular to EM but is inherent in the number system: the
- number of N-digit numbers can itself not be expressed in an N-digit number, and
- the number of addresses in an N-bit machine will itself not fit in an N-bit
- address. The problem is solved in the interpreter by having ML point to the
- highest word boundary that has bytes on either side; this makes ML+1
- expressible.
- .NH 3
- The absence of an initial Return Status Block
- .PP
- When the stack is empty, there is no legal value for AB, since there are no
- actuals; LB can be set naturally to ML+1. This is all right when the
- interpreter starts with a call of the initial routine which stores the value
- of LB in the first RSB, but causes problems when finally this call returns. We
- want this call to return completely before stopping the interpreter, to check
- the integrity of the last RSB; restoring information from it will, however,
- cause illegal values to be stored in LB and AB (ML+1 and ML+1+rsbsize, resp.).
- On top of this, the initial (illegal) Procedure Identifier of the running
- procedure will be restored; then, upon restoring the likewise illegal PC will
- cause a check to see if it still is inside the running procedure. After a few
- attempts at writing special cases, we have decided that it is possible, but not
- worth the effort; the final (= initial) RSB will not be unstacked.
- .NH 2
- Floating point numbers.
- .PP
- The interpreter is capable of working with 4- and 8-byte floating point (FP)
- numbers.
- In C-terms, this corresponds to objects of type float and double respectively.
- Both types fit in a C-double so the obvious way to manipulate these entities
- internally is in doubles.
- Pushing a 8-byte FP, all bytes of the C-double are pushed.
- Pushing a 4-byte FP causes the 4 bytes representing the smallest fraction
- to be discarded.
- .PP
- In EM, floats can be obtained in two different ways: via conversion
- of another type, or via initialization in the loadfile.
- Initialized floats are represented in the loadfile by an ASCII string in
- the syntax of a Pascal real (signed \fPUnsignedReal\fP).
- I.e. a float looks like:
- .DS
- [ \fISign\fP ] \fIDigit\fP+ [ . \fIDigit\fP+ ] [ \fIExp\fP [ \fISign\fP ] \fIDigit\fP+ ] (G1)
- .DE
- followed by a null byte.
- Here \fISign\fP = {+, \-}; \fIDigit\fP = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
- \fIExp\fP = {e, E}; [ \fIAnything\fP ] means that \fIAnything\fP is optional;
- and a + means one or more times.
- To accommodate some loose code generators, the actual grammar accepted is:
- .DS
- [ \fISign\fP ] \fIDigit\fP\(** [ . \fIDigit\fP\(** ] [ \fIExp\fP [ \fISign\fP ] \fIDigit\fP+ ] (G2)
- .DE
- followed by a null byte. Here \(** means zero or more times. A floating
- denotation which is in G2 but not in G1 draws a warning, one that is not even
- in G2 causes a fatal error.
- .LP
- A string, representing a float which does not fit in a double causes a
- warning to be given.
- In that case, the returned value will be the double 0.0.
- .LP
- Floating point arithmetic is handled by some simple routines, checking for
- over/underflow, and returning appropriate values in case of an ignored error.
- .PP
- Since not all C compilers provide floating point operations, there is a
- compile time flag NOFLOAT, which, if defined, suppresses the use of all
- fp operations in the interpreter. The resulting interpreter will still load
- EM files with floats in the global data area (and ignore them) but will give a
- fatal error upon attempt to execute a floating point instruction; consequently
- code involving floating point operations can be run as long as the actual
- instructions are avoided.
- .NH 2
- Pointers.
- .PP
- The following sub-sections both deal with problems concerning pointers.
- First, something is said about pointer arithmetic in general.
- Then, the null-pointer problem is dealt with.
- .NH 3
- Pointer arithmetic.
- .PP
- Strictly speaking, pointer arithmetic is defined only within a \fBfragment\fP.
- From the explanation of the term fragment however (as given in [1], page 3),
- it is not quite clear what a fragment should look like
- from an interpreter's point of view.
- For this reason we introduced the term \fBsegment\fP,
- bordering the various areas within which pointer arithmetic is allowed.
- Every stack-frame is a segment, and so are the global data area (GDA) and
- the heap area.
- Thus, the number of segments varies over time, and at some point in time is
- given by the number of currently active stack-frames
- (#CAL + #CAI \- #RET \- #RTT) plus 2 (gda, heap).
- Pointers in the area between heap and stack (which is inaccessible by
- definition), are assumed to be in the heap segment.
- .PP
- The interpreter, while building a new stack-frame (i.e. segment), stores the
- value of the last ActualBase in a pointer-array (\fIAB_list[\ ]\fP).
- When a pointer (say \fIP\fP) is available for arithmetic, the number
- of the segment where it points (say \fIS\d\s-2P\s+2\u\fP),
- is determined first.
- Next, the arithmetic is performed, followed by a check on the number
- of the segment where the resulting pointer \fIR\fP points
- (say \fIS\d\s-2R\s+2\u\fP).
- Now, if \fIS\d\s-2P\s+2\u != S\d\s-2R\s+2\u\fP, a warning is given:
- \fBPointer arithmetic yields pointer to bad segment\fP.
- .br
- It may also be clear now, why the illegal area between heap and stack
- was joined with the heap segment.
- When calculating a new heap pointer (\fIHP\fP), one will obtain intermediate
- results being pointers in this area just before it is made legal.
- We do not want error messages all of the time, just because someone is
- allocating space in the heap.
- .LP
- A similar treatment is given to the pointers in the SBS instruction; they have
- to point into the same fragment for subtraction to be meaningful.
- .LP
- The length of the \fIAB_list[\ ]\fP is initially 100,
- and it is reallocated in the same way the dynamically growing partitions
- are (see 1.1).
- .NH 3
- Null pointer.
- .PP
- Because the EM language lacks an instruction for loading a null pointer,
- most programs solve this problem by loading a pointer-sized integer of
- value zero, and using this as a null pointer (this is also proposed in [1]).
- \fBInt\fP allows this, and will not complain.
- A warning is given however, when an attempt is made to add something to a
- null pointer (i.e. the pointer-sized integer zero).
- .LP
- Since many programming languages use a pointer to location 0 as an illegal
- value, it is desirable to detect its use.
- The big problem is though that 0 is a perfectly legal EM address;
- address 0 holds the current line number in the source file. It may be freely
- read but is written only by means of the LIN instruction. This allows us to
- declare the area consisting of the line number and the file name pointer to be
- read-only memory. Thus a store will be caught (and result in a warning) but a
- read will succeed (and yield the EM information stored there).
- .NH 2
- Function Return Area (FRA).
- .PP
- The Function Return Area (\fIFRA[\ ]\fP) has a default size of 8 bytes;
- this default can
- be overridden through the use of the \fB\-r\fP-option, but cannot be
- made smaller than the size of two pointers, in accordance with the
- remark on page 5 of [1].
- The global variable \fIFRASize\fP keeps track of how many bytes were
- stored in the FRA, the last time a RET instruction was executed.
- The LFR instruction only works when its argument is equal to this size.
- If not, the FRA contents are loaded anyhow, but one of the following warnings
- is given:
- \fBReturned function result too large\fP (\fIFRASize\fP > LFR size) or
- \fBReturned function result too small\fP (\fIFRASize\fP < LFR size).
- .LP
- Note that a C-program, falling through the end of its code without doing
- a proper \fIreturn\fP or \fIexit()\fP, will generate this warning.
- .PP
- The only instructions that do not disturb the contents of the FRA are
- GTO, BRA, ASP and RET.
- This is expressed in the program by setting \fIFRA_def\fP to "undefined"
- in any instruction except these four.
- We realize this is a useless action most of the time, but a more
- efficient solution does not seem to be at hand.
- If a result is loaded when \fIFRA_def\fP is "undefined", the warning:
- \fBReturned function result may be garbled\fP is generated.
- .LP
- Note that the FRA needs a shadow-FRA in order to store the shadow
- information when performing a LFR instruction.
- .NH 2
- Environment interaction.
- .PP
- The EM machine represented by \fBint\fP can communicate with
- the environment in three different ways.
- A first possibility is by means of (UNIX) interrupts;
- the second by executing (relatively) high level system calls (called
- monitor calls).
- A third means of interaction, especially interesting for the debugging
- programmer, is via internal variables set on the command line.
- The former two techniques, and the way they are implemented will be described
- in this section.
- The latter has been allotted a separate section (3).
- .NH 3
- Traps and interrupts.
- .PP
- Simple user programs will generally not mess around with UNIX-signals.
- In interpreting these programs, the default actions will be taken
- when a signal is received by the program: it gives a message and
- stops running.
- .LP
- There are programs however, which try to handle certain signals
- themselves.
- In C, this is achieved by the system call \fIsignal(\ sig_no,\ catch\ )\fP,
- which calls the handling routine \fIcatch()\fP, as soon as signal
- \fBsig_no\fP occurs.
- EM does not provide this call; instead, the \fIsigtrp()\fP monitor call
- is available for mapping UNIX signals onto EM traps.
- This implies that a \fIsignal()\fP call in a C-program
- must be translated by the EM library routine to a \fIsigtrp()\fP call in EM.
- .PP
- The interpreter keeps an administration of the mapping of UNIX-signals
- onto EM traps in the array \fIsig_map[NSIG]\fP.
- Initially, the signals all have their default values.
- Now assume a \fIsigtrp()\fP occurs, telling to map signal \fBsig_no\fP onto
- trap \fBtrap_no\fP.
- This results in:
- .IP 1.
- setting the relevant array element
- \fIsig_map[sig_no]\fP to \fBtrap_no\fP (after saving the old value),
- .IP 2.
- catching the next to come \fBsig_no\fP signal with the handling routine
- \fIHndlEMSig\fP (by a plain UNIX \fIsignal()\fP of course), and
- .IP 3.
- returning the saved map-value on the stack so the user can know the previous
- trap value onto which \fBsig_no\fP was mapped.
- .LP
- On an incoming signal,
- the handling routine for signal \fBsig_no\fP arms the
- correct EM trap by calling the routine \fIarm_trap()\fP with argument
- \fIsig_map[sig_no]\fP.
- At the end of the EM instruction the proper call of \fItrap()\fP is done.
- \fITrap()\fP on its turn examines the value of the \fIHaltOnTrap\fP variable;
- if it is set, the interpreter will stop with a message. In the normal case of
- controlled trap handling this bit is not on and the interpreter examines
- the value of the \fITrapPI\fP variable,
- which contains the procedure identifier of the EM trap handling routine.
- It then initiates a call to this routine and performs a \fIlongjmp()\fP
- to the main
- loop to bypass all further processing of the instruction that caused the trap.
- \fITrapPI\fP should be set properly by the library routines, through the
- SIG instruction.
- .LP
- In short:
- .IP 1.
- A UNIX interrupt is caught by the interpreter.
- .IP 2.
- A handling routine is called which generates the corresponding EM trap
- (according to the mapping).
- .IP 3.
- The trap handler calls the corresponding EM routine which emulates a UNIX
- interrupt for the benefit of the interpreted program.
- .PP
- When considering UNIX signals, it is important to notice that some of them
- are real signals, i.e., messages coming from outside the program, like DEL
- and QUIT, but some are actually program-caused synchronous traps, like Illegal
- Instruction. The latter, if they happen, are incurred by the interpreter
- itself and consequently are of no concern to the interpreted program: it
- cannot catch them. The present code assumes that the UNIX signals between
- SIGILL (4) and SIGSYS (12) are really traps; \fIdo_sigtrp()\fP
- will fail on them.
- .LP
- To avoid losing the last line(s) of output files, the interpreter should
- always do a proper close-down, even in the presence of signals. To this end,
- all non-ignored genuine signals are initially caught by the interpreter,
- through the routine \fIHndlIntSig\fP, which gives a message and preforms a
- proper close-down.
- Synchronous trap can only be caused by the interpreter itself; they are never
- caught, and consequently the UNIX default action prevails. Generally they
- cause a core dump.
- Signals requested by the interpreted program are caught by the routine
- \fIHndlEMSig\fP, as explained above.
- .NH 3
- Monitor calls.
- .PP
- For the convenience of the programmer, as many monitor calls as possible
- have been implemented.
- The list of monitor calls given in [1] pages 20/21, has been implemented
- completely, except for \fIptrace()\fP, \fIprofil()\fP and \fImpxcall()\fP.
- The semantics of \fIptrace()\fP and \fIprofil()\fP from an interpreted program
- is unclear; the data structure passed to \fImpxcall()\fP is non-trivial
- and the system call has low portability and applicability.
- For these calls, on invocation a warning is generated, and the arguments which
- were meant for the call are popped properly, so the program can continue
- without the stack being messed up.
- The errorcode 5 (IOERROR) is pushed onto the stack (twice), in order to
- fake an unsuccessful monitor call.
- No other \- more meaningful \- errorcode is available in the errno-list.
- .LP
- Now for the implemented monitor calls.
- The returned value is zero for a successful call.
- When something goes wrong, the value of the external \fIerrno\fP variable
- is pushed, thus enabling the user to find out what the reason of failure was.
- The implementation of the majority of the monitor calls is straightforward.
- Those working with a special format buffer, (e.g. \fIioctl()\fP,
- \fItime()\fP and \fIstat()\fP variants), need some extra attention.
- This is due to the fact that working with varying word/pointer size
- combinations may cause alignment problems.
- .LP
- The data structure returned by the UNIX system call results from
- C code that has been translated with the regular C compiler, which,
- on the VAX, happens to be a 4-4 compiler.
- The data structure expected by the interpreted program conforms
- to the translation by \fBack\fP of the pertinent include file.
- Depending on the exact call of \fBack\fP, sizes and alignment may differ.
- .LP
- An example is in order. The EM MON 18 instruction in the interpreted program
- leads to a UNIX \fIstat()\fP system call by the interpreter.
- This call fills the given struct with stat information, the contents
- and alignments of which are determined by the version of UNIX and the
- used C compiler, resp.
- The interpreter, like any program wishing to do system calls that fill
- structs, has to be translated by a C compiler that uses the
- appropriate struct definition and alignments, so that it can use, e.g.,
- \fIstab.st_mtime\fP and expect to obtain the right field.
- This struct cannot be copied directly to the EM memory to fulfill the
- MON instruction.
- First, the struct may contain extraneous, system-dependent fields,
- pertaining, e.g., to symbolic links, sockets, etc.
- Second, it may contain holes, due to alignment requirements.
- The EM program runs on an EM machine, knows nothing about these
- requirements and expects UNIX Version 7 fields, with offsets as
- determined by the em22, em24 or em44 compiler, resp.
- To do the conversion, the interpreter has a built-in table of the
- offsets of all the fields in the structs that are filled by the MON
- instruction.
- The appropriate fields from the result of the UNIX \fIstat()\fP are copied
- one by one to the appropriate positions in the EM memory to be filled
- by MON 18.
- .PP
- The \fIioctl()\fP call (MON 54) poses additional problems. Not only does it
- have a second argument which is a pointer to a struct, the type of
- which is dynamically determined, but its first argument is an opcode
- that varies considerably between the versions of UNIX.
- To solve the first problem, the interpreter examines the opcode (request) and
- treats the second argument accordingly. The second problem can be solved by
- translating the UNIX Version 7 \fIioctl()\fP request codes to their proper
- values on the various systems. This is, however, not always useful, since
- some EM run-time systems use the local request codes. There is a compile-time
- flag, V7IOCTL, which, if defined, will restrict the \fIioctl()\fP call to the
- version 7 request codes and emulate them on the local system; otherwise the
- request codes of the local system will be used (as far as implemented).
- .PP
- Minor problems also showed up with the implementation of \fIexecve()\fP
- and \fIfork()\fP.
- \fIExecve()\fP expects three pointers on the stack.
- The first points to the name of the program to be executed,
- the second and third are the beginnings of the \fBargv\fP and \fBenvp\fP
- pointer arrays respectively.
- We cannot pass these pointers to the system call however, because
- the EM addresses to which they point do not correspond with UNIX
- addresses.
- Moreover, (it is not very likely to happen but) what if someone constructs
- a program holding the contents for one of these pointers in the stack?
- The stack is implemented upside down, so passing the pointer to
- \fIexecve()\fP causes trouble for this reason too.
- The only solution was to copy the pointer contents completely
- to fresh UNIX memory, constructing vectors which can be passed to the
- system call.
- Any impending memory fault while making these copies results in failure of the
- system call, with \fIerrno\fP set to EFAULT.
- .PP
- The implementation of the \fIfork()\fP call faced us with problems
- concerning IO-channels.
- Checking messages (as well as logging) must be divided over different files.
- Otherwise, these messages will coincide.
- This problem was solved by post-fixing the default message file
- \fBint.mess\fP (as well as the logging file \fBint.log\fP) with an
- automatically leveled number for every new forked process.
- Children of the original process do their diagnostics
- in files with postfix 1,2,3 etc.
- Second generation processes are assigned files numbered 11, 12, 21 etc.
- When 6 generations of processes exist at one moment, the seventh will
- get the same message file as the sixth, for the length of the filename
- will become too long.
- .PP
- Some of the monitor calls receive pointers (addresses) from to program, to be
- passed to the kernel; examples are the struct stat for \fIstat()\fP, the area
- to be filled for \fIread()\fP, etc. If the address is wrong, the kernel does
- not generate a trap, but rather the system call returns with failure, while
- \fIerrno\fP is set to EFAULT. This is implemented by consistent checking of
- all pointers in the MON instruction.
- .NH 2
- Internal arithmetic.
- .PP
- Doing arithmetic on signed integers, the smallest negative integer
- (\fIminsint\fP) is considered a legal value.
- This is in contradiction with the EM Manual [1], page 14, which proposes using
- \fIminsint\fP for uninitialized integers.
- The shadow bytes already check for uninitialized integers however,
- so we do not need this special illegal value.
- Although the EM Manual provides two traps, for undefined integers and floats,
- undefined objects occur so frequently (e.g. in block copying partially
- initialized areas) that the interpreter just gives a warning.
- .LP
- Except for arithmetic on unsigneds, all arithmetic checks for overflow.
- The value that is pushed on the stack after an overflow occurs depends
- on the UNIX behavior with regard to that particular calculation.
- If UNIX would not accept the calculation (e.g. division by zero), a zero
- is pushed as a convention.
- Illegal computations which UNIX does accept in silence (e.g. one's
- complement of \fIminsint\fP), simply push the UNIX-result after giving a
- trap message.
- .NH 2
- Shadow bytes implementation.
- .PP
- A great deal of run-time checking is performed by the interpreter (except if
- used in the fast version).
- This section gives all details about the shadow bytes.
- In order to keep track of information about the contents of D-space (stack
- and global data area), there is one shadow-byte for each byte in these spaces.
- Each bit in a shadow-byte represents some piece
- of information about the contents of its corresponding 'sun-byte'.
- All bits off indicates an undefined sun-byte.
- One or more bits on always guarantees a well-defined sun-byte.
- The bits have the following meaning:
- .IP "\(bu bit 0:" 8
- indicates that the sun-byte is (a part of) an integer.
- .IP "\(bu bit 1:" 8
- the sun-byte is a part of a floating point number.
- .IP "\(bu bit 2:" 8
- the sun-byte is a part of a pointer in dataspace.
- .IP "\(bu bit 3:" 8
- the sun-byte is a part of a pointer in the instruction space.
- According to [1] (paragraph 6.4), there are two types pointers which
- must be distinguishable.
- Conversion between these two types is impossible.
- The shadow-bytes make the distinction here.
- .IP "\(bu bit 4:" 8
- protection bit.
- Indicates that the sun-byte is part of a protected piece of memory.
- There is a protected area in the stack, the Return Status Block.
- The EM machine language has no possibility to declare protected
- memory, as is possible in EM assembly (the ROM instruction). The protection
- bit is, however, set for the line number and filename pointer area near
- location 0, to aid in catching references to location 0.
- .IP "\(bu bit 5/6/7:" 8
- free for later use.
- .LP
- The shadow bytes are managed by the routines declared in \fIshadow.h\fP.
- The warnings originating from checking these shadow-bytes during
- run-time are various.
- A list of them is given in appendix A, together with suggestions
- (primarily for the C-programmer) where to look for the trouble maker(s).
- .LP
- A point to notice is, that once a warning is generated, it may be repeated
- thousands of times.
- Since repetitive warnings carry little information, but consume much
- file space, the interpreter keeps track of the number of times a given warning
- has been produced from a given line in a given file.
- The warning message will
- be printed only if the corresponding counter is a power of four (starting at
- 1). In this way, a logarithmic back-off in warning generation is established.
- .LP
- It might be argued that the counter should be kept for each (warning, PC
- value) pair rather than for each (warning, file position) pair. Suppose,
- however, that two instruction in a given line would cause the same message
- regularly; this would produce two intertwined streams of identical messages,
- with their counters jumping up and down. This does not seem desirable.
- .NH 2
- Return Status Block (RSB)
- .PP
- According to the description in [1], at least the return address and the
- base address of the previous RSB have to be pushed when performing a call.
- Besides these two pointers, other information can be stored in the RSB
- also.
- The interpreter pushes the following items:
- .IP \-
- a pointer to the current filename,
- .IP \-
- the current line number (always four bytes),
- .IP \-
- the Local Base,
- .IP \-
- the return address (Program Counter),
- .IP \-
- the current procedure identifier
- .IP \-
- the RSB code, which distinguishes between initial start-up, normal call,
- returnable trap and non-returnable trap (a word-size integer).
- .LP
- Consequently, the size of the RSB varies, depending on
- word size and pointer size; its value is available as \fIrsbsize\fP.
- When the RSB is removed from the stack (by a RET or RTT) the RSB code is under
- the Stack Pointer for immediate checking. It is not clear what should be done
- if RSB code and return instruction do not match; at present we give a message
- and continue, for what it is worth.
- .PP
- The reason for pushing filename and line number is that some front-ends tend
- to forget the LIN and FIL instructions after returning from a function.
- This may result in error messages in wrong source files and/or line numbers.
- .PP
- The procedure identifier is kept and restored to check that the PC will not
- move out of the running procedure. The PI is an index in the proctab, which
- tells the limits in the text segment of the running procedure.
- .PP
- If the Return Status Block is generated as a result of a trap, more is
- stacked. Before stacking the normal RSB, the trap function pushes the
- following items:
- .IP \-
- the contents of the entire Function Return Area,
- .IP \-
- the number of bytes significant in the above (a word-size integer),
- .IP \-
- a word-size flag indicating if the contents of the FRA are valid,
- .IP \-
- the trap number (a word-size integer).
- .LP
- The latter is followed directly by the RSB, and consequently acts as the only
- parameter to the trap handler.
- .NH 2
- Operand access.
- .PP
- The EM Manual mentions two ways to access the operands of an instruction. It
- should be noticed that the operand in EM is often not the direct operand of the
- operation; the operand of the ADI instruction, e.g., is the width of the
- integers to be added, not one of the integers themselves. The various operand
- types are described in [1]. Each opcode in the text segment identifies an
- instruction with a particular operand type; these relations are described in
- computer-readable format in a file in the EM tree, \fIip_spec.t\fP.
- .PP
- The interpreter uses the third method. Several other approaches
- can be designed, with increasing efficiency and equally increasing complexity.
- They are briefly treated below.
- .NH 3
- The Dispatch Table, Method 1.
- .PP
- When the interpreter starts, it reads the ip_spec.t file and constructs from it
- a dispatch table. This table (of which there are actually three,
- for primary, secondary
- and tertiary opcodes) has 256 entries, each describing an instruction with
- indications on how to decode the operand. For each instruction executed, the
- interpreter finds the entry in the dispatch table, finds information there on
- how to access the operand, constructs the operand and calls the appropriate
- routine with the operand as calculated. There is one routine for each
- instruction, which is called with the ready-made operand. Method 1 is easy to
- program but requires constant interpretation of the dispatch table.
- .NH 3
- Intelligent Routines, Method 2.
- .PP
- For each opcode there is a separate routine, and since an opcode uniquely
- defines the instruction and the operand format, the routine knows how to get
- the operand; this knowledge is built into the routine. Preferably the heading
- of the routine is generated automatically from the ip_spec.t file. Operand
- decoding is immediate, and no dispatch table is needed. Generation of the
- 469 required routines is, however, far from simple. Either a generated array
- of routine names or a generated switch statement is used to map the opcode onto
- the correct routine. The switch approach has the advantage that parameters can
- be passed to the routines.
- .NH 3
- Intelligent Calls, Method 3.
- .PP
- The call in the switch statement does full operand construction, and the
- resulting operand is passed to the routine. This reduces the number of
- routines to 133, the number of EM instructions. Generation of the switch
- statement from ip_spec.t is more complicated, but the routine space is
- much cleaner. This does not give any speed-up since the same actions are still
- required; they are just performed in a different place.
- .NH 3
- Static Evaluation.
- .PP
- It can be observed that the evaluation of the operand of a given instruction in
- the text segment will always give the same result. It is therefore possible to
- preprocess the text segment, decomposing the instructions into structs which
- contain the address, the instruction code and the operand. No operand decoding
- will be necessary at run-time: all operands have been precalculated. This will
- probably give a considerable speed-up. Jumps, especially GTO jumps, will,
- however, require more attention.
- .NH 2
- Disassembly.
- .PP
- A disassembly facility is available, which gives a readable but not
- letter-perfect disassembly of the EM object. The procedure structure is
- indicated by placing the indication \fBP[n]\fP at the entry point of each
- procedure, where \fBn\fP is the procedure identifier. The number of locals is
- given in a comment.
- .LP
- The disassembler was generated by the software in the directory \fIswitch\fP
- and then further processed by hand.
|