!   Intel 80386 IMP77 compiler second pass

! Copyright 2002 NB Information Limited.  From an original
! version probably Copyright The University of Edinburgh and
! various contributions Copyright many other individuals, but
! most particularly Copyright 1977-1980 Peter Robertson

! Version 2.00 - February 2021
!   * Enabled machine code to be embedded (NO floating point implemented)
!
! Version 1.03 - October 2003
!	* Properly cleaned up GP TAG list at end of a block
!	* Tidied up some constant tables with names (a hangover from the SKIMP version)
!	* Corrected ISWORK to only be true for full-size string work blocks

include "crc32.imp"

begin

    ! debugging assistance
    routinespec checksum(string(255) which)
    integer outstream = 0
    routine errout
      outstream = output stream
      select output(0)
    end
    routine undo
      select output(outstream)
    end

    !SIZE CONSTANTS
    constinteger  max vars  = 1024
    constinteger  max stack = 16
    constinteger  max labs  = 50
    constinteger  max level = 16
    constinteger  Max GP    = 120

    ! SOME WEE ENVIRONMENTAL THINGS
    conststring(12) program ip = "Main Program";  ! Main program internal name
    conststring(8)  program ep = "__impmain";     ! Main program external name
    conststring(5)  system prefix = "_imp_";      ! prefixed to %system routine idents

    ! I/O file handles
    constinteger icode   = 1
    constinteger source  = 2
    constinteger report  = 0
    constinteger objout  = 1
    constinteger listout = 2

    !DIAGNOSE BITS
    constinteger  passId = 2;                  ! JDM Identify which IMP pass this is
    constinteger  mcode level D = (1<<13);     ! JDM peak level D debug diagnostics of Machine Code
    constinteger  mcode level C = (1<<12);     ! JDM next level C debug diagnostics of Machine Code
    constinteger  mcode level B = (1<<11);     ! JDM next level B debug diagnostics of Machine Code
    constinteger  mcode level A = (1<<10);     ! JDM base level A debug diagnostics of Machine Code

    !CONTROL BITS
    constinteger  check capacity = 1
    constinteger  check unass    = 2
    constinteger  check array    = 4
    constinteger  check bits     = check array;   ! The only one that does anything so far

    !REGISTERS - basic register number = actual value + 1
    constinteger  AX = 1
    constinteger  CX = 2
    constinteger  DX = 3
    constinteger  BX = 4
    constinteger  SP = 5
    constinteger  BP = 6
    constinteger  SI = 7
    constinteger  DI = 8

    ! Floating point coprocessor stack registers
    constinteger  FR0	= 9
    !%constinteger  FR1	= 10
    !%constinteger  FR2	= 11
    !%constinteger  FR3	= 12
    !%constinteger  FR4	= 13
    !%constinteger  FR5	= 14
    !%constinteger  FR6	= 15
    constinteger  FR7	= 16

    ! 8 bit registers - actual value + 17
    constinteger	AL	= 17
    constinteger	CL	= 18
    constinteger	DL	= 19
    constinteger	BL	= 20
    constinteger	AH	= 21
    constinteger	CH	= 22
    constinteger	DH	= 23
    constinteger	BH	= 24

    ! Pseudo Registers
    constinteger  any   = 25  ;  ! Truly any register
    constinteger  anyg  = 26  ;  ! A "General Purpose" byte accessible register (AX, BX, CX, DX)
    constinteger  anyp  = 27  ;  ! A pointing register (BX, SI, DI)
    constinteger  anyf  = 28  ;  ! Generally means the top of the 8087 stack

    !DATA FORMS

    ! EXTERNAL FORM
    constinteger  simple         = 1
    constinteger  name           = 2
    constinteger  label          = 3
    constinteger  recordformat   = 4
    constinteger  switch         = 6
    constinteger  array          = 11
    constinteger  arrayname      = 12
    constinteger  namearray      = 13
    constinteger  namearrayname  = 14

    ! INTERNAL
    constinteger  constant    = 0
    constinteger  v in r      = 1
    constinteger  av in r     = 2
    constinteger  a in r      = 3
    constinteger  v in s      = 4
    constinteger  av in s     = 5
    constinteger  a in s      = 6
    constinteger  v in rec    = 7
    constinteger  av in rec   = 8
    constinteger  a in rec    = 9
    constinteger  pgm label   = 10

    !DATA TYPES
    constinteger  general  = 0
    constinteger  integer  = 1
    constinteger  real     = 2
    constinteger  string   = 3
    constinteger  record   = 4
    ! Private internal derived types
    constinteger  byte     = 5
    constinteger  lreal    = 6

    ! SIZE OF EACH OF THOSE TYPES IN BYTES
    constbyteintegerarray  vsize(general:lreal) = 0,4,4,0,0,1,8

    ! Define type codes known externally (to pass 3 and user):
    constbyteintegerarray  gen map(general:lreal) =
      0,      1,     2,      3,     4,       6,     8

    ! GENERIC STORE ALIGNMENT - ASSUME 80386
    constinteger  align    = 3
    constinteger  wordsize = 4;   ! in bytes

    !OWN INFO
    constinteger  own      = 1
    constinteger  con      = 2
    constinteger  external = 3
    constinteger  system   = 4
    constinteger  dynamic  = 5
    constinteger  primrt   = 6
    constinteger  permrt   = 7

{Procedure end codes}

	constinteger	Map     = -2,
					Fn      = -1,               {negative implies stacked result}
					Routine =  0,
					True    =  1,
					False   =  2

    ! PERM ROUTINE INDEXES
    constinteger  iexp   =  1;   ! Integer Exponent
    constinteger  fexp   =  2;   ! floating exponent
    constinteger  smove  =  3;   ! string copy (length checked)
    constinteger  sjam   =  4;   ! string copy (whatever fits)
    constinteger  sconc  =  5;   ! string concatenate (length checked)
    constinteger  sjconc =  6;	  ! concatenate whatever fits
    constinteger  sresln =  7;   ! string resolution
    constinteger  scomp  =  8;   ! string compare
    constinteger  aref   =  9;   ! array access
    constinteger  adef   = 10;   ! array definition
    constinteger  signal = 11;   ! %signal
    constinteger  stop   = 12;   ! %stop
    constinteger  lastperm = stop

    ! and the corresponding linkage names for the perms
    const string(12)array permname(1:lastperm)=
    "_IMPIEXP",
    "_IMPFEXP",
    "_IMPSTRCPY",
    "_IMPSTRJAM",
    "_IMPSTRCAT",
    "_IMPSTRJCAT",
    "_IMPSTRRES",
    "_IMPSTRCMP",
    "_IMPAREF",
    "_IMPADEF",
    "_IMPSIGNAL",
    "_IMPSTOP"

    ! Compiler Internal Operations (not to be confused with OpCodes)
    constinteger  ADDx    = 1
    constinteger  SUBx    = 2
    constinteger  MULx    = 3
    constinteger  DIVx    = 4
    constinteger  CONCx   = 5
    constinteger  ANDx    = 6
    constinteger  ORx     = 7
    constinteger  XORx    = 8
    constinteger  LSHx    = 9
    constinteger  RSHx    = 10
    constinteger  REMx    = 11
    constinteger  EXPx    = 12
    constinteger  REXPx   = 13
    constinteger  RDIVx   = 14
    constinteger  NOTx    = 15
    constinteger  NEGx    = 16
    constinteger  ABSx    = 17
    constinteger  unaries = 15

    ! opcode indexes...

    ! simple (no operand) ones first
    constinteger NOP	= 0
    constinteger CWD   = 1
    constinteger RET   = 2
    constinteger SAHF  = 3
    constinteger LEAVE = 4

    ! simple unary math functions
    constinteger DEC = 5
    constinteger INC = 6
    constinteger NEG = 7
    constinteger NOT = 8

    ! simple unary moves
    constinteger POP  = 9
    constinteger PUSH = 10

    ! two operand moves
    constinteger LEA  = 11
    constinteger MOV  = 12
    constinteger XCHG = 13

    ! simple two operand math functions
    constinteger ADC = 14
    constinteger ADD = 15
    constinteger AND = 16
    constinteger CMP = 17
    constinteger OR  = 18
    constinteger SUB = 19
    constinteger XOR = 20
    ! slightly more complicated two operand math
    constinteger SHL  = 21
    constinteger SHR  = 22
    constinteger IDIV = 23
    constinteger IMUL = 24

    ! calls and jumps
    constinteger CALL = 25
    constinteger JE   = 26
    constinteger JNE  = 27
    constinteger JG   = 28
    constinteger JGE  = 29
    constinteger JL   = 30
    constinteger JLE  = 31
    constinteger JA   = 32
    constinteger JAE  = 33
    constinteger JB   = 34
    constinteger JBE  = 35
    constinteger JMP  = 36

    ! Floating point instructions - note that these map directly onto
    ! 8087 sequences, unlike the generic MOV, ADD style of the base
    ! operations for the 8086
    constinteger	FILD	= 37
    constinteger	FLDD	= 38
    constinteger	FLDQ	= 39
    constinteger	FSTI	= 40
    constinteger	FSTD	= 41
    constinteger	FSTQ	= 42
    constinteger	FADD	= 43
    constinteger	FSUB	= 44
    constinteger	FSUBR	= 45
    constinteger	FMUL	= 46
    constinteger	FDIV	= 47
    constinteger	FDIVR	= 48
    constinteger	FCMP	= 49
    constinteger	FCHS	= 50
    constinteger	FABS	= 51
    ! Special floating point things
    constinteger	FSTSW	= 52
    constinteger	FLDZ	= 53
    constinteger	FLDPI	= 54

    ! modifiers to memory base for accessing global memory
    constinteger	DATA	= 16_10
    constinteger	COT		= 16_20
    constinteger	BSS		= 16_30
    constinteger	DISPLAY	= 16_40
    constinteger	EXT		= 16_50
    constinteger	SWT		= 16_60
    constinteger	CODE	= 16_70

	{Condition codes}

	{ The "Never" test should never! be used. The others are all used } 
	constinteger	EQ = 1,   LT = 2,   GT = 4,   TT = 8,  Always = 7,
					NE = 6,   LE = 3,   GE = 5,   FF = 9,  Never  = 0

	constbytearray Reverse(Never:FF) =	Never  {Never},
										EQ     {EQ},
										GT     {LT},
										GE     {LE},
										LT     {GT},
										LE     {GE},
										NE     {NE},
										Always {Always},
										TT     {TT},
										FF     {FF}
	constbytearray Negated(Never:FF) =	Always {Never},
										NE     {EQ},
										GE     {LT},
										GT     {LE},
										LE     {GT},
										LT     {GE},
										EQ     {NE},
										Never  {Always},
										FF     {TT},
										TT     {FF}
	constbytearray TestToOp(Never:FF) =	JMP {Never - This is added for completeness},
											JE  {EQ},
											JL  {LT},
											JLE {LE},
											JG  {GT},
											JGE {GE},
											JNE {NE},
											JMP {Always},
											JNE {TT},
											JE  {FF}
	constbytearray TestToUnsignedOp(Never:FF) =	JMP {Never - This is added for completeness},
													JE  {EQ},
													JB  {LT},
													JBE {LE},
													JA  {GT},
													JAE {GE},
													JNE {NE},
													JMP {Always},
													JNE {TT},
													JE  {FF}

    ! Standard IMPish data structures

    ! Variables are declared here
    ! JDM JDM added idname to remember the IMP variable names
    recordformat  varfm( string(255) idname, byteinteger type, form, level, scope, dim, integer  disp, format, size, pbase, extra, extdisp )
    record(varfm)array  var(0:max vars)
    record(varfm)name   decvar
    record(varfm)     begin

    ! The compiler is stack based
    ! JDM JDM added idname to remember the IMP variable name
    recordformat  stackfm(string(255) idname, byteinteger type, form, aform, base, scope, dim, integer  disp, format, size, pbase, extra, extdisp, var no)
    record(stackfm)array  stack(1:max stack)
    record(stackfm)   null
    record(stackfm)name top

    ! Pass 1 uses a lame label redefinition that forces us to map
    ! label ID's into unique labels for pass 3, using this database
    recordformat LabelFm(integer id, tag)
    record(LabelFm)array Labels(1:Max Labs)
    integer J Tag;		 ! most recent Jump tag translation - needed when planting event blocks

    ! Status of registers
    ownintegerarray  activity(0:fr7) = 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    owninteger  claimed = 0

    ! Pointer registers may be pointing to non-local display - we remember
    ! them for future use
    ownintegerarray displayhint(AX:DI) = 0, 0, 0, 0, 0, 0, 0, 0

    ! Math Co-processor uses a stack - we remember where it should be
    ! with this pointer
    owninteger fpustack = 0

    ! A general purpose workspace resource
    recordformat gp tag(integer info, addr, flags, link)
    record(gptag)array gptags(0:Max GP)
    integer gp asl;

    owninteger control = check bits;    ! Current compiler flags (set by %control statement)
    owninteger diagnose = 0;            ! Current diagnostic flags (set by %diagnose statement)
	owninteger Language Flags = 0;		 ! Special directive flags for languages (other than standard imp)

    owninteger nextcad = 0;             ! notional code address (not real - pass3 shuffles stuff)
    owninteger level = 0;               ! current contextual level
    integer    sym, Pending;            ! CODE SYMBOL, NEXT SYMBOL
    integer    vlb,vub;                 ! VECTOR LOWER/UPPER BOUND
    owninteger current line = 0;        ! SOURCE LINE NUMBER
    owninteger stp = 0;                 ! STACK POINTER
    integer    data size;               ! CURRENT DATA ITEM SIZE
    owninteger frame = 0;               ! LOCAL STACK FRAME EXTENT
    integer    parms;                   ! START OF PARAMETER STACK
    owninteger invert = 0;              ! CONDITION INVERSION FLAG
    owninteger compare unsign = 0;      ! CONDITION WAS NON-STANDARD (GENERALLY FPU COMPARE)
    owninteger uncond jump = 0;         ! ADDRESS OF CODE HOLE
    owninteger block type = 1;          ! -1 = RECORDS, 1 = PROCEDURE, 2 = SPEC
    owninteger in params = 0;           ! NON-ZERO INSIDE PARAMETER LISTS
    integer    otype, owntype, ownform;   ! Information about OWNs currently being declared
    integer    spec, potype;            ! More about current declaration
    integer    i, j;                    ! used in the initialisation loops only
    owninteger Fp Result Loc = -1;      ! Place to store Real and LReal function results

    constinteger max switch = 1000;     ! Size in WORDS of switch segment table
    integerarray swtab(0:max switch)
    owninteger swtp = 0;                ! pointer to next switch segment entry

    ownstring(255)  external id = "",
	                 alias = "",
					 block name = ""

    byteintegerarray  current string(0:255);    ! current string literal

    integer  xlen
    byteintegerarray x sym buff(0:255);      ! current external string name

    ! WORK List - used to optimise use of temporary storage
    ! There is a head of list for each contextual level
    ownintegerarray worklist(1:max level) = 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

    longreal rvalue;		 ! floating point value for constants and initialisers

    owninteger ownval = 0;  ! value to use when initialising OWNs

    !-----------------------------------------------------------
    ! Start with machine independent utility functions and stack
    ! manipulation and debug
    !-----------------------------------------------------------

    !                                                             >> SHOW <<
    routine  show(record(stackfm)name  v)
        ! JDM The field widths have been tweaked to align columns
        write(v_varno,4)
        print string(" : Typ="); write(v_type,1)
        print string(" Frm="); write(v_form,1)
        print string(" Bse="); write(v_base,3);
        print string(" Dsp="); write(v_disp,5)
        print string(" ExtDsp="); write(v_extdisp,4)
        print string(" Siz="); write(v_size,3)
        print string(" Xtr="); write(v_extra,3)
        print string(" Fmt="); write(v_format,2)
        print string(" Dim="); write(v_dim,1)
        print string(" Pba="); write(v_pbase,4)
        if (length(v_idname) # 0)start
            print string(" Name='".v_idname."'") { JDM show the variable name also }
        finish
        newline
    end

    ! Simple ABORT routine
    routine abort(string(255) message)
        integer j

        select output(report)
        printstring("Pass 2 abandoned at line ");
        write(current line, 1);
        printstring(" : ");
        printstring(message)
        newline
        if stp # 0 start
            print string("STACK:");  newline
             spaces(11) and show(stack(j)) for j = 1,1,stp
        finish
        stop
    end

    !                                                             >> WARN <<
    routine  warn(integer  n)
        switch  w(1:8)
        select output(report)
        print string("*WARNING: line")
        write(current line, 1);  print string(": ")
        -> w(n)
w(1):   print string("division by zero");                     -> at
w(2):   print string("Illegal FOR");                          -> at
w(3):   print string("Non-local control variable?");          -> at
w(4):   print string("Invalid parameter for READ SYMBOL");    -> at
w(5):   print string("String constant too long");             -> at
w(6):   print string("No. of shifts outwith 0..31");          -> at
w(7):   print string("Illegal constant exponent");            -> at
w(8):   print string("Numerical constant too big");           -> at
at:     newline
        select output(objout)
    end

    !                                                          >> MONITOR <<
    routine  monitor(record(stackfm)name  v, string(15) text)
        select output(report)
        print string(text);  print symbol(':')
        spaces(10-length(text))
        show(v)
        select output(objout)
    end

    !                                                             >> GET GP TAG <<
    integerfn get gp tag
        integer l

        if gp asl = 0 then abort("GP Tags")
        l = gp asl
        gp asl = gp tags(l)_link
        result = l
    end

    !                                                             >> RET GP TAG <<
    integerfn ret gp tag(integer index)
        integer link

        link = gp tags(index)_link
        gp tags(index)_link = gp asl
        gp asl = index
        result = link
    end

    !------------------------------------------------------
    ! Machine dependent utility routines
    !------------------------------------------------------

    ! Routines to write the intermediate file
    ! Record format is:
    ! <type><length><data>
    ! For debug purposes, the elements are all written as ascii
    ! characters, where <type> is a single letter, <length> is a single
    ! hex digit, length refers to the number of bytes (2 chars) of data.

    ! Intermediate file types:
    constinteger IF OBJ		=  0;  ! A - plain object code
    constinteger IF DATA	    =  1;  ! B - dataseg offset code word
    constinteger IF CONST	    =  2;  ! C - const seg offset code word
    constinteger IF DISPLAY	=  3;  ! D - display seg offset code word
    constinteger IF JUMP		=  4;  ! E - unconditional jump to label
    constinteger IF JCOND	    =  5;  ! F - cond jump to label JE, JNE, JLE, JL, JGE, JG
    constinteger IF CALL		=  6;  ! G - call a label
    constinteger IF LABEL	    =  7;  ! H - define a label
    constinteger IF FIXUP	    =  8;  ! I - define location for stack fixup instruction
    constinteger IF SETFIX	    =  9;  ! J - stack fixup <location> <amount>
    constinteger IF REQEXT	    = 10;  ! K - external name spec
    constinteger IF REFLABEL   = 11;  ! L - relative address of label (JDM JDM added new IBJ command)
    constinteger IF REFEXT	    = 12;  ! M - external name relative offset code word (call external)
    constinteger IF BSS		= 13;  ! N - BSS segment offset code word
    constinteger IF COTWORD	= 14;  ! O - Constant table word
    constinteger IF DATWORD	= 15;  ! P - Data segment word
    constinteger IF SWTWORD	= 16;  ! Q - switch table entry - actually a label ID
    constinteger IF SOURCE	    = 17;  ! R - name of the source file
    constinteger IF DEFEXTCODE = 18;  ! S - define a code label that is external
    constinteger IF DEFEXTDATA = 19;  ! T - define a data label that is external
    constinteger IF SWT        = 20;  ! U - switch table offset code word
    constinteger IF LINE       = 21;  ! V - line number info for debugger
    constinteger IF ABSEXT     = 22;  ! W - external name absolute offset code word (data external)

    routine  writenibble(integer n)
        n = n & 16_f;
        if 0 <= n and n <= 9 start
            printsymbol(n + '0');
        else
            printsymbol(n + ('A' - 10));
        finish
    end

    ! print a number in hexadecimal, to "places" size
    routine write hex(integer n, places)
        integer p, shift

        shift = (places - 1) * 4
        while shift > 0 cycle
            p = n >> shift
            writenibble(p)
            shift = shift - 4
        repeat
        writenibble(n)
    end

    routine  writeifrecord(integer type, integer length, byteintegerarrayname buffer)
        integer c1, c2, i;

        select output(objout)
        printsymbol('A'+type);
        if (length > 255) then abort("Intermediate file record too long");
        writenibble(length>>4);
        writenibble(length&15);
	
        i = 0;
        while (length > 0) cycle
            c1 = buffer(i) >> 4;
            c2 = buffer(i) & 15;
            writenibble(c1);
            writenibble(c2);
            i = i + 1;
            length = length - 1;
        repeat
        newline
    end

    ! Simple buffered output of code bytes...
    own integer objectptr = 0;
	const integer objbufmax = 20
    own byte integer array objectbytes(0:objbufmax);
    ! And corresponding bytes for the listing (not always the same for fudged opcodes)
    own integer listptr = 0;
	const integer lstbufmax = 11
    own byte integer array listbytes(0:lstbufmax);

	! routine to clean to object buffer
	routine ClearObjectBuffer
		integer i
		for i = 0,1,objbufmax cycle
			objectbytes(i) = 0
		repeat
		objectptr = 0;
	end

    ! Routine to provide the address and hex opcode listing in the
    ! diagnostic output
    routine  listpreamble
        integer i;

        select output(listout)
        space; writehex(nextcad, 4); space;
        for i = 0, 1, 7 cycle
            if i < listptr start
                writehex(listbytes(i), 2)
                space
            else
                spaces(3)
            finish
        repeat
        spaces(8)
        nextcad = nextcad + listptr;
        listptr = 0;
    end

    ! flush the code buffer
    routine flushcode
        if objectptr # 0 start
            writeifrecord(IF OBJ, objectptr, objectbytes);
            ClearObjectBuffer;	! clear the output pipe
        finish
    end

    ! puts a normal code byte into the listing and code pipes
    routine  putcodebyte(integer b)
        objectbytes(objectptr) = b;
        objectptr = objectptr + 1;
    end

    ! puts a normal code byte into the listing and code pipes
    routine  putlistbyte(integer b)
        listbytes(listptr) = b;
        listptr = listptr + 1;
    end

    ! puts a normal code byte into the listing and code pipes
    routine  putbyte(integer b)
		putlistbyte( b );
		putcodebyte( b );
    end

    ! A very handy little boolean function, used for instructions
    ! with variable size immediate operands
    integerfn issmall(integer i)
        result = 1 if i < 128 and i > -128
        result = 0;
    end

    ! And aide-memoire of intel 80386 address modes...
    !-------------------------
    !     [EAX]
    !     [ECX]
    !     [EDX]
    !     [EBX]
    !     [][]
    !     [disp32]
    !     [ESI]
    !     [EDI]
    !-------------------------
    !     [EAX+disp8]
    !     [ECX+disp8]
    !     [EDX+disp8]
    !     [EBX+disp8]
    !     [][]
    !     [EBP+disp8]
    !     [ESI+disp8]
    !     [EDI+disp8]
    !-------------------------
    !     [EAX+disp32]
    !     [ECX+disp32]
    !     [EDX+disp32]
    !     [EBX+disp32]
    !     [][]
    !     [EBP+disp32]
    !     [ESI+disp32]
    !     [EDI+disp32]
    !-------------------------

    ! mod r/m format is:
    ! mod LHREG R/M
    ! where mod = 11 for rh registers

    ! plant a modrm reference where the rh operand is a register
    ! Both parameters are actual register numbers, not internal ID's
    routine  modrmreg(integer reg1, integer reg2)
        putbyte(16_C0 ! (reg1 << 3) ! (reg2));
    end

    ! tags corresponding to linker directives...
    const integer array reltag(0:6) =
    0,			{ no relocation }
	IF DATA,    { dataseg offset code word }
	IF CONST,   { const seg offset code word }
	IF BSS,		{ BSS relative code word }
	IF DISPLAY,	{ display seg offset code word }
	IF ABSEXT, 	{ external name absolute offset code word }
	IF SWT		{ switch table offset code word }

    ! plant code for a relocated (DATA/BSS/DISPLAY/EXTERNAL) code word
    routine norelocateoffset( integer offset )
        integer i;

		for i=1,1,wordsize cycle
			putbyte(offset & 255)
			offset = offset >> 8
		repeat

    end

    ! plant code for a relocated (DATA/BSS/DISPLAY/EXTERNAL) code word
    routine relocateoffset(integer reloc, integer offset, integer extdisp)
        integer tag, i;

		if reloc = 0 start
			norelocateoffset( offset )
		else
			flushcode;	! so that only the offset is going into the queue

			tag = reltag(reloc);
			
			if tag = IF ABSEXT start
				putbyte(offset & 255); offset = offset >> 8;
				putbyte(offset & 255); offset = offset >> 8;
				putbyte(extdisp & 255); extdisp = extdisp >> 8;
				putbyte(extdisp & 255); extdisp = extdisp >> 8;

				writeifrecord(tag, wordsize, objectbytes);
				ClearObjectBuffer;	! clear the queue
			else
				for i=1,1,wordsize cycle
					putbyte(offset & 255); offset = offset >> 8
				repeat
				writeifrecord(tag, wordsize, objectbytes);
				ClearObjectBuffer;	! clear the queue
			finish

		finish

    end

    ! plant a modrm reference where the rh operand is in memory
    ! Parameter REG1 is an actual register number, but BASE is an internal ID
    routine  modrmmem(integer reg1, integer base, integer disp, integer extdisp)
        integer mod, reloc;

        reloc = base>>4;
        base = base & 15;

        if base = 0 start; 	  ! no register, just a displacement
            ! mod = 000, rm = 101
            putbyte((reg1 << 3) ! 5);
            relocateoffset(reloc, disp, extdisp );
        else
            if disp = 0 and base # BP start
                mod = 0
            else
                if issmall(disp) # 0 start;		  ! fits in one byte
                    mod = 1
                else
                    mod = 2
                finish
            finish

            ! unfortunately displacement (even zero) must be output in full if
            ! the offset is relocatable
            if reloc # 0 then mod = 2;

            if base > DI or base = SP start
                abort("Internal address mode error");
            finish

            ! Note - base-1 maps internal ID to real register

            putbyte((mod << 6)!(reg1 << 3)!(base - 1));

            if mod = 1 start
                putbyte(disp);
            else
                if mod = 2 then relocateoffset(reloc, disp, extdisp )
            finish
        finish
    end

    const string(3) array regname(AX:DI) =
	"EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI"

    const string(2) array reg8name(AL:BH) =
	"AL", "CL", "DL", "BL", "AH", "CH", "DH", "BH"

    const string(7) array relocname(0:6) =
	"", "DATA", "COT", "BSS", "DISPLAY", "EXTERN", "SWTAB"

    ! Print the corresponding memory access string
    ! BASE is an internal ID, not an actual register number
    routine  printmemref(integer base, integer disp)
        integer reloc;

        reloc = base >> 4;
        base = base & 15;
        selectoutput(listout)
        printsymbol('[');
        if base # 0 start
            printstring(regname(base));
            if reloc # 0 start
                printsymbol('+'); printstring(relocname(reloc));
            finish
            if disp # 0 then start
                printsymbol('+') if disp > 0
                write(disp,1)
            finish
        else
            if reloc # 0 start
                printstring(relocname(reloc)); printsymbol('+')
            finish
            writehex(disp, 4)
        finish
        printsymbol(']');
    end

    ! opcodes
    const string(5) array opname(NOP:JMP) =
	"NOP", "CWD", "RET", "SAHF", "LEAVE",
	"DEC", "INC", "NEG", "NOT",
	"POP", "PUSH",
	"LEA", "MOV", "XCHG",
	"ADC", "ADD", "AND", "CMP", "OR", "SUB", "XOR",
	"SHL", "SHR", "IDIV", "IMUL",
	"CALL", "JE", "JNE",
    "JG", "JGE", "JL", "JLE",
    "JA", "JAE", "JB", "JBE", "JMP"

    const byte integer array opvalue(NOP:JMP) =
	16_90, 16_99, 16_C3, 16_9E, 16_C9,
	16_FF, 16_FF, 16_F7, 16_F7,
	16_8F, 16_FF,
	16_8B, 16_89, 16_87,		{ LEA is fudged as if it were m <- r, to allow the flip }
	16_11, 16_01, 16_21, 16_39, 16_09, 16_29, 16_31,
	16_D1, 16_D1, 16_F7, 16_F7,
	16_E8, 16_74, 16_75,
    16_7F, 16_7D, 16_7C, 16_7E,
    16_77, 16_73, 16_72, 16_76, 16_EB

    ! 8 bit equivalent opcodes
    const byte integer array op8value(NOP:JMP) =
	16_90, 16_99, 16_C3, 16_9E, 16_C9,      { not 8 bit, included for completeness }
	16_FE, 16_FE, 16_F6, 16_F6,
	16_8F, 16_FF,                           { not 8 bit, included for completeness }
	16_8B, 16_88, 16_86,                    { LEA is not applicable for 8 bit }
	16_10, 16_00, 16_20, 16_38, 16_08, 16_28, 16_30,
	16_D0, 16_D0, 16_F6, 16_F6,
	16_E8, 16_74, 16_75,
    16_7F, 16_7D, 16_7C, 16_7E,
    16_77, 16_73, 16_72, 16_76, 16_EB       { not 8 bit, included for completeness }

    ! An opcode with no operands (eg RET)
    routine dumpsimple(integer opn)

        putbyte(opvalue(opn));

        listpreamble;
        printstring(opname(opn));
        newline
        flushcode;
    end

    ! A special bit of magic, used in record assignment
    routine dumprepmovsb
        putbyte(16_f3);	! rep
        putbyte(16_a4);	! movsb

        listpreamble;
        printstring("REP MOVSB");
        newline
        flushcode;
    end

    ! Used in record = 0 assignment
    routine dumprepstosb
        putbyte(16_f3);	! rep
        putbyte(16_aa);	! stosb

        listpreamble;
        printstring("REP STOSB");
        newline
        flushcode;
    end

    ! unary register operation - DEC, INC, NEG, NOT, POP, PUSH, IDIV, IMUL
    ! REG is an internal ID, not an actual register number
    routine dumpur(integer opn, integer reg)
        switch ops(DEC:IMUL)  ; ! 5:24

        displayhint(reg) = 0;
        ->ops(opn)

ops(DEC):   putbyte(16_48 + reg - AX);              ->break;
ops(INC):   putbyte(16_40 + reg - AX);             	->break;
ops(NEG):   putbyte(16_F7); modrmreg(3, reg - AX);  ->break;
ops(NOT):   putbyte(16_F7); modrmreg(2, reg - AX);  ->break;
ops(POP):   putbyte(16_58 + reg - AX);              ->break;
ops(PUSH):  putbyte(16_50 + reg - AX);              ->break;
ops(IDIV):  putbyte(16_F7); modrmreg(7, reg - AX);  ->break;
ops(IMUL):  putbyte(16_F7); modrmreg(5, reg - AX);  ->break;

break:
        listpreamble;
        printstring(opname(opn))
        space
        printstring(regname(reg));
        newline
        flushcode
    end

    ! Plant code for a unary operation on memory
    ! BASE is an internal ID, not the actual register number
    routine dumpum(integer opn, integer base, integer disp, integer extdisp)
        switch ops(DEC:JMP)

        ->ops(opn)

ops(DEC):   putbyte(16_FF); modrmmem(1, base, disp, extdisp);  ->break;
ops(INC):   putbyte(16_FF); modrmmem(0, base, disp, extdisp);  ->break;
ops(NEG):   putbyte(16_F7); modrmmem(3, base, disp, extdisp);  ->break;
ops(NOT):   putbyte(16_F7); modrmmem(2, base, disp, extdisp);  ->break;
ops(POP):   putbyte(16_8F); modrmmem(0, base, disp, extdisp);  ->break;
ops(PUSH):  putbyte(16_FF); modrmmem(6, base, disp, extdisp);  ->break;
ops(IDIV):  putbyte(16_F7); modrmmem(7, base, disp, extdisp);  ->break;
ops(IMUL):  putbyte(16_F7); modrmmem(5, base, disp, extdisp);  ->break;
ops(JMP):   putbyte(16_FF); modrmmem(4, base, disp, extdisp);  ->break;
ops(CALL):  putbyte(16_FF); modrmmem(2, base, disp, extdisp);  ->break;

break:
        listpreamble;
        printstring(opname(opn))
        printstring(" WORD ");	! otherwise it's ambiguous for the reader
        printmemref(base, disp);
        newline
        flushcode;
    end

    ! Plant code for a unary operation on an 8 bit memory location
    ! Not all of the possible unary ops make sense as 8 bit destinations
    ! BASE is an internal ID, not the actual register number
    routine dumpum8(integer opn, integer base, integer disp, integer extdisp)
        integer base op, index

        if opn = DEC or opn = INC start
            base op = 16_FE
            if opn = DEC then index = 1 else index = 0
        else
            if opn = NOT or opn = NEG start
                base op = 16_F6
                if opn = NOT then index = 2 else index = 3
            else
                Abort("Invalid UM8")
            finish
        finish

        putbyte(base op)
        modrmmem(index, base, disp, extdisp )

        listpreamble;
        printstring(opname(opn))
        printstring(" BYTE ");	! otherwise it's ambiguous for the reader
        printmemref(base, disp);
        newline
        flushcode;
    end

    ! Plant a Memory <- Reg operation
    ! Both BASE and REG are internal ID's, not actual register numbers
    routine dumpmr(integer opn, integer base, integer disp, integer extdisp, integer reg)

        if opn = SHL start;	  ! special "shift by CL"
            putbyte(16_D3);
            modrmmem(4, base, disp, extdisp );
        else
            if opn = SHR start
                putbyte(16_D3);
                modrmmem(5, base, disp, extdisp );
            else;                ! normal stuff
                putbyte(opvalue(opn));
                modrmmem(reg - AX, base, disp, extdisp );
            finish
        finish

        listpreamble;
        printstring(opname(opn))
        space
        printmemref(base, disp);
        printsymbol(',')
        printstring(regname(reg));
        newline
        flushcode;
    end

    ! Plant an 8 bit Memory <- Reg operation
    ! Both BASE and REG are internal ID's, not actual register numbers
    routine dumpmr8(integer opn, integer base, integer disp, integer extdisp, integer reg)

        if opn = SHL start;	  ! special "shift by CL"
            putbyte(16_D2);
            modrmmem(4, base, disp, extdisp );
        else
            if opn = SHR start
                putbyte(16_D2);
                modrmmem(5, base, disp, extdisp );
            else;                ! normal stuff
                putbyte(op8value(opn));
                modrmmem(reg - AL, base, disp, extdisp );
            finish
        finish

        listpreamble;
        printstring(opname(opn))
        space
        printmemref(base, disp);
        printsymbol(',')
        printstring(reg8name(reg));
        newline
        flushcode;
    end

    ! Plant a 16 bit Reg <- Memory operation
    ! Both BASE and REG are internal ID's, not actual register numbers
    routine dumprm(integer opn, integer reg, integer base, integer disp, integer extdisp)

        ! We optimise the fairly common instruction MOV AX,[disp] with
        ! the special short-form quirk of the 8086...

        if reg = AX and opn = MOV and base & 15 = 0 start
            putbyte(16_A1)
            relocateoffset(base>>4, disp, extdisp)
        else
            displayhint(reg) = 0;

            putbyte(opvalue(opn)+2);
            modrmmem(reg - AX, base, disp, extdisp);
        finish

        listpreamble;
        printstring(opname(opn))
        space
        printstring(regname(reg));
        printsymbol(',')
        printmemref(base, disp);
        newline
        flushcode;
    end

    ! Plant an 8 bit Reg <- Memory operation
    ! Both BASE and REG are internal ID's, not actual register numbers
    routine dumprm8(integer opn, integer reg, integer base, integer disp, integer extdisp )

        putbyte(op8value(opn)+2);
        modrmmem(reg - AL, base, disp, extdisp );

        listpreamble;
        printstring(opname(opn))
        space
        printstring(reg8name(reg));
        printsymbol(',')
        printmemref(base, disp);
        newline
        flushcode;
    end

    ! Plant a word Reg <- Reg operation
    ! Both register parameters are internal ID's
    routine dumprr(integer opn, integer reg1, integer reg2)

        displayhint(reg1) = 0;

        if opn = SHL start;	  ! special "shift by CL"
            putbyte(16_D3);
            modrmreg(4, reg1 - AX);
        else
            if opn = SHR start
                putbyte(16_D3);
                modrmreg(5, reg1 - AX);
            else;                ! normal stuff
                putbyte(opvalue(opn));
                modrmreg(reg2 - AX, reg1 - AX);
            finish
        finish

        listpreamble;
        printstring(opname(opn))
        space
        printstring(regname(reg1));
        printsymbol(',')
        printstring(regname(reg2));
        newline
        flushcode;
    end

    routine dumprr8(integer opn, integer reg1, integer reg2)

        if opn = SHL start;	  ! special "shift by CL"
            putbyte(16_D2);
            modrmreg(4, reg1 - AL);
        else
            if opn = SHR start
                putbyte(16_D2);
                modrmreg(5, reg1 - AL);
            else
                putbyte(op8value(opn));
                modrmreg(reg2 - AL, reg1 - AL);
            finish
        finish

        listpreamble;
        printstring(opname(opn))
        space
        printstring(reg8name(reg1));
        printsymbol(',')
        printstring(reg8name(reg2));
        newline
        flushcode;
    end

    const byte integer array aximmediatevalue(NOP:XOR) =
        0, 0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0,
	0, 16_B8, 0,
	16_15, 16_05, 16_25, 16_3D, 16_0D, 16_2D, 16_35

    ! Register immediate operations - can be MOV, Math, or Shift
    ! The immediate operand may be a relocated offset as part of
    ! an address calculation
    routine dumprioffset(integer opn, integer reg, integer reloc, integer immed, integer extdisp)
        integer subop;
        switch ops(MOV:SHR)

        displayhint(reg) = 0;
        reloc = reloc >> 4;			! because we pass around the or-able version

        if reg = AX and opn <= XOR start
            putbyte(aximmediatevalue(opn));
            relocateoffset(reloc, immed, extdisp);
            ->break
        else
            ->ops(opn)
        finish

ops(MOV):
        putbyte(16_B8 + reg - AX); relocateoffset(reloc, immed, extdisp );    ->break;
ops(ADD):
        subop = 0
        if issmall(immed) # 0 and reloc = 0 start
            putbyte(16_83);
            modrmreg(subop, reg - AX);
            putbyte(immed & 255);
        else
            putbyte(16_81);
            modrmreg(subop, reg - AX);
            relocateoffset(reloc, immed, extdisp );
        finish;                                                    ->break;
ops(ADC):
        subop = 2
        if issmall(immed) # 0 and reloc = 0 start
            putbyte(16_83);
            modrmreg(subop, reg - AX);
            putbyte(immed & 255);
        else
            putbyte(16_81);
            modrmreg(subop, reg - AX);
            relocateoffset(reloc, immed, extdisp );
        finish;                                                    ->break;
ops(CMP):
        subop = 7
        if issmall(immed) # 0 and reloc = 0 start
            putbyte(16_83);
            modrmreg(subop, reg - AX);
            putbyte(immed & 255);
        else
            putbyte(16_81);
            modrmreg(subop, reg - AX);
            relocateoffset(reloc, immed, extdisp );
        finish;                                                    ->break;
ops(SUB):
        subop = 5
        if issmall(immed) # 0 and reloc = 0 start
            putbyte(16_83);
            modrmreg(subop, reg - AX);
            putbyte(immed & 255);
        else
            putbyte(16_81);
            modrmreg(subop, reg - AX);
            relocateoffset(reloc, immed, extdisp );
        finish;                                                    ->break;
ops(AND):
        subop = 4
        putbyte(16_81);
        modrmreg(subop, reg - AX);
        relocateoffset(reloc, immed, extdisp );                     ->break;
ops(OR):
        subop = 1
        putbyte(16_81);
        modrmreg(subop, reg - AX);
        relocateoffset(reloc, immed, extdisp );                     ->break;
ops(XOR):
        subop = 6
        putbyte(16_81);
        modrmreg(subop, reg - AX);
        relocateoffset(reloc, immed, extdisp );                     ->break;
ops(SHL):
        subop = 4
        if immed = 1 start;     ! special shift-by-one instruction
            putbyte(16_D1)
            modrmreg(subop, reg - AX)
        else
            putbyte(16_C1)
            modrmreg(subop, reg - AX)
            putbyte(immed)
        finish;                                                    ->break;
ops(SHR):
        subop = 5
        if immed = 1 start;     ! special shift-by-one instruction
            putbyte(16_D1)
            modrmreg(subop, reg - AX)
        else
            putbyte(16_C1)
            modrmreg(subop, reg - AX)
            putbyte(immed)
        finish;                                                    ->break;

break:
        listpreamble;
        printstring(opname(opn))
        space
        printstring(regname(reg));
        printsymbol(',')
        if reloc # 0 start
            printstring(relocname(reloc)); printsymbol('+')
        finish
        write(immed, 1)
        newline
        flushcode;
    end

    ! Register immediate operations - can be MOV, Math, or Shift
    routine dumpri(integer opn, integer reg, integer immed)
        integer subop;
        switch ops(MOV:SHR)

        displayhint(reg) = 0;

        if reg = AX and opn <= XOR start
            putbyte(aximmediatevalue(opn));
            norelocateoffset( immed );
            ->break
        else
            ->ops(opn)
        finish

ops(MOV):
        putbyte(16_B8 + reg - AX); norelocateoffset( immed );    ->break;
ops(ADD):
        subop = 0
        if issmall(immed) # 0 start
            putbyte(16_83);
            modrmreg(subop, reg - AX);
            putbyte(immed & 255);
        else
            putbyte(16_81);
            modrmreg(subop, reg - AX);
            norelocateoffset( immed );
        finish;                                                    ->break;
ops(ADC):
        subop = 2
        if issmall(immed) # 0 start
            putbyte(16_83);
            modrmreg(subop, reg - AX);
            putbyte(immed & 255);
        else
            putbyte(16_81);
            modrmreg(subop, reg - AX);
            norelocateoffset( immed );
        finish;                                                    ->break;
ops(CMP):
        subop = 7
        if issmall(immed) # 0 start
            putbyte(16_83);
            modrmreg(subop, reg - AX);
            putbyte(immed & 255);
        else
            putbyte(16_81);
            modrmreg(subop, reg - AX);
            norelocateoffset( immed );
        finish;                                                    ->break;
ops(SUB):
        subop = 5
        if issmall(immed) # 0 start
            putbyte(16_83);
            modrmreg(subop, reg - AX);
            putbyte(immed & 255);
        else
            putbyte(16_81);
            modrmreg(subop, reg - AX);
            norelocateoffset( immed );
        finish;                                                    ->break;
ops(AND):
        subop = 4
        putbyte(16_81);
        modrmreg(subop, reg - AX);
        norelocateoffset( immed );                               ->break;
ops(OR):
        subop = 1
        putbyte(16_81);
        modrmreg(subop, reg - AX);
        norelocateoffset( immed );                               ->break;
ops(XOR):
        subop = 6
        putbyte(16_81);
        modrmreg(subop, reg - AX);
        norelocateoffset( immed );                               ->break;
ops(SHL):
        subop = 4
        if immed = 1 start;     ! special shift-by-one instruction
            putbyte(16_D1)
            modrmreg(subop, reg - AX)
        else
            putbyte(16_C1)
            modrmreg(subop, reg - AX)
            putbyte(immed)
        finish;                                                    ->break;
ops(SHR):
        subop = 5
        if immed = 1 start;     ! special shift-by-one instruction
            putbyte(16_D1)
            modrmreg(subop, reg - AX)
        else
            putbyte(16_C1)
            modrmreg(subop, reg - AX)
            putbyte(immed)
        finish;                                                    ->break;

break:
        listpreamble;
        printstring(opname(opn))
        space
        printstring(regname(reg));
        printsymbol(',')
        write(immed, 1)
        newline
        flushcode;
    end

    ! Memory (word) immediate operations - can be MOV, Math, or Shift
    routine dumpmi(integer opn, integer base, disp, extdisp, integer immed)
        integer subop;
        switch ops(MOV:SHR)

        ->ops(opn)

ops(MOV):
        putbyte(16_C7)
        modrmmem(0, base, disp, extdisp )
        norelocateoffset( immed );                                  ->break;
ops(ADD):
        subop = 0
        if issmall(immed) # 0 start
            putbyte(16_83);
            modrmmem(subop, base, disp, extdisp )
            putbyte(immed & 255);
        else
            putbyte(16_81);
            modrmmem(subop, base, disp, extdisp )
            norelocateoffset( immed )
        finish;                                                    ->break;
ops(ADC):
        subop = 2
        if issmall(immed) # 0 start
            putbyte(16_83);
            modrmmem(subop, base, disp, extdisp )
            putbyte(immed & 255);
        else
            putbyte(16_81);
            modrmmem(subop, base, disp, extdisp )
            norelocateoffset( immed )
        finish;                                                    ->break;
ops(CMP):
        subop = 7
        if issmall(immed) # 0 start
            putbyte(16_83);
            modrmmem(subop, base, disp, extdisp )
            putbyte(immed & 255);
        else
            putbyte(16_81);
            modrmmem(subop, base, disp, extdisp )
            norelocateoffset( immed )
        finish;                                                    ->break;
ops(SUB):
        subop = 5
        if issmall(immed) # 0 start
            putbyte(16_83);
            modrmmem(subop, base, disp, extdisp )
            putbyte(immed & 255);
        else
            putbyte(16_81);
            modrmmem(subop, base, disp, extdisp )
            norelocateoffset( immed )
        finish;                                                    ->break;
ops(AND):
        subop = 4
        putbyte(16_81)
        modrmmem(subop, base, disp, extdisp )
        norelocateoffset( immed );                                  ->break;
ops(OR):
        subop = 1
        putbyte(16_81)
        modrmmem(subop, base, disp, extdisp )
        norelocateoffset( immed );                                  ->break;
ops(XOR):
        subop = 6
        putbyte(16_81)
        modrmmem(subop, base, disp, extdisp )
        norelocateoffset( immed );                                  ->break;
ops(SHL):
        subop = 4;
        if immed = 1 start;     ! special shift-by-one instruction
            putbyte(16_D1);
            modrmmem(subop, base, disp, extdisp )
        else
            putbyte(16_C1);
            modrmmem(subop, base, disp, extdisp )
            putbyte(immed)
        finish;                                                    ->break;
ops(SHR):
        subop = 5
        if immed = 1 start;     ! special shift-by-one instruction
            putbyte(16_D1);
            modrmmem(subop, base, disp, extdisp )
        else
            putbyte(16_C1);
            modrmmem(subop, base, disp, extdisp )
            putbyte(immed)
        finish;                                                    ->break;
break:
        listpreamble;
        printstring(opname(opn))
        printstring(" WORD ");	! otherwise it's ambiguous for the reader
        printmemref(base, disp);
        printsymbol(',')
        write(immed, 1)
        newline
        flushcode;
    end

    ! Memory (8 bit) immediate operations - can be MOV, Math, or Shift
    routine dumpmi8(integer opn, integer base, disp, extdisp, integer immed)
        integer subop;
        switch ops(MOV:SHR)

		->ops(opn)

ops(MOV):
        subop = 0
        putbyte(16_C6)
        modrmmem(subop, base, disp, extdisp )
        putbyte(immed & 255);                                       ->break;
ops(ADD):
        subop = 0
        putbyte(16_80)
        modrmmem(subop, base, disp, extdisp )
        putbyte(immed & 255);                                       ->break;
ops(ADC):
        subop = 2
        putbyte(16_80)
        modrmmem(subop, base, disp, extdisp )
        putbyte(immed & 255);                                       ->break;
ops(CMP):
        subop = 7
        putbyte(16_80)
        modrmmem(subop, base, disp, extdisp )
        putbyte(immed & 255);                                       ->break;
ops(SUB):
        subop = 5
        putbyte(16_80)
        modrmmem(subop, base, disp, extdisp )
        putbyte(immed & 255);                                       ->break;
ops(AND):
        subop = 4
        putbyte(16_80)
        modrmmem(subop, base, disp, extdisp )
        putbyte(immed & 255);                                       ->break;
ops(OR):
        subop = 1
        putbyte(16_80)
        modrmmem(subop, base, disp, extdisp )
        putbyte(immed & 255);                                       ->break;
ops(XOR):
        subop = 6
        putbyte(16_80)
        modrmmem(subop, base, disp, extdisp )
        putbyte(immed & 255);                                       ->break;
ops(SHL):
        subop = 4
        if immed = 1 start;     ! special shift-by-one instruction
            putbyte(16_D0)
            modrmmem(subop, base, disp, extdisp )
        else
            putbyte(16_C0)
            modrmmem(subop, base, disp, extdisp )
            putbyte(immed)
        finish;                                                    ->break;
ops(SHR):
        subop = 5
        if immed = 1 start;     ! special shift-by-one instruction
            putbyte(16_D0)
            modrmmem(subop, base, disp, extdisp )
        else
            putbyte(16_C0)
            modrmmem(subop, base, disp, extdisp )
            putbyte(immed)
        finish;                                                    ->break;
break:
        listpreamble;
        printstring(opname(opn))
        printstring(" BYTE ");	! otherwise it's ambiguous for the reader
        printmemref(base, disp);
        printsymbol(',')
        write(immed, 1)
        newline
        flushcode;
    end

    ! Finally, a catch-all that recasts operations using generic
    ! Var Stack structures
    ! Plant a 16 bit Reg <- Var operation
    routine dumprv(integer opn, integer reg, record(stackfm)name v)

        if v_form = V in R start
            dumprr(opn, reg, v_base)
        else
            if v_form = V in S start
                dumprm(opn, reg, v_base!v_scope, v_disp, v_extdisp )
            else
                if v_form = constant start
                    dumprioffset(opn, reg, v_scope, v_disp, v_extdisp )
                else
                    abort("Address Mode")
                finish
            finish
        finish
    end

    ! Another special dumper - the only "Unary" operation that
    ! takes an immediate operand is PUSH
    routine dumppushi(integer reloc, integer immed, integer extdisp )

        reloc = reloc >> 4;			! because we pass around the or-able version

        if reloc = 0 and is small(immed) # 0 start
            putbyte(16_6A);
            putbyte(immed & 255);
        else
            putbyte(16_68);
            relocateoffset(reloc, immed, extdisp );
        finish

        listpreamble;
        printstring("PUSH")
        space
        if reloc # 0 start
            printstring(relocname(reloc)); printsymbol('+')
        finish
        write(immed, 1)
        newline
        flushcode
    end

    routine dumpvpush(record(stackfm)name v)

        if v_form = V in R start
            dumpur(PUSH, v_base)
        else
            if v_form = V in S start
                dumpum(PUSH, v_base!v_scope, v_disp, v_extdisp )
            else
                if v_form = constant start
                    dumppushi(v_scope, v_disp, v_extdisp )
                else
                    abort("Push Mode")
                finish
            finish
        finish
    end
      
    !----------------------------------------------------------
    ! Floating point instructions - much simpler since there are
    ! only two forms - RR and RM

    conststring(10)array flopname(FILD:FLDPI) =
    "FILD", "FLD DWORD", "FLD QWORD", "FISTP",
    "FSTP DWORD", "FSTP QWORD", "FADDP", "FSUBP",
    "FSUBRP", "FMULP", "FDIVP", "FDIVRP",
    "FCOMPP", "FCHS", "FABS",
    "FSTSW AX", "FLDZ", "FLDPI"

    ! The prefix opcode
    constbyteintegerarray flprefix(FILD:FLDPI) =
    16_DB, 16_D9, 16_DD, 16_DB,
    16_D9, 16_DD, 16_DE, 16_DE,
    16_DE, 16_DE, 16_DE, 16_DE,
    16_DE, 16_D9, 16_D9,
    16_DF, 16_D9, 16_D9

    ! The function selector to put in the field in the second byte
    ! (or the second byte)
    constbyteintegerarray flindex(FILD:FLDPI) =
    16_00, 16_00, 16_00, 16_03,
    16_03, 16_03, 16_C0, 16_E8,
    16_E0, 16_C8, 16_F8, 16_F0,
    16_D8, 16_E0, 16_E1,
    16_E0, 16_EE, 16_EB

    ! Plant a Floating Point Reg <- Memory operation
    ! BASE is an internal ID, not actual register number
    ! Destination register is implicitly the stack top
    routine dumpfloprm(integer opn, integer base, integer disp, integer extdisp )

        if opn <= FLDQ start;	  ! a load type
            fpu stack = fpu stack + 1
            if fpu stack > 8 then abort("FPU Stack Overflow")
        else
            fpu stack = fpu stack - 1
            if fpu stack < 0 then abort("FPU Stack Underflow")
        finish

!		putbyte(16_9B);         ! we prepend a WAIT to everything
        putbyte(flprefix(opn));
        modrmmem(flindex(opn), base, disp, extdisp );

        listpreamble;
        printstring(flopname(opn))
        space
        printmemref(base, disp);
        newline
        flushcode;
    end

    ! Plant a Floating Point Reg <- Reg operation
    ! Both register parameters are internal ID's that we
    ! convert to stack offsets
    routine dumpfloprr(integer opn, integer reg1, integer reg2)
        integer top

        top = fpustack + (FR0 - 1)

        if reg2 # top then abort("FPU Stack Address")

        if opn < FCHS start;	  ! two operands - will pop one
            fpu stack = fpu stack - 1
            if opn = FCMP then fpu stack = fpu stack - 1;     ! COMPP pops both registers
            if fpu stack < 0 then abort("FPU Stack Underflow")
        finish

!		putbyte(16_9B);         ! we prepend a WAIT to everything
        putbyte(flprefix(opn));
        putbyte(flindex(opn)!(top - reg1))

        listpreamble;
        printstring(flopname(opn))
        space
        printstring("ST(");
        write(top-reg1, 1)
        printstring("),ST")
        newline
        flushcode;
    end

    ! Plant a "special" floating point operation
    routine dumpflopspec(integer opn)

        if opn >= FLDZ start;         ! load a constant
            fpu stack = fpu stack + 1
            if fpu stack > 8 then abort("FPU Stack Overflow")
        finish

!		putbyte(16_9B);         ! we prepend a WAIT to everything
        putbyte(flprefix(opn));
        putbyte(flindex(opn))

        listpreamble
        printstring(flopname(opn))
        newline
        flushcode
    end

    routine dumpjump(integer opn, integer labelid)
        ! we put conventional assembler into the pipe for the listing
        ! (with a zero jump offset) but then re-use the pipe for the
        ! pseudo-code for the jump
        putbyte(opvalue(opn));
        putbyte(0);
        if opn = CALL then putbyte(0);

        listpreamble;
        printstring(opname(opn))
        space
        { JDM JDM }
        if (opn = CALL) start
            ! See if we can show the routine name
            printstring("'".top_idname."' (INTERNAL ")
            printsymbol('L');
            write(labelid,1)
            printstring(" )")
        else
            printsymbol('L')
            write(labelid,1)
        finish
        { JDM JDM }
        newline

        ClearObjectBuffer;		! zap the current contents of the pipe
        if (opn = JMP) start
			putcodebyte(labelid & 255)
			putcodebyte(labelid >> 8)
			writeifrecord(IF JUMP, 2, objectbytes)
			ClearObjectBuffer;		! zap the current contents of the pipe
        finish else if (opn = CALL) start
! JDM replaced use of IF CALL command by IF REFLABEL command
!           ! Generated code using IF CALL ibj command
!           putcodebyte(labelid & 255)
!           putcodebyte(labelid >> 8)
!           writeifrecord(IF CALL, 2, objectbytes)
!           ClearObjectBuffer;     ! zap the current contents of the pipe

            ! JDM JDM Generated code using IF REFLABEL ibj command
            ! plant the CALL code
            putcodebyte( 16_E8 );   ! call with relative address
            writeifrecord(IF OBJ, 1, objectbytes);
            ClearObjectBuffer;      ! zap the current contents of the pipe
            ! plant the relative address of the label
            putcodebyte(labelid & 255)
            putcodebyte(labelid >> 8)
            putcodebyte(0); ! JDM set offset to zero
            putcodebyte(0)
            writeifrecord(IF REFLABEL, 4, objectbytes);
            ClearObjectBuffer;      ! zap the current contents of the pipe
        finish else start
            ! not an unconditional JMP or a CALL
            ! assume it is a conditional JMP (i.e. JE,JNE, etc.)
            putcodebyte(opn - JE)
            putcodebyte(labelid & 255)
            putcodebyte(labelid >> 8)
            writeifrecord(IF JCOND, 3, objectbytes)
            ClearObjectBuffer;      ! zap the current contents of the pipe
        finish

        ! finally, calls may trash registers...
        if opn = CALL start
            displayhint(BX) = 0
            displayhint(SI) = 0
            displayhint(DI) = 0
        finish
    end

    ! call the n'th external routine we've spec'ed
    routine dumpextcall(integer labelid)

        displayhint(BX) = 0
        displayhint(SI) = 0
        displayhint(DI) = 0

        putbyte(opvalue(CALL))
        flushcode;	! plant the "CALL" instruction
        putbyte(labelid & 255)
        putbyte(labelid >> 8)
        listpreamble
        ! JDM JDM attempt to show external routine name
        printstring("CALL ")
        if (labelid <= lastperm) start
            ! This is an internal "perm" routine
            ! So, show the name
            printstring("'".permname(labelid)."'")
        else
            ! this is an external routine
            printstring("'".top_idname."'")
        finish
        printstring(" (EXTERN ")
        write(labelid, 1)
        printstring(")")
        newline
        ! JDM JDM

        writeifrecord(IF REFEXT, wordsize, objectbytes);
!        writeifrecord(IF REFEXT, 2, objectbytes);
        ClearObjectBuffer;	! zap the current contents of the pipe
    end

    routine dumplabel(integer labelid)

        select output(listout)
        space; writehex(nextcad, 4); spaces(22);
        printsymbol('L');
        write(labelid, 1);
        printstring("  EQU $")
        newline

        ClearObjectBuffer;	! zap the current contents of the pipe
		putcodebyte(labelid & 255)
		putcodebyte(labelid >> 8)
        writeifrecord(IF LABEL, 2, objectbytes);
        ClearObjectBuffer;	! zap the current contents of the pipe

        displayhint(BX) = 0
        displayhint(SI) = 0
        displayhint(DI) = 0
    end

    routine dumpstaticalloc(integer which, level, string(255)name name)
        integer i, len
        ! we pretend to dump "C8 00 00 lev  ENTER 0000,lev" but we actually plant a special pass 2 directive

        putbyte(16_C8)
        putbyte(16_00)
        putbyte(16_00)
        putbyte(level)
        listpreamble
        printstring("ENTER 0000,"); write(level, 1)
        newline

        ClearObjectBuffer;	! zap the current contents of the pipe
		putcodebyte(which & 255)
		putcodebyte(which >> 8)
		putcodebyte(level)

        ! we also pass the (truncated) name of the routine for pass3 diagnostic use

        len = length(name)
        if len > 16 then len = 16
        for i=1,1,len cycle
            putcodebyte(charno(name,i))
        repeat
        writeifrecord(IF FIXUP, len+3, objectbytes);
        ClearObjectBuffer;	! zap the current contents of the pipe
    end

    ! Pass 3 goes back and plants the correct preamble code for
    ! the static allocation based on this directive, and also fills
    ! in the event trap block as appropriate
    routine dumpstaticfill(integer which, size, events, evep, evfrom)

        ClearObjectBuffer;	! zap the current contents of the pipe
        putcodebyte(which & 255);	putcodebyte(which >> 8);
        putcodebyte(size & 255);	putcodebyte(size >> 8);
        putcodebyte(events & 255);	putcodebyte(events >> 8);
        putcodebyte(evep & 255);	putcodebyte(evep >> 8);
        putcodebyte(evfrom & 255);	putcodebyte(evfrom >> 8);
        writeifrecord(IF SETFIX, 10, objectbytes);
        ClearObjectBuffer;	! zap the current contents of the pipe
    end

    ! dump words for the constant segment or the data segment
    ! Adjusts CAD so that the diagnostic listing looks sensible
    routine dumpcdword(integer word, integer which)

        integer tag, tmpcad, hi, lo
        owninteger cptr = 0
        owninteger dptr = 0
        owninteger sptr = 0

        tmpcad = next cad
        if which = 2 start
            tag = IF SWTWORD;
            next cad = sptr; sptr = sptr + 2
        else
            if which = 1 start
                tag = IF COTWORD;
                next cad = cptr; cptr = cptr + 2
            else
                tag = IF DATWORD;
                next cad = dptr; dptr = dptr + 2
            finish
        finish

        hi = word >> 8
        lo = word & 255
        putbyte(lo);
        putbyte(hi);
        listpreamble;
        printstring("db ")
        writehex(lo, 2); printsymbol(','); writehex(hi, 2);
        printstring(" ; ")
        if lo > 32 and lo < 127 then printsymbol(lo) else printsymbol('.')
        if hi > 32 and hi < 127 then printsymbol(hi) else printsymbol('.')
        newline
        writeifrecord(tag, 2, objectbytes);
        ClearObjectBuffer;	! clear the pipe
        nextcad = tmp cad;	! restore the real CAD
    end

    ! tell the object file maker what source line we are on
    byteintegerarray buffer(0:1);  ! moved outside of dump line number to make accessible to checksum()
    routine dump line number(integer line)

        buffer(0) = (line & 255);
        buffer(1) = (line >> 8);
        writeifrecord(IF LINE, 2, buffer);

    end

    ! utility to copy an IMP string into a simple buffer to
    ! pass to the IF Record routine
    routine str to xsym(string(255)name s)
        integer l

        l = length(s)
        xlen = 0
        while xlen < l cycle
            x sym buff(xlen) = charno(s, xlen+1)
            xlen = xlen + 1
        repeat
    end

    ! tell the object maker the source file name
    routine dumpsourcename(string(255) filename)

        str to xsym(filename)
        writeifrecord(IF SOURCE, xlen, x sym buff);

    end

    ! Plant a request to the linker for the external name, and
    ! return an index number to refer to it with in future
    integerfn externalref(string(255) extname)
        owninteger nextextref=1

        str to xsym(extname)
        writeifrecord(IF REQEXT, xlen, x sym buff);
        nextextref = nextextref + 1
        result = nextextref - 1;
    end

    ! tell the linker about an external definition
    routine fill external(integer seg, offset, string(255) extname)

        str to xsym(extname)
        if seg = CODE start
            writeifrecord(IF DEFEXTCODE, xlen, x sym buff);
        else
            writeifrecord(IF DEFEXTDATA, xlen, x sym buff);
            ! er, this doesn't actually work yet!
        finish
    end

    !------------------------------------------------------
    ! Constant table utility routines
    !
    ! Rather than dump literal constants as they occur, we
    ! collect them in a table.  Whenever the compiler wants
    ! any kind of literal, we look to see if we already
    ! have it.  Note this automatically solves re-use of
    ! things like floating point constants, string newline,
    ! and fixed array dope vectors.  When the table starts
    ! to get fairly full, we flush it.  Obviously that means
    ! in a large program we might not actually get full re-use
    ! of constants after we've flushed, but the idea is sound.
    !
    ! For the convenience of the caller, several versions of
    ! pretty much the same thing are provided.
    !------------------------------------------------------
    constinteger cot size = 2000
    ownbyteintegerarray contable(0:cot size)
    owninteger cotp = 0
    owninteger cotoffset = 0;        ! updated on a flush

    routine flushcot
        integer i

        ! We output a position hint to the diagnostic stream
        ! Note that although this is intended to look like
        ! 8086 assembly directives the real work is done by
        ! pass 3 - this is only to guide the human reader as
        ! to what is going on

        selectoutput(listout)
        printstring("      _TEXT  ENDS"); newline;
        printstring("      CONST  SEGMENT WORD PUBLIC 'CONST'"); newline;

        i = 0
        while i < cotp cycle
            dumpcdword((contable(i+1) << 8) ! contable(i), 1)
            i = i + 2;
        repeat

        ! Update the pointers
        cotp = 0
        cotoffset = cotoffset + i

        ! and send another hint
        selectoutput(listout)
        printstring("      CONST  ENDS"); newline;
        printstring("      _TEXT  SEGMENT WORD PUBLIC 'CODE'"); newline

    end

    ! return the offset in the const segment of a byte
    ! with value b
    integerfn getcotb(byteinteger b)
        integer i

        i = 0
        while i < cotp cycle
            if contable(i) = b then result = i + cotoffset
            i = i + 1
        repeat

        ! value wasn't there
        if cotp = cot size then flushcot

        contable(cotp) = b
        cotp = cotp + 1
        result = (cotp - 1) + cotoffset
    end

    ! return the offset in the const segment of a word
    ! with value w
    integerfn getcotw(integer w)
        integer i, cw

        i = 0
        while i < cotp-3 cycle
            cw = contable(i)!(contable(i+1)<<8)!(contable(i+2)<<16)!(contable(i+3)<<24)
            if cw = w then result = i + cotoffset
            i = i + wordsize
        repeat

        ! value wasn't there - first make sure there is space
        if cotp > cot size-wordsize then flushcot

        ! now round off the COT
        cotp = (cotp + align) & (¬align)

        for i=1,1,wordsize cycle
            contable(cotp) = w & 255
            w = w >> 8
            cotp = cotp + 1
        repeat

        result = (cotp - wordsize) + cotoffset
    end

    ! return the offset in the const segment of double precision real number
    integerfn getcotdouble(longreal double)
        integer i

        i = 0
        while i < cotp-7 cycle
            if    contable(i)   = byteinteger(addr(double))   c
              and contable(i+1) = byteinteger(addr(double)+1) c
              and contable(i+2) = byteinteger(addr(double)+2) c
              and contable(i+3) = byteinteger(addr(double)+3) c
              and contable(i+4) = byteinteger(addr(double)+4) c
              and contable(i+5) = byteinteger(addr(double)+5) c
              and contable(i+6) = byteinteger(addr(double)+6) c
              and contable(i+7) = byteinteger(addr(double)+7) then result = i + cotoffset
            i = i + 4
        repeat

        ! value wasn't there - first make sure there is space
        if cotp > cot size-8 then flushcot

        ! now round off the COT
        cotp = (cotp + align) & (¬align)

        for i=0,1,7 cycle
            contable(cotp) = byteinteger(addr(double)+i)
            cotp = cotp + 1
        repeat

        result = (cotp - 8) + cotoffset
    end

    ! return the offset in the const segment of a quad word
    ! with value q0:q1:q2:q3 (lo to hi)
    integerfn getcot4(integer q0, q1, q2, q3)
        integer i, cw0, cw1, cw2, cw3

        i = 0
        cw0 = contable(i)!(contable(i+1)<<8)!(contable(i+2)<<16)!(contable(i+3)<<24)
        cw1 = contable(i+4)!(contable(i+5)<<8)!(contable(i+6)<<16)!(contable(i+7)<<24)
        cw2 = contable(i+8)!(contable(i+9)<<8)!(contable(i+10)<<16)!(contable(i+11)<<24)
        while i < cotp-15 cycle
            cw3 = contable(i+12)!(contable(i+13)<<8)!(contable(i+14)<<16)!(contable(i+15)<<24)
            if cw0 = q0 and cw1 = q1 and cw2 = q2 and cw3 = q3 then result = i + cotoffset
            i = i + word size
            cw0 = cw1
            cw1 = cw2
            cw2 = cw3
        repeat

        ! value wasn't there - first make sure there is space
        if cotp > cot size-16 then flushcot

        ! now round off the COT
        cotp = (cotp + align) & (¬align)

        for i=1,1,wordsize cycle
            contable(cotp) = q0 & 255
            q0 = q0 >> 8
            cotp = cotp + 1
        repeat

        for i=1,1,wordsize cycle
            contable(cotp) = q1 & 255
            q1 = q1 >> 8
            cotp = cotp + 1
        repeat

        for i=1,1,wordsize cycle
            contable(cotp) = q2 & 255
            q2 = q2 >> 8
            cotp = cotp + 1
        repeat

        for i=1,1,wordsize cycle
            contable(cotp) = q3 & 255
            q3 = q3 >> 8
            cotp = cotp + 1
        repeat

        result = (cotp - 16) + cotoffset
    end

    owninteger null string = -1

    ! get an index into the constant table for the string literal
    ! in the array s
    integerfn getcots(byteintegerarrayname b)
        integer i, first, slen, match;

		slen = b(0)

        ! We optimise the Null String "" in comparisons, so we remember
        ! the location here
        if slen = 0 start
            null string = getcotb(0)
            result = null string
        finish

        first = 0;           ! offset to search in contable

        while first + slen < cotp cycle;     ! so long as there are that many bytes left
            match = 1
			! Simple check of string lengths
            if slen # contable(first) start
                match = 0
                exit
            finish
			! ok, so lengths match but do the contents
            for i = 1, 1, slen cycle
                if b(i) # contable(first + i) start
                    match = 0
                    exit
                finish
            repeat
            if match = 1 then result = first + cotoffset
            first = first + 1;               ! try the next solution
        repeat

        ! if we get here, it wasn't already in the constant table
		! Ok, so will we overflow the buffer
        if (cotp + slen + 1) >= cot size then flushcot

		! dump the string length
        first = cotp
        contable(cotp) = slen
        cotp = cotp + 1
		! Now, dump the string contents
		for i = 1,1,slen cycle
            contable(cotp) = b(i)
            cotp = cotp + 1
        repeat
        result = first + cotoffset
    end

    !------------------------------------------------------
    ! Data segment utility routines
    !
    ! Unlike constants, we can't re-use data segment items,
    ! which makes this much simpler.  We still accumulate
    ! the bytes in a table because (1) we can make life
    ! more efficient for Pass 3 that way and (2) by collecting
    ! the bytes together we can produce more convincing debug
    ! code listings, especially for programs that don't need
    ! to flush the table in the middle of the code.
    ! Note that because data segment offsets are used directly
    ! as variable displacements, our pointer DATATP doesn't
    ! wrap like the COTP does, and instead we subtract the
    ! offset before we use it...
    !------------------------------------------------------
    constinteger datat limit = 1999;    ! Size in bytes of data segment table
    byteintegerarray datat(0:datat limit)
    owninteger datatp = 0;              ! pointer to next data segment byte
    owninteger datat offset = 0;        ! updated on a flush

    ! Flush the accumulated data table
    routine flush data
        integer i, limit

        ! We output a position hint to the diagnostic stream

        selectoutput(listout)
        printstring("            ENDS"); newline
        printstring("      DATA  SEGMENT WORD PUBLIC 'DATA'"); newline

        i = 0
        limit = datatp - datat offset
        while i < limit cycle
            dumpcdword((datat(i+1) << 8) ! datat(i), 0)
            i = i + 2;
        repeat

        datat offset = datat p

        ! and send another hint
        selectoutput(listout)
        printstring("      DATA    ENDS"); newline
    end

    !                                                            >> GBYTE <<
    ! Simple byte in data segment
    routine  gbyte(integer  n)

        flush data if (datatp - datat offset) > datat limit   
        datat(datatp - datat offset) = n & 255;
        datatp = datatp + 1
    end

    !                                                            >> GPUT <<
    ! Put a word into data segment
    routine  gput(integer  n)
        integer i

        for i=1,1,wordsize cycle
            gbyte(n)
            n = n >> 8;
        repeat
    end

    !                                                             >> GFIX <<
    ! round off the datasegment pointer for alignment
    routine  gfix(integer align)
        gbyte(0) while datatp & align # 0
    end

    !-----------------------------------------------------
    ! The last table we collect as we go along is the switch
    ! table.  We don't provide individual routines to fill
    ! it in, but for tidyness we provide this routine to send
    ! the contents to pass 3

    routine flush switch
        integer i

        select output(listout)
        printstring("              ENDS"); newline
        printstring("      _SWTAB  SEGMENT WORD PUBLIC '_SWTAB'"); newline
        i = 0
        while i < swtp cycle
            dumpcdword(swtab(i), 2)
            i = i + 1
        repeat

        ! and send another hint
        selectoutput(listout)
        printstring("      _SWTAB   ENDS"); newline
    end

    !-------------------------------------------------------------
    ! Print the source code lines up to the indicated line
    ! number - these will interleave with the diagnostic assembly
    ! output
    owninteger echoline = 0

    routine echo source line
        integer ch
        owninteger source eof = 0

        echoline = echoline + 1;            ! update the count even if there's no input

        if source eof # 0 then return;      ! silently ignore lack of source file

        select input(source)
        select output(listout)
        cycle
            readsymbol(ch)
            printsymbol(ch)
            exit if ch = 10 or ch < 0
        repeat

        if ch < 0 then source eof = 1

        select input(icode)
        select output(objout)
    end

    !-----------------------------------------------------------
    ! General descriptor and register manipulation routines
    !-----------------------------------------------------------

    !                                                         >> FLOATING <<
    integerfn  floating(record(stackfm)name v)
        ! check descriptor for floating point quantity
        result = 1 if v_type = real or v_type = lreal
        result = 0
    end

    !                                                             >> ZERO <<
    integerfn  zero(record(stackfm)name  v)
        ! CHECK DESCRIPTOR FOR (INTEGER) ZERO
        result = 0 if v_disp # 0 or v_base # 0 or (v_form # constant and v_form # AV in S)
        result = 1
    end

    !                                                            >> CONST <<
    integerfn  const(record(stackfm)name  v)
        ! CHECK DESCRIPTOR FOR CONSTANT (INTEGER) VALUE
        result = 0 unless v_form = constant
        result = 0 if v_type > byte
        result = 1
    end

    integerfn Min Record Size(record(stackfm)name A, B)
        integer N, M
        N = A_format;  N = var(N)_size & 16_7FFF if N # 0
        M = B_format;  M = var(M)_size & 16_7FFF if M # 0
        N = M if N = 0 or (M # 0 and M < N)
        result = N if N > 0
        Abort("Min Rec Size")
    end

    !                                                             >> MULSHIFT <<
    integerfn mulshift(integer n)
        integer shift, ref
        ref = 1
        for shift = 1, 1, 14 cycle
            ref = ref<<1
            if ref >= n start
                if ref = n then result = shift else result = -1
            finish
        repeat
        result = -1
    end

    !                                                             >> SAME <<
    integerfn  same(record(stackfm)name  v,w)
        ! Test whether or not V and W describe the same object.
        result = 0 if v_disp # w_disp or v_base # w_base
        result = 0 if v_type # w_type or v_form # w_form
        result = 0 if v_extra # w_extra or v_scope # w_scope
        result = 1
    end


    ! grab a slab of working store in the local stack
    integerfn getwork(integer size)
        integer cell;

        cell = worklist(level);
        while cell # 0 cycle
            if gp tags(cell)_info = size and gp tags(cell)_flags = 0 start;    ! suitable candidate?
                gp tags(cell)_flags = 1;		! mark it as in use
                result = gp tags(cell)_addr;
            finish
            cell = gp tags(cell)_link;
        repeat

        ! no space available already - make more
        cell = get gp tag
        frame = (frame - size) & (¬align);   ! make them all even boundaries
        gp tags(cell)_addr = frame;
        gp tags(cell)_info = size;
        gp tags(cell)_link = worklist(level);
        worklist(level) = cell;
        gp tags(cell)_flags = 1;		! in use
        result = frame;
    end

    ! Return a slab of working store to the free pool.  Note that
    ! ReturnWork is cautious about what it accepts - it only takes
    ! in items it has previously given out, so we can call it
    ! fairly liberally with any old rubbish and it will do the
    ! right thing
    routine Return Work(integer addr)
        integer cell

        cell = worklist(level);
        while cell # 0 cycle
            if gp tags(cell)_addr = addr start
                if gp tags(cell)_flags = 0 then abort("Return Work")
                gp tags(cell)_flags = 0;		! mark it as free
                return
            finish
            cell = gp tags(cell)_link;
        repeat

        ! Here, work area was not found - it probably wasn't a work area!
    end

    ! Check to see if a variable is in a work list assigned block.  Used
    ! in string expression compilation to avoid un-necessary copying, hence
    ! only marked true for 256 byte chunks
    integerfn Is Work(record(stackfm)name v)
        integer cell

        if v_base # BP or v_disp >= 0 or v_scope # 0 or v_form # v in s then result = 0

        cell = worklist(level);
        while cell # 0 cycle
            if gp tags(cell)_addr = v_disp start
                if gp tags(cell)_flags = 0 then abort("Is Work")
                if gp tags(cell)_info # 256 then result = 0
                result = 1
            finish
            cell = gp tags(cell)_link;
        repeat

        result = 0
    end

    !                                                          >> RELEASE <<
    routine  release(integer  reg)
        ! Hazard the value in a register
        !   abort("Release bad register") %if reg > fr7
        return if reg = 0 or reg > fr7 or activity(reg) < 0;      ! LOCKED
        activity(reg) = activity(reg)-1
        abort("Release inactive") if activity(reg) < 0
        claimed = claimed - 1
    end

    !                                                            >> CLAIM <<
    routine  claim(integer  reg)
        ! Cherish the value in a register
        abort("Claim bad register") if reg > fr7
        return if reg = 0 or activity(reg) < 0
        activity(reg) = activity(reg)+1
        claimed = claimed+1
    end

    !                                                           >> HAZARD <<
    ! Protect any value in register REG by storing in a temporary.
    routine  hazard(integer  reg)
        integer  i, n, t, type

        routine  mod(record(stackfm)name  v)
            switch  sw(0:a in rec)
            v_base = BP
            n = n-1
            -> sw(v_form)
sw(a in rec):
sw(av in rec):
sw(v in rec):
sw(constant):
            abort("Mod")
sw(v in s):
            if v_disp = 0 and v_scope = 0 start
                v_disp = t
                v_form = a in s
            else
                !  change (X in S) to (X in REC)
                v_form = v_form + 3
                v_extra = t
            finish; -> out1
sw(a in s):
sw(av in s):
            v_form = v_form + 3
            v_extra = t; -> out1;    !  change (X in S) to (X in REC)
sw(v in r):
            v_form = v in s
            v_disp = t
            v_type = type; -> out1
out1:
        end

        n = activity(reg);  return if n <= 0;    ! NOT IN USE OR CLAIMED
        claimed = claimed - n
        activity(reg) = 0
        if reg >= fr0 start
            ! Note that the FPU can only save the top of the stack.
            ! If we need to save something lower down, we need to pop
            ! the things above me first...
            if reg - FR0 >= FPU Stack then hazard(reg+1);   ! and recurse as required
            type = lreal
            t = getwork(8)
            dumpfloprm(FSTQ, BP, t, 0)
        else
            type = integer
            t = getwork(word size)
            dumpmr(MOV, BP,t, 0, reg)
        finish
        for i = 1, 1, stp cycle
            mod(stack(i)) if stack(i)_base = reg
        repeat
        abort("Usage Outstanding") if n # 0;       ! USE STILL OUTSTANDING
    end

    !                                                               >> HAZARD ALL <<
    routine  hazard all
        integer  j

        if claimed # 0 start;              ! at least one register claimed
            hazard(j) for j = AX,1,FR7
        finish
    end

    !                                                          >> GP REG <<
    ! Get a general (integer) register
    ! Note that registers AX, CX, DX, BX are, in order
    ! numbers 1, 2, 3 and 4 (which is convenient)
    integerfn  gpreg
        integer  r

        ! look for an empty one
        for r = AX,1,BX cycle
            result = r if activity(r) = 0
        repeat

        ! look for an unlocked one
        for r = AX,1,BX cycle
            if activity(r) > 0 start
                hazard(r)
                result = r
            finish
        repeat

        abort("Get Reg")
    end

    !                                                          >> PT REG <<
    integerfn  pt reg
        ! Get a register we can use as a pointer.  We deliberately rotate
        ! around the candidates to make re-use more likely
        constbyteintegerarray  pt pref(0:2) =
           7,  8,  4
        ! SI, DI, BX
        owninteger next = 0
        integer  r,j

        ! look for an empty one
        for j = 1,1,3 cycle
            r = pt pref(next)
            next = next+1; next = 0 if next = 3
            result = r if activity(r) = 0
        repeat

        ! look for an unlocked one
        for j = 1,1,3 cycle
            r = pt pref(j)
            if activity(r) > 0 start
                hazard(r)
                result = r
            finish
        repeat

        abort("Get PT Reg")
    end

    !                                                          >> GET DISPLAY <<
    ! return the register to use to access display level <n>
    integerfn getdisplay(integer l)
        integer r, lev

        lev = l & 15;                  ! get rid of any relocation info
        if lev = 0 then result = l;    ! global
        if lev = level then result = BP;    ! local

        ! We now try the 3 pointer register - they are not contiguously
        ! numbered, which is why this is unrolled!
        if displayhint(BX) = lev then result = BX
        if displayhint(SI) = lev then result = SI
        if displayhint(DI) = lev then result = DI

        r = pt reg
        dumprm(MOV, r, BP, -(lev * word size), 0 ); ! displays are first words in frame
        displayhint(r) = lev
        result = r
    end

    !                                                          >> SET DOPE VECTOR <<
    ! Plants a dope vector for a 1-D constant bound array (usually
    ! OWN or CONST arrays) in the CONST segment, returns the offset
    ! Note that it also modifies the vlb and vub variables - after
    ! the call, VLB contains the byte offset for the first member
    ! and VUB contains the size to allocate for the array in bytes.
    integerfn  set dope vector
        integer  t, dv

        t = vub-vlb+1
        dv = getcot4(1, vlb, vub, data size)
        vub = t*data size;  vlb = vlb*data size
        result = dv
    end

    !                                                          >> PERM <<
    ! calls a PERM and adjusts the stack by SPACE words afterwards
    routine  perm(integer  n, space)
        ! PERM routines are written in MS C, and they preserve SI and DI,
        ! but trash the general purpose registers
        hazard(AX)
        hazard(CX)
        hazard(DX)
        hazard(BX)
        ! JDM perm routines now implemented as IMP routines
        ! so be more careful and hazard the SI,DI registers as well
        hazard(SI)
        hazard(DI)
        dumpextcall(n)
        if space # 0 then dumpri( ADD, SP, space * word size)
    end

    !                                                          >> ASSEMBLE <<
    ! AMODE:
    !  -3: initial call
    !  -2: alternate record format
    !  -1: record format
    !   0: begin block
    !   1: procedure
    !   2: %spec
    routine Assemble(integer  amode, labs, names)
        switch c(33:127)
        record(varfm)name   v;         ! General purpose pointer
        record(varfm)name   procvar;   ! Var of the current procedure we're compiling
        record(varfm)name   ap;        ! Actual parameter ptr, used to copy parms to parm area
        record(varfm)name   fp;        ! formal parameter ptr, used to copy parms to parm area
        record(stackfm)name lhs;       ! General stack pointer
        record(stackfm)name rhs;       ! General stack pointers
        integer max frame;	            ! Used for alternate records to find the largest alternate
        integer first name;            ! First descriptor at this level
        integer staticalloc;           ! Tag used by pass 3 to fix up this level's stack allocation
        integer skipproc, lastskip;    ! Used to jump around routines
        integer events, evep, evfrom;  ! Event info (mask, entry point, block start)
        integer First label;           ! First label at this level
        integer old frame;             ! Previous level's static allocation
        integer j, t
        integer dv

        routinespec   compile to string(record(stackfm)name v)
        routinespec   loadreg(record(stackfm)name  v, integer  reg);        ! JDM change name from load()
        routinespec   storereg(record(stackfm)name  v, integer  reg);       ! JDM new code
        routinespec   assign(integer  assop)
        routinespec   array ref(integer  mode)
        routinespec   Operation(integer n)
        routinespec   compare(record(stackfm)name  l,r)
        routinespec   test zero(record(stackfm)name v)
        integerfnspec new tag

        ! Actual code for Assemble is down around label NEXT

		! The following functions "parse" the parameters attached to an iCode instruction
		! It is intended that these functions are the only places where the iCode stream is read
		!                                                      >> READ TAG, and COMMA, INTEGER, REAL <<
		integerfn  ReadTag
			integer s1, s2
			s1 = Pending
			readsymbol(s2)
			readsymbol(Pending)
			result = s1<<8!s2
		end

		integerfn  ReadTagComma
			integer t
			t = ReadTag
			readsymbol(Pending)
			result = t
		end

		integerfn  ReadInteger
			integer s1, s2, s3, s4
			s1 = Pending
			readsymbol(s2)
			readsymbol(s3)
			readsymbol(s4)
			readsymbol(Pending)
			result = (s1<<24)!(s2<<16)!(s3<<8)!s4
		end

		integerfn ReadByte
			integer s1
			s1 = Pending
			readsymbol(Pending)
			result = s1
		end

		!                                                      >> READ REAL <<
		! Read a floating point literal.  Pass 1 treats these as strings
		! and leaves it up to us to make a floating point number out of it
		! We therefore expect [COUNT]NNN.NNN@NN
		longrealfn  ReadReal
			integer n
			longreal p, r
			n = ReadTagComma;	! char count, skip comma

			r = 0
			! Start with the bit ahead of the decimal point
			cycle
				sym = Pending;  read symbol(Pending)
				exit if sym = '.'
				n = n-1
				-> power if sym = '@'
				r = r*10+(sym-'0')
				-> SIGN if n = 0
			repeat
			p = 1
			cycle
				n = n-1;  -> SIGN if n = 0
				sym = Pending;  read symbol(Pending)
				-> POWER if sym = '@'
				p = p/10
				r = r + (sym-'0')*p
			repeat

POWER:
			n = ReadTag
            ! Tag is unsigned 16-bit integer (0..65535)
            ! but is read into a 32-bit signed integer
            ! and so 0 < n < 65535
            ! BUT - tag is to be regarded as a 16-bit signed integer
            ! So 0 < n < 32768 is to be regarded as a positive integer
            ! and 32767 < n < 65536 is a negative integer
            ! n     => correct n
            ! 65536 =>  0
            ! 65535 => -1      (65536 - n)
            ! 65534 => -2      (65536 - n)
            ! ..
            ! 32768 => -32768  (65536 - n)

            ! Now to tweak the floating point value. This method is
			! somewhat clunky so that we can be portable to a system that
			! doesn't do exponents

            ! This version of the pass2 code generator targets the 8087
            ! and later versions as the numeric processor for floating
            ! point arithmetic
            ! e.g. double real (== %longreal)
            ! Double real uses an 11-bit exponent so we should ensure
            ! that the tag represents an exponent in the range
            !             -1023 <= exp <= 1023
            ! -1024 is omitted to ensure no overflow for reciprocals
            ! The exponent however, has a bias of 1023 so the actual
            ! 8087 exponent is in the range 0 <= exp <= 2046

            ! Currently don't bother to check that the exponent is in
            ! the range -1023 < exp < 1023
            if n # 0 start
                ! ok, non-zero exponent
                if 0 < n < 32768 start
                    ! positive exponent
                    while n > 0 cycle
                        r = r * 10
                        n = n - 1
                    repeat
                else
                    ! a negative exponent
                    ! So, convert to negative value
                    n = n - 65536

                    ! Now, attempt to update the float value
                    while n < 0 cycle
                        r = r / 10
                        n = n + 1
                    repeat
                finish
            finish
SIGN:
            ! sign of whole value
			if Pending = 'U' start
				read symbol(Pending)
				r = -r
			finish

			result = r
		end

		string(255)function ReadString
			integer J, Sym, Limit
			string(255) s

			Limit = Size of(s)-1
			s = ""
			for J = Pending, -1,1 cycle
				Readsymbol(Sym)
				s = s.Tostring(Sym) if Length(s) < Limit
			repeat
			Readsymbol(Pending)
			
			result = s
		end

		string(255)function Get Ascii( integer terminator)
			string(255) a
			integer Sym
			a = ""
			cycle
				sym = Pending;  read symbol(Pending);  exit if sym = terminator
				if length( a ) # 255 start
					a = a.to string(sym)
				finish
			repeat
			result = a
		end
		! End of parsing routines

        !                                              >> DEFINE VAR <<
        routine  Define Var( integer decl, string(255) internal id, integer tf, size, scope )
            integer  type, form, format, s, new, round, dimension
            integer  dv;               ! dope vector offset
            owninteger  prim no = 0

            new = 0
            round = align

            ! Get the var index
            if decl = 0 start
                ! RECORD FORMAT ELEMENT NAME
                parms = parms-1
                abort("Def Var Parms") if parms <= names
                decvar == var(parms)
                decvar = 0
            else
                abort("Def Var Names (decl=".itos(decl,0)." parms=".itos(parms,0).")") if decl >= parms
                decvar == var(decl)
                if decl > names start
                    names = decl
                    new = 1
                    decvar = 0
                finish
            finish

            ! Now parse the type and form word
            type = tf>>4
            form = tf&15

            ! Map external type numbers into internal equivalents,
            ! and adjust for short/byte/long things
            if (type = integer) and (size # 1) start
                ! INTEGER
                type = byte  and round = 0 if size = 2
                size = vsize(type)
            finish else if (type = real) start
                ! REAL
                type = lreal if size = 4;              ! LONG REAL
                size = vsize(type)
            finish else if (type = record) start
                ! record
                format = size
                decvar_format = format
                size = var(format)_size if format <= names
            finish else if (type = string) start
                ! string
                round = 0
                decvar_size = size
                size = size + 1
            else
                size = vsize(type)
            finish

            ! JDM JDM remember the variable name
            ! Needed should an embedded code fragment refer to an IMP variable
            var(decl)_idname = internal id


            decvar_size = size if type # string
            decvar_type = type
            decvar_form = form

            ! Now analyse the Scope word
            spec = (scope>>3)&1
            dimension = (scope>>8)&255
            otype = scope&7

            if (otype # 0) start
                ! Set external linkage name if appropriate
                if (otype >= external) start
                    if length(alias) # 0 start
                        external id = alias
                    finish else if (otype = system) start
                        external id = system prefix.internal id
                    else
                        external id = "_".internal id
                    finish
                    otype = external if otype <= dynamic
                    ! external, system, dynamic
                finish
            finish
            alias = ""

			! JDM: Ensure the external displacement is zero
			decvar_extdisp = 0

            if (switch < form) and (form < array) start
                ! PROCEDURE
                block type = 1 + spec;             ! 1 for normal proc, 2 for spec
                if (otype # 0) and (spec # 0) start
                    ! external spec
                    if otype = primrt start
                        primno = primno + 1
                        decvar_level = 128
                        decvar_disp = prim no
                        return
                    finish
                    decvar_disp = externalref(external id)
					decvar_extdisp = decvar_disp;		! JDM: Remember the base external displacement
                    decvar_level = 0
                    decvar_scope = EXT
                    return
                finish

                if (in params = 0) start
                    ! NOT A PARAMETER
                    potype = otype
                    if new # 0 start
                        ! NEW NAME
                        decvar_disp = new tag
                        ! Procedure ID
                    finish
                    block name = internal id if spec = 0
                    return
                finish

                otype = 0
                size = word size
                data size = word size
                ! procedure parameter

            else
                ! This is not a procedure declaration
                data size = size
                if (form # simple) start
                    Round = Align
                    if (type = general) start
                        ! General %name
                        decvar_extra = in params;          ! FOR LABELS
                        size = word size * 2
                    finish else if (form = array) or (form = name array) start
                        ! We will fill in dimensions and allocate space when
                        ! we are told the bounds later
                        size = 0
                        data size = word size if (form = name array)
                    finish else if (form = array name) or (form = name array name) start
                        decvar_dim = dimension
                        size = word size * 2
                        round = align;   ! array header
                    else
                        size = word size;                       ! integer (etc) %name
                    finish
                finish
            finish

            ! Now deal with OWN (or const/extern/etc) data items
            if (otype # 0) start
                ! OWN DATA
                if (otype = con) start
                    ! CONST INTEGER ETC.
                    data size = 0 if (type = string) and (form = simple);      ! use actual size for plain strings
                    if (form = name) or (form = arrayname) or (form = namearrayname) start
                        otype = 0;        ! Treat as special later
                    finish
                else
                    ! OWN, not CONST
                    gfix(round);         ! so make it even if needed
                finish
                ! set globals used by our data collection utilities
                own type = type
                own form = form
                own type = integer and data size = word size if form = 2;   ! %name's are really integers

                if (spec = 0) start
                    if (form = array) or (form = name array) start
                        gfix(align)
                        dv = set dope vector;      ! N.B.  changes vlb, vub
                        ! We treat OWN and CONST arrays identically - both are in data segment
                        gfix(align)
                        decvar_disp = datatp - vlb;
                        decvar_level = 0
                        decvar_scope = DATA
                        decvar_pbase = dv;                  ! save the dope vector pointer here
                        decvar_dim = 1;                     ! own arrays are always 1-D
                    finish
                    fill external(DATA, decvar_disp, external id) if otype = external
                else
                    decvar_level = 0
                    decvar_scope = EXT
                    decvar_disp = external ref(external id)
					! JDM: We have a reference to external data so note the external ref id
					!      inside the _extdisp field
					!      _extdisp will NEVER be modified unlike _disp
					!      Eventually it will be used when generating ABSEXT ibj records
                    !      The difference between _disp and _extdisp represents the offset
                    !      from the location specified by _disp
                    !      offset == _extdisp - _disp
                    decvar_extdisp = decvar_disp
                finish
            finish else if (form = label) start
                !%label
                decvar_disp = new tag
            finish else if (form = switch) start
                size = vub - vlb
                if swtp + size > Max Switch then abort("Switch Table Full")
                decvar_scope = SWT
                decvar_disp = swtp - vlb
                decvar_extra = set dope vector
                for s = swtp, 1, swtp + size cycle
                    swtab(s) = 0;     ! should really deal with undefined switch entries
                repeat
                swtp = swtp + size + 1
            finish else if (form = record format) start
                if (in params # 0) start
                    frame = decvar_size if decvar_size > frame
                else
                    block type = -1;  spec = -1
                finish
            finish else start
                ! Here we've got an ordinary local variable, parameter or record entry
                decvar_level = level
                if (in params = 0) start
                    ! local variable
                    frame = (frame - size) & (¬round)
                    decvar_disp = frame
                finish else if (block type > 0) start
                    ! procedure parameters
                    frame = (frame + size + align) & (¬align);	! parameters are always word aligned
                    decvar_disp = frame;						! offset will be adjusted at '}'
                finish else start
                    ! records
                    frame = (frame + round) & (¬round)
                    decvar_disp = frame
                    frame = frame + size
                    decvar_level = 0;          ! no base register
                finish
            finish
        end;  !    Define Var

        !---------------------------------------------------------------------
        ! Stack manipulation routines
        !---------------------------------------------------------------------
        !                                                      >> POP STACK <<
        ! Pop the top of the stack
        routine  Pop Stack
            if stp = 0 then abort("Pop")
            monitor(top, "Pop") if diagnose&1 # 0
            stp = stp - 1
            if stp # 0 then top == stack(stp) else top == null
        end

        !                                                             >> POP REL <<
        ! Pop the top of the stack, and release its' register
        routine  pop rel
            release(top_base)
            pop stack
        end

        constbyteintegerarray fmap(0:15) =
                 0, v in s,    a in s, pgm label, recordformat,         0,        switch,       0,
        {     void, simple,      name,     label, recordformat,         ?,        switch, routine, }
            v in r, v in s,    v in r,    v in s,       a in s,    v in s,        a in s,       0
        { function,    map, predicate,     array,    arrayname, namearray, namearrayname,       ?  }

        !                                                           >> STACK VAR <<
        ! Push a descriptor on the stack corresponding to Var "var no"
        ! We map the variable form to a stack form, and assign a register
        ! for the base if it is non local.  Finally, we absorb the scope
        ! into the base register.
        routine  Stack Var(integer  var no)
            record(varfm)name  w
   
            abort("Stack Var Idx") unless 0 <= var no and var no <= max vars
            w == var(varno)
            stp = stp + 1
            if stp > Max Stack then abort("Push V Stack Overflow")
            top == stack(stp)
            top = 0

            ! Translate "level" into "base register" - if it is non local
            ! we flag it by adding 16 to the desired level, which later will
            ! force us to pick up a pointer register
            if w_level # 0 start
                if w_level = level then top_base = BP else top_base = w_level + 16
            else
                top_base = 0
            finish

            ! AFORM contains the real original declared form, while
            ! FORM contains the on-the-stack subset of possible forms
            top_idname = w_idname;   ! JDM remember variable name
            top_aform = w_form
            top_form = fmap(w_form)
            top_dim = w_dim
            top_type = w_type
            top_disp = w_disp
			top_extdisp = w_disp
            top_scope = w_scope
            top_format = w_format
            top_size = w_size
            top_extra = w_extra
            top_pbase = w_pbase
            top_varno = varno

            monitor(top, "Var stack") if diagnose&1 # 0
        end

        !                                              >> PUSH COPY <<
        ! Push a duplicate of a stack record onto the stack
        routine  push copy(record(stackfm)name  v)
            stp = stp + 1
            if stp > Max Stack then abort("Stack Copy")
            top == stack(stp)
            top = v

            monitor(top, "Stack Copy") if diagnose&1 # 0
        end

        !                                              >> PUSH CONST <<
        ! Push a constant on the stack
        routine  push const(integer  n)
            stp = stp + 1
            if stp > Max Stack then abort("Stack Const")
            top == stack(stp)
            top = 0
            top_disp = n
			top_extdisp = 0
            top_type = integer
            top_form = constant
            monitor(top, "push const") if diagnose&1 # 0
        end

        !---------------------------------------------------------------------
        !STRING PROCESSING
        !---------------------------------------------------------------------
        !                                              >> INPUT STRING VALUE<<
        ! Read a string literal from the iCode stream
        routine  Input String Value( string(255) s)
            integer  i

			current string(0)= length(s)
			for i = 1,1,length(s) cycle
				current string(i) = charno(s,i)
			repeat

            ! if this is about to be used as a literal, put it straight into
            ! the CONST segment and stack it, otherwise leave it in curr string to see
            ! what comes next and stack a dummy zero
            if Pending # 'A' and Pending # '$' start
                otype = con;        ! anonymous %const
                push const( getcots(current string) );
				top_type = string
                top_base = 0;
				top_scope = COT;
				top_form = VinS;
				top_format = current string(0)+1
            else
                push const(0);         ! explicit string initialisation coming next
            finish
        end

		routine Get Alias Value( string(255) s )

			alias = s

		end

		routine Input Real Value( longreal r)
			if r = 0 then start
				push const(0)
			else
				if Pending # 'A' then start
					otype = con;          !  anonymous %const
					push const(0)
					top_type = lreal;
					top_scope = COT;
					top_disp = getcotdouble(r);    !  N.B. ** %fn + side-effect **
					top_extdisp = 0
					top_form = v in s
				finish
			finish
			rvalue = r
		end

        !-------------------------------------------------------
        !LABEL PROCESSING
        !
        ! Labels fixups are handled by pass 3 - we just plant
        ! numerical labels for code locations, and then jump to or call
        ! those labels.  Pass 3 turns them into real locations.
        ! Unfortunately Pass 3 needs unique label numbers whereas
        ! Pass 1 produces lame local label numbers that can
        ! be reused once they've been defined.  We therefore
        ! maintain an indirect database to map Pass 1 label numbers
        ! into unique tags

        !                                                          >> NEW TAG <<
        ! Get the next consecutive Pass 3 label ID
        integerfn  new tag
            owninteger free tag = 999

            free tag = free tag + 1
            result = free tag
        end

        !                                                        >> NEW LABEL <<
        ! Get the next available label database index
        integerfn  New Label
            labs = labs+1;  abort("Labels") if labs > Max Labs
            result = labs
        end

        !                                                        >> FIND LABEL<<
        ! return the index in our label table of the Pass 1 label
        integerfn  Find Label(integer  label)
            integer  lp

            lp = labs
            while lp # first label cycle
                result = lp if labels(lp)_id = label
                lp = lp-1
            repeat
            result = 0
        end

        !                                                     >> DEFINE LABEL <<
        ! This label is "here"
        routine  Define Label(integer  label)
            integer lp
            record(labelfm)name  l

            lp = Find Label(label)
            if lp = 0 start;				  ! Not yet been used
                lp = New Label
                l == labels(lp)
                l_id = label
                l_tag = new tag
            else
                l == labels(lp)
                if l_tag & 16_8000 # 0 and label > 0 then l_tag = new tag
            finish

            dump label(l_tag)
            l_tag = l_tag ! 16_8000
            uncond jump = 0;            ! You can get here
        end;        ! define label

        !                                                          >> JUMP TO <<
        ! A wrapper for conditional jumps to labels that we're going
        ! to map into tags
        routine  Jump To(integer  label, op, flag)
            record(labelfm)name  l
            integer  lp

            lp = Find Label(label)
            if lp = 0 start
                lp = New Label
                l == labels(lp)
                l_id = label
                l_tag = new tag
            else
                l == labels(lp)
                if flag # 0 and l_tag & 16_8000 # 0 then l_tag = new tag
            finish

            ! As a side effect, we also set the global J Tag, which is used
            ! in planting Event block information (a bit hacky, but a PSR feature)
            JTag = l_tag & 16_7FFF
            dump jump(op, JTag)
            if op = JMP then uncond jump = nextcad
        end;				 ! jump to

		routine Jump Forward( integer val, test )
			integer opr

			! FF,TT tests need a value to compare
			! TT == TRUE (#0)
			! FF == FALSE (=0)
			if (Test = FF) or (Test = TT) then dumpri(CMP, AX, 0)
			! Get the required operator for the test
			! We may need to amend the choice of operator
			! depending on the invert/compare unsign "flags"
			opr = TestToOp( test )

			if val = 0 then start
				if last skip # next cad then start
					skip proc = new tag
					dumpjump(opr, skip proc)
				finish
			else
				! Check if we need to reverse the test
				! So, re-choose the operator
				if invert # 0 then Test = Reverse( Test )
				invert = 0;

				! convert the operators to unsigned versions if needed
				if compare unsign # 0 then opr = TestToUnsignedOp( Test ) else opr = TestToOp( Test )
				compare unsign = 0

				jump to(val, opr, 1)
			finish
		end;				 ! Jump Forward

		routine Jump Backward( integer val )
			jump to(val, JMP, 0);
		end

        !-------------------------------------------------------
        ! Stack variable transformations
        !-------------------------------------------------------
        !                                           >> REDUCE <<
        ! Convert a variable which is addressed in a Rec into a simple variable
        ! by loading the indirect value into a register and changing the form
        routine  reduce(record(stackfm)name  v)
            integer  type, form, disp, scope, extdisp
   
            form = v_form - 3;         ! X in REC => X in S
            type = v_type
            disp = v_disp
			extdisp = v_extdisp
            ! Here's a trick - we've got two displacements, DISP and EXTRA, but only
            ! one SCOPE hint.  Which does it belong to?  If the REC form came from
            ! a HAZARD then the scope belongs to the DISP, but for all other cases
            ! the scope belongs to the EXTRA.  If we got here through HAZARD then
            ! the BASE will be BP - for all other cases it will be either a different
            ! register, or zero.
            if v_base = BP start
                scope = v_scope
                v_scope = 0
            else
                scope = 0
            finish
            v_disp = v_extra;
			v_type = integer;
			v_form = v in s

            loadreg(v, anyp)

            v_type = type;
			v_form = form
            v_disp = disp;
			v_extdisp = extdisp;
			v_scope = scope
        end

        !                                              >> AMAP <<
        ! convert V into a descriptor for the address of V
        routine  amap(record(stackfm)name  v)
            integer f
            constintegerarray  addr map(0:15) =
            {    0,        1,  2,  3,       4,   5,               6,                   7, }
                -1,       -2, -3, -4, AV in S,  -5,          V in S,           AV in REC,

            {    8,        9, 10, 11,      12,  13,              14,                  15  }
                -6, V in REC, -7, -8,      -9, -10, {PGM LABEL} -11, {record format} -12

            ! ABD - should be code here to deal with ADDR(pgm label)

            f = addr map(v_form)
            if f < 0 start
                monitor(v, "AMAP target")
                abort("AMAP")
            finish

            ! Try to simplify some forms...
            if v_disp = 0 and v_scope = 0 start
                if (f = AV in S) start
                    if v_base = 0 then f = constant else f = V in R
                finish else if (f = V in REC) or (f = AV in REC) start
                    ! eliminate redundant LOAD
                    if (f = V in REC) then f = A in S else f = V in S
                    v_disp = v_extra
                finish
            finish
            v_type = integer
            v_form = f
        end

        !                                              >> VMAP <<
        ! The inverse of AMAP:  i.e. vmap(amap(x)) => x
        routine  vmap(record(stackfm)name  v)
            integer  f, t
            constintegerarray  var map(0:8) =
            {      0,      1,  2,  3,      4,      5,  6,        7,        8 }
              V in S, V in S, -1, -2, A in S, V in S, -3, A in REC, V in REC

            if (v_form = A in S) or (v_form = A in REC) start
                t = v_type
                amap(v)
                loadreg(v,anyp)
                v_type = t
                v_form = V in S
            finish
            f = var map(v_form)
            v_form = f
            abort("VMap") if f < 0
        end;             !  v map

        !                                              >> ADDRESS <<
        ! convert V into a form in which it is directly addressable
        ! that means either V in R, V in S or Constant
        routine  address(record(stackfm)name  v)
            integer  type, form
   
            monitor(v, "ADDRESS") if diagnose&2 # 0

            form = v_form
            type = v_type
            if form >= V in REC start
                reduce(v)
                form = v_form
            finish

            ! Now pick up a base register if we needed one...
            if v_base > 16 start
                v_base = get display(v_base - 16)
                claim(v_base)
            finish

            return if form = V in R or form = constant

            if form = AV in S start
                if v_base = 0 start
                    v_form = constant
                else
                    if v_disp = 0 and v_scope = 0 start
                        v_form = V in R
                    else
                        loadreg(v, any)
                    finish
                finish
                return
            finish

            return if form = V in S
   
            if form = A in S start
                v_form = V in S
                v_type = integer
                loadreg(v,anyp)
                v_type = type
                v_form = V in S
                v_disp = 0
            finish
        end;             ! address

        !                                              >> LOAD REG <<
        ! Load variable V into register R
        ! Along the way any register the variable owned is released, and
        ! the new register is claimed.
		routine LoadReg(record(stackfm)name v, integer r)
			switch f(0:9)
			integer ptr, op

			monitor(v, "LOAD") if diagnose&2 # 0

			if r = anyf start
				! Equivalents for real numbers...
				! because there's very little clever we can do, we first simplify somewhat...
				Address(v)
				! Now it's either Constant, V in R or V in S - we now turn them
				! all into V in S - the only thing we can load
				! Start with one we have no instructions for, and promote it to
				! something we know how to handle...
				if v_type = byte then loadreg(v, any)
				if v_form = V in R start
					return if v_base >= FR0
					! This must be an integer in a CPU register - we need to store it
					! before we can use it
					v_disp = getwork(word size)
					dumpmr(MOV, BP, v_disp, v_extdisp, v_base)
					release(v_base)
					v_base = BP
					v_scope = 0
					v_form = V in S
					! Now it looks like an integer V in S
				finish
				if v_form = constant start;		  ! This is an integer constant
					if v_disp = 0 start;			  ! We have a special instruction for zero
						r = FR0 + FPU Stack
						dumpflopspec(FLDZ)
						v_base = r
						claim(r)
						v_disp = 0;
						v_form = v in r
						v_type = real
						return
					finish
					! Otherwise, we need it in store
					v_disp = getcotw(v_disp)
					v_form = V in S
					v_base = 0
					v_scope = COT
				finish

				! Now everything that's left is a V in S
				if v_type = integer start
					op = FILD
				else
					if v_type = real start
						op = FLDD
					else
						op = FLDQ
					finish
				finish

				! register is going to be the top of stack
				r = FR0 + FPU Stack
		
				dumpfloprm(op, v_base!v_scope, v_disp, v_extdisp )
				release(v_base)
				v_base = r
				claim(r)
				v_disp = 0;
				v_form = v in r
				v_type = real
				return
			finish

			! If the request is one of the variations on "any" then we need
			! to first allocate a target register.  First, we make a local
			! adjustment because we can't load bytes into "any" register,
			! only into the GP registers...
			if v_type = byte start
				if r = any then r = anyg
				! What's more, there is only one register that is both a pointer
				! and a legal byte destination
				if r = anyp then r = BX
			finish

			! We also map the virtual display into a real register if we
			! need to.  Also, it is possible that an in-store form may
			! be derived from a non-pointer register, so we fix that too.
			if v_base > 16 then start
				v_base = get display(v_base - 16)
				claim(v_base)
			finish

			! Now go ahead and allocate a register
			if r = any then start
				! If we've got a base,
                ! it's not in use by anyone else,
                ! and isn't a display register,
                ! then use it
				if v_base # 0 and activity(v_base) = 1 and displayhint(v_base) = 0 start
					r = v_base
				else
					r = gp reg
				finish
			else
				if r = anyg then start
					if 0 < v_base and v_base <= BX and activity(v_base) = 1 start
						r = v_base
					else
						r = gp reg
					finish
				else
					if r = anyp then start
						if activity(v_base) = 1 and (v_base = BX or v_base = SI or v_base = DI) then start
							r = v_base
						else
							r = pt reg
						finish
					else
!errout;printstring("Line 3583: r = ");write(r, 0);printstring("  v_base = ");write(v_base, 0);printstring("  (Note: reg code 3 is EDX)");newline;undo
						if v_base = r start
							if activity(r) > 1 start;    ! protect other uses
								release(r);  v_base = 0; ! Hide my ownership for the moment
								hazard(r);               ! Zap everybody else
								claim(r);    v_base = r; ! Get it back
							finish
						else
							hazard(r)
						finish
					finish
				finish
			finish
			-> f(v_form)

f(v in rec):
			reduce(v); ->f(v_form)

f(av in rec):
			reduce(v); ->f(v_form)

f(a in rec):
			reduce(v); ->f(v_form)

f(av in r):	Abort("Unexpected Stack Form")

f(a in r):	Abort("Unexpected Stack Form")

f(constant):
			if v_disp = 0 and v_scope = 0 start
				dumprr(XOR, r, r)
			else
				dumprioffset(MOV, r, v_scope, v_disp, v_extdisp )
			finish
			v_base = r
			v_disp = 0
			v_scope = 0
			v_form = v in r
			claim(r)
			return

f(v in r):
            return if v_base = r
			dumprr(MOV, r, v_base)
			release(v_base)
			v_base = r
			v_disp = 0
			v_scope = 0
			v_form = v in r
			claim(r)
			return

f(a in s):
            ! is the register a pointer?
			if r = BX or r = SI or r = DI start
				ptr = r
			else
				ptr = pt reg
			finish
			dumprm(MOV, ptr, v_base!v_scope, v_disp, v_extdisp )
			release(v_base); claim(ptr)
			v_base = ptr
			v_disp = 0
			v_scope = 0
			if v_type = integer start
				dumprm(MOV, r, v_base!v_scope, v_disp, v_extdisp )
			else
				if v_type = byte start
					! watch out for register re-use here...
					if r # v_base then dumprr(XOR, r, r);        ! clear it, but only if it isn't needed
					dumprm8(MOV, r+16, v_base!v_scope, v_disp, v_extdisp )
					if r = v_base then dumpri(AND, r, 255);      ! otherwise a more expensive clear later
					v_type = integer
				else;  ! reals
					abort("Load Real")
				finish
			finish
			release(v_base)
			v_base = r
			v_disp = 0
			v_scope = 0
			v_form = v in r
			claim(r)
			return

f(v in s):
            if v_type = integer start
				dumprm(MOV, r, v_base!v_scope, v_disp, v_extdisp )
			else
				if v_type = byte start
					! watch out for register re-use here...
					if r # v_base then dumprr(XOR, r, r);        ! clear it, but only if it isn't needed
					dumprm8(MOV, r+16, v_base!v_scope, v_disp, v_extdisp )
					if r = v_base then dumpri(AND, r, 255);      ! otherwise a more expensive clear later
					v_type = integer
				else;  ! reals
					abort("Load Real")
				finish
			finish
			release(v_base)
			v_base = r
			v_disp = 0
			v_scope = 0
			v_form = v in r
			claim(r)
			return

f(av in s):
			if v_base # 0 start
				dumprm(LEA, r, v_base!v_scope, v_disp, v_extdisp )
				release(v_base)
				v_type = integer
			else
				! else
				if v_disp = 0 and v_scope = 0 start
					dumprr(XOR, r, r)
				else
					dumprioffset(MOV, r, v_scope, v_disp, v_extdisp )
				finish
			finish
			v_base = r
			v_disp = 0
			v_scope = 0
			v_form = v in r
			claim(r)
			return
		end;	 ! LOAD REG

        ! JDM JDM Adapted from Store routine in Assign
        ! Store the register item reg in location given by LHS stackfm.
        ! This only deals with the integer registers.
        ! Store Reg does NOT cater for floating point registers.
        ! The destination can be one of:
        ! 1) Integer
        ! 2) Byte
        ! 3) Name/Pointer
        routine Store Reg(record(stackfm)name lhs, integer reg)

            if lhs_base = SP start;			  ! it's a push
                if lhs_type = integer or lhs_type = byte start
                    dumpur(PUSH, reg)
                finish
            finish else if lhs_type = integer start
                dumpmr(MOV, lhs_base!lhs_scope, lhs_disp, lhs_extdisp, reg)
            finish else if lhs_type = byte start
                dumpmr8(MOV, lhs_base!lhs_scope, lhs_disp, lhs_extdisp, reg+16)
            finish else if lhs_type = record start
                dumpmr(MOV, lhs_base!lhs_scope, lhs_disp, lhs_extdisp, reg)
            finish
        end;     ! STORE REG

        !                                              >> OPERATION <<
        ! perform the operation OP on the top two elements of the stack.
        !   (single element for unary operators)
        routine  Operation(integer op)
            record(stackfm)name  lhs, rhs
            integer assign pending, work, value, s
            switch oper(1:17), roper(1:17), fold(1:17)
            constintegerarray opmap(1:17) = 
            ADD, SUB, IMUL, IDIV,  0,  AND, OR, XOR, SHL, SHR, IDIV,   0,    0,   0,  NOT, NEG,   0

            constintegerarray flopmap(1:17) = 
            FADD, FSUB, FMUL, FDIV, 0, 0, 0, 0, 0, 0, 0,   0,   0,    FDIV, 0, FCHS, FABS

            constintegerarray indec(-1:1) = DEC, 0, INC;  ! decrement, and increment opcodes

            routine swap
                record(stackfm) temp
                temp = lhs
                lhs = rhs
                rhs = temp
            end

            assign pending = 0
            rhs == top
            if op < Unaries then start
                lhs == stack(stp-1)
                if lhs_type = real or lhs_type = lreal or op >= REXPx then ->reals
            finish

            if rhs_type = real or rhs_type = lreal then ->reals

            if rhs_form = constant and (op >= Unaries or lhs_form = constant) then ->fold(op)

            ! now look for optimisations for x = x <op> whatever
            if Pending = 'S' or Pending = 'j' start;              ! the next task is an assignment
                if op >= Unaries start
                    if same(top, stack(stp-1)) # 0 then assign pending = 1
                else
                    if same(lhs, stack(stp-2)) # 0 then assign pending = 1
                finish
            finish

            ->oper(op)
oper(NOTx):
oper(NEGx):
            ! we optimise for e.g. fred = -fred as one instruction
            if assign pending # 0 then start
                read symbol(Pending)
                address(rhs)
                if rhs_type = byte start
                    dumpum8(opmap(op), rhs_base!rhs_scope, rhs_disp, rhs_extdisp )
                else
                    dumpum(opmap(op), rhs_base!rhs_scope, rhs_disp, rhs_extdisp )
                finish
                pop rel
                pop rel
                return
            finish
            loadreg(rhs, any)
            dumpur(opmap(op), rhs_base)
            return

            ! 8086 has no "abs" instructions, so we do a test and jump
oper(ABSx):
            loadreg(rhs, any)
			dumpri(CMP, rhs_base, 0)
            work = new tag
            dumpjump(JGE, work)
            dumpur(NEG, rhs_base)
            dumplabel(work)
            return

oper(ADDX):
            if lhs_form = constant then swap
            ! and fall through to minus
oper(SUBx):
            ! First look for fred = fred + <whatever>
            ! We can only safely do this for bytes if we're jamming or ignoring overflow
            if (assign pending # 0) c
              and ((lhs_type = integer) or ((control & check capacity) = 0) or (Pending = 'j')) then start
                readsymbol(Pending)  ; ! we will do the assignment ourselves
                address(lhs)      ; ! make LHS accessible
                if rhs_form = constant then start
                    value = rhs_disp
                    if value # 0 start
                        if op = SUBx then value = -value
                        ! look for increment or decrement instructions
                        if value < 2 and value > -2 then start
                            if lhs_type = byte start
                                dumpum8(indec(value), lhs_base!lhs_scope, lhs_disp, lhs_extdisp )
                            else
                                dumpum(indec(value), lhs_base!lhs_scope, lhs_disp, lhs_extdisp )
                            finish
                        else
                            if lhs_type = byte start
                                dumpmi8(opmap(op), lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_disp)
                            else
                                dumpmi(opmap(op), lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_disp)
                            finish
                        finish
                    finish
                else;          ! RHS not a constant
                    loadreg(rhs, any)
                    if lhs_type = byte start
                        dumpmr8(opmap(op), lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_base+16)
                    else
                        dumpmr(opmap(op), lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_base)
                    finish
                finish
                pop rel
                pop rel
                pop rel
                return
            finish

            ! So, there is no assign pending        
            if rhs_form = constant then start
                value = rhs_disp
                if op = SUBx then value = -value
                ! If it is already an address, do the math on the address offset
                if lhs_form = avins or lhs_form = avinrec start
                    lhs_disp = lhs_disp + value
                else
                    loadreg(lhs, any)
                    ! We don't particulary try for it, but if we ended up with a pointer
                    ! register, we might as well convert this to use the address form...
                    if lhs_base = BX start;		  ! BX is the only GP reg that's also a pointer
                        lhs_form = avins
                        lhs_disp = value
                    else;								 ! otherwise, don't bother deferring the operation
                        ! look for increment or decrement instructions
                        if value < 2 and value > -2 then start
                            if value # 0 then dumpur(indec(value), lhs_base)
                        else
							dumpri(opmap(op), lhs_base, rhs_disp)
                        finish
                    finish
                finish
            else;                   ! not a constant
                if op = ADDx and rhs_form = v in r then swap;    ! commutative, so flip it
                loadreg(lhs, any)
                if rhs_type = byte start
                    loadreg(rhs, any)
                else
                    address(rhs)
                finish
                dumprv(opmap(op), lhs_base, rhs)
            finish
            pop rel;    ! the RHS
            return
oper(ANDx):
oper(ORx):
oper(XORx):
            !  Logical ops are a subset of ADD - similar behaviour, but no inc/dec/addr short forms
            if lhs_form = constant then swap
            ! First look for fred = fred <op> <whatever>
            if assign pending # 0 then start
                readsymbol(Pending);   ! we will do the assignment ourselves
                address(lhs);				! make LHS accessible
                if rhs_form = constant then start
                    value = rhs_disp
                    if lhs_type = byte start
                        warn(8) if rhs_disp & (¬255) # 0
                        dumpmi8(opmap(op), lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_disp)
                    else
                        dumpmi(opmap(op), lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_disp)
                    finish
                else;          ! RHS not a constant
                    loadreg(rhs, any)
                    if lhs_type = byte start
                        dumpmr8(opmap(op), lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_base+16)
                    else
                        dumpmr(opmap(op), lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_base)
                    finish
                finish
                pop rel; ! RHS
                pop rel; ! LHS
                pop rel; ! Assignment destination
                return
            finish

            ! So, there is no assign pending        
            if rhs_form = constant then start
                value = rhs_disp
                loadreg(lhs, any)
				dumpri(opmap(op), lhs_base, value)
            else;                   ! not a constant
                if rhs_form = v in r then swap;   ! all these are commutative, so flip it to make it easier
                loadreg(lhs, any)
                if rhs_type = byte and op = ANDx start;    ! AND needs all the bits to make sense
                    loadreg(rhs, any);                        ! NB Load changes type to Integer
                else
                    address(rhs)
                finish
                if rhs_type = byte start;   ! must be V in S - everything else would be Integer
                    dumprm8(opmap(op), lhs_base+16, rhs_scope!rhs_base, rhs_disp, rhs_extdisp )
                else
                    dumprv(opmap(op), lhs_base, rhs)
                finish
            finish
			pop rel;    ! the RHS
            return

oper(MULx):
            if lhs_form = constant or rhs_base = AX then swap
            if rhs_form = constant then start
                value = rhs_disp
                if value = 0 then start;    ! mul by zero is zero
                    release(lhs_base)
                    lhs = rhs
                    pop stack
                    return
                finish
                if value = 1 then start;    ! mul by 1 is the identity
                    pop stack
                    return
                finish
                s = mulshift(value);        ! find a shift factor
                if s > 0 then start
                    rhs_disp = s
                    op = LSHx
                    -> shift it
                finish
                ! 8086 multiply instruction doesn't have an immediate operand form
                ! so we use an entry in the constant table...
                rhs_base = 0; rhs_scope = COT; rhs_disp = getcotw(value)
                rhs_form = V in S
                ! and fall through to the not-a-constant path
            finish
do mul:
            loadreg(lhs, AX)
            address(rhs)
            hazard(DX)
            if rhs_form = V in R start
                dumpur(IMUL, rhs_base)
            else
                dumpum(IMUL, rhs_base!rhs_scope, rhs_disp, rhs_extdisp )
            finish
            pop rel
            return
oper(DIVx):
oper(REMx):
            loadreg(lhs, AX)
            address(rhs)
            hazard(DX)
            dumpsimple(CWD)
            ! Plain 8086 Divide instruction also has no immediate operand form, so
            ! we move constants to the COT
            if rhs_form = constant start
                if rhs_disp = 0 then warn(1)
                rhs_base = 0; rhs_scope = COT; rhs_disp = getcotw(rhs_disp)
                rhs_form = V in S
            finish
            if rhs_form = V in R start
                dumpur(IDIV, rhs_base)
            else
                dumpum(IDIV, rhs_base!rhs_scope, rhs_disp, rhs_extdisp )
            finish
            pop rel
            if op = DIVx then start
                lhs_base = AX
            else
                lhs_base = DX
                release(AX)
                claim(DX)
            finish
            return
oper(LSHx):
oper(RSHx):
shift it:
            if (assign pending # 0) c
              and ((op = RSHx) or (lhs_type = integer) or (control & check capacity = 0) or (Pending = 'j')) then start
                readsymbol(Pending);   ! we will do the assignment ourselves
                address(lhs);				! make LHS accessible
                if rhs_form = constant start
                    warn(6) unless 0 <= rhs_disp <= 31
                    if rhs_disp # 0 start;     ! shift by zero is a no-op
                        if lhs_type = byte start
                            dumpmi8(opmap(op), lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_disp)
                        else
                            dumpmi(opmap(op), lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_disp)
                        finish
                    finish
                else;          ! RHS not a constant
                    ! Since the shift instruction only uses the bottom 5 bits of the
                    ! value in CX, the value is "byte safe".  Rather than do a full
                    ! "loadreg(rhs,CX)" we therefore fiddle about and do it the hard way
                    ! to save redundant coding
                    if rhs_type = byte start
                        hazard(CX)
                        address(rhs)
                        dumprm8(MOV, CL, rhs_scope!rhs_base, rhs_disp, rhs_extdisp )
                    else
                        loadreg(rhs,CX)
                    finish
                    if lhs_type = byte start
                        dumpmr8(opmap(op), lhs_base!lhs_scope, lhs_disp, lhs_extdisp, CL)
                    else
                        dumpmr(opmap(op), lhs_base!lhs_scope, lhs_disp, lhs_extdisp, CX)
                    finish
                finish
                pop rel; ! RHS
                pop rel; ! LHS
                pop rel; ! Assignment destination
                return
            finish

            ! deal with constant shifts first...
            if rhs_form = constant then start
                value = rhs_disp
                warn(6) unless 0 <= value <= 31
                if value # 0 start
                    loadreg(lhs, any)
					dumpri(opmap(op), lhs_base, value)
                finish
            else;                               ! RHS variable
                ! Since the shift instruction only uses the bottom 4 bits of the
                ! value in CX, the value is "byte safe".  Rather than do a full
                ! "loadreg(rhs,CX)" we therefore fiddle about and do it the hard way
                ! to save redundant coding
                if rhs_type = byte start
                    hazard(CX)
                    address(rhs)
                    dumprm8(MOV, CL, rhs_scope!rhs_base, rhs_disp, rhs_extdisp )
                    release(rhs_base)
                    rhs_base = CX
                    claim(CX)
                else
                    loadreg(rhs,CX)
                finish
                loadreg(lhs, any)
                dumprr(opmap(op), lhs_base, CX);
            finish
            pop rel
            return
oper(EXPx):
            if rhs_form = constant then start
                if rhs_disp = 0 start
                    pop rel
                    pop rel
                    push const(1)
                    return
                finish
                if rhs_disp = 1 then start
                    pop rel
                    return
                finish
                if rhs_disp = 2 then start
                    rhs = lhs
                    claim(rhs_base)
                    ->do mul
                finish
            finish
            loadreg(rhs, any)
            dumpur(PUSH, rhs_base)
            pop rel
            loadreg(lhs, any)
            dumpur(PUSH, lhs_base)
            release(lhs_base)
            perm(iexp, 2)
            lhs_base = AX; claim(AX)
            lhs_form = V in R
            return
oper(REXPx):
oper(RDIVx):
            abort("Oper unexpected op")

            !-----------------------------------------------
            ! Fold constant expressions at compile time
fold(NEGx):
            value = -rhs_disp; -> set unary
fold(NOTx):
            value = ¬rhs_disp; -> set unary
fold(ABSx):
            value = rhs_disp; if value < 0 then value = -value; -> set value
fold(ADDx):
            value = lhs_disp + rhs_disp; -> set value
fold(SUBx):
            value = lhs_disp - rhs_disp; -> set value
fold(ORx):
            value = lhs_disp ! rhs_disp; -> set value
fold(ANDx):
            value = lhs_disp & rhs_disp; -> set value
fold(XORx):
            value = lhs_disp !! rhs_disp; -> set value
fold(LSHx):
            value = lhs_disp << rhs_disp; -> set value
fold(MULx):
            value = lhs_disp * rhs_disp; -> set value
fold(RSHx):
            value = lhs_disp >> rhs_disp; -> set value
fold(EXPx):
            if rhs_disp < 0 then abort("Fold -ve Exp")
			value = 1
            for op=1, 1, rhs_disp cycle
                value = value * lhs_disp
            repeat
            -> set value
fold(REMx):
fold(DIVx):
            value = rhs_disp;
            warn(1) and value = 1 if value = 0
            value = lhs_disp // value
            if op = DIVx then -> set value
            value = lhs_disp - (rhs_disp * value)
            -> set value
fold(REXPx):
            abort("Fold REXPx - Not implemented")
fold(RDIVx):
            abort("Fold RDIVx - Not implemented")

set value:
            pop stack
set unary:
            top_disp = value
            return
fold(CONCx):
            abort("Fold CONCx - Not implemented")

            !--------------------------------------------------------------------
            ! String operations - the only one is concatenate...
oper(CONCx):
            if assign pending # 0 start;	  ! It's S = S.T
                amap(lhs)
                loadreg(lhs, any)
                dumpur(PUSH, lhs_base)
                amap(rhs)
                loadreg(rhs, any)
                dumpur(PUSH, rhs_base)
                pop rel
                pop rel
                dumppushi(0, lhs_size, 0)
                if Pending = 'S' then perm(sconc, 3) else perm(sjconc, 3)
                ! and finally, skip the pending assignment, and drop the LHS
                readsymbol(Pending)
                pop rel
                return
            finish

            ! here we've got T.U - if T is already in a WORK location
            ! we've got a simple append.  If it is a user variable, we've
            ! got to both copy it to a temp area and do the append
            if Is Work(lhs) = 0 start;		  ! Not a work area
                work = getwork(256)
                push const(work)
                top_form = av in s
                top_base = BP
                loadreg(top, any)
                dumpur(PUSH, top_base)
                pop rel
                amap(lhs)
                loadreg(lhs, any)
                dumpur(PUSH, lhs_base)
                release(lhs_base)
                dumppushi(0, 255, 0)
                perm(smove, 3)
                ! Now we need to redefine the LHS as our temporary area
                lhs = 0;			! gratuitous clear-it-all-out
                lhs_type = string
                lhs_form = V in S
                lhs_base = BP
                lhs_disp = work
                lhs_size = 255
            finish

            ! Here we are doing an in-situ concatenation
            ! We want to leave the result as a normal variable, so we
            ! suck up a copy for the AMAP fiddling
            push copy(lhs)
            amap(top)
            loadreg(top, any)
            dumpur(PUSH, top_base)
            poprel
            amap(rhs)
            loadreg(rhs, any)
            dumpur(PUSH, rhs_base)
            pop rel
            dumppushi(0, lhs_size, 0)
            perm(sconc, 3)
            return
Reals:
            if op < Unaries then loadreg(lhs, anyf)
            if op # REXPx then loadreg(rhs, anyf)
            ->roper(op)

roper(NEGx):
roper(ABSx):
            dumpfloprr(flopmap(op), rhs_base, rhs_base)
            return

roper(ADDx):
roper(MULx):
            ! Commutative, so we don't care
            if lhs_base > rhs_base then swap
            dumpfloprr(flopmap(op), lhs_base, rhs_base)
            pop rel
            return

roper(SUBx):
roper(DIVx):
roper(RDIVx):
            ! We can't swap these, so we use the reverse form of
            ! the opcode (which in our internal form is always one
            ! more than the basic opcode index)
            op = flopmap(op)
            if lhs_base > rhs_base start
                swap
                op = op + 1
            finish
            dumpfloprr(op, lhs_base, rhs_base)
            pop rel
            return

roper(REXPx):
            ! This is implemented as a PERM routine
            loadreg(rhs, any)
            dumpur(PUSH, rhs_base)
            pop rel
            ! The usual slightly clunky floating point "push"
            work = ptreg
			dumpri(SUB, SP, 8)
            dumprr(MOV, work, SP)
            dumpfloprm(FSTQ, work, 0, 0)
            release(lhs_base)
            perm(fexp, 1 + (8//wordsize))
            ! Since rexp is actually a standard C routine, the result will
            ! be on the FPU stack
            lhs_base = FR0; claim(FR0)
            fpu stack = 1
            lhs_form = V in R
            lhs_type = lreal
            return

roper(NOTx):
			abort("NOTx: Unsupported Real Operation")
roper(ANDx):
			abort("ANDx: Unsupported Real Operation")
roper(ORx):
			abort("ORx: Unsupported Real Operation")
roper(XORx):
			abort("XORx: Unsupported Real Operation")
roper(REMx):
			abort("REMx: Unsupported Real Operation")
roper(LSHx):
			abort("LSHx: Unsupported Real Operation")
roper(RSHx):
			abort("RSHx: Unsupported Real Operation")
roper(EXPx):
			abort("EXPx: Unsupported Real Operation")

        end;         !     Operation

        !                                                           >> ASSIGN <<
        ! ASSOP =   -1:  parameter assignment
        !            0:  == assignment
        !            1:  =  assignment
        !            2:  <- assignment
        routine  assign(integer  assop)
            record(stackfm)name  lh,rh
            record(stackfm)  temp
            integer  n, p, form, r

            ! Store the item in RHS to LHS.  Encapsulates the dificulties
            ! of variable length items and pushing things on the stack to
            ! keep the rest of "Assign" looking tidy
            routine Store(record(stackfm)name lhs, rhs)
                integer pt, s, op

                if lhs_base = SP start;			  ! it's a push
                    if lhs_type = integer or lhs_type = byte start
                        if rhs_type = byte start
                            loadreg(rhs, any)
                        else
                            address(rhs)
                        finish
                        dumpvpush(rhs)
                    else;		 ! must be a real
                        if lhs_type = real start
                            s = 4
                            op = FSTD
                        else
                            s = 8
                            op = FSTQ
                        finish
                        loadreg(rhs, anyf)
                        pt = ptreg
						dumpri(SUB, SP, s)
                        dumprr(MOV, pt, SP)
                        dumpfloprm(op, pt, 0, 0)
                    finish
                    return
                finish
                if lhs_type = integer start
                    if rhs_form = constant and rhs_scope = 0 start
                        dumpmi(MOV, lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_disp)
                    else
                        loadreg(rhs, any)
                        dumpmr(MOV, lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_base)
                    finish
                else
                    if lhs_type = byte start
                        if rhs_form = constant and rhs_scope = 0 start
                            dumpmi8(MOV, lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_disp)
                        else
                            if rhs_type = byte start;     ! try to avoid pointless promoting to an int
                                ! We will reproduce a "Load" but without the word extension
                                address(rhs)
                                pt = gp reg
                                dumprm8(MOV, pt+16, rhs_base!rhs_scope, rhs_disp, rhs_extdisp )
                                release(rhs_base)
                                rhs_base = pt; rhs_form = V in R; rhs_type = Integer
                                claim(pt)
                            else
                                loadreg(rhs, any)
                                ! ABD - should add a capacity check here
                            finish
                            dumpmr8(MOV, lhs_base!lhs_scope, lhs_disp, lhs_extdisp, rhs_base+16)
                        finish
                    else
                        loadreg(rhs, anyf)
                        if lhs_type = real start
                            op = FSTD
                        else;  ! long real
                            op = FSTQ
                        finish
                        dumpfloprm(op, lhs_base!lhs_scope, lhs_disp, lhs_extdisp )
                    finish
                finish
            end

            abort("Assign Stack") if stp < 2

            rh == top
            lh == stack(stp-1)

            form = lh_form;        ! to avoid the ravages of amap, load etc
   
            if diagnose&4 # 0 start
                monitor(lh, "ASS LH")
                monitor(rh, "ASS RH")
            finish
   
            if same(lh, rh) # 0 then start
                pop rel
                pop rel
                return
            finish

            if assop < 0 start;             ! Parameter
                if lh_base >= 128 start;       ! Special - prim routine
                    temp = lh;  lh = rh;  rh = temp
                    return
                finish

                ! Extract the next formal parameter and make it our target
                lh_pbase = lh_pbase - 1
                Stack Var(lh_pbase)
                ! Now make our destination look reasonable
                lh == top
                lh_base = SP;							! target is the stack
                assop = 0 if lh_form # v in s;	 ! %name parameter is '=='

                ! We need special treatment for procedure parameters
                if 7 <= lh_aform <= 10 start;	  ! this is a procedure
                    assop = 1;							! we will treat it as a value assignment
                    rh_type = integer;				! of an integer
                    lh_type = integer; lh_form = V in S
                    if rh_base # 0 start;			  ! RH is already a parameter
                        rh_form = V in S
                    else
                        if rh_scope = EXT start;	  ! it is an external procedure
                            rh_form = A V in S;		! pick up the addres
                        else;							 ! it is a local procedure
                            ! HACK: local procedures are Tags until Pass3 fixes them up.  The
                            ! only way we have of converting tags to addresses is with the switch
                            ! table - so we'll plant a fake switch entry for the label of the
                            ! local routine, and then load that value!
                            if swtp >= Max Switch then abort("Proc - Switch Table Full")
                            swtab(swtp) = rh_disp; rh_disp = swtp * word size; swtp = swtp+1
                            rh_scope = SWT
                            rh_form = V in S
                        finish
                    finish
                finish
            finish

            if array <= rh_aform and rh_aform <= namearrayname start;           ! Arrayname
                ! An array name is two words - a pointer to the data and a
                ! pointer to the dope vector.  If the RHS is already one of these
                ! then we just want to copy the two words.  If it is a static
                ! array, we need to map the data to make a pointer, and its' dope
                ! vector will be in the constant table, so we fetch that.
                amap(lh)
                address(lh)
                amap(rh);            ! This works because arrays are stacked as V in S, arraynames are A in S
                address(rh)
                ! We do the dope vector first - that makes it easier when we're parameter passing
                if rh_aform = array or rh_aform = name array start;      ! simple static - DV in COT
                    ! We will rustle up a dummy record for the DV address
                    temp = 0
                    temp_form = A V in S
                    temp_type = integer
                    temp_disp = rh_pbase
                    temp_scope = COT
                else;                                                    ! already an array name
                    temp = rh; claim(temp_base)
                    temp_disp = temp_disp + word size
                finish
                lh_disp = lh_disp+word size
                store(lh, temp)
                release(temp_base)
                lh_disp = lh_disp-word size
                store(lh, rh)
                pop rel
                pop rel
                return
            finish

            if lh_type = general start;       ! general %name parameter
                abort("Assign GenName") unless assop = 0;  ! Only '==' is allowed
                ! A general name pointer is two words - the pointer itself
                ! and a second word to convey type information.  If the RHS
                ! is already one of thse guys it's easy - just copy the two
                ! words.  Otherwise, we need to rustle up the second word at
                ! compile time.
                amap(lh)
                address(lh)
                if rh_type = general start
                    temp = rh;	! make a copy for the second word
                    claim(temp_base); temp_disp = temp_disp + word size
                    amap(temp)
                else
                    temp = 0
                    temp_type = integer
                    temp_disp = (rh_size << 4) + genmap(rh_type)
                finish
                ! We do the words backwards, so that parameter push works
                lh_disp = lh_disp + word size
                store(lh, temp)
                release(temp_base)
                lh_disp = lh_disp-word size
                amap(rh)
                store(lh, rh)
                pop rel
                pop rel
                return
            finish

            if assop = 0 start;          ! ==
                amap(lh);      ! destination
                amap(rh);      ! ABD %string(*)%name  NOT handled special here - should be?
            finish

            if Lh_Type = record start
                if lh_base = SP start;		  ! pass record by value - destination is the stack
                    n = lh_size
                    hazard(DI)
					dumpri(SUB, SP, lh_size)
                    dumprr(MOV, DI, SP)
                    claim(DI)
                    lh_base = DI
                else
                    n = Min Record Size(Lh, Rh)
                    amap(lh)
                    loadreg(lh, DI)
                finish
                hazard(CX)
				dumpri(MOV, CX, n)
                if rh_Form = Constant start
                    hazard(AX)
                    dumprr(XOR, AX,AX);		! get a zero
                    dumprepstosb
                else
                    amap(rh)
                    loadreg(rh, SI)
                    dumprepmovsb
                finish
                pop rel
                pop rel
                return
            finish

            if lh_type = string start
                if assop > 0 and rh_format = 1 start;       ! null string as zero byte ?
                    lh_type = byte
                    pop rel;       ! zap current RHS
                    push const(0);     ! get a zero
                    assign(assop); ! and assign it
                    return
                finish

                ! our copy routines expect DEST then SOURCE then LENGTH on the stack
                if lh_base = SP start;		  ! pass string by value - destination is the stack
                    ! space is string size, plus one for length, plus make it even
                    p = lh_size + 1; p = (p+align) & (¬align)
					dumpri(SUB, SP, p)
                    ! we want to Push SP here - sadly different versions of x86
                    ! architecture have different interpretations of "PUSH SP", so...
                    r = gp reg
                    dumprr(MOV, r, SP)
                    dumpur(PUSH, r)
                else
                    amap(lh)
                    loadreg(lh, any)
                    dumpur(PUSH, lh_base)
                finish
                ! It is likely that the RH variable is a temporary work area
                ! Before we trash the information, we try to release it
                Return Work(rh_disp)
                amap(rh)
                loadreg(rh, any)
                dumpur(PUSH, rh_base)
                pop rel
                pop rel
                dumppushi(0, lh_size, 0)
                if assop = 2 then perm(sjam, 3) else perm(smove, 3)
                return
            finish

            address(lh)
            store(lh, rh)
            pop rel
            pop rel
        end;             !  assign

        !                                                      >> ARRAY REF <<
        ! Array references always use the PERM
        ! unless they are 1 dimensional,
        ! AND the %control bit has been turned off
        routine  array ref(integer mode)
            record(stackfm)name av
            integer type, form, size, format

            if mode#0 then start
                ! Put non-terminal index onto stack for PERM
                if top_type = byte start
                    loadreg(top, any)
                else
                    address(top)
                finish
                dumpvpush(top)
                pop rel
                return
            finish
   
            av == stack(stp-1)
            size = av_size
            size = size + 1 if av_type = string
            form = av_aform
            if form=namearray or form=namearrayname then size = word size

            if control & check array = 0 and av_dim = 1 start
                ! This will be unchecked, the top of the stack is the only index (1D),
                ! so we can do a cheap multiplication here
                if size#1 start;        ! multiply offset by var size
                    push const(size)
                    Operation(MULx)
                finish
            else
                ! This is the final (and perhaps only) subscript for a checked array,
                ! so we are going to use the Perm - therefore pass this as a parameter
                if top_type = byte start
                    loadreg(top, any)
                else
                    address(top)
                finish
                dumpvpush(top)
                pop rel
            finish

            ! How we do the rest of the access depends on whether this is a simple
            ! static array, or an array name...

            if form = arrayname or form = namearrayname start;      ! array is a "name"
                ! We will AMAP the name, so we remember the info and then put it all back later
                type = av_type
                format = av_format
                size = av_size
                if form = arrayname then form = v in s else form = a in s
                amap(av)

                if control & check array # 0 or av_dim > 1 start;        ! do the rest of the check
                    ! This is a bit clunky, because we may load registers in order
                    ! to access AV, only to Hazard them for the PERM
                    address(av)
                    push copy(av); claim(top_base)
                    top_disp = top_disp + word size;   ! Dope Vector address follows A(0)
                    dumpvpush(top)
                    pop rel
                    perm(aref, av_dim + 1); ! DV word, plus a word for every subscript
                    push const(0)
                    top_form = V in R; top_base = AX; claim(AX)
                finish

                loadreg(top, anyp);        ! make sure index is in a pointer register
                Operation(ADDx)
                top_type = type
                top_form = form
                top_format = format
                top_size = size
                top_disp = 0
            else;      ! simple arrays are always 1D, but can still be checked
                if control & check array # 0 start
                    ! Pass a pointer to the Dope Vector
                    dumppushi(COT, av_pbase, 0);     ! simple arrays have compile-time DV's in the COT
                    perm(aref, 2)
                    push const(0)
                    top_form = V in R; top_base = AX; claim(AX)
                finish

                address(av)
                if av_form # v in s then abort("Aref Form")
                if top_form = constant start;    ! simple constant a(k)
                    av_disp = av_disp + top_disp; ! just add it to the offset
                else
                    loadreg(top, anyp);              ! pick up index in a pointer
                    if av_base # 0 start;         ! add the base we've already got
                        dumprr(ADD, top_base, av_base)
                        release(av_base)
                    finish
                    av_base = top_base
                finish
                if form = array then av_form = v in s else av_form = a in s
                pop stack
            finish

            top_aform = 0; ! not an array any more

        end;        !  array ref

        !                                              >> TEST ZERO <<
        ! test a real/integer/byte variable against zero
        routine  test zero(record(stackfm)name  v)

            if v_type = integer or v_type = byte start
                loadreg(v,any)
		dumpri(CMP, v_base, 0)
            else
                abort("Test Zero")
            finish
        end;       ! test zero

        routine Compare Records(record(stackfm)name L, R, integer N)
            ! JDM eventually compare the byte values of each record
            ! in the interim, barf
            abort("Compare Records")
        end

        !                                              >> COMPARE REALS <<
        routine  compare reals(record(stackfm)name  l,r)
            loadreg(l, anyf)
            loadreg(r, anyf)
            hazard(AX)
            ! who's ended up on top?
            if l_base > r_base start;     ! l_base is the top of the FPU stack
                dumpfloprr(FCMP, r_base, l_base)
            else
                dumpfloprr(FCMP, l_base, r_base)
                invert = invert !! 1
            finish
            dumpflopspec(FSTSW); ! puts status into AX
            dumpsimple(SAHF);    ! and move it to flags
            compare unsign = 1;  ! because FPU reports as if operands were unsigned
        end;         ! compare reals

        !                                              >> COMPARE STRINGS <<
        routine  compare strings(record(stackfm)name  l,r)
            record(stackfm)name  temp
            if l_base = COT and l_disp = null string start
                temp == r;  r == l; l == temp
                invert = invert !! 1
            finish
            if r_base = COT and r_disp = null string start
                l_type = byte
                test zero(l)
            else
                amap(l)
                loadreg(l, any)
                dumpur(PUSH, l_base)
                amap(r)
                loadreg(r, any)
                dumpur(PUSH, r_base)
                perm(scomp, 2)
				dumpri(CMP, AX, 0)
            finish
        end;        ! compare strings

        !                                              >> COMPARE <<
        routine  compare(record(stackfm)name  l,r)
            if l_type = 0 or l_type = string start
                compare strings(l,r);  return
            finish
            if floating(l)#0 or floating(r)#0 start
                compare reals(l,r);  return
            finish
            if zero(r) # 0 start
                test zero(l);  return
            finish
            if zero(l) # 0 start
                test zero(r);  invert = invert !! 1
                return
            finish
            if l_Type = Record start
                Compare Records(L, R, Min Record Size(L, R)) ;! currently aborts
                return
            finish
            loadreg(l,any)
            if r_type = byte start
                loadreg(r, anyg)
            else
                address(r)
            finish
            dumprv(CMP, l_base, r)
        end;         ! compare

        !                                              >> RESOLVE <<
        routine  resolve(integer  flag)
            !S -> A.(B).C
            if flag&1 = 0 then push const(0) else amap(top);    ! C missing?
            loadreg(top, any)
            dumpur(PUSH, top_base)
            pop rel
            amap(top);  ! B
            loadreg(top, any)
            dumpur(PUSH, top_base)
            pop rel
            if flag&2 = 0 then push const(0) else amap(top);    ! A missing?
            loadreg(top, any)
            dumpur(PUSH, top_base)
            pop rel
            amap(top);  ! S
            loadreg(top, any)
            dumpur(PUSH, top_base)
            pop rel
            perm(sresln, 4)
            if flag&4 # 0 then dumpri(CMP, AX, 0)
        end;            ! resolve

        integerfn enter
            integer cad;

            uncond jump = -1; ! can get here

            ! This is a convenient place to include external definitions if needed
            if potype >= external start
               fill external(CODE, next cad, external id)
            finish

            cad = nextcad;
            dumpstaticalloc(cad, level, block name); ! plant dummy ENTER instruction and pass marker to pass 3
            result = cad;
        end

        !                                              >> DUMP RETURN <<
        routine  dump return
            return if uncond jump = next cad;            !  can't get here ?

            ! Pure 8086 would need these two
            !	dumprr(MOV, SP, BP)
            !	dumpur(POP, BP)
            ! but now we use this instead...
            dumpsimple(LEAVE)

            dumpsimple(RET);

            uncond jump = next cad
        end;           ! return

        ! Routine to do "to string" as an in-line, either by making
        ! a constant string in the CONST area, or putting one onto
        ! the current workspace
        routine  compile to string(record(stackfm)name  v)
            integer tmp

            if const(v)#0 start
                current string(0) = 1;  current string(1) = v_disp&255
                v_base = 0; v_scope = COT; v_disp = getcots(current string)
            else
                tmp = getwork(word size)
                loadreg(v,anyg);        ! Must be a byte-addressable register
                dumpmi(MOV, BP, tmp, 0, 1)
                dumpmr8(MOV, BP, tmp+1, 0, v_base+16)
                release(v_base)
                v_base = BP;  v_scope = 0; v_disp = tmp
            finish
            v_type = string;  v_form = VinS;  v_size = 1
        end

        !                                              >> COMPILE CALL <<
        ! Call the routine on the top of the stack.  Note - the parameters
        ! are all hidden underneath the routine, so we need to push them
        ! here
        routine  Compile Call(record(stackfm)name  v)
            switch  b(1:15)
            !  1 = rem
            !  2 = float
            !  3 = to string
            !  4 = addr
            !  5 = integer
            !  6 = byte integer
            !  7 = string
            !  8 = record
            !  9 = real
            ! 10 = long real
            ! 11 = length
            ! 12 = charno
            ! 13 = type of ( type of general name parameter )
            ! 14 = size of ( physical length in bytes )
            ! 15 = int (from real)

            constbyteintegerarray  new type(5:12) =
                        1,       5,    3,       4,    2,    6,     5,    5
            !         integer, byte, string, record, real, lreal, byte, byte
            integer  t,l,p

            if v_base >= 128 start;           !  built-in primitive
                l = 0;  t = v_disp;  sym = 0;      ! 'sym=0' used as flag elsewhere
                pop rel
                -> b(t)
b(1):			Operation(REMx);		-> esac;	! REM
b(2):			loadreg(top, anyf);		-> esac;	! FLOAT
b(3):			compile to string(top);	-> esac;	! TO STRING
b(4):			amap(top);				-> esac;	! ADDR
b(5):;                                              ! INTEGER
b(6):;                                              ! BYTE
b(7):;                                              ! STRING
b(8):;                                              ! RECORD
b(9):;                                              ! REAL
b(10):;                                             ! LONG REAL
                vmap(top);  top_type = new type(t)
                top_size = v size(top_Type)
				-> esac
b(11):;                                             ! LENGTH
                push const(0);                      ! length is charno zero
                amap(stack(stp-1))
                Operation(ADDx);  !LHS&RHS reversed in Operation??
                vmap(top);  top_type = new type(t)
                top_size = v size(top_Type)
				-> esac
b(12):;                                             ! CHARNO
                amap(stack(stp-1))
                Operation(ADDx);  !LHS&RHS reversed in Operation??
                vmap(top);  top_type = new type(t)
                top_size = v size(top_Type)
				-> esac
b(13):;                                             ! type of(..)
b(14):;                                             ! size of(..)
                if top_type # general start;        ! type explicitly specified
                    if t = 13 start;        ! type of
                        p = gen map(top_type)
                    else
                        p = top_size;  p = p+1 if top_type = string
                    finish
                    release(top_base)
                    top_type = integer;  top_form = constant
                    top_base = 0;  top_disp = p
                else
                    top_disp = top_disp + word size;      ! reference property-word
                    top_form = V in S;  top_type = integer
                    if t = 13 start;           ! type of
                        push const(15);  Operation(ANDx)
                    else;                      ! size of
                        push const(4);  Operation(RSHx)
                    finish
                finish
				-> esac
b(15):;                                             ! INT(real)
                loadreg(top, anyf)
                release(top_base)
                p = getwork(word size)
                dumpfloprm(FSTI, BP, p, 0 )
                top_type = integer
                top_form = V in S
                top_base = BP
                top_disp = p
				-> esac
esac:
			else
                !   -- normal routine calls --
                ! String functions have a hidden last parameter to point
                ! to the result area
                if v_type = string and v_aform = 8 start
                    t = getwork(v_size+1)
                    p = gp reg
                    dumprm(LEA, p, BP, t, 0)
                    dumpur(PUSH, p)
                finish
                hazard all
                if v_scope = EXT start;          ! external
                    dumpextcall(v_disp)
                else
                    if v_base # 0 start;                          ! procedure-as-parameter
                        dumpum(CALL, v_base, v_disp, v_extdisp ); ! plants call indirect through variable
                    else;                                         ! local routine
                        dumpjump(CALL, v_disp);                   ! plants fixup for the tag
                    finish
                finish
                ! adjust the stack
                if v_extra # 0 then dumpri(ADD, SP, v_extra)
                if v_type = 0 start;             ! not function or map
                    pop rel
                else;                            ! Here we've got a result
                    v_scope = 0;                 ! Result is local, even if the function wasn't
                    if v_type = string and v_aform = 8 start
                        v_base = BP;             ! String result will have been copied back here
                        v_disp = t
                        v_form = V in S
                    else
                        if (v_type = real or v_type = lreal) and v_aform = 8 start
                            ! Floating result will be on the FPU stack
                            v_form = V in R
                            v_base = FR0; claim(FR0)
                            fpu stack = 1
                        else
                            v_base = AX;               ! Result is always in AX
                            v_disp = 0;                ! Clear this for MAP results
                            claim(AX)
                        finish
                    finish
                finish
			finish
        end;             !  Compile Call

        !                                              >> COMPILE FOR <<
        routine  compile for( integer lab )
            record(stackfm)name  cv, iv, inc, fv
            integer  n

            ! Lock a value into a temporary to make sure it is invariant
            routine  stab(record(stackfm)name  v,  integer  type)
                integer  t,r
                return if const(v)#0
                loadreg(v,any);  r = v_base
                t = getwork(word size)
                dumpmr(MOV, BP,t, 0, r)
                v_base = BP; v_disp = t; v_scope = 0
                v_type = type;  v_form = V in S
                release(r)
            end

            iv == top
            fv == stack(stp-1)
            inc == stack(stp-2)
            cv == stack(stp-3)

            stab(fv,integer)
            stab(inc,integer)

            ! Check control variable is a plain value - otherwise save a pointer to it
            ! in case it changes
            if cv_form # v in s or (0 < cv_base <= DI and cv_base # BP) start
                n = cv_type
                amap(cv)
                stab(cv, n)
                cv_form = a in s
            finish
   
            push copy(cv)
            push copy(iv)
            push copy(inc);
            Operation(SUBx)
            assign(1);        ! cv = iv - inc

            define label(lab)
            pop stack;! zap unwanted copy of IV

            ! Stack is now  top->[FV[INC[CV 
            push copy(cv);		! in case compare alters it
            compare(top, fv)
            jump to(lab+1, JE, 1)
            invert = 0; ! because the compare might have flipped this (N/A for JE)
            ! Stack is now top->[CV'[FV[INC[CV where CV' is a register copy of CV
            release(fv_base); fv = top;	! trash FV and make a copy of CV' in that slot
            pop stack;							! discard the top copy
            ! stack is now top->[CV'[INC[CV
            Operation(ADDx)
            assign(1)
        end;             ! for

		routine End of Block
			if amode >= 0 start;   ! No return code for %endoffile
				dump return
				dumpstaticfill(staticalloc, frame+(level*word size), events, evep, evfrom);   ! don't include the display
			finish
		end

		routine Compile Begin
                        decvar == begin
                        decvar_disp = new tag
			otype = 0
                        spec = 0
                        potype = 0
			if level # 0 start;    ! not outermost %begin
				push const(decvar_disp)
				top_type = 0;      ! it's not a function!
				compile call(top)
				skip proc = new tag
				dump jump(JMP, skip proc)
				dump label(decvar_disp);  ! this is where to call
			finish
			assemble(0,labs,names)
			if level # 0 start
				dump label(skip proc)
				last skip = next cad
				Uncond Jump = 0
			finish
		end

        ! Utility routine used when dumping initialisers for OWNs
        ! Note non-portable use of real values
        routine  adump
            integer i
            real		rv32
            switch ot(0:6)

            ->ot(own type)
ot(general):
            abort("General Own?")
ot(integer):
            gput(ownval); -> done
ot(real):
            rv32 = rvalue;  ! because our default variable is a 64 bit long real
            for i = 0,1,3 cycle
				gbyte(byteinteger(addr(rv32)+i))
			repeat
			-> done
ot(string):
            if current string(0)+1 > data size start;	  ! check for overflow
				! String constant too long - warn and truncate
				warn(5);  current string(0) = data size-1
			finish

			for i = 0,1,data size-1 cycle
				gbyte(current string(i))
			repeat
			-> done
ot(record):
            for i = 1,1,data size cycle
				gbyte(0)
			repeat
			-> done
ot(byte):
            gbyte(ownval); -> done
ot(lreal):
            for i = 0,1,7 cycle
				gbyte(byteinteger(addr(rvalue)+i))
			repeat
			-> done
done:
        end

        integerfn user label(integer lab)
            record(varfm)name v
            if lab > names start
                names = lab
                v == var(lab)
                v = 0
                v_form = pgm label
                v_disp = new tag
                result = v_disp
            finish
            result = var(lab)_disp
        end

		routine Compare Double
                        LHS == stack(stp-1)
			RHS == top
			loadreg(rhs, any)
			! We happen to know that Compare loads the left parameter in a register.
			! We've already got RHS in a register, so we flip the LHS and RHS to the
			! comparison and set Invert accordingly
			compare(rhs, lhs)
			invert = 1
			! release LH and then overwrite it with RH
			release(lhs_base)
			lhs = rhs
			pop stack
		end

		routine Compare Values
			LHS == stack(stp-1)
			RHS == top
			compare(lhs,rhs)
			pop rel
			pop rel
		end

		routine Compare Addresses
			amap(top);
			amap(stack(stp-1));                  ! Now do same as compare values
			Compare Values
		end

		routine Define Compiler Label( integer label)
			if label = 0 start
				dump label(skipproc)
				last skip = next cad
				Uncond Jump = 0
			else
				define label( label );
			finish
		end

		routine Init( integer N )
			! N = Number of values to assign
			integer j
			if stp # 0 start;       ! Value supplied?
				own val = top_disp
				if own type = real or own type = lreal start
					rvalue = own val if top_type = integer;   ! copy integer supplied into floater
				finish
				pop stack
			else;             ! initialise to default pattern
				ownval = 0
				current string(0) = 0;     ! in case it's a string
			finish

			if own form = array or own form = name array start
				adump for j = 1,1,N
			else
				if otype = 0 start ;     ! %const .... %name
!					Abort("Constant Name");
					! JDM attempt to allow assignment of %const ... %name
					decvar_scope = COT
					decvar_level = 0
					decvar_disp = own val
				else
					! non-array normal variables
					decvar_level = 0
					if otype = con start
						! constant - must be string or real type, because
						! const integers are substituted by value in Pass 1
						! Constant strings and reals are treated as literals
						decvar_scope = COT
						if own type = string start
							decvar_disp = getcots(current string)
						else
							if own type = real or own type = lreal start
								! constant reals are put in the COT.  Depending on how
								! the value was formed, ReadReal may have already planted this.
								! Not to worry, because "real constant" will find it again.
								decvar_disp = getcotdouble(rvalue)
							else
								abort("Init?")
							finish
						finish
					else
						! must be %own or %external - use adump to put it in DATA segment
						decvar_scope = DATA
						decvar_disp = datatp
						adump
					finish
				finish
			finish
		end

		routine User Jump ( integer label )
			dumpjump(JMP, user label( label ))
		end

		routine Define User Label( integer label )
			dump label(user label( label ))
		end

		routine Return( integer mode)
			integer i

			if mode = False start
				dumpri(MOV, AX, 0)
			finish

			if mode = True start
				dumpri(MOV, AX, -1)
			finish

			if mode = Map start
				amap(top)
				loadreg(top, AX)
				pop rel
			finish

			if mode = Fn start
				if procvar_type = integer start
					loadreg(top, AX)
					pop rel
				else
					if procvar_type = real or procvar_type = lreal start
						! Floating point results are put into store, and AX contains
						! the address
						! JDM - No, not for 32-bit code for IA-32 architecture ABI
						! JDM - floating point results go onto the floating point stack in ST(0)
						! JDM - that is the returned floating point stack should only be 1 deep
						! JDM: loadreg(top,anyf) should push the result onto the floating point stack
						loadreg(top, anyf);
						pop rel
					else ;  ! string or record - pass back through the hidden parameter
						push copy(top);               ! Make a copy of the thing on top
						lhs == stack(stp-1);       ! point to the (now spare) next item
						lhs_type = procvar_type;   ! and make it look like a destination
						lhs_size = procvar_size
						lhs_format = procvar_format
						lhs_base = BP
						lhs_disp = word size * 2;  ! At the offset of the last parameter
						lhs_form = a in s
						assign(1)
					finish
				finish
			finish

			if mode = Routine start
				! no need to do anything special
			finish

			dump return
		end

		routine Dimension( integer dim, n )
			integer i,j

			! Validate the ICODE Parameters
			abort("Array Dim") unless 0 < dim < 6

			if in params # 0 start;           ! Array in record
				parms = parms+n
				vub = top_disp; pop stack
				vlb = top_disp; pop stack
				abort("Array Bounds") if vlb > vub
				dv = set dope vector
			else
				names = names-n
				! Now we need to plant code to manufacture a dope vector
				frame = (frame - ((dim * (2 * word size))+(2 * word size)))&(¬align);  ! space for :Dim:<bound pairs>:DataSize:
				dv = frame
				! First store the dimension
				dumpmi(MOV, BP, dv, 0, dim)
				! And the data size is also constant
				dumpmi(MOV, BP, dv + (dim * (2 * word size)) + word size, 0, data size)
				! Now the bounds
				j = 0;                    ! points to before the first stack value
				for i=1,1,dim*2 cycle
					j = j + 1; lhs == stack(j)
					if lhs_form = constant start
						dumpmi(MOV, BP, dv + (i*word size), 0, lhs_disp)
					else
						loadreg(lhs, any)
						dumpmr(MOV, BP, dv + (i*word size), 0, lhs_base)
					finish
				repeat
				! Now we need to allocate the space for the array
				if dim > 1 or control & check array # 0 start
					! Do it with the PERM
					while stp # 0 cycle
						pop rel; ! get rid of all the bounds - they are in the DV already
					repeat
					dumprm(LEA, AX, BP, dv, 0)
					dumpur(PUSH, AX)
					perm(adef, 1)
					! We now need to make our result match the inline version
					! by putting AX and DX into stacklike variables
					push const(0); lhs == top
					push const(0); rhs == top
					lhs_base = AX; lhs_form = V in R; claim(AX)
					rhs_base = DX; rhs_form = V in R; claim(DX)
					pop stack
					pop stack
				else
					push const(1)
					Operation(ADDx)
					push const(data size)
					Operation(MULx)
					push copy(stack(stp-1));   ! suck up the lower bound
					push const(data size)
					Operation(MULx)
					! top is now the lower bound, next is the upper, and a bogus copy of lb is next
					loadreg(top, any);   ! Make sure this is in a register
					lhs == top;          ! Point to it
					pop stack;           ! and drop (without release) this copy
					loadreg(top, any);   ! This is now UB - load it in a register as well
					rhs == top;          ! Point to it
					pop stack;           ! and keep RHS (Upper)
					pop stack;           ! dump the bogus lb
				finish
				! Note - there are 4 GP registers, and we're going to need them ALL here
				t = gp reg;             ! get a working register for the dope vector address
				dumprm(LEA, t, BP, dv, 0); ! load it
				dv = t; claim(dv);      ! use this to hold the register number
				t = gp reg;             ! the last one! (which we don't claim, 'cos we can't lose it)
				dumprr(MOV, t, SP);	   ! working copy of SP so that real SP is always "OK"
			finish

			for i = 1,1,n cycle
				decvar_dim = dim
				if in params = 0 start;        !  array not in record
					names = names+1;  decvar == var(names)
					decvar_level = level
					frame = frame - (word size * 2);  ! 2-word header
					decvar_disp = frame
					if decvar_form = array or decvar_form = namearray then decvar_form = decvar_form + 1;            ! force arrayname
					dumprr(SUB, t, rhs_base);
					dumpmr(MOV, BP, frame, 0, t);             ! store a(0) address
					dumpmr(MOV, BP, frame + word size, 0, dv);! store dope vector pointer
					dumprr(ADD, t, lhs_base);
				else;           ! array-in-record
					parms = parms-1;  decvar == var(parms)
					decvar_disp = frame - vlb
					frame = frame + vub; ! noting that Set Dope Vector has changed VUB to the array size
					decvar_pbase = dv
				finish
			repeat

			if in params = 0 start
				! We need to keep the stack pointer word aligned - 8086's run faster that way,
				! and more importantly, Pentiums throw an exception if you don't!
				if data size & align # 0 then dumpri(AND, t, ¬align)
				dumprr(MOV,SP,t)
				release(lhs_base)
				release(rhs_base)
				release(dv)
			finish
		end

		routine Update Line( integer line)
			current line = line
			abort("Stack?") if stp # 0
			abort("Claimed") if claimed # 0
			! Pass1 sends the line number multiple times if there's more than
			! one statement per line - for debugging we only want "real" line numbers
			if echo line < current line start
				dump line number(current line)
				while echo line < current line cycle
					echo source line
				repeat
			finish
		end

		routine Switch Jump( integer switch id )
			v == var( switch id )
			push const(word size);  Operation(MULx);        ! subscript X WordSize
			loadreg(top, anyp)
			dumpum(JMP, SWT!top_base, v_disp * word size, 0);	! swtab is word-size
			pop rel
			uncond jump = next cad
		end

		routine Set Record Format( integer format id )
			top_format = format id
			top_type   = record
		end

		routine Switch Label( integer switch label )
			v == var( switch label )
			uncond jump = 0
			j = top_disp; pop stack
			t = new tag
			dumplabel(t);
			swtab(v_disp+j) = t;
		end

		routine Constant Bounds
			vub = top_disp; pop stack
			vlb = top_disp; pop stack
		end

		routine Internal Handler ( integer id )
			push const(0) while stp < 2
			push const( id )
			loadreg(top, any); dumpur(PUSH, top_base); pop rel
			loadreg(top, any); dumpur(PUSH, top_base); pop rel
			loadreg(top, any); dumpur(PUSH, top_base); pop rel
			perm(signal, 3)
			uncond jump = next cad if id # -1;      ! %monitor will return
		end

		routine Signal Event( integer event id )
			Internal Handler ( event id )
		end

		routine Monitor
			Internal Handler ( -1 )
		end

		routine SelectField( integer field index )
			! Contrary to earlier iCode versions, this one seems to use 'n' for
			! both normal record member access and alternate formats?
			lhs == top;										! Points to the base record
			Stack Var(var(top_format)_pbase - field index);	! Push descriptor for the i-th member

			if top_aform # recordformat start;     !  not record format - must be a member
				if lhs_form = v in s or lhs_form = VinRec start
					top_disp = top_disp + lhs_disp
					lhs_form = lhs_form - v in s + top_form
				else
					if lhs_form = a in rec start
						lhs_form = VinRec;  lhs_type = integer
						loadreg(lhs,any)
						lhs_form = top_form
					else
						if lhs_form <= VinR start
							lhs_form = top_form;    ! ????
						else;		 ! A in S
							lhs_extra = lhs_disp
							lhs_form = top_form + 3
						finish
					finish
				finish
				lhs_disp = top_disp
				lhs_type = top_type
				lhs_aform = top_aform
				lhs_dim = top_dim
			finish
			lhs_size = top_size;  lhs_format = top_format
			pop stack
		end

		routine EventTrap( integer anevent, evfrom )
			! events: Events to trap (then comma)
			! evfrom: Label to skip to
			integer temp

			events = anevent
			temp = getwork(wordsize);	    ! get a temp location for SP
			dumpmr(MOV, BP, temp, 0, SP);	! because our signaller doesn't restore it
			jump to(evfrom, JMP, 1);	    ! go there now
			! We need to make EVFROM into a label ID that pass 3 will recognise
			! to build the trap table, so Jump To sets a variable we pick up here...
			evfrom = J Tag
			evep = new tag;				    ! tag for the event body entry point
			dump label(evep);			    ! which is here
			dumprm(MOV, SP, BP, temp, 0);	! First thing we do is restore SP
		end

		routine DoubleOp( integer opr )
			integer j,t

			lhs == stack(stp-1)
			t = lhs_type;
			j = lhs_size
			j = j+1 if t = string
			amap(lhs)
			abort("++/-- size") if j = 0
			push const(j)
			Operation(MULx)
			Operation(opr)
			vmap(top);  top_type = t
		end

		routine Set CD(integer Value, integername CD)
            ! JDM set value for the appropriate compiler pass
            ! In this case we are in pass2
			CD = Value&x'3FFF' if Value&x'C000' = (PassId&3)<<14
		end

		predicate Finish Params
			integer j

			true if amode < 0;              ! end of %record %format defn.
			true if procvar_level = 128;    ! prim routine reference

			! Here it's a real subroutine - copy any parameters to the PARM area
			if names > first name start
                procvar_pbase = parms;  ! Point one beyond the first parameter
				frame = (frame + align) & (¬align);	! Even up the stack size
				if procvar_type = string and procvar_form = 8 start
					frame = frame + word size;  ! string functions have a hidden result parameter
				finish
				procvar_extra = frame;             ! Remember the stack offset
				procvar_dim = names - first name;  ! and the number of parameters
				frame = frame + (2 * word size);   ! leave space for return linkage (IP + BP)
				for j = first name+1, 1, names cycle
					ap == var(j)
					parms = parms-1;  fp == var(parms)
					fp = ap
					! formal parameter base and displacement is implicit (on the stack)
					fp_level = 0
					! we also need to adjust the offsets of the actual parameters, because
					! they were allocated going "forwards", but will be pushed on the stack
					! "backwards" - that is, the first item passed will end up with the
					! highest address.  DefineVar has done part of the work for us by tagging
					! the displacements in the right style, but it can't tell the whole frame
					! offset, so we calculate the final offsets here...
					ap_disp = frame - ap_disp
				repeat
				abort("Params") if parms < names
			finish
			true if amode = 2;    ! this was just a spec
                        dumplabel(procvar_disp)
			static alloc = enter
			frame = -(level * word size);  ! one word for each display entry
			false
		end

		predicate AlternateFormat( integer N )
			! Check the ICODE for faults
            ! and abort for any faulty intermediate code
			abort("Alt Record '".tostring(sym)."'.") unless (N = 'A') or (N = 'B') or (N = 'C')

			true if N = 'B';               ! alt end

			if N = 'A' start;              ! alt start
				decvar == procvar
				assemble(-2,labs,names)
			finish

			if N = 'C' start
				! Compile the next alternate - update limit and set frame back to where we started
				max frame = frame if frame > max frame
				frame = old frame
			finish
			false
		end

! ******************************************
! JDM JDM attempt to include the plant icode and machine code icode
        routine  PLANT
            ! Plant in-line code values (from "*=constant")
            integer  j

            ! We only expect one item on the stack
            abort("Machine Literal") if (stp <> 1)

            for j = 1, 1, stp cycle
! JDM JDM not sure what next 3 lines do, so commented out
!                lhs == stacked(j)
!                word (lhs_disp)
!                drop (lhs)


            repeat
            ! JDM empty the icode stack
            stp = 0
        end

        string(255) function get type name( integer f)
            string(8) name
            name = "????"
            name = "general" if (f = 0)
            name = "integer" if (f = 1)
            name = "real" if (f = 2)
            name = "string" if (f = 3)
            name = "record" if (f = 4)
            name = "byte" if (f = 5)
            name = "lreal" if (f = 6)
            result = name
        end

        string(255) function get form name( integer f )
            string(24) name
            name = "????"
            switch n(0:15)
            label esac
            
            -> n(f & 15)
            n( 0):  name = "void";           -> esac
            n( 1):  name = "simple";         -> esac
            n( 2):  name = "name";           -> esac
            n( 3):  name = "label";          -> esac
            n( 4):  name = "recordformat";   -> esac
            n( 5):  name = "?????";          -> esac
            n( 6):  name = "switch";         -> esac
            n( 7):  name = "routine";        -> esac
            n( 8):  name = "function";       -> esac
            n( 9):  name = "map";            -> esac
            n(10):  name = "predicate";      -> esac
            n(11):  name = "array";          -> esac
            n(12):  name = "arrayname";      -> esac
            n(13):  name = "namearray";      -> esac
            n(14):  name = "namearrayname";  -> esac
            n(15):  name = "?????????????";  -> esac

            esac:
            result = name;
        end

        ! classify the type of the machine code instruction parameter
        constant integer     unknown = 0, variable = 1, register = 2, number = 3, mask = 4,    name = 5,  pointer = 6
        ! param type is one of unknown,     variable,     register,     number,     mask,        name,      pointer
        ! param value is       ???,         tag,          reg id,       number,     32-bit mask, integer,   reg id,
        ! param data  is       ???,         tag name,     reg name,     N/A,        N/A,         name,      reg name
        ! param offset is      N/A,         N/A,          N/A,          N/A,        N/A,         N/A,       offset
        !

        routine dump tag var( integer tag, string(3) prefix )

            print string(" ".prefix."     tag=".itos(tag,0)); newline
            print string(" ".prefix."    name=".var(tag)_idname); newline
            print string(" ".prefix."    type=".itos(var(tag)_type,0)." ".get type name(var(tag)_type)); newline
            print string(" ".prefix."    form=".itos(var(tag)_form,0)." ".get form name(var(tag)_form)); newline
            print string(" ".prefix."   level=".itos(var(tag)_level,0)); newline
            print string(" ".prefix."   scope=".itos(var(tag)_scope,0)); printstring(" ".relocname(var(tag)_scope>>4) );newline
            print string(" ".prefix."    disp=".itos(var(tag)_disp,0)); newline
            print string(" ".prefix." extdisp=".itos(var(tag)_extdisp,0)); newline
            print string(" ".prefix."    size=".itos(var(tag)_size,0)); newline
            print string(" ".prefix."   extra=".itos(var(tag)_extra,0)); newline
            print string(" ".prefix."  format=".itos(var(tag)_format,0)); newline
            print string(" ".prefix."     dim=".itos(var(tag)_dim,0)); newline
            print string(" ".prefix."   pbase=".itos(var(tag)_pbase,0)); newlines(2)

        end

        routine dump parameter( integer param index, integer param type, string(255) param name, integer param value, param offset )
            string(255) t
            integer tag,n

            printstring( "Parameter(".itos(param index,0).")='".param name."'"); newline

            if (param type = pointer) start

                ! dump the pointer data
                if (param offset = 0) start
                    print string(" PTR   id=".itos(param value,0)); newline
                    print string(" PTR name=[".param name."]"); newline
                    print string(" PTR offset=0"); newlines(2)
                finish else start
                    print string(" PTR   id=".itos(param value,0)); newline
                    print string(" PTR name=[".param name.itos(param offset,0)."]"); newline
                    print string(" PTR offset=".itos(param offset,0)); newlines(2)
                finish

            finish else if (param type = variable) start

                ! dump the variable data
                dump tag var( param value, "VAR" )

            finish else if (param type = register) start

                ! dump the register data
                print string(" REG   id=".itos(param value,0)); newline
                print string(" REG name=".param name); newlines(2)

            finish else if (param type = number) start

                ! dump the number data
                print string(" NUMBER value=".itos(param value,0)); newlines(2)

            finish else if (param type = mask) start

                ! dump the mask data
                print string(" MASK value=2_".int2ascii(param value,2,0)); newlines(2)

            finish else if (param type = name) start

                ! dump the name data
                print string(" NAME  name=".param name); newline
                print string(" NAME value=2_".int2ascii(param value,2,0)); newlines(2)

            finish

        end

        !                                              >> MACHINE CODE <<
        routine Machine Code( string(255) code )
            ! This is meant to insert a machine code fragment into the code stream
            ! For now do nothing with the machine code text
            ! JDM JDM JDM

            ! ok, lets go
            ! 1) need to parse the machine code text
            string(255) s,t,rname
            string(5) instruction
            string(255) parameters
            integer params count

            ! ass-u-me that a machine code instruction has at most 8 parameters
            constant integer param limit = 8;

            ! Remember number of CPU registers (1..register limit)
            constant integer register limit = 8

            ! A machine code string has the form *op_ item*
            ! where op is an instruction name (a sequence of alphanumeric chars terminated by '_')
            ! An item has one of the forms:
            ! 1) varname  == ' ' BB    (where 0 <= B <= 255  and BB represent a definition tag)
            ! 2) constant == 'N' BBBB  (where 0 <= B <= 255  and BBBB represents a 32-bit signed integer)
            ! 3) text     == B+        (where 128 <= B <= 255 and then convert b = B - 128, so text is an ASCII sequence b+)
            ! and the code string can include the ASCII chars (excluding any varname,constant,text format)
            ! 4) chars    == c*        (where c is one of '<','>','[',']','(',')','#',',')
            !
            ! An instruction can have 0,1,2 parameters separated by a ','
            ! One parameter type is a register mask of form '<' number (',' number)* '>'
            ! This is the ONLY other legal use of a ','
            ! The following defines the legal opcode parameters
            ! 1) register == constant                          (a register index, beware register range)
            ! 2) number   == # constant                    (a 32-bit signed integer)
            ! 3) mask     == '<' register (',' register)* '>'  (a bit set of registers, beware limit on count of registers)
            ! 4) modifier == text number
            ! 5) variable == varname, pointer
            ! 6) pointer == '[' register ']', '[' register '+' offset ']', '{ register '-' offset ']'
            ! 7) offset   == constant                          (a 32-bit signed integer)
            !
            ! N.B. a variable could be the value held in varname or the address of varname.
            ! N.B. register always refers to its value, but pointer becomes an address
            !
            ! Legal Intel 386 instruction formats
            ! The modifier, mask parameters are unused
            ! No-op instruction
            ! *op_
            !
            ! One-op instruction
            ! *op_ register
            ! *op_ number
            ! *op_ variable
            !
            ! Two-op MOV instruction
            ! *op_ register ',' register2  == register := register2
            ! *op_ register ',' number     == register := number
            ! *op_ register ',' variable   == register := variable
            ! *op_ variable ',' register   == variable := register
            ! *op_ variable ',' number     == variable := number
            !
            ! Two-op instruction (non-MOV instruction)
            ! *op_ register ',' register2  == register := register op register2
            ! *op_ register ',' number     == register := register op number
            ! *op_ register ',' variable   == register := register op variable
            ! *op_ variable ',' register   == variable := variable op register
            ! *op_ variable ',' number     == variable := variable op number

            recordformat paramFm(string(255) data, integer scomma,pcomma,start,end, string(255) param name, integer param type,param value, param offset)
            record(paramFm)array params(1:param limit)

            ! JDM being lazy I created a dual purpose list to map
            ! op (NOP:JMP) to a corresponding opX
            ! op (NOP:JMP) to a text version of opX
            
            ! This list maps opId to internal opX
            constant integer array opGenericId(NOP:JMP) =
                  -1,   -1,    -1,     -1,     -1,     -1,     -1,   NEGx,
            {    NOP,  CWD,   RET,   SAHF,  LEAVE,    DEC,    INC,    NEG, }
                 NOTx,     POP,    PUSH,      -1,      -1,      -1,      -1,    ADDx,
            {    NOT,  POP,  PUSH,    LEA,    MOV,   XCHG,    ADC,    ADD, }
                ANDx,   -1,   ORx,   SUBx,   XORx,   LSHx,   RSHx,   DIVx,
            {    AND,  CMP,    OR,    SUB,    XOR,    SHL,    SHR,   IDIV, }
                MULx,   -1,    -1,     -1,     -1,     -1,     -1,     -1,
            {   IMUL, CALL,    JE,    JNE,     JG,    JGE,     JL,    JLE, }
                  -1,   -1,    -1,     -1,     -1
            {     JA,  JAE,    JB,    JBE,    JMP }

            ! This list maps opId to internal opX name
            constant string(5) array opGenericName(NOP:JMP) =
                "NOP",   "CWD",   "RET",  "SAHF", "LEAVE",   "DEC",   "INC",  "NEGx",
                "NOT",   "POP",  "PUSH",   "LEA",   "MOV",  "XCHG",   "ADC",   "ADD",
                "AND",   "CMP",    "OR",   "SUB",   "XOR",   "SHL",   "SHR",  "IDIV",
               "IMUL",  "CALL",    "JE",   "JNE",    "JG",   "JGE",    "JL",   "JLE",
                 "JA",   "JAE",    "JB",   "JBE",   "JMP"

            string(255) varname
            byte ch
            string(5) opNameX
            integer i,j,k,n,plen,tag,rval,opId,opIdx
            byte inrbflag,insbflag,inabflag,hashflag,plusFlag,minusFlag
            switch c(0:127)
            label esac, default
            integer start,end

            if ((diagnose&mcode level A) # 0) start
                selectoutput(listout)
                newline
            finish

            code -> instruction.("_").parameters  ;! This is not good IMP.  The string contains binary data.

            s = ""

            if (parameters # "") start
                ! parameters is a non-empty string so we ass-u-me at least one parameter
                params count = 1
                plen = length(parameters);
                inrbFlag = 0; ! not inside round bracket sequence
                insbFlag = 0; ! not inside square bracket sequence
                inabFlag = 0; ! not inside angle bracket sequence
                hashFlag = 0; ! not expecting a number to follow

                i = 1;

                while (i <= plen) cycle

                    ch = charno( parameters, i)

                    -> c(ch) if (ch < 128);  ! this is an ordinary ASCII char

                    ! So, ch > 127, thus this "char" starts a tweaked "name"
                    t = "%"
                    while (charno(parameters, i) > 127) cycle
                        ! Append the converted char
                        length(t) = length(t) + 1
                        ! tweak appended "char" to be a legal 7-bit ASCII char
                        charno( t, length(t)) = charno(parameters, i) - 128
                        i = i + 1
                    repeat

                    params( params count )_param type  = name
                    params( params count )_param value = 0; ! value acquired by next N section

                    params( params count )_param name  = t
                    s = s.t." "

                    -> esac

             c(' '):
                    ! a variable/pointer reference is prefixed by a space.
                    n = (charno( parameters, i+1 ) << 8) + charno( parameters, i+2 )

                    ! now determine the variable name
                    t = var( n )_idname

                    ! remember this parameter is a variable/pointer (and its tag)
                    if (insbflag = 1) start
                        params( params count )_param type = pointer
                    finish else start
                        params( params count )_param type = variable
                    finish
                    params( params count )_param value = n
                    params( params count )_param name = t
                    s = s.t

                    i = i + 3
                    -> esac
             c('N'):
                    ! A number is prefixed by an ASCII 'N'
                    n = 0
!errout;printstring("charno( parameters, i+1 ) = "); write(charno( parameters, i+1 ), 0); newline; undo
                    n = n + charno( parameters, i+1 ); n = n << 8
!errout;printstring("charno( parameters, i+2 ) = "); write(charno( parameters, i+2 ), 0); newline; undo
                    n = n + charno( parameters, i+2 ); n = n << 8
!errout;printstring("charno( parameters, i+3 ) = "); write(charno( parameters, i+3 ), 0); newline; undo
                    n = n + charno( parameters, i+3 ); n = n << 8
!errout;printstring("charno( parameters, i+4 ) = "); write(charno( parameters, i+4 ), 0); newline; undo
                    n = n + charno( parameters, i+4 )
!errout;printstring("N: n = "); write(n, 0); newline; undo

                    if (params( params count )_param type  = name) start
                        ! this number is associated with a "name" (i.e. %shl 4)
                        hashFlag = 0

                        ! we have the "name" (i.e %shl)
                        ! but now to get the associated numeric value
                        params( params count )_param value = n

                        ! convert number to text
                        if (n > 127) start
                            t = "16_".int2ascii( n, 16, 0 )
                        finish else start
                            t = itos( n, 0 )
                        finish

                        ! now to add the associated number to the s string
                        s = s.t

                    finish else if (hash flag <> 0) and (params( params count )_param type = unknown) start
                        ! hashflag indicates this is a genuine integer
                        hashFlag = 0

                        ! remember this parameter is a number
                        params( params count )_param type  = number
                        params( params count )_param value = n
                        params( params count )_param name  = ""

                        if (n > 127) start
                            t = "16_".int2ascii( n, 16, 0 )
                        finish else start
                            t = itos( n, 0 )
                        finish
                            
                        s = s.t
                        params( params count )_param name  = t

                    finish else if (params( params count )_param type  = mask) start

                        ! Ah, we are between <> == mask
                        ! So we need to update the mask
                        if (0 < n <= register limit) start
                            ! ok, legal register mask range
                            k = 1<<(n - 1)
                        finish else if (0 < n <= 32) start
                            ! oops, bad mask specifier for this CPU
                            k = 1<<(n - 1)
                        finish else start
                            ! oops, even worse! Is this a CPU with > 32 registers.
                            ! we can't fit this mask into a 32-bit integer
                            ! so, we won't try
                            k = 0
                        finish

                        ! add the register flag to the mask
                        params( params count )_param value = params( params count )_param value!k

                        ! remember N represents the register number but add the reg name
                        ! Ensure we are referencing a valid register
                        ! Adjust register limit for a specific CPU
                        if (0 < n <= register limit) start
                            s = s.regname(n)
                        finish else start
                            s = s."R??"
                        finish
                            
                    finish else start
                        ! ok this came from a constant integer in the IMP program
                        ! ASS-U-ME that this constant represents a register
                        ! So, replace the number with the register name
                        ! Register name is specific to a processor architecture
                        ! IMP code with embedded assembler should reference a
                        ! register by number.
                        ! The IMP pass2 for that processor should store a mapping
                        ! between "register" number and register name.
                        ! eg Intel eax or ebp

                        ! remember this parameter is a variable/pointer (and its tag)
                        if (insbflag = 1) start
                            params( params count )_param type = pointer
                        finish else start
                        params( params count )_param type  = register
                        finish

                        if (plusFlag = 1) start
                            ! remember this "parameter" is a positives pointer offset
                            params( params count )_param offset = n
                            t = itos( n, 0)
                        finish else if (minusFlag = 1) start
                            ! remember this "parameter" is a negative pointer offset
                            params( params count )_param offset = -n
                            !however, negative sign (and or #) already output
                            t = itos( n, 0)
!errout;printstring("Line 5955: params(params count=");write(params count,0);printstring(")_param offset = ");write(params(params count)_param offset,0);printstring("  (minusflag == -1, remember this parameter is a negative pointer offset)");newline;undo
                        finish else start
                            ! remember this parameter is a register
                        	params( params count )_param value = n

	                        ! Ensure we are referencing a valid register
    	                    ! Adjust register limit for a specific CPU
        	                if (0 < n <= register limit) start
            	                t = regname(n)
                	        finish else start
                    	        t = "R??"
                        	finish
!errout;printstring("Line 5967: t = """);printstring(t);printsymbol('"');newline;undo
                            params( params count )_param name  = t
                        finish

                        s = s.t
!errout;printstring("Line 5972: s = """);printstring(s);printsymbol('"');newline;undo
                    finish
                    i = i + 5
                    -> esac

             c('#'):
                    ! let this char through
                    ! BUT remember # is assumed to prefix a positive number
                    hashFlag = 1
                    -> default

             c(','):
                    ! let this char through
                    ! comma separates instruction parameters
                    ! (or values between brackets)

                    { ok, check to see if this is a parameter separator }
                    if ((inabFlag + inrbFlag + insbFlag) = 0) start
                       { ok, we are not inside one of the "bracket" types }

                       ! REMEMBER, the parameter type and value should have been
                       ! determined previously
                       ! note comma location in the s string
                       params(params count)_scomma = length(s) + 1
                       ! note comma location in the parameters string
                       params(params count)_pcomma = i
                       ! beware fence post error
                       ! we are counting fence posts (,)
                       ! and their locations
                       ! So "last" fence post at end of parameters string

                       ! we have an additional parameter
                       params count = params count + 1

                       ! BUT set the param type appropriately
                       params( params count )_param type = unknown
                       params( params count )_param offset = 0

                    finish
                    -> default

             c('+'):
                    ! pass this char( only allowed between [] brackets
                    plusFlag = 1
                    minusFlag = 0; -> default
                    
             c('-'):
                    ! pass this char( only allowed between [] brackets
                    plusFlag = 0
                    minusFlag = 1; -> default
                    
             c('('):
                    ! pass this char (opening round brackets)
                    inrbFlag = 1; -> default

             c(')'):
                    ! pass this char (closing round brackets)
                    inrbFlag = 0; -> default

             c('['):
                    ! we are referencing an indirect variable
                    params( params count )_param type = pointer
                    ! initialise the name,value and offset
                    params( params count )_param name  = ""
                    params( params count )_param value = 0
                    params( params count )_param offset = 0

                    ! pass this char (opening square brackets)
                    insbFlag = 1; -> default

             c(']'):
                    ! pass this char (closing square brackets)
                    plusFlag = 0
                    minusFlag = 0
                    insbFlag = 0; -> default

             c('<'):
                    ! We are starting a mask parameter
                    params( params count )_param type  = mask
                    ! initialise the value and name
                    params( params count )_param name  = ""
                    params( params count )_param value = 0
                    params( params count )_param offset = 0
                    ! pass this char (opening angle brackets)
                    inabFlag = 1; -> default

             c('>'):
                    ! pass this char (closing angle brackets)
                    inabFlag = 0; -> default

            default:
               c(*):
                    ! pass these chars
                    ! chars > 127 are already dealt with
                    ! So, this deals with remaining chars
                    s = s.tostring( charno( parameters, i) )
                    i = i + 1
                    -> esac
               esac:

                repeat

            finish else start
                ! Oh, this instruction has no parameters
                params count = 0
            finish

            if (params count # 0) start
                ! now to identify each instruction parameter inside the s string
                for i = 1,1,params count cycle

                    if (i = 1) then params(i)_start = 1 else params(i)_start = params(i-1)_scomma + 1
                    if (i = params count) then params(i)_end = length(s) else params(i)_end = params(i)_scomma - 1
                    params(i)_data = ""
                    for j = params(i)_start,1,params(i)_end cycle
                        params(i)_data = params(i)_data.toString( charno(s,j) )
                    repeat

                repeat
            finish

            ! determine the opId for this instruction
            ! set a default "ILLEGAL" value for the opId
            ! Although Intel 386 has opCodes 0..255
            ! the count of opCode names is much less than 255
            ! so, we are safe to set opId and opIdx = 255
            opId = -1
            opIdx = -1
            for i = NOP,1,JMP cycle
                if (instruction = opGenericName(i)) start ;  ! string compare
                    opId = i
                    opIdx = opGenericId(opId)
                    if (opIdx # -1) start
                        opNameX = instruction
                    finish else start
                        opNameX = itos(opId,0)
                    finish
                    !%exit; ! added by gt to speed it up a little...
                finish
            repeat

            ! We are NOT allowing any floating point instructions
!            %for i = FILD,1,FLDPI %cycle
!               %if instruction = flopname(i) %then opId = i
!            %repeat
!            %if (opId < FILD) %then instruction = opName(opId) %else instruction = flopName(opId)
           
            ! use short form of %if statement (as an example)
            abort("MCODE has illegal/unknown instruction name") if (opId = -1)

            if ((diagnose&mcode level A) # 0) start
                selectoutput(listout)
                printstring( "**** START MCODE ****" ); newline

                if ((diagnose&mcode level D) # 0) start
                    printstring( "  Raw Instruction text: '".instruction."'_".parameters ); newline
                finish

                printstring( "Translated Instruction: '".instruction."' ".s ); newline
                printstring( "           Instruction: '".instruction."' has ".itos( params count, 0)." parameter")
                if (params count # 1) then printsymbol( 's' )
                newline

                printstring( "      Instruction OpId: ".itos(opId,0)); newline
                printstring( "     Instruction OpIdx: ".itos(opIdx,0)); newline

                ! now to identify each instruction parameter inside the s string
                printstring( "*** start parameters ****" ); newline

                ! Dump any parameters specified
                for i = 1,1,params count cycle

                    dump parameter( i, params(i)_param type, params(i)_param name, params(i)_param value, params(i)_param offset )
 
                repeat

                printstring( "*** end parameters ****" ); newline

                comment Add an extra newline to split the above debug code from
                comment the following code generation code 

                newline
                printstring( "**** START CODE GEN **********" )
                newline

            finish

            ! 2) need to interpret parsed code
            if (params count = 0) start
                selectoutput(listout)
                printstring( "**** Instructions with no parameters not yet implemented" ); newline
            finish else if (params count = 1) start

                if (opId # -1) start

                    if (params(1)_param type = variable) start

                        if ((diagnose&mcode level A) # 0) start
                            printstring( instruction." ".params(1)_param name)
                            newline
                        finish

                        Stack Var( params(1)_param value )
                        Operation(opIdx);

                    finish else if (params(1)_param type = pointer) start

                        selectoutput(listout)
                        printstring( "Opcode ".instruction." with one parameter can only operate on an address/register ".params(1)_param name ); newline
                        abort( "Opcode ".instruction." with one parameter can only operate on an address/register ".params(1)_param name )

                    finish else if (params(1)_param type = register) start

                        if ((diagnose&mcode level A) # 0) start
                            printstring( instruction." ".params(1)_param name)
                            newline
                        finish

                        dumpur(opId, params(1)_param value)

                    finish else start
                        abort( "Opcode ".instruction." is attempting to operate on unexpected location ".params(1)_param name )
                    finish
                finish else start
                    abort( "Attempting to apply unknown opcode ".instruction )
                finish

            finish else if (params count = 2) start

                ! 3) output the implied code fragment
                if (opId = MOV) start
                    if (params(1)_param type = variable) start

                        if (params(2)_param type = variable) or (params(2)_param type = pointer) start

                            selectoutput(listout)
                            printstring(" ILLEGAL PARAMETER COMBINATION"); newline
                            printstring(" ILLEGAL ADDRESSING MODE for Intel assembler"); newline
                            printstring(" No INTEL instruction can have indirect pointers for both source and destination"); newline

                        finish else if (params(2)_param type = register) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a ASSIGN var1,reg2 scenario"); newline
                                printstring( params(1)_param name." := ".params(2)_param name); newline
                            finish

                            Stack Var( params(1)_param value )

                            if (top_type = general) or (top_type = integer) or (top_type = byte) or (top_type = record) start
                                storeReg( top, params(2)_param value )
                            finish else start
                                abort( "Attempting to store reg ".params(2)_param name." in a non-integer variable" )
                            finish

                            pop rel

                        finish else if (params(2)_param type = number) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have an ASSIGN var1,#const2 scenario"); newline
                                printstring( params(1)_param name." := #".itos(params(2)_param value,0)); newline
                            finish

                            Stack Var( params(1)_param value )
                            Push Const( params(2)_param value )
                            Assign(1);

                        finish else start
                            abort( "Attempting to store unexpected type in variable ".params(1)_param name )
                        finish

                    finish else if (params(1)_param type = pointer) start

                        if (params(2)_param type = variable) or (params(2)_param type = pointer) start

	                        selectoutput(listout)
                            printstring(" ILLEGAL PARAMETER COMBINATION"); newline
                            printstring(" ILLEGAL ADDRESSING MODE for Intel assembler"); newline
                            printstring(" No INTEL instruction can have indirect pointers for both source and destination"); newline

                        finish else if (params(2)_param type = register) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a STORE [reg ((+,-) offset)?],reg2 scenario"); newline
                                printstring( params(1)_param name." := &".params(2)_param name); newline
                            finish

                            dumpmr(opId, params(1)_param value, params(1)_param offset, 0, params(2)_param value)

                        finish else if (params(2)_param type = number) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a STORE [reg ((+,-) offset)?],const2 scenario"); newline
                                printstring( params(1)_param name." := &".params(2)_param name); newline
                            finish

                            selectoutput(listout)
                            printstring(" EXPERIMENTAL IMPLEMENTATION"); newline
                            dumpmi(opId, params(1)_param value, params(1)_param offset, 0, params(2)_param value)
                            printstring(" NOT YET IMPLEMENTED"); newline

                        finish else start
                            abort( "Attempting to store unexpected type in variable ".params(1)_param name )
                        finish

                    finish else if (params(1)_param type = register) start

                        if (params(2)_param type = variable) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a LOAD reg1,var2 scenario"); newline
                                printstring( params(1)_param name." := ".params(2)_param name); newline
                            finish

!errout;printstring("Stack Var( params(2)_param value=");write(params(2)_param value,0);printstring(" )");newline;undo
                            Stack Var( params(2)_param value )
!errout;printstring("LoadReg( top, params(1)_param value=");write(params(1)_param value,0);printstring(" )");newline;undo
                            LoadReg( top, params(1)_param value )
!errout;printstring("Should this be ""Stack Var( -params(2)_param offset )""  ???");newline;undo
                            pop rel

                        finish else if (params(2)_param type = pointer) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a LOAD reg1,[reg2 ((+,-) offset)?] scenario"); newline
                                if (params(2)_param offset = 0) start
                                    printstring( params(1)_param name." := [".params(2)_param name."]"); newline
                                finish else start
                                    printstring( params(1)_param name." := [".params(2)_param name.itos(params(2)_param offset,0)."]"); newline
                                finish
                            finish

                            dumprm(opId, params(1)_param value, params(2)_param value, params(2)_param offset, 0)

                        finish else if (params(2)_param type = register) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a MOVE reg1,reg2 scenario"); newline
                                printstring( params(1)_param name." := ".params(2)_param name); newline
                            finish

                            dumprr(opId, params(1)_param value, params(2)_param value )

                        finish else if (params(2)_param type = number) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a LOAD reg1,#const2 scenario"); newline
                                printstring( params(1)_param name." := #".itos(params(2)_param value,0)); newline
                            finish

                            Push Const( params(2)_param value )
                            LoadReg( top, params(1)_param value )
                            pop rel

                        finish else start
                            abort( "Attempting to store unexpected type in register ".params(1)_param name )
                        finish
                    finish else start
                        abort( "Attempting to ".instruction." into non-variable/register location" )
                    finish
                finish else if (opIdx # -1) start

                    if (params(1)_param type = variable) start

                        if (params(2)_param type = variable) or (params(2)_param type = pointer) start

                            selectoutput(listout)
                            printstring(" ILLEGAL PARAMETER COMBINATION"); newline
                            printstring(" ILLEGAL ADDRESSING MODE for Intel assembler"); newline
                            printstring(" No INTEL instruction can have indirect pointers for both source and destination"); newline

                        finish else if (params(2)_param type = register) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a ".instruction." var1,reg2 scenario"); newline
                                printstring( params(1)_param name." := " )
                                printstring( params(1)_param name." ".opNameX." ".params(2)_param name)
                            finish

                            Stack Var( params(1)_param value )
                            dumpmr(opId, top_base!top_scope, top_disp, top_extdisp, top_base)
                            pop rel;

                        finish else if (params(2)_param type = number) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a ".instruction." var1,#const2 scenario"); newline
                                printstring( params(1)_param name." := " )
                                printstring( params(1)_param name." ".opNameX." #".itos(params(2)_param value,0))
                                newline
                            finish

                            Stack Var( params(1)_param value )
                            Stack Var( params(1)_param value )
                            Push Const( params(2)_param value )
                            Operation(opIdx);
                            Assign(1);

                        finish else start
                            abort( "Opcode ".instruction." is attempting to store unexpected type in variable ".params(1)_param name )
                        finish

                    finish else if (params(1)_param type = pointer) start

                        if (params(2)_param type = variable) or (params(2)_param type = pointer) start

           		            selectoutput(listout)
                            printstring(" ILLEGAL PARAMETER COMBINATION"); newline
                            printstring(" ILLEGAL ADDRESSING MODE for Intel assembler"); newline
                            printstring(" No INTEL instruction can have indirect pointers for both source and destination"); newline

                        finish else if (params(2)_param type = register) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a ".instruction." [reg1 ((+,-) offset)?],reg2 scenario"); newline
                                if (params(1)_param offset = 0) start
                                    printstring( "[".params(1)_param name."] := " )
                                    printstring( "[".params(1)_param name."] ".opNameX." ".params(2)_param name)
                                finish else start
                                    printstring( "[".params(1)_param name.itos(params(1)_param offset,0)."] := " )
                                    printstring( "[".params(1)_param name.itos(params(1)_param offset,0)."] ".opNameX." ".params(2)_param name)
                                finish
               			        newline
                            finish

                            dumpmr(opId, params(1)_param value, params(1)_param offset, 0, params(2)_param value)

                        finish else if (params(2)_param type = number) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a ".instruction." [reg1 ((+,-) offset)?],const2 scenario"); newline
                                if (params(1)_param offset = 0) start
                                    printstring( "[".params(1)_param name."] := " )
                                    printstring( "[".params(1)_param name."] ".opNameX." ".params(2)_param name)
                                finish else start
                                    printstring( "[".params(1)_param name.itos(params(1)_param offset,0)."] := " )
                                    printstring( "[".params(1)_param name.itos(params(1)_param offset,0)."] ".opNameX." ".params(2)_param name)
                                finish
                                newline
                            finish

                            selectoutput(listout)
                            printstring(" EXPERIMENTAL IMPLEMENTATION"); newline
                            dumpmi(opId, params(1)_param value, params(1)_param offset, 0, params(2)_param value)
                            printstring(" NOT YET IMPLEMENTED"); newline

                        finish else start
                            abort( "Attempting to store unexpected type in variable ".params(1)_param name )
                        finish

                    finish else if (params(1)_param type = register) start

                        if (params(2)_param type = variable) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a ".instruction." reg1,var2 scenario"); newline
                                printstring( params(1)_param name." := " )
                                printstring( params(1)_param name." ".opNameX." ".params(2)_param name)
                                newline
                            finish

                            Stack Var( params(2)_param value )
                            dumprv( opId, params(1)_param value, top)
                            pop rel;
 
                        finish else if (params(2)_param type = pointer) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a ".instruction." reg1,[reg2 (('+','-')offset)?] scenario"); newline
                                printstring( params(1)_param name." := " )
                                printstring( params(1)_param name." ".opNameX." ".params(2)_param name)
                                newline
                            finish

                            selectoutput(listout)
                            printstring(" EXPERIMENTAL IMPLEMENTATION"); newline
                            dumprm(opId, params(1)_param value, params(2)_param value, params(1)_param offset, 0 )
                            printstring(" NOT YET IMPLEMENTED"); newline

                        finish else if (params(2)_param type = register) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a ".instruction." reg1,reg2 scenario"); newline
                                printstring( params(1)_param name." := " )
                                printstring( params(1)_param name." ".opNameX." ".params(2)_param name)
                                newline
                            finish

                            dumprr(opId, params(1)_param value, params(2)_param value )

                        finish else if (params(2)_param type = number) start

                            if ((diagnose&mcode level A) # 0) start
                                printstring("We have a ".instruction." reg1,const2 scenario"); newline
                                printstring( params(1)_param name." := " )
                                printstring( params(1)_param name." ".opNameX." #".itos(params(2)_param value,0))
                                newline
                            finish

                            dumpri(opId, params(1)_param value, params(2)_param value )

                        finish else start
                            abort( "Opcode ".instruction." is attempting to store unexpected type in register ".params(1)_param name )
                        finish
                    finish else start
                        abort( "Opcode ".instruction." is attempting to store in unexpected location ".params(1)_param name )
                    finish
                finish else start
                    abort( "Attempting to apply unknown opcode ".instruction )
                finish

            finish else start
                abort("Opcode ".instruction." has unexpected number ".itos(params count,0)."of parameters.")
            finish

            if ((diagnose&mcode level A) # 0) start
                selectoutput(listout)
                newline
                printstring( "**** END   CODE GEN ********" ); newline
                printstring( "**** END   MCODE ****" ); newlines(2)
            finish

        end
! ******************************************

        !--------------------------------------------------------------
        ! Code for ASSEMBLE starts here...
        First Name = Names
        First Label = Labs
        Proc Var == Decvar
        last skip = -1
        old frame = frame
        frame = 0
        events = 0; evep = 0; evfrom = 0

        if amode >= 0 start;                           ! NOT A RECORDFORMAT
            level = level+1;  abort("Level") if level > max level and spec = 0
            worklist(level) = 0

            if amode = 0 start;                ! %begin block
                if level = 1 start;            ! Initial %begin ?
                    block name = program ip;   ! For stack traceback readability
                    external id = program ep;  ! linkage to program entry
                    otype = external;  potype = otype
                else
                    block name = "%begin block"
                finish
                static alloc = enter
                frame = -(level * word size);          ! 1 word for every display entry
            finish
        else
            if amode = -1 start;                       ! normal record format
                ProcVar_pbase = parms;                 ! where our members start
            else
                if amode = -2 then frame = oldframe;   ! alternates start at the current offset
            finish
            max frame = frame;                         ! start counting space here
        finish

		!  --- main compilation loop ---
		cycle
                        sym = Pending
                        read symbol(Pending)
			if sym < 33 or sym > 127 then start
				selectoutput(0)
				printsymbol('(')
                                write(sym, 1)
                                printsymbol(',')
                                write(Pending, 1)
                                printsymbol(')')
				abort("Bad I Code")
			finish
			-> c(sym)

c('!'):		Operation(ORx); 					continue
c('"'):		CompareDouble;						continue
c('#'):		Jump Forward( ReadTag, NE );		continue
c('$'):         ! Imp defines left to right evaluation of parameters, but to be safe we could do this:
                begin
                  integer rt0,rt1,rt2,rt3
                  string(255)ga
                  rt0=ReadTag ; ga=Get Ascii( ',' ) ; rt1=ReadTagComma ; rt2=ReadTagComma ; rt3=ReadTag
		Define Var( rt0,ga,rt1,rt2,rt3 );
!!		Define Var( ReadTag, Get Ascii( ',' ), ReadTagComma, ReadTagComma, ReadTag );
end
	        continue

c('%'):		Operation(XORx);					continue
c('&'):		Operation(ANDx);					continue
c(''''):	Input String Value( ReadString );	continue;	 ! Stack string constant
c('('):		Jump Forward( ReadTag, LE );		continue
c(')'):		Jump Forward( ReadTag, GE );		continue
c('*'):		Operation(MULx);					continue
c('+'):		Operation(ADDx);					continue
c('-'):		Operation(SUBx);					continue
c('.'):		Operation(CONCx);					continue
c('/'):		Operation(DIVx);					continue
c(':'):		Define Compiler Label( ReadTag );	continue;	 ! Define compiler label
c(';'):		End of Block;						exit
c('<'):		Jump Forward( ReadTag, LT );		continue
c('='):		Jump Forward( ReadTag, EQ );		continue
c('>'):		Jump Forward( ReadTag, GT );		continue
c('?'):		Compare Values;						continue;	 ! Compare values
c('@'):		Stack Var( ReadTag );				continue;	 ! Stack variable descriptor
c('A'):     Init( ReadTag );					continue;	 ! Initialise OWN variable
c('B'):     Jump Backward( ReadTag );			continue;	 ! Backward Jump
c('C'):     Compare Addresses;					continue;	 ! Compare addresses
c('D'):		Input Real Value( ReadReal );		continue;	 ! Stack real constant
c('E'):		Compile Call(top);					continue
c('F'):     Jump Forward( ReadTag, Always );	continue;	 ! Forward Jump
c('G'):		Get Alias Value( ReadString );		continue;	 ! Alias for item about to be declared
c('H'):		Compile Begin;						continue;	 ! Start of BEGIN block
c('I'):		Abort("Pascal?");					!%continue;	! {ESCAPE for Pascal etc.}
c('J'):		User Jump( ReadTag );				continue;	 ! Jump to user label
c('K'):		Return( False );					continue;	 ! %false
c('L'):		Define User Label( ReadTag );		continue;	 ! Define user label
c('M'):		Return( Map );						continue;	 ! MAP result
c('N'):		Push Const( ReadInteger );			continue;	 ! Stack integer constant
c('O'):		Update Line( ReadTag );				continue;	 ! Set line number
c('P'):		Plant;				                continue;	 ! Machine code literal
c('Q'):		Operation(RDIVx);					continue
c('R'):		Return( Routine );					continue;	 ! RETURN
c('S'):		assign(1);							continue;	 ! Normal value assignment
c('T'):		Return( True );						continue;	 ! %true
c('U'):		Operation(NEGx);					continue
c('V'):		Return ( Fn );						continue;	 ! FN result
c('W'):		Switch Jump( ReadTag );				continue;	 ! Jump to switch
c('X'):		Operation(EXPx);					continue
!'Y' - UNUSED
c('Z'):		assign(0);							continue;	 ! Assign address '=='
c('['):		Operation(LSHx);					continue
c('¬'):		Operation(NOTx);					continue
c(']'):		Operation(RSHx);					continue
c('^'):		Set Record Format( ReadTag);		continue;	 !   {Set Format}
c('_'):		Switch Label( ReadTag);				continue;	 ! Define switch label
c('a'):		array ref(0);						continue
c('b'):		Constant Bounds;					continue;	 ! Define constant bounded Dope Vector
!'c' NOT IMPLEMENTED
c('d'):		Dimension( ReadTagComma, ReadTag );	continue;	 ! dimensions, count of variables - NB in params:  =0 -> simple array,  # 0 -> array-in-record
c('e'):		Signal Event( ReadTag);				continue;	 ! %signal event
c('f'):     Compile For( ReadTag );				continue
c('g'):		Dimension( ReadTagComma, ReadTag );	continue;	 ! (different to PSR) dimensions, count of variables - NB in params:  =0 -> simple array,  # 0 -> array-in-record
c('h'):
			! compiler op(n)
!			compiler op(ReadTag)
			continue
c('i'):		array ref(1);						continue
c('j'):		assign(2);							continue;	 ! JAM transfer
c('k'):		Jump Forward( ReadTag, FF );		continue;	 ! Branch on FALSE (= 0)
c('l'):		Language Flags = ReadTag;			continue;	 ! We currently only support standard IMP - who knows the future
c('m'):		Monitor;							continue;	 ! %monitor
c('n'):		SelectField( ReadTag );				continue;	 ! Select member from record format
c('o'):		EventTrap( ReadTagComma, ReadTag );	continue;	 ! %on %event block
c('p'):		assign(-1);							continue;	 ! Pass a parameter
c('q'):		DoubleOp( SUBx);					continue;	 !--
c('r'):		resolve( ReadTag );					continue
c('s'):		perm(stop, 0);						continue;	 ! %stop
!!c('t'):		Jump Forward( ReadTag, JNE );		%continue;	! Branch on TRUE (# 0)
c('t'):		Jump Forward( ReadTag, TT );		continue;	 ! Branch on TRUE (# 0)   GT temp fix applied until JDE confirms.
c('u'):		DoubleOp( ADDx );					continue;	 !++
c('v'):		Operation(ABSx);					continue
c('w'):		MachineCode(Get Ascii( ';' ));	    continue;	 ! JDM: allowed call to Machine code
c('x'):		Operation(REXPx);					continue
c('y'):		Set CD( ReadTag, diagnose);			continue;	 ! %diagnose n (what about pass3? how do we send to pass3)
c('z'):		Set CD( ReadTag, control );			continue;	 ! %control n
c('{'):		in params = -1;
			! this is either a record format, a procedure, or a proc spec;
			!   - block type was set by decvar to tell us which
			assemble(block type,labs,names)
			continue; ! Start of formal parameters
c('}'):		in params = 0; exit if Finish Params; continue;	   ! End of formal parameters
c('~'):		exit if AlternateFormat( ReadByte );	continue;	   ! alternate record format

C(*):		abort("Bad I Code");				!%continue;	! To catch the sinners!! (that is - an unimplemented iCode)
		repeat

        if amode >= 0 start;           ! end of declarative block
            while worklist(level) # 0 cycle
                worklist(level) = ret gp tag(worklist(level))
            repeat
            level = level-1
        else;                         ! end of record format defn
            if amode = -2 start;      ! end of alternative only
                frame = max frame if max frame > frame;   ! use the longest alternative
                old frame = frame
            else
                frame = (frame+align)&(¬align);  ! **** temporary ****
                procvar_size = frame
            finish
        finish
        frame = old frame

    end;   !  assemble


    !              -------- it all starts here ---------
    ! JDM - Before we do any file I/O we need to get the source file name
    ! (as used to feed the 'source' stream)
    string(255) the source file name 
    select input( source )
    the source file name = input name

    ! Initialise some arrays that are not declared as %own - this is to match pass2.c and the checksumming
    worklist(i) = 0 for i = 1, 1, maxlevel
    listbytes(i) = 0 for i = 1, 1, lstbufmax
    contable(i) = 0 for i = 0, 1, cotsize
    xsymbuff(i) = 0 for i = 0, 1, 255
    currentstring(i) = 0 for i = 0, 1, 255
    swtab(i) = 0 for i = 0, 1,  maxswitch 
    for i = 0, 1, maxgp cycle
      gptags(i)_info = 0; gptags(i)_addr = 0; gptags(i)_flags = 0; gptags(i)_link = 0
    repeat
    for i = 1, 1, maxstack cycle  ;  ! or maybe stp, for speed...
       ! %string (255) idname; ... lets handle strings later, when we're sure which are Imp and which are C strings, and which are pointers
       stack(i)_type = 0;
       stack(i)_form = 0;
       stack(i)_aform = 0;
       stack(i)_base = 0;
       stack(i)_scope = 0;
       stack(i)_dim = 0;
       stack(i)_disp = 0;
       stack(i)_format = 0;
       stack(i)_size = 0;
       stack(i)_pbase = 0;
       stack(i)_extra = 0;
       stack(i)_extdisp = 0;
       stack(i)_varno = 0;
    repeat
    for i = 1, 1, maxlabs cycle
       Labels(i)_id = 0;
       Labels(i)_tag = 0;
    repeat
    for i = 0, 1, maxvars cycle
      ! ignore *idname for now
      var(i)_type = 0;
      var(i)_form = 0;
      var(i)_level = 0;
      var(i)_scope = 0;
      var(i)_dim = 0;
      var(i)_disp = 0;
      var(i)_format = 0;
      var(i)_size = 0;
      var(i)_pbase = 0;
      var(i)_extra = 0;
      var(i)_extdisp = 0;
    repeat

    buffer(0) = 0;
    buffer(1) = 0;

    ! %byteintegerarray datat(0:datat limit)
    datat(i) = 0 for i = 0, 1, datatlimit
    Pending = 0;


    ! JDM - ok, now we can really start
    select input( icode )
    select output( objout ) 

    var(0) = 0;              !  for %RECORD(*) . . . . .
    parms = max vars

    ! Initialise the GP Tag ASL
    for i=1,1,Max GP cycle
        GP Tags(i)_link = i - 1
    repeat
    gp asl = Max GP

    ! Tell the linker our source file name
    dumpsourcename( the source file name );   ! JDM - hopefully not so bogus now!

    ! predefine the perms for the linker.  We ignore
    ! the number (j) because we know they are in sequence
    for i=1,1,lastperm cycle
        j = externalref(permname(i))
    repeat

    read symbol(Pending);  !  Prime SYM/NEXT pair
    Spec = 0
    decvar == begin
    assemble(-3,0,0)

    ! We flush constants
    flush cot
    flush data
    flush switch
    checksum("at exit")

routine checksum(string(255) which)
  {long} integer crc = 0
  own integer sequence = 0
  integer i, saved
  sequence = sequence + 1
  !%return;
  saved = output stream
  select output(0)

  ! print a checksum of 'interesting' memory locations.  Can be done at any location
  ! in the code. Each checksum is accompanied by a sequence number.  As long as the
  ! program behaves consistently, you can re-run it with the same inputs, and turn
  ! on more detailed debugging just before the checksums diverge from the Imp77 version.

  long test = 16_89AB0123;
  crc = crc32mem(crc, ADDR(test), 4);

  crc = crc32mem(crc, ADDR(Pending), 4);
  crc = crc32mem(crc, ADDR(stp), 4);

  for i = 1, 1, maxstack cycle  ;  ! or maybe stp, for speed...
    ! char *idname; ... lets handle strings later, when we're sure which are Imp and which are C strings.
    crc = crc32mem(crc, ADDR(stack(i)_type), 1);
    crc = crc32mem(crc, ADDR(stack(i)_form), 1);
    crc = crc32mem(crc, ADDR(stack(i)_aform), 1);
    crc = crc32mem(crc, ADDR(stack(i)_base), 1);
    crc = crc32mem(crc, ADDR(stack(i)_scope), 1);
    crc = crc32mem(crc, ADDR(stack(i)_dim), 1);
    crc = crc32mem(crc, ADDR(stack(i)_disp), 4);
    crc = crc32mem(crc, ADDR(stack(i)_format), 4);
    crc = crc32mem(crc, ADDR(stack(i)_size), 4);
    crc = crc32mem(crc, ADDR(stack(i)_pbase), 4);
    crc = crc32mem(crc, ADDR(stack(i)_extra), 4);
    crc = crc32mem(crc, ADDR(stack(i)_extdisp), 4);
    crc = crc32mem(crc, ADDR(stack(i)_varno), 4);
  repeat

  !  %recordformat LabelFm(%integer id, tag)
  !  %record(LabelFm)%array Labels(1:Max Labs)
  for i = 1, 1, maxlabs cycle
    crc = crc32mem(crc, ADDR(Labels(i)_id), 4);
    crc = crc32mem(crc, ADDR(Labels(i)_tag), 4);
  repeat
 
  ! /* static */ int worklist[maxlevel + 1]; // re-based at 0 for efficiency imptoc: EXPLICIT INIT
  crc = crc32mem(crc, ADDR(worklist(i)), 4) for i = 1,1,maxlevel;
        
  !  %recordformat  varfm( %string(255) idname, %byteinteger type, form, level, scope, dim, %integer  disp, format, size, pbase, extra, extdisp )
  !  %record(varfm)%array  var(0:max vars)
  !!crc = crc32mem(crc, ADDR(var(0)), (maxvars+1)*sizeof(var(0)));
  for i = 0, 1, maxvars cycle
      ! ignore *idname for now
      crc = crc32mem(crc, ADDR(var(i)_type), 1)
      crc = crc32mem(crc, ADDR(var(i)_form), 1)
      crc = crc32mem(crc, ADDR(var(i)_level), 1)
      crc = crc32mem(crc, ADDR(var(i)_scope), 1)
      crc = crc32mem(crc, ADDR(var(i)_dim), 1)
      crc = crc32mem(crc, ADDR(var(i)_disp), 4)
      crc = crc32mem(crc, ADDR(var(i)_format), 4)
      crc = crc32mem(crc, ADDR(var(i)_size), 4)
      crc = crc32mem(crc, ADDR(var(i)_pbase), 4)
      crc = crc32mem(crc, ADDR(var(i)_extra), 4)
      crc = crc32mem(crc, ADDR(var(i)_extdisp), 4)
  repeat
    
  ! auto /* static */ int activity[ 16 /* fr7 */ + 1] = { 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // zero-based array
  crc = crc32mem(crc, ADDR(activity(0)), (16+1)*sizeof(activity(0)));

  !crc = crc32mem(crc, &maxgp, sizeof(maxgp));
  ! gptag gptags[maxgp + 1]; // zero-based array  121 els
  !crc = crc32mem(crc, &gptags[0], sizeof(gptag)*121);
  crc = crc32mem(crc, ADDR(gptags(i)), sizeof(gptags(0))) for i = 0, 1, maxgp
  
  ! int swtab[maxswitch + 1]; // zero-based array
  crc = crc32mem(crc, ADDR(swtab(0)), (maxswitch+1)*sizeof(swtab(0)));
  
  ! unsigned char currentstring[255 + 1];   // current string literal // zero-based array
  crc = crc32mem(crc, ADDR(currentstring(0)), 256);
    
  ! unsigned char xsymbuff[255 - 0 + 1];    // current external string name // zero-based array
  crc = crc32mem(crc, ADDR(xsymbuff(0)), 256);
    
  ! static unsigned char objectbytes[ objbufmax + 1 ];  // zero-based array // initialised to all 0
  crc = crc32mem(crc, ADDR(objectbytes(0)), objbufmax+1);

  ! static unsigned char listbytes[ lstbufmax + 1 ]; // initialised to all 0 // zero-based array
  crc = crc32mem(crc, ADDR(listbytes(0)), lstbufmax+1);
    
  ! unsigned char buffer[1 + 1]; // zero-based array
  crc = crc32mem(crc, ADDR(buffer(0)), 1);
  crc = crc32mem(crc, ADDR(buffer(1)), 1);
  
  ! static unsigned char contable[ 2000 /* cotsize */ - 0 + 1]; // zero-based array // initialise to all 0
  crc = crc32mem(crc, ADDR(contable(0)), 2001);

  !!    %byteintegerarray datat(0:datat limit)
  ! unsigned char datat[datatlimit - 0 + 1]; // zero-based array
  crc = crc32mem(crc, ADDR(datat(i)), 1) for i = 0, 1, datatlimit

  if which = "at exit" start
    printstring("Imp executable post-execution checksum "); write(crc,0);newline
  finish else start
    printstring(which); printsymbol(' '); write(crc,0); newline
  finish
  select output(saved)
end

endofprogram