[Template fetch failed for http://spivey.oriel.ox.ac.uk/corner/Template:Sitenotice?action=render: HTTP 404]

Keiko assembly language

From Compilers
Jump to navigation Jump to search

Syntax

This section gives the syntax of Keiko assembly language programs in the form that is accepted by the bytecode assembler/linker oblink. The style of syntax description is similar to that used in the Kernighan & Ritchie book on C: a syntactic category is followed by a sequence of alternatives, each on a separate line. A subscript opt indicates that a construct is optional.

Lexical conventions

  • Each element on its own line (nl used below to denote a line boundary)
  • Blank lines and lines beginning # are ignored
  • Identifiers can be any sequence of non-blank characters, including e.g. Files.Read. It's wise to avoid indentifoers that begin with a digit or minus sign, as in some contexts these may be interpreted as numeric constants.

The operands of instructions are described as instances of the class constant. In most contexts, constants may be specified in decimal or hexadecimal (as in 0x1234abcd), or may be symbols defined elsewhere in the program.

constant:
    decimal-constant
    hexadecimal-constant
    ident

Files

A Keiko file contains a heading that gives the name of the module and lists (in IMPORT directives) other modules that it depends upon. A compiler that outputs Keiko code can generate a checksum for the public interface of a module and embed this checksum in each other module that uses it, and the assembler/linker will then check across all modules in a program that the checksums are consistent. Unused checksums can be replaced by 0. The module header also contains a count of source lines in the module that is used to allocate counters for line-count profiling; this too can be replaced by 0 of profiling is not going to be used on the program.

file:
    heading bodyopt
heading:
    module-directive importsopt endhdr-directive
module-directive:
    MODULE ident checksum linecnt nl
imports:
    import-directive
    import-directive imports
import-directive:
    IMPORT ident checksum nl
endhdr-directive:
    ENDHDR nl

The body of a module constists of multi-line procedures interspersed with other single-line directives that (among other things) allocate global storage.

body:
    phrase
    phrase body
phrase:
    directive
    procedure

Directives

Directives appear between the procedures of a program.

directive:
    DEFINE ident nl
    WORD constant nl
    LONG constant nl
    FLOAT float nl
    DOUBLE float nl
    STRING hex-string nl
    GLOVAR ident integer nl
    PRIMDEF ident ident type-string nl
  • a DEFINE directive defines a symbol at the current location in the data segment. That location is the address of any following data item created with another directive such as WORD, FLOAT or STRING.
  • the WORD, LONG, FLOAT and DOUBLE directives each contribute a numeric constant to the data segment, allowing global data tables to be initialised; the table can be accessed through a label defined by a preceding DEFINE directive.
  • a STRING directive contributes a sequence of characters, specified by a hexadecimal string, to the data segment. If a terminating null character is needed, then this should be included in the hex string. The length of the string is padded to a multiple of 4 bytes. When convenient, it is possible to build up a string in several parts by giving multiple successive STRING directives, provided the length of all but the last directive is a multiple of 4 to prevent padding.
  • a GLOVAR directive allocates space of a specified size in the bss segment, and defines a symbol with its address. The size is rounded up to a multiple of 4, so that the current location in the bss segment is always aligned.
  • a PRIMDEF directive declares a named primitive whose definition is a C subroutine. A directive such as PRIMDEF Math.sqrt sqrtf FF declares a primitive that will be named Math.sqrt in the Keiko program, and interfaces to the standard C library function sqrtf, which the type string FF describes as taking a single float argument and yielding a float result. Some implementations of Keiko are able to link dynamically to libaries containing C functions, and others require an interpreter containing the primitives to be compiled specially.

Procedures

Each procedure has a heading that gives its name and some other information. This is followed by a sequence of mingled Keiko machine instructions and pseudo-operations. The pseudo-operations typically assemble into an entry in the procedure's constant pool, together with an instruction that loads the constant onto the stack.

procedure:
    proc-directive bodyopt end-directive
proc-directive:
    PROC ident integer integer constant nl
body:
    element
    element body
end-directive:
    END nl
element:
    pseudo-operation
    instruction
  • A PROC directive begins a procedure. The three arguments are:
    • The size of the procedure's local variable space in bytes; this should be a multiple of 4.
    • The maximum number of values pushed on the evaluation stack during the procedure, counting most types as one value, but long integers and doubles as two values. This argument is not currently used by implementations of the Keiko machine, and can be repaced by zero; the only possible disadvatage in future Keiko implementations is that stack overflow not be detected promptly. At present, the stack overflow check leaves a generous margin of space for each procedure to use.
    • A garbage collector map for the stack frame. If the Kieko machine is built without the optional garbage collector, or if the stack frame of the procedure contains no pointers into the heap, then this argument can be zero. If garbage collection is enabled, then every procedure that stores pointers in its frame must have a garbage collector map, which will be either a bitmap expressed as a hexdecimal constant, or the address of a program written in a special mini-language that describes the layout of the frame. This mini-language is described elsewhere.
  • An END directive ends a procedure and can be followed by further material that appears between procedures.

Instructions

instruction:
    opcode operandsopt nl
operands:
    constant
    constant operands

Each instruction has an optional list of operands, which (depending on the instruction) can be integer constants, assembler symbols, and labels. Details of the instructions and what operands they take appear later in this document.

Pseudo-operations

These pseudo-operations should appear inside a Keiko procedure; most behave like intructions but also contribute additional information to the current procedure.

pseudo-operation:
    LABEL ident nl
    CONST constant nl
    GLOBAL ident nl
    FCONST float nl
    DCONST float nl
    QCONST constant nl
    STKMAP constant nl
    LINE integer nl
  • The LABEL pseudo-op defines its argument to as a label for the next instruction in the procedure. Labels can be arbitrary identifiers and have a scope that is the whole of the current procedure. They are used only in branch instructions, and do not have a value that can be stored in a variables.
  • The next few pseudo-ops act as instructions that push a value on the stack, but are capable of handling 32-bit or 64-bit values that are stored out of line in the constant pool for the procedure. CONST pushes an arbitrary integer or address; GLOBAL is similar, but retricted to the addresses of globals; FCONST, DCONST and QCONST push float, double and long integer constants respectively, with the double and long integer constants taking up two stack slots. These pseudo-ops are typically translated by the Keiko assembler into LDKW or LDKD instructions that reference a slot it has allocated in the constant pool. As a special case CONST pseudo-ops that contain a small constant are translated into PUSH instructions that use an inline constant, either encoded directly in the opcode byte, or following it as the next one or two bytes of the instruction stream. All this is hidden from programmers and compilers by the Keiko assembler.
  • The STKMAP pseudo-op specifes a pointer map for the evaluation stack that holds at an immediately following CALL instruction. Any pointer values on the evaluation stack that are used as arguments to the procedure call will be covered by the procedure's own stack map, so this pseudo-op is needed only in the rare case where other values near the bottom of the evaluation stack will persist over the call. These stack maps are gathered for the whole procedure and used by the assembler to compile a stack map table that – alongside the code and the constant pool – forms part of the runtime representation of the procedure. If the Keiko machine is built without a garbage collector, then naturally enough these stack maps can be omitted.
  • The LINE pseudo-op marks a source line, with an argument that is the line number. It adds the line number to a table that the assembler includes with the object program, and also generates an LNUM instruction in the code. The LNUM instructions are used both by the Keiko profiler, which can count how many times each line is executed, and by debuggers, which can replace them with BREAK insructions to implement breakpoints.

Load and store instructions

LOCAL n
OFFSET
INDEXS
INDEXW
INDEXD
LOADW
STOREW
LOADS
LOADC
LOADF
STORES
STOREC
STOREF
LDLW
LDLS
LDLC
LDLF
STLW
SLTS
STLC
STLF 
LDGW
LDGS
LDGC
LDGF
STGW
STGS
STGC
STGF
LDNW
STNW
LDIW
LDIS
LDIC
LDIF
STIW
STIS
STIC
STIF

Integer arithmetic

Conditional branches

Floating point arithmetic

Floating point conditions

inst LOADD 0 M.dp { getdbl($1) } { deref(MEMQ, FLO, 2, 0); } inst STORED 0 S2dp { putdbl($2, $1.d); } { store(MEMQ, 2, 0); } inst LDKD {1 2} V.d { getdbl(&const($a)) } { konst(KONQ, FLO, arg1, 2); }

inst LOADQ 0 M.qp { getlong($1) } { loadq(); } inst STOREQ 0 S2qp { putlong($2, $1.q); } { store(MEMQ, 2, 0); } inst LDKQ {1 2} V.q { getlong(&const($a)) } { konst(KONQ, INT, arg1, 2); }

equiv LDID 0 { INDEXD, LOADD } equiv STID 0 { INDEXD, STORED } equiv LDLD 1 { LOCAL $a, LOADD } equiv STLD 1 { LOCAL $a, STORED } equiv LDGD K { LDKW $a, LOADD } equiv STGD K { LDKW $a, STORED }

equiv LDIQ 0 { INDEXD, LOADQ } equiv STIQ 0 { INDEXD, STOREQ } equiv LDLQ 1 { LOCAL $a, LOADQ } equiv STLQ 1 { LOCAL $a, STOREQ } equiv LDGQ K { LDKW $a, LOADQ } equiv STGQ K { LDKW $a, STOREQ }

  1. ASSORTED INSTRUCTIONS

inst INCL 1 S0 { indir(local($a), int)++; } {= LDLW $a, INC, STLW $a } inst DECL 1 S0 { indir(local($a), int)--; } {= LDLW $a, DEC, STLW $a } equiv INCL 2 { LDLW $a, INC, STLW $a } equiv DECL 2 { LDLW $a, DEC, STLW $a }

inst DUP {[0,2,1]} S0 { dup($a, $s); } inst SWAP 0 S0 { swap($s); } inst POP 1 S0 { sp += $a; } { pop(arg1); }


  1. INTEGER OPERATORS

inst PLUS 0 B.i { $1.i + $2.i } { ibinop(ADD); } inst MINUS 0 B.i { $1.i - $2.i } { ibinop(SUB); } inst TIMES 0 B.i { $1.i * $2.i } { multiply(); } inst UMINUS 0 M.i { - $1.i } { imonop(NEG); }

inst AND 0 B.i { $1.i && $2.i } { ibinop(AND); } inst OR 0 B.i { $1.i || $2.i } { ibinop(OR); } inst NOT 0 M.i { ! $1.i } { push_con(1); ibinop(XOR); } inst INC 0 M.i { $1.i + 1 } {= PUSH 1, PLUS } inst DEC 0 M.i { $1.i - 1 } {= PUSH 1, MINUS } inst BITAND 0 B.i { $1.i & $2.i } { ibinop(AND); } inst BITOR 0 B.i { $1.i | $2.i } { ibinop(OR); } inst BITXOR 0 B.i { $1.i ^ $2.i } { ibinop(XOR); } inst BITNOT 0 M.i { ~ $1.i } { imonop(NOT); }

inst LSL 0 B.i { $1.i << $2.i } { lsl(); } inst LSR 0 B.i { rshu($1.i, $2.i) } { ibinop(RSHu); } inst ASR 0 B.i { $1.i >> $2.i } { ibinop(RSH); } inst ROR 0 B.i { ror($1.i, $2.i) } { ibinop(ROR); }

inst DIV 0 T2 { int_div(sp); } { callout(int_div,2,INT,1); } inst MOD 0 T2 { int_mod(sp); } { callout(int_mod,2,INT,1); }

inst EQ 0 B.i { $1.i == $2.i } { compare(EQ); } inst LT 0 B.i { $1.i < $2.i } { compare(LT); } inst GT 0 B.i { $1.i > $2.i } { compare(GT); } inst LEQ 0 B.i { $1.i <= $2.i } { compare(LE); } inst GEQ 0 B.i { $1.i >= $2.i } { compare(GE); } inst NEQ 0 B.i { $1.i != $2.i } { compare(NE); }

inst JEQ {S R} S2 { condj($1.i==$2.i, $a); } { condj(&j_eq, arg1); } inst JLT {S R} S2 { condj($1.i<$2.i, $a); } { condj(&j_lt, arg1); } inst JGT {S R} S2 { condj($1.i>$2.i, $a); } { condj(&j_gt, arg1); } inst JLEQ {S R} S2 { condj($1.i<=$2.i, $a); } { condj(&j_le, arg1); } inst JGEQ {S R} S2 { condj($1.i>=$2.i, $a); } { condj(&j_ge, arg1); } inst JNEQ {S R} S2 { condj($1.i!=$2.i, $a); } { condj(&j_ne, arg1); }

inst JNEQZ {S R} S1 { condj($1.i != 0, $a); } {= PUSH 0, JNEQ $a } inst JEQZ {S R} S1 { condj($1.i == 0, $a); } {= PUSH 0, JEQ $a } inst JLTZ S S1 { condj($1.i < 0, $a); } {= PUSH 0, JLT $a } equiv JLTZ R { PUSH 0, JLT $a } inst JGTZ S S1 { condj($1.i > 0, $a); } {= PUSH 0, JGT $a } equiv JGTZ R { PUSH 0, JGT $a } inst JLEQZ S S1 { condj($1.i <= 0, $a); } {= PUSH 0, JLEQ $a } equiv JLEQZ R { PUSH 0, JLEQ $a } inst JGEQZ S S1 { condj($1.i >= 0, $a); } {= PUSH 0, JGEQ $a } equiv JGEQZ R { PUSH 0, JGEQ $a }

inst JUMP {S R} S0 { jump($a); } { jump(arg1); }


  1. LONGINT OPERATORS

inst QPLUS 0 B.q { $1.q + $2.q } { qbinop(ADDq); } inst QMINUS 0 B.q { $1.q - $2.q } { qbinop(SUBq); } inst QTIMES 0 B.q { $1.q * $2.q } { qbinop(MULq); } inst QUMINUS 0 M.q { - $1.q } { qmonop(NEGq); } inst QDIV 0 T2q { long_div(sp); } { callout(long_div,2,INT,2); } inst QMOD 0 T2q { long_mod(sp); } { callout(long_mod,2,INT,2); }

equiv QINC 0 { PUSH 1, CONVNQ, QPLUS } equiv QDEC 0 { PUSH 1, CONVNQ, QMINUS }

inst QCMP 0 B.iqq { lcmp($1.q, $2.q) }

equiv QEQ 0 { QCMP, PUSH 0, EQ } equiv QLT 0 { QCMP, PUSH 0, LT } equiv QGT 0 { QCMP, PUSH 0, GT } equiv QLEQ 0 { QCMP, PUSH 0, LEQ } equiv QGEQ 0 { QCMP, PUSH 0, GEQ } equiv QNEQ 0 { QCMP, PUSH 0, NEQ }

equiv QJEQ R { QCMP, JEQZ $a } equiv QJLT R { QCMP, JLTZ $a } equiv QJGT R { QCMP, JGTZ $a } equiv QJLEQ R { QCMP, JLEQZ $a } equiv QJGEQ R { QCMP, JGEQZ $a } equiv QJNEQ R { QCMP, JNEQZ $a }


  1. CASE STATEMENTS

inst JCASE 1 S1 {

    if ((unsigned) $1.i < (unsigned) $a)
         pc0 = pc + 2*$1.i, jump(get2(pc0)); else pc += 2*$a;

} zinst CASEL R

inst JRANGE {S R} S3 {

    if ($1.i >= $2.i && $1.i <= $3.i) jump($a);

}

  1. The "T2" means take two arguments, but leave one of them on the stack

inst TESTGEQ {S R} T2 {

    if ($1.i >= $2.i) jump($a);

}


  1. FLOATING-POINT OPERATORS

inst FPLUS 0 B.f { $1.f + $2.f } { fbinop(ADDf); } inst FMINUS 0 B.f { $1.f - $2.f } { fbinop(SUBf); } inst FTIMES 0 B.f { $1.f * $2.f } { fbinop(MULf); } inst FDIV 0 B.f { $1.f / $2.f } { fbinop(DIVf); } inst FUMINUS 0 M.f { - $1.f } { fmonop(NEGf); }

inst FCMPL 0 B.i { fcmpl($1.f, $2.f) } { fcompare(FCMPL); } inst FCMPG 0 B.i { fcmpg($1.f, $2.f) } { fcompare(FCMPG); }

equiv FEQ 0 { FCMPL, PUSH 0, EQ } equiv FNEQ 0 { FCMPL, PUSH 0, NEQ } equiv FLT 0 { FCMPG, PUSH 0, LT } equiv FGT 0 { FCMPL, PUSH 0, GT } equiv FLEQ 0 { FCMPG, PUSH 0, LEQ } equiv FGEQ 0 { FCMPL, PUSH 0, GEQ }

  1. The floating-point conditional jumps are just shorthand for a
  2. comparison and an integer jump. This saves valuable opcodes for more
  3. important functions.

equiv FJEQ R { FCMPL, JEQZ $a } equiv FJNEQ R { FCMPL, JNEQZ $a } equiv FJLT R { FCMPG, JLTZ $a } equiv FJGT R { FCMPL, JGTZ $a } equiv FJLEQ R { FCMPG, JLEQZ $a } equiv FJGEQ R { FCMPL, JGEQZ $a } equiv FJNLT R { FCMPG, JGEQZ $a } equiv FJNGT R { FCMPL, JLEQZ $a } equiv FJNLEQ R { FCMPG, JGTZ $a } equiv FJNGEQ R { FCMPL, JLTZ $a }


  1. DOUBLE-PRECISION OPERATORS

inst DPLUS 0 B.d { $1.d + $2.d } { dbinop(ADDd); } inst DMINUS 0 B.d { $1.d - $2.d } { dbinop(SUBd); } inst DTIMES 0 B.d { $1.d * $2.d } { dbinop(MULd); } inst DDIV 0 B.d { $1.d / $2.d } { dbinop(DIVd); } inst DUMINUS 0 M.d { - $1.d } { dmonop(NEGd); } inst DCMPL 0 B.idd { fcmpl($1.d, $2.d) } { fcompare(DCMPL); } inst DCMPG 0 B.idd { fcmpg($1.d, $2.d) } { fcompare(DCMPG); }

equiv DEQ 0 { DCMPL, PUSH 0, EQ } equiv DNEQ 0 { DCMPL, PUSH 0, NEQ } equiv DLT 0 { DCMPG, PUSH 0, LT } equiv DGT 0 { DCMPL, PUSH 0, GT } equiv DLEQ 0 { DCMPG, PUSH 0, LEQ } equiv DGEQ 0 { DCMPL, PUSH 0, GEQ }

equiv DJEQ R { DCMPL, JEQZ $a } equiv DJNEQ R { DCMPL, JNEQZ $a } equiv DJLT R { DCMPG, JLTZ $a } equiv DJGT R { DCMPL, JGTZ $a } equiv DJLEQ R { DCMPG, JLEQZ $a } equiv DJGEQ R { DCMPL, JGEQZ $a } equiv DJNLT R { DCMPG, JGEQZ $a } equiv DJNGT R { DCMPL, JLEQZ $a } equiv DJNLEQ R { DCMPG, JGTZ $a } equiv DJNGEQ R { DCMPL, JLTZ $a }


  1. CONVERSIONS

inst CONVNF 0 M.f { flo_conv($1.i) } { fdmonop(CONVif,INT,FLO,1); } inst CONVND 0 M.di { flo_conv($1.i) } { fdmonop(CONVid,INT,FLO,2); } inst CONVFN 0 M.i { (int) $1.f } { fdmonop(CONVfi,FLO,INT,1); } inst CONVDN 0 M.id { (int) $1.d } { fdmonop(CONVdi,FLO,INT,1); } inst CONVFD 0 M.df { $1.f } { fdmonop(CONVfd,FLO,FLO,2); } inst CONVDF 0 M.fd { (float) $1.d } { fdmonop(CONVdf,FLO,FLO,1); } inst CONVNC 0 M.i { $1.i & 0xff } { push_con(0xff); ibinop(AND); } inst CONVNS 0 M.i { (short) $1.i } { gmonop(CONVis,INT,INT,1); } inst CONVNQ 0 M.qi { $1.i } inst CONVQN 0 M.iq { (int) $1.q } inst CONVQD 0 M.dq { flo_convq($1.q) } { callout(long_flo,1,FLO,2); }


  1. RUN-TIME CHECKS
  1. The operand of these checks is the line number to show in the error
  2. message. God forbid people should make source files containing
  3. more than 65536 lines

inst BOUND 2 T2 \

   { if ((unsigned) $1.i >= (unsigned) $2.i) error(E_BOUND, $a); }

inst NCHECK 2 T1 \

   { if (pointer($1) == NULL) error(E_NULL, $a); }

inst GCHECK 2 S1 \

   { if (valptr($1) != NULL) error(E_GLOB, $a); }

inst ZCHECK 2 T1 \

   { if ($1.i == 0) error(E_DIV, $a); }

inst FZCHECK 2 T1 \

   { if ($1.f == 0.0) error(E_FDIV, $a); }

inst DZCHECK 2 T1d \

   { if ($1.d == 0.0) error(E_FDIV, $a); }

inst QZCHECK 2 T1q \

   { if ($1.q == 0) error(E_DIV, $a); }

inst ERROR 12 S0 \

   { error($a, $b); }


  1. MORE BITS AND PIECES
  1. ALIGNs instructions are used on big-endian machines like the SPARC
  2. to ensure that CHAR and SHORT parameters appear at the right address.

inst ALIGNC 0 M.i { alignx($1.i, 8) } inst ALIGNS 0 M.i { alignx($1.i, 16) }

  1. FIXCOPY copies a fixed number of bytes; it is used for structure
  2. assignment and also for value parameters of (fixed) array or record type. */

inst FIXCOPY 0 S3 {

    prof_charge($3.i/4);
    memcpy(pointer($1), pointer($2), $3.i);

}

  1. FLEXCOPY expects to find on the stack the address of a flex array parameter
  2. and a size in bytes; it copies the parameter to dynamic local space,
  3. then overwrites the parameter with the new address.

inst FLEXCOPY 0 S0 {

    value *d = pointer(sp[1]); int size = sp[0].i;           
    int sizew = (size+3)/4; prof_charge(sizew);                        
    sp -= sizew - 2;                                                   
    if ((uchar *) sp < stack + SLIMIT) error(E_STACK, 0);              
    memcpy(sp, pointer(d[0]), size);                                   
    d[0].a = stkaddr(sp);

}

  1. In the interpreter, the CALLW and CALLD instructions are implemented as
  2. two operations, an ordinary CALL followed by a SLIDE; the return address
  3. of the called routine points to the SLIDE instruction, which is
  4. responsible for copying the result.

equiv CALL 1 { JPROC, SLIDE $a } equiv CALLW 1 { JPROC, SLIDEW $a } equiv CALLQ 1 { JPROC, SLIDEQ $a } equiv CALLF 1 { JPROC, SLIDEF $a } equiv CALLD 1 { JPROC, SLIDED $a }

  1. STATLINK saves a static link in a 'secret place' just before a proedure
  2. call. The secret place is chosen to be the location where the called
  3. procedure will store its static link, which can be computed as a negative
  4. offset from the stack pointer. The fixed offset means that STATLINK
  5. will work properly only as part of the calling sequence
  6. <static link> / STATLINK / <load proc addr> / [STKMAP] / CALL
  7. and not (e.g.) if the static link is saved *before* the parameters
  8. are put on the stack, or if <load proc addr> requires a stack depth
  9. of more than 3.
  10. NOW LATER
  11. param 2 param 2
  12. param 1 param 1
  13. sp: stat link proc addr |
  14. ret addr | HEAD
  15. bp: dyn link V
  16. stat link

inst STATLINK 0 S0 \

   { sp[1-HEAD+SL].a = sp[0].a; sp++; } { statlink(); }
  1. SAVELINK moves the static link from the 'secret place' to its proper
  2. location in the frame of the called procedure. Because the secret
  3. place is actually the proper location already, all that is needed is
  4. to protect the static link when the frame is cleared (see the call
  5. to memset in interp.c).

inst SAVELINK 0 S0 { } { }

inst JPROC 0 S0 {

    value *p = valptr(sp[0]);
    sp -= HEAD-1;
    sp[BP].a = stkaddr(bp);
    sp[PC].a = codeaddr(pc);
    if (interpreted(p)) {
         cp = p; pc = codeptr(cp[CP_CODE].a);
         goto enter;
    }
  1. ifdef PROFILE
    /* Calling a native-code routine */
    prof_enter(dsegaddr(p), ticks, PROF_PRIM);
    ticks = 0;
  1. endif
  2. ifdef OBXDEB
    prim_bp = sp;
  1. endif
    rp = primcall(p, sp);
  1. ifdef OBXDEB
    prim_bp = NULL;
  1. endif

}

inst SLIDE 1 S0 { slide($a); } \

   { proc_call(pc, arg1, 0, 0, 0); }

inst SLIDEW 1 S0 { slide($a); sp--; sp[0].i = (*rp).i; } \

   { proc_call(pc, arg1, INT, MEMW, 1); }

inst SLIDEF 1 S0 { slide($a); sp--; sp[0].f = (*rp).f; } \

   { proc_call(pc, arg1, FLO, MEMW, 1); }

inst SLIDED 1 S0 { slide($a); sp -= 2; putdbl(&sp[0], getdbl(rp)); } \

   { proc_call(pc, arg1, FLO, MEMQ, 2); }

inst SLIDEQ 1 S0 { slide($a); sp -= 2; putlong(&sp[0], getlong(rp)); } \

   { proc_call(pc, arg1, INT, MEMQ, 2); }
   

inst RETURN 0 S0 {

    if (bp == base) {
         level--;
  1. ifdef PROFILE
         prof_exit(0, ticks);
  1. endif
         return sp;
    }
    rp = sp; sp = bp; pc = codeptr(sp[PC].a);
    bp = valptr(sp[BP]); cp = valptr(bp[CP]);
    do_find_proc;
  1. ifdef PROFILE
    prof_exit(dsegaddr(cp), ticks);
    ticks = 0;
  1. endif

}

inst LNUM 2 S0 {

  1. ifdef PROFILE
    if (lflag) {
         static module m = NULL; /* Cache most recent module */
         ticks--;
         if (m == NULL || dsegaddr(cp) < m->m_addr 
             || dsegaddr(cp) >= m->m_addr + m->m_length) {
              m = find_module(dsegaddr(cp));
         }
         m->m_lcount[$a-1]++; 
     }
  1. endif
  2. ifdef OBXDEB
     if (intflag)
          breakpoint(cp, bp, pc0, "interrupt");
     else if (one_shot) 
          breakpoint(cp, bp, pc0, "line");
  1. endif

}

inst BREAK 2 S0 {

  1. ifdef OBXDEB
    breakpoint(cp, bp, pc0, "break");
  1. endif

}


  1. DIRECTIVES

dir CONST ? dir FCONST ? dir DCONST ? dir QCONST ? dir GLOBAL ? dir LABEL ? dir PROC ???? dir END 0 dir PRIMDEF ??? dir DEFINE ? dir STRING ? dir GLOVAR ?? dir WORD ? dir LONG ? dir FLOAT ? dir DOUBLE ? dir MODULE ??? dir ENDHDR 0 dir IMPORT ?? dir STKMAP ? dir LINE ?