;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Microsoft Research Singularity
;;; 
;;; Copyright (c) Microsoft Corporation.  All rights reserved.
;;;
;;; This file contains ARM-specific assembly code.
;;;

; Assembler source for FPA support code and emulator
; ==================================================
; Some useful assembler macros. Also used by "fplib".
;
; Copyright (C) Advanced RISC Machines Limited, 1992-7. All rights reserved.
;
; RCS Revision: 1 
; Checkin Date: 2007/06/29 02:59:16 
; Revising Author

;===========================================================================

; Register names used when isolating the base register of a PC-relative or
; register-relative expression in the macros below. The technique is to set
; a temporary arithmetic variable Base to :BASE:(expression), then refer to
; R$Base.

R00000000       RN      R0
R00000001       RN      R1
R00000002       RN      R2
R00000003       RN      R3
R00000004       RN      R4
R00000005       RN      R5
R00000006       RN      R6
R00000007       RN      R7
R00000008       RN      R8
R00000009       RN      R9
R0000000A       RN      R10
R0000000B       RN      R11
R0000000C       RN      R12
R0000000D       RN      R13
R0000000E       RN      R14
R0000000F       RN      R15

;===========================================================================

; Two general purpose arithmetic variables.

        GBLA    Tempa
        GBLA    Tempa2

;===========================================================================

; The following macro is useful for shifting bit fields around when their
; positions are symbolic constants - which makes it unclear to the author
; whether LSR or LSL is needed.

        MACRO
$label  BiShift $opc,$Rd,$Rn,$Rm,$rshift,$lshift
        [ "$lshift":LEFT:5 <> "LSL #"
          ! 4,"Left shift must start exactly 'LSL #'"
        |
          [ "$rshift":LEFT:5 <> "LSR #"
            ! 4,"Right shift must start exactly 'LSR #'"
          |
            LCLS left
            LCLS right
left        SETS "$lshift":RIGHT:(:LEN:"$lshift" - 5)
right       SETS "$rshift":RIGHT:(:LEN:"$rshift" - 5)
            [ "$Rn" = ""
              ASSERT (("$opc":LEFT:3) <> "LDR") :LAND: (("$opc":LEFT:3) <> "STR")
              [ ($right) > ($left)
$label                          $opc    $Rd,$Rm,LSR #(($right) - ($left))
              |
$label                          $opc    $Rd,$Rm,LSL #(($left) - ($right))
              ]
            |
              [ ($right) > ($left)
                [ (("$opc":LEFT:3) = "LDR") :LOR: (("$opc":LEFT:3) = "STR")
$label                          $opc    $Rd,[$Rn,$Rm,LSR #(($right) - ($left))]
                |
$label                          $opc    $Rd,$Rn,$Rm,LSR #(($right) - ($left))
                ]
              |
                [ (("$opc":LEFT:3) = "LDR") :LOR: (("$opc":LEFT:3) = "STR")
$label                          $opc    $Rd,[$Rn,$Rm,LSL #(($left) - ($right))]
                |
$label                          $opc    $Rd,$Rn,$Rm,LSL #(($left) - ($right))
                ]
              ]
            ]
          ]
        ]
        MEND

;===========================================================================

; The following macro isolates the exponent field from the standard sign/
; uncommon bit/exponent word, putting it at the top of the destination
; register.

        MACRO
$label  Exp2Top $dest,$src,$cond,$s
        [ EIExp_pos = 0
$label    MOV$cond$s $dest,$src,LSL #32-EIExp_len
        |
$label    MOV$cond   $dest,$src,LSR #EIExp_pos
          MOV$cond$s $dest,$dest,LSL #32-EIExp_len
        ]
        MEND

;===========================================================================

; The following macros isolate the exponent fields from two standard sign/
; uncommon bit/exponent words, putting the first one at the top of a
; destination register. ExpDiff puts the difference at the top of another
; register and sets the condition codes on it, while ExpComp simply sets the
; condition codes on the difference.

        MACRO
$label  ExpComp $dest,$src1,$src2,$tmp
        ASSERT  $dest <> $src1
        ASSERT  $dest <> $src2
        ASSERT  $dest <> $tmp
        ASSERT  $tmp <> $src1
        ASSERT  $tmp <> $src2
        [ EIExp_pos = 0
$label    MOV     $dest,$src1,LSL #32-EIExp_len
          CMP     $dest,$src2,LSL #32-EIExp_len
        |
$label    MOV     $dest,$src1,LSR #EIExp_pos
          MOV     $dest,$dest,LSL #32-EIExp_len
          MOV     $tmp,$src2,LSR #EIExp_pos
          CMP     $dest,$tmp,LSL #32-EIExp_len
        ]
        MEND

        MACRO
$label  ExpDiff $diff,$dest,$src1,$src2
        ASSERT  $diff <> $dest
        ASSERT  $diff <> $src1
        ASSERT  $diff <> $src2
        ASSERT  $dest <> $src1
        ASSERT  $dest <> $src2
        [ EIExp_pos = 0
$label    MOV     $dest,$src1,LSL #32-EIExp_len
          SUBS    $diff,$dest,$src2,LSL #32-EIExp_len
        |
$label    MOV     $dest,$src1,LSR #EIExp_pos
          MOV     $dest,$dest,LSL #32-EIExp_len
          MOV     $diff,$src2,LSR #EIExp_pos
          SUBS    $diff,$dest,$diff,LSL #32-EIExp_len
        ]
        MEND

;===========================================================================

; The following macro performs the standard test for infinities or NaNs on
; an internal floating point number. It only works on legitimate internal
; precision numbers - i.e. it produces undefined results if the bit pattern
; in the internal precision number is an undefined one. The parameters are:
;   $res: On exit, the top bit of this register is set if the number is a
;         NaN or infinity, clear if it isn't;
;   $sue: Register holding sign, uncommon bit and exponent of number to be
;         tested; preserved on exit;
;   $mhi: Register containing high word of mantissa of number to be tested;
;         preserved on exit;
;   $mlo: Register containing low word of mantissa of number to be tested;
;         preserved on exit;
;
; In addition, the N flag is set on exit if the number is a NaN or infinity,
; clear if it isn't.
;
; The criterion used for a number to be a NaN or infinity is:
;
;   Uncommon bit = 1; and
;   Exponent top bit = 1; and
;   Exponent = MAX or units bit = 1.
;
; Whether the operand is in fact a NaN or an infinity is then determined by
; seeing whether the fraction is non-zero or zero.

        MACRO
$label  TNaNInf $res,$sue,$mhi
        ASSERT  $res <> $sue
        ASSERT  $res <> $mhi
$label  MOV     $res,$sue,LSL #32-(EIExp_pos+EIExp_len) ;Top bit of exponent
        CMN     $res,#1:SHL:(32-(EIExp_pos+EIExp_len))  ;Is exp. = MAX? If
        ANDCC   $res,$res,$mhi                          ; not, use units bit
        ANDS    $res,$res,$sue,LSL #31-Uncommon_pos     ;Use uncommon anyway
        MEND

;===========================================================================

; The following macro contains the standard code for denormalising a
; mantissa by a specified amount, producing guard, round and sticky bits in
; the process. The parameters are:
;   $mhi: Register containing mantissa high word; updated on exit;
;   $mlo: Register containing mantissa low word; updated on exit;
;   $grs: Register that will contain the guard bit (in bit 31), the round
;         bit (in bit 30) and the sticky bit (in whether bits 29:0 are zero
;         or non-zero) on exit;
;   $sh:  Register containing the shift amount; corrupt on exit;
;   $t1,$t2: Registers used as temporaries; corrupt on exit.
; $grs may be null to indicate that the guard, round and sticky information
; isn't wanted. $mlo can be null to indicate that we only need to
; denormalise a single word: in this case, $grs must be null.
;   Note that the $grs register may alternatively be interpreted as
; containing a round bit in bit 31 and a sticky bit in bits 30:0, in cases
; when there is no need for a guard bit. Also note that $sh may be the same
; register as either of $grs and $t2; otherwise, the registers must be
; distinct from each other.
;   Finally, note that branch instructions are used around a 4 instruction
; sequence and a 5 instruction sequence. This is because statistics show
; that larger shift amounts are less common than smaller ones in general:
; thus these instruction sequences are obeyed less than 50% of the time,
; which makes the code with branches slightly faster.

        MACRO
$label  Denorm  $mhi,$mlo,$grs,$sh,$t1,$t2
        ASSERT  $mhi <> $sh
        ASSERT  $mhi <> $t1
        ASSERT  $mhi <> $t2
        ASSERT  $sh <> $t1
        ASSERT  $t1 <> $t2
$label  MOV     $t1,$sh,LSR #5          ;Number of words to shift by
        BIC     $t2,$sh,$t1,LSL #5      ;Number of odd bits to shift by
        [ "$mlo" = ""
          ASSERT  "$grs" = ""
          CMP     $t1,#1                  ;At least one word?
          MOVLO   $mhi,$mhi,LSR $t2       ;Shift by odd bits if not
          MOVHS   $mhi,#0                 ;And clear out completely if so
        |
          ASSERT  $mlo <> $mhi
          ASSERT  $mlo <> $sh
          ASSERT  $mlo <> $t1
          ASSERT  $mlo <> $t2
          [ "$grs" = ""
            CMP     $t1,#1                  ;HI for 2+ words, EQ for 1, LO for 0
            RSBLS   $t1,$t2,#32             ;Shift by the number of odd bits
            MOVLS   $mlo,$mlo,LSR $t2
            ORRLS   $mlo,$mlo,$mhi,LSL $t1
            MOVLS   $mhi,$mhi,LSR $t2
            MOVEQ   $mlo,$mhi               ;Now do full words
            MOVHI   $mlo,#0
            MOVHS   $mhi,#0
          |
            ASSERT  $grs <> $mhi
            ASSERT  $grs <> $mlo
            ASSERT  $grs <> $t1
            ASSERT  $grs <> $t2
            CMP     $t1,#2                  ;CS/NE for 3+ words, CS/EQ for 2,
            TEQCC   $t1,#0                  ; CC/NE for 1 and CC/EQ for 0.
            RSB     $t1,$t2,#32             ;Shift by the number of odd bits
            MOV     $grs,$mlo,LSL $t1
            MOV     $mlo,$mlo,LSR $t2
            ORR     $mlo,$mlo,$mhi,LSL $t1
            MOV     $mhi,$mhi,LSR $t2
            BEQ     %f90                    ;Branch if no 32-bit shift
            ORRNE   $grs,$grs,$grs,LSL #2   ;Shift by 32 bits, accumulating
            ORRNE   $grs,$mlo,$grs,LSR #2   ; sticky bit
            MOVNE   $mlo,$mhi
            MOVNE   $mhi,#0
90
            BCC     %f99                    ;Branch if no 64-bit shift
            ORRCS   $grs,$grs,$mlo          ;Shift by 64 bits, accumulating
            ORRCS   $grs,$grs,$grs,LSL #2   ; sticky bit
            ORRCS   $grs,$mhi,$grs,LSR #2
            MOVCS   $mlo,#0
            MOVCS   $mhi,#0
99
          ]
        ]
        MEND

;===========================================================================

; Macro to separate a 32-bit value in a register into its two 16-bit halves.

        MACRO
$label  Split16 $resh,$resl,$src
        ASSERT  $resh <> $src
$label  MOV     $resh,$src,LSR #16
        BIC     $resl,$src,$resh,LSL #16
        MEND

;===========================================================================

; Macro to do a (16,16)x32 -> 64 multiplication. Done by breaking it up into
; four 16x16 multiplications and recombining the pieces. (N.B. The trick
; described in Knuth section 4.3.3 for reducing the four multiplications to
; three plus some additions and sign manipulations is not profitable at this
; size: it only becomes profitable when trying to synthesise a 64x64
; multiplication out of 32x32 multiplications.)
;   Also allows the flags to be set on the high word of the result and an
; optional addend to be added into the high word of the result: however,
; combining these does *not* result in the C flag being set correctly for
; the carry-out from the notional addition of the addend and the high word.
; Only the Z and N flags have meaningful values.
;   The operands are:
; $resh,$resl: Registers that will receive the 64-bit product;
; $op1h,$op1l: Registers containing the high and low 16 bits of the first
;              32-bit operand;
; $op2:        Register containing the second 32-bit operand;
; $add:        If present, register containing the addend;
; $s:          "S" to set the condition codes;
; $t1,$t2,$t3: Three temporary registers required during the calculation.
; The restrictions on which registers may be the same are complicated and
; are detailed in the ASSERT statements below.

        MACRO
$label  Mul64   $resh,$resl,$op1h,$op1l,$op2,$add,$s,$t1,$t2,$t3
        ASSERT  $resh <> $resl
        ASSERT  $resl <> $op1h
        ASSERT  $resl <> $t1
        ASSERT  $resl <> $t2
        ASSERT  $resl <> $t3
        ASSERT  $op1h <> $op1l
        ASSERT  $op1h <> $op2
        ASSERT  $op1h <> $t1
        ASSERT  $op1h <> $t2
        ASSERT  $op1h <> $t3
        ASSERT  $op1l <> $op2
        ASSERT  $op1l <> $t1
        ASSERT  $op1l <> $t2
        ASSERT  $op1l <> $t3
        ASSERT  $op2  <> $t1
        ASSERT  $t1   <> $t2
        ASSERT  $t1   <> $t3
        ASSERT  $t2   <> $t3
$label  Split16 $t1,$t2,$op2            ;t1 := op2h, t2 := op2l
        [ "$add" <> ""
          ASSERT  $add <> $op1h
          ASSERT  $add <> $op1l
          ASSERT  $add <> $op2
          ASSERT  $add <> $t1
          ASSERT  $add <> $t2
          MLA     $t3,$op1h,$t1,$add      ;t3 := op1h * op2h + add
        |
          MUL     $t3,$op1h,$t1           ;t3 := op1h * op2h
        ]
        MUL     $t1,$op1l,$t1           ;t1 := op1l * op2h
        MUL     $resl,$t2,$op1l         ;resl := op1l * op2l
        ADDS    $resl,$resl,$t1,LSL #16 ;Add op1l * op2h into (t3,resl)
        ADC     $t3,$t3,$t1,LSR #16
        MUL     $t2,$op1h,$t2           ;t2 := op1h * op2l
        ADDS    $resl,$resl,$t2,LSL #16 ;Add op1h * op2l into (t3,resl)
        ADC$s   $resh,$t3,$t2,LSR #16   ; to produce (resh,resl)
        MEND

;===========================================================================

; Macro to transfer the destination register of an instruction to a set of
; registers. Operands are:
; $type:     "FPASC" or "FPE";
; $dest:     The destination register list;
; $instr:    The instruction whose destination is to be transferred;
; $t:        A temporary.

        MACRO
$label  GetDst  $type,$dest,$instr,$t
        LCLA    Base
        LCLA    Offset
        ASSERT  ("$type" = "FPASC") :LOR: ("$type" = "FPE")
        [ ("$type" = "FPASC")
$label    TST     $instr,#4:SHL:Ds_pos          ;Check whether F0-3 or F4-7
          SFMEQFD F0,4,[Rsp]!                   ;Dump set of 4 registers -
          SFMNEFD F4,4,[Rsp]!                   ; faster than trying to get
                                                ; the correct register only
          AND     $t,$instr,#3:SHL:Ds_pos       ;Get position in dump
          ADD     $t,$t,$t,LSL #1               ;Convert to number of words
          BiShift ADD,$t,Rsp,$t,LSR #Ds_pos,LSL #2 ;Make address of register
          LDMIA   $t,$dest                      ; value, then get value
          ADD     Rsp,Rsp,#48                   ;Discard dumped registers
          ASSERT  Ds_mask = ((4+3):SHL:Ds_pos)
        |
$label    AND     $t,$instr,#Ds_mask
          [ :LNOT:FPE4WordsPerReg
            ADD     $t,$t,$t,LSL #1
            ASSERT  Ds_pos <= 27
          ]
Base      SETA            :BASE:FPE_Regs
          [ Base = 15
Offset      SETA            FPE_Regs-({PC}+8)
          |
Offset      SETA            :INDEX:FPE_Regs
          ]
          [ FPE4WordsPerReg
            BiShift ADD,$t,R$Base,$t,LSR #Ds_pos,LSL #4
          |
            BiShift ADD,$t,R$Base,$t,LSR #Ds_pos,LSL #2
          ]
          [ Offset <> 0
            ADD     $t,$t,#Offset
          ]
          LDMIA   $t,$dest
        ]
        MEND

;===========================================================================

; Macro to transfer the destination register of a non-FLT instruction from a
; set of registers. Operands are:
; $type:     "FPASC" or "FPE";
; $source:   The source register list;
; $instr:    The instruction whose destination is to be transferred;
; $t:        A temporary.
; $l:        If present, this produces a "long" form of the macro

        MACRO
$label  PutDst  $type,$source,$instr,$t,$l
        ASSERT  $t <> $instr
        LCLA    Base
        LCLA    Offset
        ASSERT  ("$type" = "FPASC") :LOR: ("$type" = "FPE")
        [ ("$type" = "FPASC")
          ALIGN
$label
          STMFD   Rsp!,$source
          AND     $t,$instr,#Ds_mask
10
          BiShift ADD,$t,PC,$t,LSR #Ds_pos,LSL #3
          [ "$l"=""
            MOV     LR,PC
            ADD     PC,$t,#($type._PutDstRoutines - (%b10+8))
          |
            ADD     $t,$t,#($type._PutDstRoutines - (%b10+8)):AND:&FF
            MOV     LR,PC
            ADD     PC,$t,#($type._PutDstRoutines - (%b10+8)):AND::NOT:&FF
          ]
        |
        ; "$type" = "FPE"
$label    AND     $t,$instr,#Ds_mask
          [ :LNOT:FPE4WordsPerReg
            ADD     $t,$t,$t,LSL #1
            ASSERT  Ds_pos <= 27
          ]
Base      SETA            :BASE:FPE_Regs
          [ Base = 15
Offset      SETA            FPE_Regs-({PC}+8)
          |
Offset      SETA            :INDEX:FPE_Regs
          ]
          [ FPE4WordsPerReg
            BiShift ADD,$t,R$Base,$t,LSR #Ds_pos,LSL #4
          |
            BiShift ADD,$t,R$Base,$t,LSR #Ds_pos,LSL #2
          ]
          [ Offset <> 0
            ADD     $t,$t,#Offset
          ]
          STMIA   $t,$source
        ]
        MEND

;===========================================================================

; Macro to transfer the destination register of a FLT instruction from a set
; of registers. Operands are:
; $type:     "FPASC" or "FPE";
; $source:   The source register list;
; $instr:    The instruction whose destination is to be transferred;
; $t:        A temporary.

        MACRO
$label  PutFDst $type,$source,$instr,$t
        ASSERT  $t <> $instr
        LCLA    Base
        LCLA    Offset
        ASSERT  ("$type" = "FPASC") :LOR: ("$type" = "FPE")
        [ ("$type" = "FPASC")
          ALIGN
$label
          STMFD   Rsp!,$source
          AND     $t,$instr,#S1_mask
10
          BiShift ADD,$t,PC,$t,LSR #S1_pos,LSL #3
          MOV     LR,PC
          ADD     PC,$t,#($type._PutDstRoutines - (%b10+8))
        |
        ; "$type" = "FPE"
$label    AND     $t,$instr,#S1_mask
          [ :LNOT:FPE4WordsPerReg
            ADD     $t,$t,$t,LSL #1
            ASSERT  S1_pos <= 27
          ]
Base      SETA            :BASE:FPE_Regs
          [ Base = 15
Offset      SETA            FPE_Regs-({PC}+8)
          |
Offset      SETA            :INDEX:FPE_Regs
          ]
          [ FPE4WordsPerReg
            BiShift ADD,$t,R$Base,$t,LSR #S1_pos,LSL #4
          |
            BiShift ADD,$t,R$Base,$t,LSR #S1_pos,LSL #2
          ]
          [ Offset <> 0
            ADD     $t,$t,#Offset
          ]
          STMIA   $t,$source
        ]
        MEND

;===========================================================================

; Macro to get the first source register of an instruction into three
; registers. Operands are:
; $type:     "FPASC" or "FPE";
; $dest:     The destination register list;
; $instr:    The instruction whose first source is to be transferred;
; $t:        A temporary.

        MACRO
$label  GetS1   $type,$dest,$instr,$t
        LCLA    Base
        LCLA    Offset
        ASSERT  ("$type" = "FPASC") :LOR: ("$type" = "FPE")
        [ ("$type" = "FPASC")
$label    TST     $instr,#4:SHL:S1_pos          ;Check whether F0-3 or F4-7
          SFMEQFD F0,4,[Rsp]!                   ;Dump set of 4 registers -
          SFMNEFD F4,4,[Rsp]!                   ; faster than trying to get
                                                ; the correct register only
          AND     $t,$instr,#3:SHL:S1_pos       ;Get position in dump
          ADD     $t,$t,$t,LSL #1               ;Convert to number of words
          BiShift ADD,$t,Rsp,$t,LSR #S1_pos,LSL #2 ;Make address of register
          LDMIA   $t,$dest                      ; value, then get value
          ADD     Rsp,Rsp,#48                   ;Discard dumped registers
          ASSERT  S1_mask = ((4+3):SHL:S1_pos)
        |
$label    AND     $t,$instr,#S1_mask
          [ :LNOT:FPE4WordsPerReg
            ADD     $t,$t,$t,LSL #1
            ASSERT  S1_pos <= 27
          ]
Base      SETA            :BASE:FPE_Regs
          [ Base = 15
Offset      SETA            FPE_Regs-({PC}+8)
          |
Offset      SETA            :INDEX:FPE_Regs
          ]
          [ FPE4WordsPerReg
            BiShift ADD,$t,R$Base,$t,LSR #S1_pos,LSL #4
          |
            BiShift ADD,$t,R$Base,$t,LSR #S1_pos,LSL #2
          ]
          [ Offset <> 0
            ADD     $t,$t,#Offset
          ]
          LDMIA   $t,$dest
        ]
        MEND

;===========================================================================

; Macro to get the second source register or constant of an instruction into
; three registers. Operands are:
; $type:     "FPASC" or "FPE";
; $dest:     The destination register list;
; $instr:    The instruction whose second source is to be transferred;
; $t,$t2:    Temporaries.

        MACRO
$label  GetS2   $type,$dest,$instr,$t,$t2
        LCLA    Base
        LCLA    Offset
        ASSERT  ("$type" = "FPASC") :LOR: ("$type" = "FPE")
        ASSERT  S2_Ibit = 1:SHL:(S2_pos+3)
        ASSERT  S2_pos = 0
        ASSERT  $t <> $t2
$label  MOVS    $t2,$instr,LSL #29      ;C:=S2_Ibit, N:=F4-7, not F0-3
                                        ;$t2 := left-al. reg/const no.
        [ ("$type" = "FPASC")
Base      SETA            :BASE:$type.ConstTable
          [ Base = 15
Offset      SETA            $type.ConstTable-({PC}+8)
          |
Offset      SETA            :INDEX:$type.ConstTable
          ]
          ADDCS   $t,R$Base,$t2,LSR #25         ;Address the constant if it
          [ Offset <> 0
            ADDCS   $t,$t,#Offset               ; is one
          ]
          BCS     %f10
          SFMPLFD F0,4,[Rsp]!                   ;Dump set of 4 registers -
          SFMMIFD F4,4,[Rsp]!                   ; faster than trying to get
                                                ; the correct register only
          BIC     $t2,$t2,#TopBit               ;Get position within set
          ADD     $t,Rsp,$t2,LSR #27            ;Make address of register
          ADD     $t,$t,$t2,LSR #26             ; value
10
          LDMIA   $t,$dest                      ;Get reg. value or constant
          ADDCC   Rsp,Rsp,#48                   ;If reg, discard dumped regs
        |
Base      SETA            :BASE:FPEConstTable
          [ Base = 15
Offset      SETA            FPEConstTable-({PC}+8)
          |
Offset      SETA            :INDEX:FPEConstTable
          ]
          ADDCS   $t,R$Base,$t2,LSR #25
          [ Offset <> 0
            ADDCS   $t,$t,#Offset
          ]
Base      SETA            :BASE:FPE_Regs
          [ Base = 15
Offset      SETA            FPE_Regs-({PC}+8)
          |
Offset      SETA            :INDEX:FPE_Regs
          ]
          [ FPE4WordsPerReg
            ADDCC   $t,R$Base,$t2,LSR #25
          |
            ADDCC   $t,R$Base,$t2,LSR #27
            ADDCC   $t,$t,$t2,LSR #26
          ]
          [ Offset <> 0
            ADDCC   $t,$t,#Offset
          ]
          LDMIA   $t,$dest
        ]
        MEND

;===========================================================================

; Macro to get both source operands of an instruction into two groups of
; three registers. Operands are:
; $type:     "FPASC" or "FPE";
; $dest1:    The destination register list for the first operand;
; $dest2:    The destination register list for the second operand;
; $instr:    The instruction whose second source is to be transferred;
; $t, $t2:   Temporaries.

        MACRO
$label  GetS12  $type,$dest1,$dest2,$instr,$t,$t2
        LCLA    Base
        LCLA    Offset
        ASSERT  ("$type" = "FPASC") :LOR: ("$type" = "FPE")
        [ ("$type" = "FPASC")
          SFMFD   F4,4,[Rsp]!                   ;Dump all registers
          SFMFD   F0,4,[Rsp]!
          AND     $t,$instr,#S1_mask            ;Get S1 position in dump
          ADD     $t,$t,$t,LSL #1               ;Convert to number of words
          BiShift ADD,$t,Rsp,$t,LSR #S1_pos,LSL #2 ;Make address of register
          LDMIA   $t,$dest1                     ; value, then get value
          ASSERT  S2_Ibit = 1:SHL:(S2_pos+3)
          ASSERT  S2_pos = 0
          MOVS    $t2,$instr,LSL #29    ;C:=S2_Ibit, N:=F4-7, not F0-3
                                        ;$t2 := left-al. reg/const no.
Base      SETA            :BASE:$type.ConstTable
          [ Base = 15
Offset      SETA            $type.ConstTable-({PC}+8)
          |
Offset      SETA            :INDEX:$type.ConstTable
          ]
          ADDCS   $t,R$Base,$t2,LSR #25         ;Address the constant if it
          [ Offset <> 0
            ADDCS   $t,$t,#Offset               ; is one
          ]
          ADDCC   $t,Rsp,$t2,LSR #27            ;Otherwise address the register
          ADDCC   $t,$t,$t2,LSR #26             ; value
          LDMIA   $t,$dest2
          ADD     Rsp,Rsp,#96                   ;Discard the register dump
        |
$label    GetS1   $type,$dest1,$instr,$t
          GetS2   $type,$dest2,$instr,$t,$t2
        ]
        MEND

;===========================================================================

; The standard macro to return to the caller. Note that care has to be taken
; here never to leave Rsp pointing above any useful stack contents, in case
; of a badly-timed interrupt.

        MACRO
$label  Return
        [ {CONFIG} = 26
$label    MOV     Rsp,Rfp               ;Discard now-spurious stack contents
        ]
        [ {CONFIG} = 32
$label    LDMDB   Rfp,{Rtmp,Rtmp2}      ;Recover the SPSR and CPSR
          MSR     CPSR_all,Rtmp2        ; (restoring the CPSR re-disables
          MSR     SPSR_all,Rtmp         ; interrupts, so the SPSR isn't ever
                                        ; valid when interrupts are enabled)
          MOV     Rsp,Rfp               ;Discard now-spurious stack contents
        ]
        LDMIA   Rfp,{R0-R14}^           ;Coding rules: cannot use write-back
        NOP                             ; and must protect next instruction
        ADD     Rsp,Rsp,#15*4           ;Do the write-back
        LDMFD   Rsp!,{PC}^              ;Restore R13_svr/R13_und, PC and PSR
        MEND

;===========================================================================

; Macro to get the processor mode for the caller, with the 26/32 bit
; distinction removed. The flags are set on the result value, so Z indicates
; whether we're in user mode.

        MACRO
$label  GetMode $res
        [ {CONFIG}=32
$label    LDR     $res,[Rfp,#-8]                ;Recover original SPSR value
          ANDS    $res,$res,#Mode_mask-Mode_32not26
          ASSERT  (Mode_USR26:AND::NOT:Mode_32not26) = 0
          ASSERT  (Mode_USR32:AND::NOT:Mode_32not26) = 0
        ]
        [ {CONFIG}=26
$label    LDR     $res,[Rfp,#15*4]              ;Recover original LR value
          ANDS    $res,$res,#Mode_mask
          ASSERT  Mode_USR26 = 0
        ]
        MEND

;===========================================================================

; Macro to insert the right amount of padding between a table-driven branch
; instruction (e.g. ADD PC,PC,reg,LSL #2) and the in-line branch table that
; follows it. Made into a macro for documentation purposes, and also just in
; case the user-visible pipeline depth has to change at some point in the
; future.

        MACRO
        BranchTablePad
        DCD     0               ;Padding before branch table
        MEND

;===========================================================================

; Macro to re-enable interrupts if "EnableInterrupts" is {TRUE}. Only
; argument is a temporary register.

        MACRO
$label  InterruptEnable $t
        [ EnableInterrupts
          [ {CONFIG} = 32
$label      MRS     $t,CPSR_all
            BIC     $t,$t,#I_bit
            MSR     CPSR_all,$t
          ]
          [ {CONFIG} = 26
$label      MOV     $t,PC
            BIC     $t,$t,#I_bit
            TEQP    PC,$t
          ]
          NOP
        ]
        MEND

;===========================================================================

; Macro to re-disable interrupts if "EnableInterrupts" is {TRUE}. Only
; argument is a temporary register.

        MACRO
$label  InterruptDisable $t
        [ EnableInterrupts
          [ {CONFIG} = 32
$label      MRS     $t,CPSR_all
            ORR     $t,$t,#I_bit
            MSR     CPSR_all,$t
          ]
          [ {CONFIG} = 26
$label      MOV     $t,PC
            ORR     $t,$t,#I_bit
            TEQP    PC,$t
          ]
          NOP
        ]
        MEND

;===========================================================================

; Macro to do the standard "enter recursive floating point code" processing.
; The operands are:
; $freeregs: Number of floating point registers to free up, in the range 1
;            to 8;
; $extra:    Number of bytes of extra space to be left on the stack above
;            the register dump;
; $addr:     Register to contain address of extra space - also used to hold
;            temporary values during macro, so must be present even if no
;            extra space is wanted;
; $nofpsr:   If this operand is non-null, it inhibits the FPSR changes
;            described below.
; Rsp and Rfpsr are also operands.
;   This code is written quite carefully, to avoid the use of floating point
; instructions the FPE won't like (e.g. those which use mode-dependent
; registers). It also disables interrupts (if they were ever enabled) and
; leaves a record of what floating point registers are on the stack, to make
; certain core_abort works. Finally, it clears out the exception enable bits
; and cumulative flags from the real FPSR, since exceptions in recursive
; code must not go out to the user trap handlers. Note that Rfpsr holds the
; real FPSR value and will be written back to the real FPSR before control
; is returned to the user, either via a trap handler or by normal return.

        MACRO
$label  EnterRecursive $freeregs,$extra,$addr,$nofpsr
        ASSERT  ($freeregs) <= 8
        ASSERT  ($freeregs) >= 1
$label  SUB     Rsp,Rsp,#($freeregs)*12+4+($extra)
        MOV     $addr,Rfpsr,LSL #8
        ORR     $addr,$addr,#(1:SHL:($freeregs))-1
        STR     $addr,[Rsp]
        InterruptDisable $addr
        [ "$nofpsr" = ""
          BIC     $addr,Rfpsr,#IOE_bit+DZE_bit+OFE_bit+UFE_bit+IXE_bit
          BIC     $addr,$addr,#IOC_bit+DZC_bit+OFC_bit+UFC_bit+IXC_bit
          WFS     $addr
        ]
        ADD     $addr,Rsp,#($freeregs)*12+4
        [ ($freeregs) <= 4
          SFM     F0,($freeregs),[$addr,#-12*($freeregs)]
        |
          SFM     F0,4,[$addr,#-12*($freeregs)]
          SFM     F4,($freeregs)-4,[$addr,#-12*($freeregs)+48]
        ]
        MEND

;===========================================================================

; Macro to do the standard "exit recursive floating point code" processing.
; The operands are:
; $freeregs: Number of floating point registers to recover from the stack,
;            in the range 1 to 8;
; $extra:    Number of bytes of extra space to recover from the stack;
; $t:        A temporary register;
; $fpres:    Floating point register that contains the floating point result
;            - null if no such result;
; $result:   Register list to take 3 word floating point result - null if
;            no such result.
; Rsp is also an operand.

        MACRO
$label  ExitRecursive $freeregs,$extra,$t,$fpres,$result
        ASSERT  ($freeregs) <= 8
        ASSERT  ($freeregs) >= 1
        ADD     $t,Rsp,#($freeregs)*12+4
        [ "$fpres" <> ""
          ASSERT  ($extra) >= 12
          SFM     $fpres,1,[$t]
          LDMIA   $t,$result
        ]
        [ ($freeregs) <= 4
          LFM     F0,($freeregs),[$t,#-12*($freeregs)]
        |
          LFM     F0,4,[$t,#-12*($freeregs)]
          LFM     F4,($freeregs)-4,[$t,#-12*($freeregs)+48]
        ]
        ADD     Rsp,Rsp,#($freeregs)*12+4+($extra)
        InterruptEnable $t
        MEND

;===========================================================================

; Macro to determine whether an exception was precise or imprecise. Implicit
; operands are Rfpsr (for the SO bit and to determine whether it is a
; hardware or software context) and Rins (to determine what type of
; instruction this is). Explicit operands are:
;   $dst: Set to zero if precise, non-zero if imprecise;
;   $t:   A temporary register;
;   $tbl_fpa: The address of a bit table used to determine whether FPA
;         instructions are capable of producing imprecise exceptions.
;
; The full set of conditions for the operation to be imprecise are:
;
;   * The FPA hardware is being used;
;   * The SO bit is clear in the FPSR;
;   * The instruction is a CPDO or CPRT;
;   * The instruction is capable of delivering an imprecise exception (e.g.
;     not a purely software-implemented instruction, a FIX or a compare);
;
; Testing this last condition is complicated: it is done via the bit table
; mentioned above.
;
; The optimisation that the whole test is unnecessary for FPE-only code is
; not performed here: it is instead done in the main assembly source and
; this macro is never used.

        MACRO
$label  TestImp $dst,$t,$tbl_fpa
        ASSERT  $dst <> Rins
        ASSERT  $dst <> Rfpsr
        ASSERT  $t   <> Rins
        ASSERT  $t   <> Rfpsr
        ASSERT  $t   <> $dst
$label
        ; Address the table
        ADR     $t,$tbl_fpa
        ; Then look up correct table entry
        AND     $dst,Rins,#RTnotDO_bit
        BiShift LDR,$dst,$t,$dst,LSR #RTnotDO_pos,LSL #2
        TST     Rins,#Op2_mask
        MOVNE   $dst,$dst,LSR #16
        AND     $t,Rins,#Op1_mask
        MOV     $t,$t,LSR #Op1_pos
        ; Now incorporate other tests
        MOV     $dst,$dst,LSR $t                ;Bit0 is result so far
        AND     $dst,$dst,Rfpsr,LSR #31         ;Isolate bit0 and AND
                                                ; with (hardware in use)
        BIC     $dst,$dst,Rfpsr,LSR #SO_pos     ;AND with (SO bit clear)
        AND     $dst,$dst,Rins,LSR #RTDOnotDT_pos ;AND with (CPRT or CPDO)
        MEND

;===========================================================================

; Macros used for byte arrays.

                GBLA    ByteArrayCount
ByteArrayCount  SETA    0

        MACRO
$label  BytesStart
        ALIGN
$label
ByteArray_$ByteArrayCount
        MEND

        MACRO
$label  BytesEnd
        ALIGN
$label
ByteArrayEnd_$ByteArrayCount
ByteArrayCount  SETA    ByteArrayCount+1
        MEND

;===========================================================================

        END
