;
;	Microsoft Confidential
;	Copyright (C) Microsoft Corporation 1991
;	All Rights Reserved.
;

;**	Macro definitions for MSDOS.
;
;       Revision history:
;        	M019 DB 10/26/90 - Added Cmp32 macro.

TRUE    EQU 0FFFFh
FALSE   EQU 0

SUBTTL BREAK a listing into pages and give new subtitles
PAGE
BREAK   MACRO   subtitle
	SUBTTL  subtitle
	PAGE
ENDM
.xcref  break

BREAK   <ASMVAR - handle assembly variables once and for all>

AsmVars Macro   varlist
IRP     var,<varlist>
AsmVar  var
ENDM
ENDM

AsmVar  Macro   var
IFNDEF  var
var = FALSE
ENDIF
ENDM

BREAK <I_NEED: declare a variable external, if necessary, and allocate a size>

;
; declare a variable external and allocate a size
;
AsmVar  InstalledData
I_NEED  MACRO   sym,len
IF NOT InstalledData
	DOSDATA    SEGMENT WORD PUBLIC 'DATA'
    IFIDN   <len>,<WORD>
	EXTRN   &sym:WORD
    ELSE
	IFIDN   <len>,<DWORD>
	EXTRN   &sym:DWORD
	ELSE
	EXTRN   &sym:BYTE
	ENDIF
    ENDIF
	DOSDATA    ENDS
ENDIF
ENDM
	.xcref I_need


; call a procedure that may be external.  The call will be short.

invoke  MACRO   name
.xcref
	IF2
	    IFNDEF name
	EXTRN   name:NEAR
	    ENDIF
	ENDIF
.cref
	CALL    name
ENDM
.xcref  invoke

PAGE
;
; jump to a label that may be external.  The jump will be near.
;
transfer    MACRO   name
.xcref
	IF2
	    IFNDEF name
	EXTRN   name:NEAR
	    ENDIF
	ENDIF
.cref
	JUMP    name
ENDM
.xcref  transfer

;
; get a short address in a word
;
short_addr  MACRO   name
    IFDIF   <name>,<?>
.xcref
	IF2
	    IFNDEF name
	EXTRN   name:NEAR
	    ENDIF
	ENDIF
.cref
	DW  OFFSET DOSCODE:name
    ELSE
	DW  ?
    ENDIF
ENDM
.xcref  short_addr

;
; get a long address in a dword
;
long_addr   MACRO   name
.xcref
	IF2
	    IFNDEF name
	EXTRN   name:NEAR
	    ENDIF
	ENDIF
.cref
	DD  name
ENDM
.xcref  long_addr

;
; declare a PROC near or far but PUBLIC nonetheless
;
.xcref ?frame
.xcref ?aframe
.xcref ?stackdepth
.xcref ?initstack
?frame        =   0                     ; initial
?aframe       =   0                     ; initial
?stackdepth   =   0                     ; initial stack size
?initstack    =   0                     ; initial stack size

procedure   MACRO   name,distance
	?frame =    0
	?aframe =   2                   ;; remember the pushed BP
	PUBLIC  name
name    PROC    distance
	ASSUME	DS:nothing,ES:nothing
	?initstack    = ?stackdepth     ;; beginning of procedure
ENDM
.xcref  procedure


; end a procedure and check that stack depth is preserved

EndProc MACRO   name, chk
	IFDIF   <chk>,<NoCheck>       ;; check the stack size
	IF2
	IF ?initstack NE ?stackdepth    ;; is it different?
	%OUT    ***** Possible stack size error in name *****
	ENDIF
	ENDIF
	ENDIF
name    ENDP
ENDM
.xcref  endproc
PAGE
;
; define a data item to be public and of an appropriate size/type
;

I_AM    MACRO   name,size,init
;; declare the object public
	PUBLIC  name
;; declare the type of the object
    IFIDN <size>,<WORD>
name    LABEL WORD
	I_AM_SIZE   =   1
	I_AM_LEN    =   2
    ELSE
	IFIDN <size>,<DWORD>
name    LABEL DWORD
	I_AM_SIZE   =   2
	I_AM_LEN    =   2
	ELSE
	    IFIDN <size>,<BYTE>
name    LABEL BYTE
	I_AM_SIZE   =   1
	I_AM_LEN    =   1
	    ELSE
name    LABEL BYTE
	I_AM_SIZE   =   size
	I_AM_LEN    =   1
	    ENDIF
	ENDIF
    ENDIF
;; if no initialize then allocate blank storage
    IFB <init>
	DB  I_AM_SIZE*I_AM_LEN DUP (?)
    ELSE
IF NOT InstalledData
	IRP itm,<init>
	    IF I_AM_LEN EQ 1
		DB  itm
	    ELSE
		DW  itm
	    ENDIF
	    I_AM_SIZE = I_AM_SIZE - 1
	ENDM
	IF I_AM_SIZE NE 0
	    %out    ***** initialization of name not complete *****
	ENDIF
ELSE
	DB  I_AM_SIZE*I_AM_LEN DUP (?)
ENDIF
    ENDIF
ENDM
.xcref  I_AM
.xcref  I_AM_SIZE
.xcref  I_AM_LEN
I_AM_SIZE   = 0
I_AM_LEN    = 0

PAGE

;
; define an entry in a procedure
;
entry macro name
	PUBLIC  name
name:
endm
.xcref  entry

BREAK <ERROR - store an error code then jump to a label>

error macro code
.xcref
	MOV AL,code
	transfer    SYS_RET_ERR
.cref
ENDM
.xcref  error

BREAK <JUMP - real jump that links up shortwise>
;
; given a label <lbl> either 2 byte jump to another label <lbl>_J
; if it is near enough or 3 byte jump to <lbl>
;

jump    macro lbl
    local a
.xcref

    ifndef lbl&_J                       ;; is this the first invocation
a:      JMP lbl
    ELSE
	IF (lbl&_J GE $) OR ($-lbl&_J GT 126)
a:      JMP lbl                         ;; is the jump too far away?
	ELSE
a:      JMP lbl&_J                      ;; do the short one...
	ENDIF
    ENDIF
    lbl&_j = a
.cref
endm
.xcref  jump

BREAK <RETURN - return from a function>

return  macro x
    local a
.xcref
a:
	RET
ret_l = a
.cref
endm
.xcref  return

BREAK <CONDRET - conditional return>

condret macro   cc,ncc
    local   a
.xcref
.xcref a
.cref
    ifdef   ret_l                       ;; if ret_l is defined
	if (($ - ret_l) le 126) and ($ gt ret_l)
					;;     if ret_l is near enough then
	    a:  j&cc    ret_l           ;;         a: j<CC> to ret_l
	    ret_&cc = a                 ;;         define ret_<CC> to be a:
	    exitm
	endif
    endif
    ifdef   ret_&cc                     ;; if ret_<CC> defined
	if (($ - ret_&cc) le 126) and ($ gt ret_&cc)
					;;     if ret_<CC> is near enough
	    a:  j&cc    ret_&cc         ;;         a: j<CC> to ret_<CC>
	    ret_&cc = a                 ;;         define ret_<CC> to be a:
	    exitm
	endif
    endif
    j&ncc   a                           ;; j<NCC> a:
    return                              ;; return
    a:                                  ;; a:
    ret_&cc = ret_l                     ;; define ret_<CC> to be ret_l
endm
.xcref  condret

BREAK <RETZ - return if zero, links up shortwise if necessary>

retz    macro
    condret z,nz
endm
.xcref  retz

BREAK <RETNZ - return if not zero, links up shortwise if necessary>

retnz   macro
    condret nz,z
endm
.xcref  retnz

BREAK <RETC - return if carry set, links up shortwise if necessary>

retc    macro
    condret c,nc
endm
.xcref  retc

BREAK <RETNC - return if not carry, links up shortwise if necessary>

retnc   macro
    condret nc,c
endm
.xcref  retnc

BREAK <CONTEXT - set the DOS context to a particular register>

context macro   r
	PUSH    SS
	POP     r
	ASSUME  r:DOSDATA
endm
.xcref  context

BREAK <SaveReg - save a set of registers>

SaveReg MACRO	reglist 		;; push those registers
IRP reg,<reglist>
	?stackdepth = ?stackdepth + 1
	PUSH    reg
ENDM
ENDM
.xcref  SaveReg

Save	MACRO	arglist		;; push those arguments
IRP arg,<arglist>
	?stackdepth = ?stackdepth + 1
	PUSH	arg
ENDM
ENDM
.xcref	Save

BREAK <RestoreReg - unsave some registers>

RestoreReg  MACRO   reglist		;; pop those registers
IRP reg,<reglist>
	?stackdepth = ?stackdepth - 1
	POP     reg
ENDM
ENDM
.xcref  RestoreReg

Restore MACRO	 arglist		;; pop those arguments
IRP arg,<arglist>
	?stackdepth = ?stackdepth - 1
	POP	arg
ENDM
ENDM
.xcref	Restore

BREAK <Critical section macros>

EnterCrit MACRO section
	Invoke  E&section
ENDM

LeaveCrit MACRO section
	Invoke  L&section
ENDM

Break   <message - display a message>

AsmVars <ShareF,Cargs,Redirector>

if debug
fmt     MACRO   typ,lev,fmts,args
local   a,b,c
	PUSHF
IFNB <typ>
	TEST    BugTyp,typ
	JZ      c
	CMP     BugLev,lev
	JB      c
ENDIF
	PUSH    AX
	PUSH    BP
	MOV     BP,SP
If (not sharef) and (not redirector)
DOSDATA segment
a       db      fmts,0
DOSDATA	 ends
	MOV	AX,OFFSET DOSDATA:a
else
	jmp     short b
a       db      fmts,0
if sharef
b:      mov     ax,offset share:a
else
b:      mov     ax,offset netwrk:a
endif
endif
	PUSH    AX
cargs = 2
IRP item,<args>
IFIDN   <AX>,<item>
	push	[bp+2]
     ;	MOV	AX,[BP+2]
ELSE
     ;	MOV	AX,item
	push	item
ENDIF
   ;	PUSH	AX
cargs = cargs + 2
ENDM
	invoke  PFMT
	ADD     SP,Cargs
	POP     BP
	POP     AX
c:
	POPF
ENDM
else
fmt macro
endm
endif

Break   <DOSAssume - validate assumes>

AsmVar  Debug,$temp

;**	DOSAssume - Check that a register addresses DOSSEG
;
;	DOSAssume  reglist, message


IF debug
DOSAssume   Macro   reglist,message
local a,b
    $temp   =	0
    IRP r,<reglist>
	IFIDN <r>,<DS>
	    $temp = $temp OR 2
	ELSE
	    IFIDN <r>,<ES>
		$temp = $temp OR 4
	    ELSE
		IFIDN <r>,<SS>
		    $temp = $temp OR 1
		ELSE
		    %out ***** Invalid register reg in DOSAssume *****
		ENDIF
	    ENDIF
	ENDIF
    ENDM
	Invoke  SegCheck
	jmp	short a
	db	$temp
	db	message,0
a:

    IRP r,<reglist>
	ASSUME	r:DOSDATA
    ENDM
ENDM

ELSE

DOSAssume   Macro   reglist,message
IRP r,<reglist>
    ASSUME  r:DOSDATA
ENDM
ENDM
ENDIF

BREAK   <ASSERT - make assertions about registers>

IF DEBUG
Assert  MACRO   kind, objs, message
	LOCAL   a,b
    IFIDN   <kind>,<Z>
	CMP     objs,0
	JZ      a
	fmt <>,<>,<message>
a:
    ELSE
	IFIDN   <kind>,<NZ>
	CMP     objs,0
	JNZ     a
	fmt <>,<>,<message>
a:
	ELSE
    PUSH    AX
	    IRP obj,<objs>
	PUSH    obj
	    ENDM
	    IF SHAREF
    MOV     AX,OFFSET b
	    ELSE
    MOV     AX,OFFSET DOSDATA:b
	    ENDIF
    PUSH    AX
	    IFIDN   <kind>,<ISBUF>
	Invoke  BUFCheck
	    ENDIF
	    IFIDN   <kind>,<ISSFT>
	Invoke  SFTCheck
	    ENDIF
	    IFIDN   <kind>,<ISDPB>
	Invoke  DPBCheck
	    ENDIF
    POP     AX
	    IF SHAREF
    JMP SHORT a
b   DB  Message,0
a:
	    ELSE
DOSDATA	 segment
b   db  Message,0
DOSDATA	 ends
	    ENDIF
	ENDIF
    ENDIF
ENDM
ELSE
Assert  Macro
ENDM
ENDIF

Break   <CallInstall - hook to installable pieces>

CallInstall MACRO   name,mpx,fn,save,restore
IF Installed
    IFNB    <save>
	SaveReg <save>
    ENDIF
	MOV     AX,(mpx SHL 8) + fn
	INT     2Fh
    IFNB    <restore>
	RestoreReg  <restore>
    ENDIF
ELSE
	Invoke  name
ENDIF
ENDM

Break   <Stack frame manipulators>

localvar    macro   name,length
local   a
	ifidn   <length>,<BYTE>
	    ?frame =  ?frame + 1
	    a = ?frame
	    name EQU  BYTE PTR [BP-a]
	else
	    ifidn   <length>,<WORD>
		?frame =  ?frame + 2
		a = ?frame
		name EQU  WORD PTR [BP-a]
	    else
		ifidn   <length>,<DWORD>
		    ?frame =  ?frame + 4
		    a = ?frame
		    name EQU  DWORD PTR [BP-a]
		    name&l EQU  WORD PTR [BP-a]
		    name&h EQU  WORD PTR [BP-a+2]
		else
		    ?frame =  ?frame + length
		    a = ?frame
		    name EQU  BYTE PTR [BP-a]
		endif
	    endif
	endif
endm

enter   macro
	push    bp
	mov     bp,sp
	sub     sp,?frame
endm

leave   macro
	mov     sp,bp
	pop     bp
endm

Argvar  macro   name,length
local   a
	ifidn   <length>,<BYTE>
	    a = ?aframe
	    ?aframe =  ?aframe + 1
	    name EQU  BYTE PTR [BP+a]
	else
	    ifidn   <length>,<WORD>
		a = ?aframe
		?aframe =  ?aframe + 2
		name EQU  WORD PTR [BP+a]
	    else
		ifidn   <length>,<DWORD>
		    a = ?aframe
		    ?aframe =  ?aframe + 4
		    name EQU  DWORD PTR [BP+a]
		    name&l EQU  WORD PTR [BP+a]
		    name&h EQU  WORD PTR [BP+a+2]
		else
		    a = ?aframe
		    ?aframe =  ?aframe + length
		    name EQU  BYTE PTR [BP+a]
		endif
	    endif
	endif
endm


save_world 	macro

	push	es
	invoke	save_user_world

endm

restore_world	macro

	invoke	restore_user_world
	pop	es

endm

;
; This macro gets the DOS data segment value and puts it in the specified
; segment register.  This can only be used in the DOSCODE segment.
;

getdseg	macro 	r

ifdef ROMDOS
	mov	r, cs:[BioDataSeg]
	assume	r:bdata
	mov	r, r:[DosDataSg]
else
	mov	r, cs:[DosDseg]
endif
	assume	r:dosdata
endm

;
; This macro does the necessary extrns to allow use of the getdseg macro.
;
allow_getdseg macro

ifdef ROMDOS
	extrn	BioDataSeg:word
	bdata	segment at 70H
	extrn	DosDataSg:word
	bdata	ends
else
	extrn	DosDseg:word
endif

endm



BREAK <LJcc - Long Conditional Jumps>

LJE macro l
 LJ JE JNE l
endm

LJNE macro l
 LJ jne JE l
endm

LJZ macro l
 LJE l
endm

LJNZ macro l
 LJNE l
endm

LJC macro l
 LJ jc JNC l
endm

LJNC macro l
 LJ jnc JC l
endm

LJA macro l
 LJ ja JNA l
endm

LJNA macro l
 LJ jna JA l
endm

LJB macro l
 LJ jb JNB l
endm

LJNB macro l
 LJ jnb JB l
endm

LJS macro l
 LJ js JNS l
endm

LJNS macro l
 LJ jns JS l
endm

LJAE macro l
 LJ jae JNAE l
endm

LJBE macro l
 LJ jbe JNBE l
endm

LJL macro l
 LJ jl JNL l
endm

LJG macro l
 LJ jg JNG l
endm

LJLE macro l
 LJ jle JNLE l
endm

DLJE macro l
 DLJ JE JNE l
endm

DLJNE macro l
 DLJ jne JE l
endm

DLJZ macro l
 DLJE l
endm

DLJNZ macro l
 DLJNE l
endm

DLJC macro l
 DLJ jc JNC l
endm

DLJNC macro l
 DLJ jnc JC l
endm

DLJA macro l
 DLJ ja JNA l
endm

DLJNA macro l
 DLJ jna JA l
endm

DLJB macro l
 DLJ jb JNB l
endm

DLJNB macro l
 DLJ jnb JB l
endm

DLJS macro l
 DLJ js JNS l
endm

DLJNS macro l
 DLJ jns JS l
endm

DLJAE macro l
 DLJ jae JNAE l
endm

DLJBE macro l
 DLJ jbe JNBE l
endm

DLJG macro l
 DLJ jg JNG l
endm

DLJL macro l
 DLJ jl JNL l
endm

DLJLE macro l
 DLJ jle JNLE l
endm


;*	LJ - generate long conditional jump
;
;	if target preceeds us and is in range just use a short jump
;	else use a long jump
;
;	LJ <direct jmp>,<skip jmp>,<label>

LJ MACRO dirop,idirop,l
 local a
 IF ((.TYPE l) XOR 20h) AND 0A0h
  idirop a			       ;; not defined or is external
  jmp l
a:
 ELSE				       ;; is local definied
  IF (($-l) LT 124) AND ($ GT l)
   dirop l			      ;; is before and within range
  ELSE
   idirop a			      ;; is out of range or forward (pass 2)
   jmp l
a:
  ENDIF
 ENDIF
ENDM


;*	DLJ - generate debug long conditional jump
;
;	If DEBUG is defined then we generate a long jump, else a short
;	one.
;
;	DLJ <direct jmp>,<skip jmp>,<label>

DLJ MACRO dirop,idirop,l
 local a
 IF DEBUG
  idirop a
  jmp l
a:
 ELSE
  dirop l
 ENDIF
ENDM

.xcref	LJE, LJNE, LJZ, LJNZ, LJC, LJNC, LJA, LJNA
.xcref	LJB, LJNB, LJS, LJNS, LJAE, LJBE, LJG, LJL, LJLE
.xcref	DLJE, DLJNE, DLJZ, DLJNZ, DLJC, DLJNC, DLJA, DLJNA
.xcref	DLJB, DLJNB, DLJS, DLJNS, DLJAE, DLJBE, DLJG, DLJL, DLJLE
.xcref	LJ,DLJ



;**	SHORT offset macro
;
;	expands to SHORT if non debug, to nul if debug.
;
;	this allows us to code
;
;		jmp	SHRT foobar
;
;	and get a long form if debugging is turned on (because the extra
;	debug code puts the target out of range)

if DEBUG
  SHRT	EQU	<>
else
  SHRT	EQU	SHORT
endif


;**	FALLTHRU - Verifies Fallthrough Validity

FALLTHRU MACRO	labl
;	BUGBUG - restore align when we make code segment word aligned
;	align	2		; don't have errnz fail due to alignment
    IF2 			; of following label
	.errnz	labl-$
    ENDIF
	ENDM


;**	INTTEST - Generate an INT 3 for testing

INTTEST MACRO
if DEBUG
	int	3
endif
	ENDM


;**	DPUBLIC - Make a public symbol for debugging

DPUBLIC MACRO	arg
if DEBUG
	public	arg
endif
	ENDM

;*	Debug Traps
;
;	These are removed as the code is exercised

TRAP	macro			; Like INTTEST but is normally left in during
	int	3		; debugging; indicates "should not occur"
	ENDM

TRAPC	macro
	local	l
	jnc	short l
	int	3
l:
	ENDM

TRAPNC	macro
	local	l
	jc	short l
	int	3
l:
	ENDM

TRAPA	macro
	local	l
	jna	short l
	int	3
l:
	ENDM

TRAPNA	macro
	local	l
	ja	short l
	int	3
l:
	ENDM

TRAPZ	macro
	local	l
	jnz	short l
	int	3
l:
	ENDM

TRAPNZ	macro
	local	l
	jz	short l
	int	3
l:
	ENDM



	BREAK	<Structure Field Macros>


;**	Follows - Verify that a field follows another
;
;	FOLLOWS field1, field2 [, size]
;
;	This macro generates an error if "field1" doesn't immeidately
;	follow "field2".  If "size" is specified then an error is generated
;	if Field1 is not of the proper size.

FOLLOWS macro	field1, field2, fldsiz
	.errnz	field1 - size field2 - field2
IFNB	<fldsiz>
	.errnz	size field1 - fldsiz
ENDIF
	ENDM



;**	LAST - Verify that a field is the last field in a structure
;
;	LAST	fieldname, structname
;
;	Generates an error if fieldname is not last in structname.

LAST	macro	fldnam, strunam
	.errnz	size strunam - fldnam - size fldnam
	endm



;**	TESTB - Use Byte form for Word TESTS, when possible
;
;	TESTB is used in place of 16-bit TEST instructions.  It substitutes
;	a smaller 8-bit test when possible.

TESTB	macro	targ,mask,thirdarg
	local	mask2,delta

ifnb <thirdarg>
	.err	mask must be enclosed in brackets
endif

ifidn <targ>,<[bx]>
	test	targ,mask
	exitm
endif
ifidn <targ>,<[si]>
	test	targ,mask
	exitm
endif
ifidn <targ>,<[di]>		; don't process these operands specially
	test	targ,mask
	exitm
endif
ifidn <targ>,<[BX]>
	test	targ,mask
	exitm
endif
ifidn <targ>,<[SI]>
	test	targ,mask
	exitm
endif
ifidn <targ>,<[DI]>
	test	targ,mask
	exitm
endif
ifidn <targ>,<SI>
	test	targ,mask
	exitm
endif
ifidn <targ>,<DI>
	test	targ,mask
	exitm
endif
ifidn <targ>,<BP>
	test	targ,mask
	exitm
endif
delta	=	0
mask2	=	mask

    if	mask2 AND 0ff00h
;	have a mask bit in the high half
	if mask2 AND 0ffh
	    test targ,mask
	    exitm
	endif
	mask2 = mask2 SHR 8
	delta = 1
    endif

ifidn <targ>,<AX>
	if delta
	    test targ,mask
	else
	    test AL,mask2
	endif
	exitm
endif
ifidn <targ>,<BX>
	if delta
	  test	BH,mask2
	else
	  test	BL,mask2
	endif
	exitm
endif
ifidn <targ>,<CX>
	if delta
	  test	CH,mask2
	else
	  test	CL,mask2
	endif
	exitm
endif
ifidn <targ>,<DX>
	if delta
	  test	DH,mask2
	else
	  test	DL,mask2
	endif
	exitm
endif
ifidn <targ>,<ax>
	if delta
	    test targ,mask
	else
	    test AL,mask2
	endif
	exitm
endif
ifidn <targ>,<bx>
	if delta
	  test	BH,mask2
	else
	  test	BL,mask2
	endif
	exitm
endif
ifidn <targ>,<cx>
	if delta
	  test	CH,mask2
	else
	  test	CL,mask2
	endif
	exitm
endif
ifidn <targ>,<dx>
	if delta
	  test	DH,mask2
	else
	  test	DL,mask2
	endif
	exitm
endif

	test	byte ptr targ+delta,mask2
	endm


;
; Some old versions of the 80286 have a bug in the chip.  The popf
; instruction will enable interrupts.  Therefore in a section of code with
; interrupts disabled and you need a popf instruction use the 'popff'
; macro instead.
;

POPFF	macro
	jmp	$+3
	iret
	push	cs
	call	$-2
	endm

Break   <Cmp32 - 32-bit compare>
;----------------------------------------------------------------------------
;
; Macro Name : Cmp32
;
; Inputs:
;       msw1 -- 1st operand, most  significant word; MUST BE REGISTER.
;       lsw1 -- 1st operand, least significant word; MUST BE REGISTER.
;       msw2 -- 2nd operand, most  significant word.
;       lsw2 -- 2nd operand, least significant word.
; Function:
;       Compare 2 32-bit operands.  Implemented as a macro.
; Outputs:
;       CF = 1 if 1st operand <  2nd operand
;          = 0 if 1st operand >= 2nd operand
;	ZF = 1 if 1st operand == 2nd operand
;          = 0 if 1st operand <> 2nd operand
;-----------------------------------------------------------------------------
;M019: Created.

Cmp32   MACRO   msw1,lsw1,msw2,lsw2
        LOCAL   cmp32x

        cmp     msw1,msw2
        jne     cmp32x
        cmp     lsw1,lsw2
cmp32x:
        ENDM
