;++
;
; Copyright (c) Microsoft Corporation.  All rights reserved.
;
;
; Module:
;
;   kxarmunw.w
;
; Abstract:
;
;   Contains ARM unwind code helper macros
;
;   This file is not really useful on its own without the support from
;   kxarm.h.
;
;--

;
; The following macros are defined here:
;
;   PROLOG_PUSH {reglist}
;   PROLOG_VPUSH {reglist}
;   PROLOG_PUSH_TRAP_FRAME
;   PROLOG_PUSH_MACHINE_FRAME
;   PROLOG_PUSH_CONTEXT
;   PROLOG_REDZONE_RESTORE_LR
;   PROLOG_DECLARE_PROLOG_HELPER
;   PROLOG_STACK_ALLOC <amount>
;   PROLOG_STACK_SAVE <reg>
;   PROLOG_NOP <operation>
;
;   EPILOG_NOP <operation>
;   EPILOG_STACK_RESTORE <reg>
;   EPILOG_STACK_FREE <amount>
;   EPILOG_VPOP {reglist}
;   EPILOG_POP {reglist}
;   EPILOG_BRANCH <target>
;   EPILOG_BRANCH_REG <target>
;   EPILOG_LDRPC_POSTINC <postinc>
;   EPILOG_RETURN
;   EPILOG_REDZONE_RESTORE_LR
;

        ;
        ; Global variables
        ;

        ; results from __ParseIntRegister[List], __ParseVfpRegister[List]
        GBLS __ParsedRegisterString
        GBLA __ParsedRegisterMask
        GBLA __VfpParsedRegisterMaskBase
        
        ; results from __ComputeCodes[...]
        GBLS __ComputedCodes
        
        ; input and result from __MinMaxRegFromMask
        GBLA __RegInputMask
        GBLA __MinRegNum
        GBLA __MaxRegNum
        
        ; result from __CountBitsInMask
        GBLA __MaskBitCount
        
        ; global state and accumulators
        GBLS __PrologUnwindString
        GBLS __PrologLastLabel
        GBLA __EpilogUnwindCount
        GBLS __Epilog1UnwindString
        GBLS __Epilog2UnwindString
        GBLS __Epilog3UnwindString
        GBLS __Epilog4UnwindString
        GBLL __EpilogStartNotDefined
        GBLA __RunningIndex
        GBLS __RunningLabel


        ;
        ; Helper macro: compute minimum/maximum register indexes from a mask
        ;
        ; Input goes into __RegInputMask
        ; Output is placed in __MinRegNum and __MaxRegNum
        ;

        MACRO
        __MinMaxRegFromMask

        LCLA CurMask

CurMask SETA __RegInputMask
__MinRegNum SETA -1
__MaxRegNum SETA -1

        WHILE CurMask != 0
        IF ((CurMask:AND:1) != 0) && (__MinRegNum == -1)
__MinRegNum SETA __MaxRegNum + 1
        ENDIF
CurMask SETA CurMask:SHR:1
__MaxRegNum SETA __MaxRegNum + 1
        WEND
        
        MEND
        

        ;
        ; Helper macro: compute number of bits in a mask
        ;
        ; Input goes into __RegInputMask
        ; Output is placed in __MaskBitCount
        ;

        MACRO
        __CountBitsInMask

        LCLA CurMask

CurMask SETA __RegInputMask
__MaskBitCount SETA 0

        WHILE CurMask != 0
        IF (CurMask:AND:1) != 0
__MaskBitCount SETA __MaskBitCount + 1
        ENDIF
CurMask SETA CurMask:SHR:1
        WEND
        
        MEND
        

        ;
        ; Helper macro: emit an opcode with a generated label
        ;
        ; Output: Name of label is in $__RunningLabel
        ;
        
        MACRO
        __EmitRunningLabelAndOpcode $O1,$O2,$O3,$O4,$O5,$O6

__RunningLabel SETS "|Temp.$__RunningIndex|"
__RunningIndex SETA __RunningIndex + 1

        IF "$O6" != ""
$__RunningLabel $O1,$O2,$O3,$O4,$O5,$O6
        ELIF "$O5" != ""
$__RunningLabel $O1,$O2,$O3,$O4,$O5
        ELIF "$O4" != ""
$__RunningLabel $O1,$O2,$O3,$O4
        ELIF "$O3" != ""
$__RunningLabel $O1,$O2,$O3
        ELIF "$O2" != ""
$__RunningLabel $O1,$O2
        ELIF "$O1" != ""
$__RunningLabel $O1
        ELSE
$__RunningLabel
        ENDIF

        MEND


        ;
        ; Helper macro: append unwind codes to the prolog string
        ;
        ; Input is in __ComputedCodes
        ;
        
        MACRO
        __AppendPrologCodes
        
__PrologUnwindString SETS "$__ComputedCodes,$__PrologUnwindString"

        MEND


        ;
        ; Helper macro: append unwind codes to the epilog string
        ;
        ; Input is in __ComputedCodes
        ;
        
        MACRO
        __AppendEpilogCodes

        IF __EpilogUnwindCount == 1
__Epilog1UnwindString SETS "$__Epilog1UnwindString,$__ComputedCodes"
        ELIF __EpilogUnwindCount == 2
__Epilog2UnwindString SETS "$__Epilog2UnwindString,$__ComputedCodes"
        ELIF __EpilogUnwindCount == 3
__Epilog3UnwindString SETS "$__Epilog3UnwindString,$__ComputedCodes"
        ELIF __EpilogUnwindCount == 4
__Epilog4UnwindString SETS "$__Epilog4UnwindString,$__ComputedCodes"
        ENDIF

        MEND


        ;
        ; Helper macro: detect prolog end
        ;

        MACRO
        __DeclarePrologEnd

__PrologLastLabel SETS "$__RunningLabel"

        MEND


        ;
        ; Helper macro: detect epilog start
        ;

        MACRO
        __DeclareEpilogStart

        IF __EpilogStartNotDefined
__EpilogStartNotDefined SETL {false}
__EpilogUnwindCount SETA __EpilogUnwindCount + 1
        IF __EpilogUnwindCount == 1
$__FuncEpilog1StartLabel
        ELIF __EpilogUnwindCount == 2
$__FuncEpilog2StartLabel
        ELIF __EpilogUnwindCount == 3
$__FuncEpilog3StartLabel
        ELIF __EpilogUnwindCount == 4
$__FuncEpilog4StartLabel
        ELSE
        INFO    1, "Too many epilogues!"
        ENDIF
        ENDIF

        MEND
        
        
        ;
        ; Helper macro: specify epilog end
        ;

        MACRO
        __DeclareEpilogEnd

__EpilogStartNotDefined SETL {true}

        MEND
        
        
        ;
        ; Convoluted macro to parse a parameter that should be an integer register
        ; or register range, and return the string and mask
        ;
        ; Output is placed in __ParsedRegisterString and __ParsedRegisterMask
        ;

        MACRO
        __ParseIntRegister $Text
        
        LCLS CurText
        LCLS LReg
        LCLA LRegNum
        LCLA LRegMask
        LCLS RReg
        LCLA RRegNum
        LCLA RRegMask

CurText SETS "$Text"
LReg    SETS ""
LRegMask SETA 0
RReg    SETS ""
RRegMask SETA 0

        ; start with everything empty
__ParsedRegisterString SETS ""
__ParsedRegisterMask SETA 0

        ; strip leading open brace
        IF :LEN:CurText >= 1 && CurText:LEFT:1 == "{"
CurText SETS CurText:RIGHT:(:LEN:CurText - 1)
        ENDIF

        ; strip trailing close brace
        IF :LEN:CurText >= 1 && CurText:RIGHT:1 == "}"
CurText SETS CurText:LEFT:(:LEN:CurText - 1)
        ENDIF
        
        ; parse into register pair if 5 or more characters
        IF (:LEN:CurText) >= 5

        IF (CurText:LEFT:3):RIGHT:1 == "-"
LReg    SETS CurText:LEFT:2
RReg    SETS CurText:RIGHT:(:LEN:CurText - 3)
        ENDIF
        
        IF (CurText:LEFT:4):RIGHT:1 == "-"
LReg    SETS CurText:LEFT:3
RReg    SETS CurText:RIGHT:(:LEN:CurText - 4)
        ENDIF

        ; otherwise, parse as a single register
        ELSE
LReg    SETS CurText
RReg    SETS ""
        ENDIF
        
        ; fail if the registers aren't integer registers
        IF LReg != "lr" && LReg != "sp" && LReg != "pc" && LReg:LEFT:1 != "r"
        MEXIT
        ENDIF

        ; determine register masks
LRegNum SETA :RCONST:$LReg
LRegMask SETA 1:SHL:LRegNum

        ; if no right register, assign the single register
        IF RReg == ""
__ParsedRegisterString SETS LReg
__ParsedRegisterMask SETA LRegMask

        ; otherwise, validate the right register and generate the range
        ELSE
        IF RReg != "lr" && RReg != "sp" && RReg != "pc" && RReg:LEFT:1 != "r"
        MEXIT
        ENDIF
RRegNum SETA :RCONST:$RReg
RRegMask SETA 1:SHL:RRegNum
__ParsedRegisterString SETS LReg:CC:"-":CC:RReg
__ParsedRegisterMask SETA (RRegMask + RRegMask - 1) - (LRegMask - 1)
        ENDIF

        MEND


        ;
        ; Macro to parse a list of integer registers into a string and a mask
        ;
        ; Output is placed in __ParsedRegisterString and __ParsedRegisterMask
        ;

        MACRO 
        __ParseIntRegisterList $Func,$R1,$R2,$R3,$R4,$R5
        
        LCLS    OverallString
        LCLA    OverallMask
        
        __ParseIntRegister $R1
        IF __ParsedRegisterMask == 0
        INFO    1, "Invalid register in $Func: $R1"
        ENDIF
OverallMask SETA __ParsedRegisterMask
OverallString SETS __ParsedRegisterString

        IF "$R2" != ""
        __ParseIntRegister $R2
        IF __ParsedRegisterMask == 0
        INFO    1, "Invalid register in $Func: $R2"
        ENDIF
OverallMask SETA OverallMask:OR:__ParsedRegisterMask
OverallString SETS OverallString:CC:",":CC:__ParsedRegisterString
        ENDIF

        IF "$R3" != ""
        __ParseIntRegister $R3
        IF __ParsedRegisterMask == 0
        INFO    1, "Invalid register in $Func: $R3"
        ENDIF
OverallMask SETA OverallMask:OR:__ParsedRegisterMask
OverallString SETS OverallString:CC:",":CC:__ParsedRegisterString
        ENDIF

        IF "$R4" != ""
        __ParseIntRegister $R4
        IF __ParsedRegisterMask == 0
        INFO    1, "Invalid register in $Func: $R4"
        ENDIF
OverallMask SETA OverallMask:OR:__ParsedRegisterMask
OverallString SETS OverallString:CC:",":CC:__ParsedRegisterString
        ENDIF

        IF "$R5" != ""
        __ParseIntRegister $R5
        IF __ParsedRegisterMask == 0
        INFO    1, "Invalid register in $Func: $R5"
        ENDIF
OverallMask SETA OverallMask:OR:__ParsedRegisterMask
OverallString SETS OverallString:CC:",":CC:__ParsedRegisterString
        ENDIF

__ParsedRegisterMask SETA OverallMask
__ParsedRegisterString SETS OverallString
        
        MEND


        ;
        ; Convoluted macro to parse a parameter that should be a VFP register
        ; or register range, and return the string and mask
        ;
        ; Output is placed in __ParsedRegisterString and __ParsedRegisterMask
        ;

        MACRO
        __ParseVfpRegister $Text
        
        LCLS CurText
        LCLS LReg
        LCLA LRegNum
        LCLA LRegMask
        LCLS RReg
        LCLA RRegNum
        LCLA RRegMask

CurText SETS "$Text"
LReg    SETS ""
LRegMask SETA 0
RReg    SETS ""
RRegMask SETA 0

        ; start with everything empty
__ParsedRegisterString SETS ""
__ParsedRegisterMask SETA 0

        ; strip leading open brace
        IF :LEN:CurText >= 1 && CurText:LEFT:1 == "{"
CurText SETS CurText:RIGHT:(:LEN:CurText - 1)
        ENDIF

        ; strip trailing close brace
        IF :LEN:CurText >= 1 && CurText:RIGHT:1 == "}"
CurText SETS CurText:LEFT:(:LEN:CurText - 1)
        ENDIF
        
        ; parse into register pair if 5 or more characters
        IF (:LEN:CurText) >= 5

        IF (CurText:LEFT:3):RIGHT:1 == "-"
LReg    SETS CurText:LEFT:2
RReg    SETS CurText:RIGHT:(:LEN:CurText - 3)
        ENDIF
        
        IF (CurText:LEFT:4):RIGHT:1 == "-"
LReg    SETS CurText:LEFT:3
RReg    SETS CurText:RIGHT:(:LEN:CurText - 4)
        ENDIF

        ; otherwise, parse as a single register
        ELSE
LReg    SETS CurText
RReg    SETS ""
        ENDIF
        
        ; fail if the registers aren't VFP registers
        IF LReg:LEFT:1 != "d"
        MEXIT
        ENDIF
        
        ; determine register masks
LReg    SETS LReg:RIGHT:(:LEN:LReg - 1)
LRegNum SETA $LReg

        ; set the base to 0 or 16 if not yet determined
        IF __VfpParsedRegisterMaskBase == 1
        IF LRegNum >= 16
__VfpParsedRegisterMaskBase SETA 16
        ELSE
__VfpParsedRegisterMaskBase SETA 0
        ENDIF
        ENDIF
        
        IF (LRegNum >= __VfpParsedRegisterMaskBase) && (LRegNum - __VfpParsedRegisterMaskBase < 16)
LRegMask SETA 1:SHL:(LRegNum - __VfpParsedRegisterMaskBase)
        ELSE
LRegMask SETA 0
        ENDIF

        ; if no right register, assign the single register
        IF RReg == ""
__ParsedRegisterString SETS "d":CC:LReg
__ParsedRegisterMask SETA LRegMask

        ; otherwise, validate the right register and generate the range
        ELSE
        IF RReg:LEFT:1 != "d"
        MEXIT
        ENDIF
RReg    SETS RReg:RIGHT:(:LEN:RReg - 1)
RRegNum SETA $RReg

        IF (RRegNum >= __VfpParsedRegisterMaskBase) && (RRegNum - __VfpParsedRegisterMaskBase < 16)
RRegMask SETA 1:SHL:(RRegNum - __VfpParsedRegisterMaskBase)
        ELSE
RRegMask SETA 0
        ENDIF

__ParsedRegisterString SETS "d":CC:LReg:CC:"-d":CC:RReg
__ParsedRegisterMask SETA (RRegMask + RRegMask - 1) - (LRegMask - 1)
        ENDIF

        MEND


        ;
        ; Macro to parse a list of VFP registers into a string and a mask
        ;
        ; Output is placed in __ParsedRegisterString and __ParsedRegisterMask
        ;

        MACRO 
        __ParseVfpRegisterList $Func,$R1,$R2,$R3,$R4,$R5
        
        LCLS    OverallString
        LCLA    OverallMask
        
__VfpParsedRegisterMaskBase SETA 1
        
        __ParseVfpRegister $R1
        IF __ParsedRegisterMask == 0
        INFO    1, "Invalid register in $Func: $R1"
        ENDIF
OverallMask SETA __ParsedRegisterMask
OverallString SETS __ParsedRegisterString

        IF "$R2" != ""
        __ParseVfpRegister $R2
        IF __ParsedRegisterMask == 0
        INFO    1, "Invalid register in $Func: $R2"
        ENDIF
OverallMask SETA OverallMask:OR:__ParsedRegisterMask
OverallString SETS OverallString:CC:",":CC:__ParsedRegisterString
        ENDIF

        IF "$R3" != ""
        __ParseVfpRegister $R3
        IF __ParsedRegisterMask == 0
        INFO    1, "Invalid register in $Func: $R3"
        ENDIF
OverallMask SETA OverallMask:OR:__ParsedRegisterMask
OverallString SETS OverallString:CC:",":CC:__ParsedRegisterString
        ENDIF

        IF "$R4" != ""
        __ParseVfpRegister $R4
        IF __ParsedRegisterMask == 0
        INFO    1, "Invalid register in $Func: $R4"
        ENDIF
OverallMask SETA OverallMask:OR:__ParsedRegisterMask
OverallString SETS OverallString:CC:",":CC:__ParsedRegisterString
        ENDIF

        IF "$R5" != ""
        __ParseVfpRegister $R5
        IF __ParsedRegisterMask == 0
        INFO    1, "Invalid register in $Func: $R5"
        ENDIF
OverallMask SETA OverallMask:OR:__ParsedRegisterMask
OverallString SETS OverallString:CC:",":CC:__ParsedRegisterString
        ENDIF

__ParsedRegisterMask SETA OverallMask
__ParsedRegisterString SETS OverallString
        
        MEND


        ;
        ; Compute unwind codes for a PUSH or POP operation
        ;
        ; Input is in __ParsedRegisterMask
        ; Output is placed in __ComputedCodes
        ;
        
        MACRO
        __ComputePushPopCodes $Name,$FreeMask
        
        LCLA    MaskMinusFree
        LCLA    ByteVal
        LCLA    ByteVal2
        LCLA    PcLrVal

        ; See if LR/PC was included in the mask
PcLrVal SETA    0
        IF (__ParsedRegisterMask:AND:0xc000) != 0
PcLrVal SETA    1
        ENDIF

        ; Compute a mask without LR/PC
MaskMinusFree SETA __ParsedRegisterMask:AND:(:NOT:$FreeMask)

        ; Determine minimum/maximum registers
__RegInputMask SETA MaskMinusFree
        __MinMaxRegFromMask

        ; single byte, 16-bit push r4-r[4-7]
        IF ((MaskMinusFree:AND:0xff1f) == 0x0010) && (((MaskMinusFree + 0x10):AND:MaskMinusFree) == 0)
ByteVal SETA    0xd0 :OR: (PcLrVal:SHL:2) :OR: (__MaxRegNum:AND:3)
__ComputedCodes SETS "0x":CC:((:STR:ByteVal):RIGHT:2)

        ; single byte, 16-bit push r4-r[8-11]
        ELIF ((MaskMinusFree:AND:0xf01f) == 0x0010) && (((MaskMinusFree + 0x10):AND:MaskMinusFree) == 0)
ByteVal SETA    0xd8 :OR: (PcLrVal:SHL:2) :OR: (__MaxRegNum:AND:3)
__ComputedCodes SETS "0x":CC:((:STR:ByteVal):RIGHT:2)

        ; double byte, 16-bit push r0-r7 via bitmask
        ELIF ((MaskMinusFree:AND:0xff00) == 0x0000)
ByteVal SETA    0xec :OR: PcLrVal
__ComputedCodes SETS "0x":CC:((:STR:ByteVal):RIGHT:2):CC:",0x":CC:((:STR:MaskMinusFree):RIGHT:2)

        ; double byte, 32-bit push r0-r12,lr via bitmask
        ELIF ((MaskMinusFree:AND:0xa000) == 0x0000)
ByteVal SETA    0x80 :OR: (PcLrVal:SHL:5) :OR: ((__ParsedRegisterMask:SHR:8):AND:0x1f)
ByteVal2 SETA   __ParsedRegisterMask:AND:0xff
__ComputedCodes SETS "0x":CC:((:STR:ByteVal):RIGHT:2):CC:",0x":CC:((:STR:ByteVal2):RIGHT:2)

        ; unsupported case
        ELSE
        INFO    1, "Invalid register sequence specified in $Name"
        ENDIF

        MEND
        
        
        ;
        ; Compute unwind codes for a VPUSH or VPOP operation
        ;
        ; Input is in __ParsedRegisterMask
        ; Output is placed in __ComputedCodes
        ;
        
        MACRO
        __ComputeVpushVpopCodes $Name
        
        LCLA    ByteVal
        LCLA    MinRegNum
        LCLA    MaxRegNum

        ; Determine minimum/maximum registers
__RegInputMask SETA __ParsedRegisterMask
        __MinMaxRegFromMask
MinRegNum SETA __MinRegNum + __VfpParsedRegisterMaskBase
MaxRegNum SETA __MaxRegNum + __VfpParsedRegisterMaskBase

        ; Only contiguous sequences are supported
        IF ((__ParsedRegisterMask + (1:SHL:__MinRegNum)):AND:__ParsedRegisterMask) != 0
        INFO    1, "Discontiguous register sequence specified in PROLOG_VPUSH"

        ; single byte, 32-bit vpush d8-d[8-15]
        ELIF (MinRegNum == 8) && (MaxRegNum <= 15)
ByteVal SETA    0xe0 :OR: (MaxRegNum:AND:7)
__ComputedCodes SETS "0x":CC:((:STR:ByteVal):RIGHT:2)

        ; double byte, 32-bit vpush d0-d15 via start/end values
        ELIF (MinRegNum >= 0) && (MaxRegNum <= 15)
ByteVal SETA    ((MinRegNum:AND:15):SHL:4) :OR: (MaxRegNum:AND:15)
__ComputedCodes SETS "0xF5,0x":CC:((:STR:ByteVal):RIGHT:2)

        ; double byte, 32-bit vpush d16-d31 via start/end values
        ELIF (MinRegNum >= 16) && (MaxRegNum <= 31)
ByteVal SETA    ((MinRegNum:AND:15):SHL:4) :OR: (MaxRegNum:AND:15)
__ComputedCodes SETS "0xF6,0x":CC:((:STR:ByteVal):RIGHT:2)

        ; unsupported case
        ELSE
        INFO    1, "Invalid register sequence specified in $Name"
        ENDIF

        MEND
        
        
        ;
        ; Compute unwind codes for a stack alloc/dealloc operation
        ;
        ; Output is placed in __ComputedCodes
        ;

        MACRO
        __ComputeStackAllocCodes $Name, $Amount
        
        LCLA    BytesDiv4
        LCLA    BytesHigh
        LCLA    BytesLow
BytesDiv4 SETA  ($Amount) / 4

        ; single byte, 16-bit add sp, sp, #x
        IF BytesDiv4 <= 0x7f
__ComputedCodes SETS "0x":CC:((:STR:BytesDiv4):RIGHT:2)

        ; double byte, 32-bit addw sp, sp, #x
        ELIF BytesDiv4 <= 0x3ff
BytesHigh SETA  (BytesDiv4:SHR:8):OR:0xe8
BytesLow SETA   BytesDiv4:AND:0xff
__ComputedCodes SETS "0x":CC:((:STR:BytesHigh):RIGHT:2):CC:",0x":CC:((:STR:BytesLow):RIGHT:2)

        ; don't support anything bigger
        ELSE
        INFO    1, "$Name too large for unwind code encoding"
        ENDIF
        
        MEND
        
        ;
        ; Compute unwind codes for a stack save/restore operation
        ;
        ; Output is placed in __ComputedCodes
        ;

        MACRO
        __ComputeStackSaveRestoreCodes $Name, $Register
        
        LCLA    ByteVal
        
        __ParseIntRegister $Register

        ; error if no valid register
        IF __ParsedRegisterMask == 0
        INFO    1, "Invalid register in $Name: $Register"

        ; determine min/max registers in mask
        ELSE
__RegInputMask SETA __ParsedRegisterMask
        __MinMaxRegFromMask

        ; error if we were passed a range
        IF __MinRegNum != __MaxRegNum
        INFO    1, "Register range not allowed in $Name: $Register"

        ; single byte, 16-bit mov rN, sp
        ELSE
ByteVal SETA    0xc0 :OR: __MinRegNum
__ComputedCodes SETS "0x":CC:((:STR:ByteVal):RIGHT:2)
        ENDIF
        ENDIF
        
        MEND

        
        ;
        ; Macro for including an arbitrary operation in the prolog
        ;

        MACRO
        PROLOG_NOP $O1,$O2,$O3,$O4
        
        __EmitRunningLabelAndOpcode $O1,$O2,$O3,$O4
        __DeclarePrologEnd

        IF ?$__RunningLabel == 2
__ComputedCodes SETS "0xFB"
        ELSE
__ComputedCodes SETS "0xFC"
        ENDIF
        __AppendPrologCodes
        
        MEND
        
        
        ;
        ; Macro for an integer register PUSH operation in a prologue
        ;

        MACRO 
        PROLOG_PUSH $R1,$R2,$R3,$R4,$R5
        
        __ParseIntRegisterList "PROLOG_PUSH",$R1,$R2,$R3,$R4,$R5
        __EmitRunningLabelAndOpcode push {$__ParsedRegisterString}
        __DeclarePrologEnd
        
        __ComputePushPopCodes "PROLOG_PUSH",0x4000
        __AppendPrologCodes

#if ENABLE_FRAME_POINTER

        IF (__ParsedRegisterMask:AND:0x0800) != 0
__RegInputMask SETA __ParsedRegisterMask:AND:0x07ff
        __CountBitsInMask

        IF __MaskBitCount == 0
        PROLOG_NOP mov r11, sp
        ELSE
        PROLOG_NOP add r11, sp, #(__MaskBitCount * 4)
        ENDIF

        ENDIF

#endif

        MEND


        ;
        ; Macro for a floating-point register VPUSH operation in a prologue
        ;

        MACRO 
        PROLOG_VPUSH $R1,$R2,$R3,$R4,$R5
        
        LCLA    ByteVal

        __ParseVfpRegisterList "PROLOG_VPUSH",$R1,$R2,$R3,$R4,$R5
        __EmitRunningLabelAndOpcode vpush {$__ParsedRegisterString}
        __DeclarePrologEnd

        __ComputeVpushVpopCodes "PROLOG_VPUSH"
        __AppendPrologCodes

        MEND
        
        
        ;
        ; Macro for indicating a trap frame lives above us
        ;

        MACRO
        PROLOG_PUSH_TRAP_FRAME
        
__ComputedCodes SETS "0xEE,0x03"
        __AppendPrologCodes
        
        MEND
        
        
        ;
        ; Macro for indicating a machine frame lives above us
        ;

        MACRO
        PROLOG_PUSH_MACHINE_FRAME
        
__ComputedCodes SETS "0xEE,0x01"
        __AppendPrologCodes
        
        MEND
        
        
        ;
        ; Macro for indicating a context lives above us
        ;

        MACRO
        PROLOG_PUSH_CONTEXT
        
__ComputedCodes SETS "0xEE,0x02"
        __AppendPrologCodes
        
        MEND
        
        
        ;
        ; Macro for indicating a save LR was saved in the
        ; red zone at [sp-8]
        ;

        MACRO
        PROLOG_REDZONE_RESTORE_LR
        
__ComputedCodes SETS "0xEE,0x04"
        __AppendPrologCodes
        
        MEND

        MACRO
        EPILOG_REDZONE_RESTORE_LR
        
__ComputedCodes SETS "0xEE,0x04"
        __AppendEpilogCodes
        
        MEND


        ;
        ; Macro for allocating space on the stack in the prolog
        ;

        MACRO
        PROLOG_STACK_ALLOC $Amount
        
        __EmitRunningLabelAndOpcode sub sp, sp, #$Amount
        __DeclarePrologEnd

        __ComputeStackAllocCodes "PROLOG_STACK_ALLOC", $Amount
        __AppendPrologCodes
        
        MEND
        
        
        ;
        ; Macro for saving the stack pointer in another register
        ;

        MACRO
        PROLOG_STACK_SAVE $Register
        
        __EmitRunningLabelAndOpcode mov $Register, sp
        __DeclarePrologEnd

        __ComputeStackSaveRestoreCodes "PROLOG_STACK_SAVE", $Register
        __AppendPrologCodes
        
        MEND
        
        
        ;
        ; Macro for including an arbitrary operation in the epilog
        ;

        MACRO
        EPILOG_NOP $O1,$O2,$O3,$O4
        
        __DeclareEpilogStart
        __EmitRunningLabelAndOpcode $O1,$O2,$O3,$O4

        IF ?$__RunningLabel == 2
__ComputedCodes SETS "0xFB"
        ELSE
__ComputedCodes SETS "0xFC"
        ENDIF
        __AppendEpilogCodes
        
        MEND
        
        
        ;
        ; Macro for saving the stack pointer in another register
        ;

        MACRO
        EPILOG_STACK_RESTORE $Register
        
        __DeclareEpilogStart
        __EmitRunningLabelAndOpcode mov sp, $Register
        
        __ComputeStackSaveRestoreCodes "EPILOG_STACK_RESTORE", $Register
        __AppendEpilogCodes
        
        MEND
        
        
        ;
        ; Macro for deallocating space on the stack in the prolog
        ;

        MACRO
        EPILOG_STACK_FREE $Amount
        
        __DeclareEpilogStart
        __EmitRunningLabelAndOpcode add sp, sp, #$Amount
        
        __ComputeStackAllocCodes "EPILOG_STACK_FREE", $Amount
        __AppendEpilogCodes
        
        MEND
        
        
        ;
        ; Macro for an integer register POP operation in an epilogue
        ;

        MACRO 
        EPILOG_POP $R1,$R2,$R3,$R4,$R5

        __ParseIntRegisterList "EPILOG_POP",$R1,$R2,$R3,$R4,$R5
        __DeclareEpilogStart
        __EmitRunningLabelAndOpcode pop {$__ParsedRegisterString}

        __ComputePushPopCodes "EPILOG_POP",0x8000
        __AppendEpilogCodes
        
        IF (__ParsedRegisterMask:AND:0x8000) != 0
        __DeclareEpilogEnd
        ENDIF
        
        MEND


        ;
        ; Macro for a floating-point register VPOP operation in a prologue
        ;

        MACRO 
        EPILOG_VPOP $R1,$R2,$R3,$R4,$R5
        
        LCLA    ByteVal

        __ParseVfpRegisterList "EPILOG_VPOP",$R1,$R2,$R3,$R4,$R5
        __DeclareEpilogStart
        __EmitRunningLabelAndOpcode vpop {$__ParsedRegisterString}
        
        __ComputeVpushVpopCodes "EPILOG_VPOP"
        __AppendEpilogCodes
        
        MEND
        
        
        ;
        ; Macro for a b <target> end to the epilog (tail-call)
        ;

        MACRO
        EPILOG_BRANCH $Target
        
        __DeclareEpilogStart
        __EmitRunningLabelAndOpcode b $Target

        IF ?$__RunningLabel == 2
__ComputedCodes SETS "0xFD"
        ELSE
__ComputedCodes SETS "0xFE"
        ENDIF
        __AppendEpilogCodes

        __DeclareEpilogEnd

        MEND
        

        ;
        ; Macro for a bx register-style return in the epilog
        ;

        MACRO
        EPILOG_BRANCH_REG $Register
        
        __DeclareEpilogStart
        __EmitRunningLabelAndOpcode bx $Register

__ComputedCodes SETS "0xFD"
        __AppendEpilogCodes

        __DeclareEpilogEnd

        MEND
        

        ;
        ; Macro for a ldr pc, [sp], #x style return in the epilog
        ;

        MACRO
        EPILOG_LDRPC_POSTINC $Amount
        
        LCLA    ByteVal

        __DeclareEpilogStart
        __EmitRunningLabelAndOpcode ldr pc, [sp], #$Amount

ByteVal SETA    0x00 :OR: ($Amount/4)
__ComputedCodes SETS "0xEF,0x":CC:((:STR:ByteVal):RIGHT:2)
        __AppendEpilogCodes

        __DeclareEpilogEnd

        MEND
        

        ;
        ; Macro for a bx lr-style return in the epilog
        ;

        MACRO
        EPILOG_RETURN
        EPILOG_BRANCH_REG lr
        MEND
        

        ;
        ; Macro to reset the internal uwninding states
        ;

        MACRO
        __ResetUnwindState
__PrologUnwindString SETS ""
__EpilogUnwindCount SETA 0
__Epilog1UnwindString SETS ""
__Epilog2UnwindString SETS ""
__Epilog3UnwindString SETS ""
__Epilog4UnwindString SETS ""
__EpilogStartNotDefined SETL {true}
        MEND
        

        ;
        ; Macro to emit the xdata for unwinding
        ;

        MACRO
        __EmitUnwindXData
        
        LCLA    XBit
        LCLA    FBit

        ; determine 
FBit    SETA    0
        IF "$__PrologUnwindString" == ""
FBit    SETA    1:SHL:22
        ENDIF

XBit    SETA    0
        IF "$__FuncExceptionHandler" != ""
XBit    SETA    1:SHL:20
        ENDIF
        
        ;
        ; Append terminators where necessary
        ;
        IF __EpilogUnwindCount >= 1
__Epilog1UnwindString SETS __Epilog1UnwindString:RIGHT:(:LEN:__Epilog1UnwindString - 1)
        IF (:LEN:__Epilog1UnwindString) >= 5
        IF __Epilog1UnwindString:RIGHT:4 < "0xFD"
__Epilog1UnwindString SETS __Epilog1UnwindString:CC:",0xFF"
        ENDIF
        ENDIF
        ENDIF
        
        IF __EpilogUnwindCount >= 2
__Epilog2UnwindString SETS __Epilog2UnwindString:RIGHT:(:LEN:__Epilog2UnwindString - 1)
        IF (:LEN:__Epilog2UnwindString) >= 5
        IF __Epilog2UnwindString:RIGHT:4 < "0xFD"
__Epilog2UnwindString SETS __Epilog2UnwindString:CC:",0xFF"
        ENDIF
        ENDIF
        ENDIF
        
        IF __EpilogUnwindCount >= 3
__Epilog3UnwindString SETS __Epilog3UnwindString:RIGHT:(:LEN:__Epilog3UnwindString - 1)
        IF (:LEN:__Epilog3UnwindString) >= 5
        IF __Epilog3UnwindString:RIGHT:4 < "0xFD"
__Epilog3UnwindString SETS __Epilog3UnwindString:CC:",0xFF"
        ENDIF
        ENDIF
        ENDIF
        
        IF __EpilogUnwindCount >= 4
__Epilog4UnwindString SETS __Epilog4UnwindString:RIGHT:(:LEN:__Epilog4UnwindString - 1)
        IF (:LEN:__Epilog4UnwindString) >= 5
        IF __Epilog4UnwindString:RIGHT:4 < "0xFD"
__Epilog4UnwindString SETS __Epilog4UnwindString:CC:",0xFF"
        ENDIF
        ENDIF
        ENDIF

        ; optimize out the prolog string if it matches
        IF (:LEN:__Epilog1UnwindString) >= 6
        IF __Epilog1UnwindString:LEFT:(:LEN:__Epilog1UnwindString - 4) == __PrologUnwindString
__PrologUnwindString SETS ""
        ENDIF
        ENDIF

        IF "$__PrologUnwindString" != ""
__PrologUnwindString SETS __PrologUnwindString:CC:"0xFF"
        ENDIF

        ;
        ; Switch to the .xdata section, aligned to a DWORD
        ;
        AREA    |.xdata|,ALIGN=2,READONLY
        ALIGN   4
        
        ; declare the xdata header with unwind code size, epilog count, 
        ; exception bit, and function length
$__FuncXDataLabel
        DCD     ((($__FuncXDataEndLabel - $__FuncXDataPrologLabel)/4):SHL:28) :OR: (__EpilogUnwindCount:SHL:23) :OR: FBit :OR: XBit :OR: (($__FuncEndLabel - $__FuncStartLabel)/2)
        
        ; if we have an epilogue, output a single scope record
        IF __EpilogUnwindCount >= 1
        DCD     (($__FuncXDataEpilog1Label - $__FuncXDataPrologLabel):SHL:24) :OR: (14:SHL:20) :OR: (($__FuncEpilog1StartLabel - $__FuncStartLabel)/2)
        ENDIF
        IF __EpilogUnwindCount >= 2
        DCD     (($__FuncXDataEpilog2Label - $__FuncXDataPrologLabel):SHL:24) :OR: (14:SHL:20) :OR: (($__FuncEpilog2StartLabel - $__FuncStartLabel)/2)
        ENDIF
        IF __EpilogUnwindCount >= 3
        DCD     (($__FuncXDataEpilog3Label - $__FuncXDataPrologLabel):SHL:24) :OR: (14:SHL:20) :OR: (($__FuncEpilog3StartLabel - $__FuncStartLabel)/2)
        ENDIF
        IF __EpilogUnwindCount >= 4
        DCD     (($__FuncXDataEpilog4Label - $__FuncXDataPrologLabel):SHL:24) :OR: (14:SHL:20) :OR: (($__FuncEpilog4StartLabel - $__FuncStartLabel)/2)
        ENDIF
        
        ; output the prolog unwind string
$__FuncXDataPrologLabel
        IF "$__PrologUnwindString" != ""
        DCB     $__PrologUnwindString
        ENDIF
        
        ; if we have an epilogue, output the epilog unwind codes
        IF __EpilogUnwindCount >= 1
$__FuncXDataEpilog1Label
        DCB     $__Epilog1UnwindString,0xff
        ENDIF
        IF __EpilogUnwindCount >= 2
$__FuncXDataEpilog2Label
        DCB     $__Epilog2UnwindString,0xff
        ENDIF
        IF __EpilogUnwindCount >= 3
$__FuncXDataEpilog3Label
        DCB     $__Epilog3UnwindString,0xff
        ENDIF
        IF __EpilogUnwindCount >= 4
$__FuncXDataEpilog4Label
        DCB     $__Epilog4UnwindString,0xff
        ENDIF

        ; watch out for 0 epilogs and empty prolog -- that will create a problem
        ; due to special encoding meaning look for a second header word
        IF __EpilogUnwindCount == 0
        IF "$__PrologUnwindString" == ""
        DCB     0xff
        ENDIF
        ENDIF

        ALIGN   4
$__FuncXDataEndLabel

        ; output the exception handler information
        IF "$__FuncExceptionHandler" != ""
        DCD     $__FuncExceptionHandler
        RELOC   2                                       ; make this relative to image base
        DCD     0                                       ; append a 0 for the data (keeps Vulcan happy)
        ENDIF

        ; switch back to the original area
        AREA    $__FuncArea,CODE,READONLY

        MEND
