dos_compilers/Logitech Modula-2 v1/SERVICES.ASM

1225 lines
30 KiB
NASM
Raw Permalink Normal View History

2024-07-01 00:16:10 +02:00
;*************************************************************
;
; Copyrigth (C) 1984 Logitech. All Rights Reserved.
;
; Permission is hereby granted to registered users to use or
; abstract the following program in the implementation of
; customized versions. This permission does not include the
; right to redistribute the source code of this program.
;
;
; Modula-2/86 Run Time Support package
;
; SERVICES.ASM - Module 2 of the Run Time Support
;
; Release 1.0 - Feb 29 84
;
;*************************************************************
include RTS.INC
code segment public
extrn RTS_DS:word ; yes, this goes in CODE segment!
extrn TERMINATE:near
extrn COMP_STACK:near
extrn NORM_ADDR:near
extrn WRITE_STATUS:near
extrn REST_INTERRUPT_MASK:near
extrn SYS_RESET:near
extrn TRANSFER:near
extrn IOTRANSFER:near
extrn NEWPROCESS:near
extrn MON_ENTRY:near
extrn MON_EXIT:near
extrn LISTEN:near
extrn FREE_INT_VECT:near
data segment public
extrn CUR_PROCESS:byte ; ProcessDescriptor
extrn CUR_P_PTR:dword ; (ptr to ProcessDescriptor)
extrn BASE_PAGE_PTR:dword ; pointer to prog seg prefix
public FCT_CODE
FCT_CODE db ?
even
TempWord dw ? ; temporary word storage..
TEMP_W dw ? ; another temporary word..
OldIP dw ? ; interrupt frame, saved and
OldCS dw ? ; restored by DYN_PAR_COPY
OldFlags dw ? ;
; Run Time Support system JUMP TABLE
;
; The entries in this table cannot be changed without corresponding
; changes to the Modula-2/86 Compiler!
; It is suggested that extensions to the RTS be implemented with a
; different interrupt than the standard RTS interrupt.
;
RTS_JMP_TBL dw SYS_RESET ; 00h
dw M2_HALT ; 01h
dw TRANSFER ; 02h
dw IOTRANSFER ; 03h
dw NEWPROCESS ; 04h
dw MON_ENTRY ; 05h
dw MON_EXIT ; 06h
dw LISTEN ; 07h
dw GET_RETURN_POINT; 08h
dw SET_RETURN_POINT; 09h
dw RUN_PROGRAM ; 0Ah
dw TERMINATE ; 0Bh
dw COM_CASE ; 0Ch
dw COM_CASE ; 0Dh
dw COM_CASE ; 0Eh
dw COM_CASE ; 0Fh
dw COM_CASE ; 10h
dw CASE_ERROR ; 11h
dw PAR_COPY ; 12h
dw DYN_PAR_COPY ; 13h
dw STACK_CHECK ; 14h
dw FREE_INT_VECT ; 15h
dw WRITE_STAT ; 16h
dw GET_PD_ADDR ; 17h
dw ALLOC_HEAP ; 18h
dw FCT_RET_ERR ; 19h
dw NORM_ADDRESS ; 1Ah
dw ADD_ADDR ; 1Bh
dw ADD_A_C ; 1Ch
dw SUB_ADDR ; 1Dh
dw SUB_A_C ; 1Eh
dw EQ_ADDR ; 1Fh
dw GT_EQ_ADDR ; 20h
dw GT_EQ_ADDR ; 21h
dw CONV_A_C ; 22h
dw CARD_OVF ; 23h
dw INTEGER_OVF ; 24h
dw RANGE_ERROR ; 25h
dw PSP_POINTER ; 26H
data ends
FAST_JUMP dw SLOW_BRANCH ; 00h
dw SLOW_BRANCH ; 01h
dw SLOW_BRANCH ; 02h
dw SLOW_BRANCH ; 03h
dw SLOW_BRANCH ; 04h
dw SLOW_BRANCH ; 05h
dw SLOW_BRANCH ; 06h
dw SLOW_BRANCH ; 07h
dw SLOW_BRANCH ; 08h
dw SLOW_BRANCH ; 09h
dw SLOW_BRANCH ; 0Ah
dw SLOW_BRANCH ; 0Bh
dw COM_CASE ; 0Ch
dw COM_CASE ; 0Dh
dw COM_CASE ; 0Eh
dw COM_CASE ; 0Fh
dw COM_CASE ; 10h
dw SLOW_BRANCH ; 11h
dw PAR_COPY ; 12h
dw SLOW_BRANCH ; 13h
dw STACK_CHECK ; 14h
dw SLOW_BRANCH ; 15h
dw SLOW_BRANCH ; 16h
dw SLOW_BRANCH ; 17h
dw ALLOC_HEAP ; 18h
dw SLOW_BRANCH ; 19h
dw Norm_ADDRESS ; 1Ah
dw ADD_ADDR ; 1Bh
dw ADD_A_C ; 1Ch
dw SUB_ADDR ; 1Dh
dw SUB_A_C ; 1Eh
dw EQ_ADDR ; 1Fh
dw GT_EQ_ADDR ; 20h
dw GT_EQ_ADDR ; 21h
dw CONV_A_C ; 22h
dw SLOW_BRANCH ; 23h
dw SLOW_BRANCH ; 24h
dw SLOW_BRANCH ; 25h
dw SLOW_BRANCH ; 26h
public RTS_BRANCH
assume CS:code
RTS_BRANCH:
;==========
; This is the entry point for all the functions
; of RTS. During execution of these functions,
; interrupts are disabled.
; Upon entry:
; AL contains the function code. Parameters
; for the functions are on stack or in registers.
; SI and ES must not be used for parameters,
; they are overwritten here.
; We don't need to save all the registers, since we
; come here on explicite demande (SWI 228) and not
; through a hardware interrupt.
; Save current DS and set the one of RTS:
; Note: in the current release, the compiler
; does not assume the DS to hold the
; value of the current data segment.
; It is however safer not just to destroy it.
MOV ES, RTS_DS
assume ES:data
xor ah,ah
;lines added to process invalid function calls properly
cmp al,NBR_FCT
jge SLOW_BRANCH
mov si,ax
add si,si
jmp FAST_JUMP[si] ; do fast routines
;
SLOW_BRANCH:
CALL SAVE_CPU_INFO
assume DS:data
; AL contains the function code
MOV FCT_CODE, AL
CMP AL, NBR_FCT
JB VALID_FCT
MOV CUR_PROCESS.PD_STATUS, ILL_FCT_CODE
JMP TERMINATE
VALID_FCT:
xor ah, ah
MOV SI, AX
SHL SI, 1
; At this point:
; AL holds the RTS-Function-Code, SI = 2 * AX.
; DS and ES hold the Data Segment of RTS,
; while DS of the running process is already saved
; in the Process Descriptor.
JMP RTS_JMP_TBL [SI]
;*********************** That's the branch
;-----------------------------------------------------------
public SAVE_CPU_INFO
SAVE_CPU_INFO PROC NEAR
;============
; Utility routine to save registers in the process descr:
; Upon entry: ES is data segment of RTS
; Upon exit: DS is data segment of RTS
MOV ES:CUR_PROCESS.PD_DS, DS ; save process' DS
MOV DS, RTS_DS ; now switch to RTS data seg
; We have to save some more information
; (used for the P_M_DUMP and for TRANSFER):
MOV CUR_PROCESS.PD_SP, SP
MOV CUR_PROCESS.PD_BP, BP
MOV CUR_PROCESS.PD_SS, SS
POP SI ; save ret addr of caller
POP CUR_PROCESS.PD_IP
; offset of return address
POP CUR_PROCESS.PD_CS
; segment of return address
POP CUR_PROCESS.PD_FLAGS
; restore the return block:
PUSH CUR_PROCESS.PD_FLAGS
PUSH CUR_PROCESS.PD_CS
PUSH CUR_PROCESS.PD_IP
PUSH SI
RET
SAVE_CPU_INFO ENDP
;-----------------------------------------------------------
STACK_CHECK:
;===========
; BX = stack clearance requested, in bytes
; we first have to check, if the current stack is the one
; of the modula program. If we are interrupted inside MS-DOS,
; the stack points to an area inside MS-DOS and the test for
; stack-overflow we make here has no sens.
mov ax, ss
cmp ax, ES:CUR_PROCESS.PD_SS
jne SP_OK
add bx, sp_reserve ; BX is space required on stack
mov ax, sp
sub ax, bx ; compute new limit
jb STACK_BOO ; oops, wrap thru 0
mov ES:CUR_PROCESS.PD_SP_LIM, ax ; record limit, for heap
mov bx, ss
mov cl,4
shr ax,cl
add bx,ax ; convert SP to paragraph pointer
mov ax,ES:CUR_PROCESS.PD_HEAP_TOP
shr ax,cl
add ax,ES:CUR_PROCESS.PD_HEAP_TOP+2 ; ditto with heap top ptr
sub bx,ax ; stack below HeapTop?
jbe STACK_BOO ; yup
SP_OK: IRET
STACK_BOO:
CALL SAVE_CPU_INFO
MOV FCT_CODE, STACK_CHECK_FCT
;;; JMP SHORT STACK_OVF
;-----------------------------------------------------------
public STACK_OVF
STACK_OVF:
;=========
; This is the entry through RTS_BRANCH for treatment
; of a stack overflow:
MOV CUR_PROCESS.PD_STATUS, STACK_OVF_CODE
JMP TERMINATE ; No return!
page
;-----------------------------------------------------------
public DIV_BY_ZERO
DIV_BY_ZERO:
;===========
; We arrive here NOT through RTS_BRANCH, but directly
; from the interrupt, that the CPU performs in case
; of a division by zero. So, we have to save the registers
; that are relevant for the dump and the debugger:
MOV ES, CS: RTS_DS
CALL SAVE_CPU_INFO
; Set the function code to some resonable value:
MOV FCT_CODE, TERMINATE_FCT
MOV CUR_PROCESS.PD_STATUS, ZERO_DIVIDE_CODE
JMP TERMINATE
;-----------------------------------------------------------
M2_HALT:
;=======
; The following registers are destroyed: SI, ES.
; (DS is already saved)
MOV CUR_PROCESS.PD_STATUS, HALT_CODE
JMP TERMINATE
;-----------------------------------------------------------
GET_RETURN_POINT:
;================
SET_RETURN_POINT:
;================
JMP NOT_YET
; Reserved entries for use in connection with
; separate program loading and execution.
;-----------------------------------------------------------------
GET_OLD_PROGRAM:
; We arrive here after termination of an
; overlay and - more precisely - after
; execution of TERMINATE. Stack is already set
; to top-of-stack of father program.
; BP and DS are restores for father program.
MOV DS, RTS_DS
; Save the interrupt mask of terminating program:
PUSH CUR_PROCESS.PD_PRIO_MASK
; We have to swap to the main process
; of the terminating program (if father <> NIL):
MOV AX, CUR_PROCESS.PD_FATHER_PROC + 2
CMP AX, 0FFFFH ; check if not NIL
JE REST_OLD_PD ; seg test is enough
; copy the status from the terminating process
; to the P.D. of father process:
MOV CX, CUR_PROCESS.PD_STATUS
MOV TempWord, CX
; update pointer to current P.D:
MOV CUR_P_PTR + 2, AX
MOV SI, CUR_PROCESS.PD_FATHER_PROC
MOV CUR_P_PTR, SI
MOV CX, DS
MOV ES, CX
MOV DI, OFFSET CUR_PROCESS
MOV DS, AX
; (DS,SI) hold addr of process descriptor
; of father process, (ES,DI) hold addr
; of copy in RTS.
MOV CX, size ProcessDescriptor / 2
rep movsw
MOV CX, ES
MOV DS, CX
MOV CX, TempWord ; copy status
MOV CUR_PROCESS.PD_STATUS, CX
REST_OLD_PD:
POP BX ; interrupt mask of term. prog.
; Restore old P.D.:
POP word ptr CUR_PROCESS.PD_FATHER_PROC
POP word ptr CUR_PROCESS.PD_FATHER_PROC + 2
POP word ptr CUR_PROCESS.PD_PROG_END
POP word ptr CUR_PROCESS.PD_PROG_END + 2
POP CUR_PROCESS.PD_SP_LIM
POP AX
MOV CUR_PROCESS.PD_PRIO_MASK, AX
; compare current and new priority and change system's
; priority if they are not equal:
CMP AX, BX
JE EQUAL_PRIO
CALL REST_INTERRUPT_MASK
EQUAL_PRIO:
POP DS ; has to be restored last
; Return to the father program:
IRET
page
data segment
NEW_PROG_START DW ? ; variable for 'RUN_PROGRAM'
NEW_PROG_ENTRY DW ?,? ; the same
data ends
RUN_PROGRAM:
;===========
; This function prepares the stack and
; starts a new program. Parameters:
; BX= segment addr of program area
; (used to prepare the new stack).
; DX:CX segment:offset of program entry point,
; Save the parameters:
MOV NEW_PROG_START, BX
MOV NEW_PROG_ENTRY, CX
MOV NEW_PROG_ENTRY + 2, DX
; The old stack (current-one) still holds
; the return block, to go back to the father
; program upon termination.
; Save some values of the P.D. on the old stack:
PUSH CUR_PROCESS.PD_DS ; has to be first
PUSH CUR_PROCESS.PD_PRIO_MASK
PUSH CUR_PROCESS.PD_SP_LIM
PUSH word ptr CUR_PROCESS.PD_PROG_END + 2
PUSH word ptr CUR_PROCESS.PD_PROG_END
PUSH word ptr CUR_PROCESS.PD_FATHER_PROC + 2
PUSH word ptr CUR_PROCESS.PD_FATHER_PROC
; Now, we push the entry of the termination
; routine and set the new values for PROG_END:
MOV AX, 0 ; interrupt disable
PUSH AX ; flags
PUSH CS
MOV AX, OFFSET GET_OLD_PROGRAM
PUSH AX
PUSH DS
PUSH CUR_PROCESS.PD_BP
MOV CUR_PROCESS.PD_PROG_END + 2, SS
MOV CUR_PROCESS.PD_PROG_END, SP
; New value for father process. It becomes
; NIL, because the current process will be
; the main of the new program:
MOV AX, NIL_OFF
MOV BX, NIL_SEG
MOV CUR_PROCESS.PD_FATHER_PROC + 2, BX
MOV CUR_PROCESS.PD_FATHER_PROC, AX
; Now, we create the new stack:
MOV AX, CUR_PROCESS.PD_HEAP_TOP
MOV BX, CUR_PROCESS.PD_HEAP_TOP + 2
CALL NORM_ADDR
INC BX
; BX= seg of free memory
MOV AX, NEW_PROG_START
SUB AX, BX
JA NEW_PROG_OK
MOV CUR_PROCESS.PD_STATUS, CALL_ERR_CODE
POP BP
POP DS
IRET
; To return in this case (error), we
; execute the termination routine
NEW_PROG_OK:
; Set the new stack:
CALL COMP_STACK
MOV SS, BX
MOV SP, AX
; the old value is stored in PROG_END
; Put the address of the termination routine
; on the new stack. In case of normal termination,
; a RETF will be executed by the program and
; we will arrive in TERMINATE with status=normal.
PUSH CS
MOV AX, OFFSET TERMINATE
PUSH AX
; Now push the entry address of
; the new program:
PUSH CUR_PROCESS.PD_FLAGS
PUSH NEW_PROG_ENTRY + 2
PUSH NEW_PROG_ENTRY
; BP is set to 0FFFFH, so the debugger
; can recognize the beginning
; of a new overlay:
MOV BP, 0FFFFH
; it will be pushed in new program
;...and call it:
IRET
;---------------------------------------------------------------------------
COM_CASE:
;========
; Common Entry Point for all kind of CASE evaluations
; The actual value of the tag is in BX.
; The parameters are in the code segment, right after the INT instr.
; First fetch the return addr, to get the addr of the parameters:
POP DI
POP ES
PUSH ES ; Restore it, used for IRET
; Get the first parameter:
MOV CX, ES: [DI]
; Set DI to the next parameter:
INC DI
INC DI
; Now select the corresponding routine:
CMP AL, CASE_3_CARD_FCT
JAE CASE_3
CMP AL, CASE_2_CARD_FCT
JAE CASE_2
; otherwise, it must be CASE_1:
CASE_1:
MOV DX, CX ; just to save it
INC CX
; Search 1 more than the actual number of value. This is needed
; distinguish the case where the last element matches from the case
; where no element matches.
MOV AX, BX
cld
REPNE SCASW ; Search the actual tag in the list
; DI points now to the element after the one that matches the actual
; tag. If no value matches, DI points to the word 2 positions after
; the last one in the list. DI is now used as the index in the table
; with the entry points:
SHL DX, 1 ; Size of list to skip
ADD DI, DX
PUSH ES: WORD PTR [DI] - 2
; Entry point for actual tag. The '-2' corrects for the incrementation
; of DI after the search. If no element had matched, we will find the
; address of the ELSE part.
IRET
CASE_2:
MOV DX, ES: [DI] ; Lowest value
; CX holds the highest value, DX the lowest one.
; Set DI to poin to the jumptable:
INC DI
INC DI
INC CX ; highest value + 1
CMP AL, CASE_2_CARD_FCT
JNE CASE_2_INT
CASE_2_CARD: ; The tag is a CARDINAL
CMP BX, DX ; Test if lower than lowest value
JAE CASE_2_1
MOV BX, CX ; actual tag was below lowest value
CASE_2_1: ; The tag is above or equal to lowest value
CMP BX, CX ; Test if higher than highest value
JB CASE_2_OK
MOV BX, CX
JMP SHORT CASE_2_OK
CASE_2_INT: ; The tag is an INTEGER
CMP BX, DX ; Test for lowest value
JGE CASE_2_2
MOV BX, CX
CASE_2_2: ; Tag is greater or equal to lowest value
CMP BX, CX ; Test for highest value
JL CASE_2_OK
MOV BX, CX
CASE_2_OK:
SUB BX, DX ; Tag - Lowest Value
SHL BX, 1
PUSH ES: WORD PTR [BX + DI]
IRET
CASE_3:
PUSH DI ; just to save it
MOV DX, 0 ; Counter
CASE_3_NEXT:
INC DX
CMP DX, CX
JA CASE_3_FOUND
; The tag value was not found: proceed with the counter (DX)
; pointing to the ELSE part.
MOV SI, ES: [DI] ; low limit of next intervall
; Set DI to the next high limit:
INC DI
INC DI
CMP AL, CASE_3_CARD_FCT
JNE CASE_3_INT
CASE_3_CARD: ; Tag is a CARDINAL
CMP BX, SI
JB CASE_3_BELOW
MOV SI, ES: [DI] ; high limit
CMP BX, SI
JBE CASE_3_FOUND
JMP SHORT CASE_3_ABOVE ; It's not this one
CASE_3_INT: ; Tag is an INTEGER
CMP BX, SI
JL CASE_3_BELOW
MOV SI, ES: [DI] ; high limit
CMP BX, SI
JLE CASE_3_FOUND
JMP SHORT CASE_3_ABOVE ; It's not this one
CASE_3_BELOW:
INC DI
INC DI
CASE_3_ABOVE:
; Set DI to the low limit of next intervall
INC DI
INC DI
JMP SHORT CASE_3_NEXT
CASE_3_FOUND:
; DX is the index in the jumptable
; CX is the number of listed intervalls
SHL CX, 1
SHL CX, 1 ; CX is now size of list
POP DI
INC DI
INC DI ; DI is the addr of the list
ADD DI, CX
SHL DX, 1
ADD DI, DX
PUSH ES: WORD PTR [DI]
IRET
CASE_ERROR:
;==========
MOV CUR_PROCESS.PD_STATUS, CASE_ERR_CODE
JMP TERMINATE
; END CASE
;---------------------------------------------------------------------------
PAR_COPY:
;========
; Used to copy a fix size value-parameter from its actual argument
; into the place inside the local variables of a procedure, reserved
; for that copy:
; Upon entry: CX = size of parameter,
; BX = offset, relativ to BP, where the addr of argument is
; DI = offset, relativ to BP, where to copy it.
MOV SI, BX
LDS SI, DWORD PTR [BP+SI] ; (DS,SI) hold source addr
MOV AX, SS
MOV ES, AX
ADD DI, BP ; (ES,DI) hold dest addr
MOV AX, CX ; save the counter
SHR CX, 1 ; number of words to copy
REP MOVSW
AND AX, 1 ; check if odd
JZ PAR_COPY_1
MOVSB ; move the last byte, if any
PAR_COPY_1:
MOV DS, CUR_PROCESS.PD_DS
IRET
DYN_PAR_COPY:
;============
; Used to copy a value-parameter of type ARRAY OF T from the actual
; argument on the stack of the called procedure. The copy is placed
; topstack and its address (SS and offset) is put in the procedure
; interface.
; Upon entry: CX holds size of the element of the array.
; DI holds offset, relativ to BP, where the address and
; the high index stand (Offset, Segment, High).
; The low index is assumed to be zero.
; Upon exit: The address of the copy replaces the address of the
; original ([BP+DI] upon entry).
MOV AX, [BP+DI] + 4 ; High index value
INC AX ; # of elements = high+1
CMP CX, 1
JE SIZE_IN_AX ; no multiplication needed
CMP CX, 4
JA MUL_NEEDED
SHL AX, 1
CMP CX, 2
JBE SIZE_IN_AX ; NOTE: in case the size was an odd
; number, we still have to multiply
; by the next higher even number.
SHL AX, 1
JMP SHORT SIZE_IN_AX
MUL_NEEDED:
MUL CX
SIZE_IN_AX:
; Save the return block from the stack:
POP OldIP
POP OldCS
POP OldFlags
; Check, if there is enough room on the stack:
MOV BX, AX
PUSH AX ; just to save it
PUSH DI ; just to save it
CALL SP_TEST ; returns AX<>0, if error
CMP AX, 0
JZ STACK_GOOD
CALL STACK_OVF ; no room for the copy
STACK_GOOD:
POP DI ; restore it
POP CX ; restore it
SUB SP, CX
AND SP, 0FFFEH ; Mask out last bit, to
; ensure an even address.
MOV DX, DS ; save DS (don't use stack)
LDS SI, DWORD PTR [BP+DI] ; Source address
MOV [BP+DI], SP ; Store the destination addr
MOV [BP+DI] + 2, SS
MOV DI, SP
MOV AX, SS
MOV ES, AX ; (ES,DI) = Dest addr
INC CX ; number of bytes
SHR CX, 1 ; CX = number of words
REP MOVSW
; Restore the return block:
MOV DS, DX ; restore DS
PUSH OldFlags
PUSH OldCS
PUSH OldIP
MOV DS, CUR_PROCESS.PD_DS
IRET
; END PARAMETER_COPY
;---------------------------------------------------------------------------
SP_TEST PROC near
; Used registers: AX, BX, CX, DX, SI, DI
; BX holds the required size. SP is checked for room to grow by
; the required number of bytes + some reserve. AX returns 0 if ok
; and 0FFH if overflow occurs.
ADD BX, SP_RESERVE
; first check, if SP does not go through zero:
CMP BX, SP
JA STACK_BAD
MOV AX, SP
SUB AX, BX ; that's the new stack limit
; update the stack limit, it is used when heap wants to grow:
; MOV CUR_PROCESS.PD_SP_LIM, AX
; removed, SP_LIM is not used in heap test (27/4/83).
MOV BX, SS
CALL NORM_ADDR ; Returns: BX=seg, AX=offset (<16)
MOV SI, AX
MOV DI, BX
MOV AX, CUR_PROCESS.PD_HEAP_TOP
MOV BX, CUR_PROCESS.PD_HEAP_TOP + 2
CALL NORM_ADDR ; Returns: BX=seg, AX=offset (<16)
CMP DI, BX ; test segment
JA STACK_OK
JB STACK_BAD
CMP SI, AX ; test offset
JA STACK_OK
STACK_BAD:
MOV AX, 0FFH ; means: error
RET
STACK_OK:
MOV AX, 0 ; means: no error
RET
SP_TEST ENDP
WRITE_STAT:
;==========
; BX holds the status value to be interpreted
CALL WRITE_STATUS
MOV DS, CUR_PROCESS.PD_DS
IRET
;---------------------------------------------------------------------------
GET_PD_ADDR:
;===========
; Upon entry: (DX,BX) hold address, where to put the addr of CUR_PROCESS
MOV ES, DX
MOV ES:WORD PTR [BX], OFFSET CUR_PROCESS
MOV ES:WORD PTR 2[BX],DS ; DS of RTS
MOV DS, CUR_PROCESS.PD_DS
IRET
;---------------------------------------------------------------------------
ALLOC_HEAP:
;==========
; Increases the Heap by the requested size
; (in register BX). Checks for collision
; Heap - Stack.
;;;;;;;;; Fast procedure
MOV ES: CUR_PROCESS.PD_DS, DS
MOV AX, ES
MOV DS, AX
MOV AX, CUR_PROCESS.PD_HEAP_TOP + 2
ADD BX, CUR_PROCESS.PD_HEAP_TOP
JC FIX_OFFSET
; save the new heap_top:
PUSH AX ; segment
PUSH BX ; offset
JMP NORM_HEAP_TOP
FIX_OFFSET:
; there was an overflow of the offset:
ADD AX, 1000H
PUSH AX ; new segment
PUSH BX ; and old offset
JC HEAP_BAD ; we ask for too much
NORM_HEAP_TOP:
MOV CL, 4
SHR BX, CL
INC BX
ADD BX, AX ; normalized new segment
JC HEAP_BAD
; BX is the segment value just above the new
; Heap_Top. On the stack we have saved that
; new Heap_Top. Now we have to normalize the stack:
MOV AX, SP
MOV CL, 4
SHR AX, CL
MOV DX, SS
ADD AX, DX ; norm. stack segment
CMP AX, BX ; compare segments only
JB HEAP_BAD
HEAP_OK:
POP word ptr CUR_PROCESS.PD_HEAP_TOP
POP word ptr CUR_PROCESS.PD_HEAP_TOP + 2
HEAP_RET:
MOV DS, CUR_PROCESS.PD_DS
IRET
HEAP_BAD:
MOV CUR_PROCESS.PD_STATUS, HEAP_OVF_CODE
POP AX ; dummy
POP AX
JMP SHORT HEAP_RET
;---------------------------------------------------------------------------
FCT_RET_ERR:
;===========
; This error will occur, if a function terminates without an
; explicite RETURN statement.
MOV CUR_PROCESS.PD_STATUS, FCT_RET_ERR_CODE
JMP TERMINATE ; No return!
;---------------------------------------------------------------------------
NORM_ADDRESS:
;============
; GOAL: brings an address variable in its normalized form,
; i.e. segment as large as possible, offset = [0..15].
; The program is terminated in case of overflow.
; INPUT: the address in (DS,BX)
; OUTPUT: same as input
MOV AX, BX
MOV BX, DS
CALL NORM_ADDR
MOV DS, BX
MOV BX, AX
JC ADDR_OVF ; address larger than 20 Bits!
IRET
;---------------------------------------------------------------------------
ADD_ADDR:
;========
; GOAL: Adds two addresses and checks the result for overflow.
; INPUT: the 2 addresses to add are in (DX,DI) and (DS,BX).
; OUTPUT: the resulting address in (DS,BX).
MOV AX, DI
; add the offsets:
ADD AX, BX
JNC OFF_OK_1
; IF CARRY means: the sum of the offsets gives an overflow,
; we have to add 1000H to the segment values:
ADD DX, 1000H
JC ADDR_OVF
OFF_OK_1:
MOV BX, DS
; add the segments:
ADD BX, DX
JC ADDR_OVF
;;; Don't make the following shortcut!
;;; It returns a non-normalized address and
;;; therefore the address comparison is slower!
;;; CMP BX, 0F000H
;;; JB ADD_ADDR_DONE ; overflow not possible
; check for overflow:
CALL NORM_ADDR
JC ADDR_OVF
ADD_ADDR_DONE:
MOV DS, BX
MOV BX, AX
IRET
ADD_A_C:
;=======
; GOAL: Adds an ADDRESS and a CARDINAL and checks the result
; for overflow.
; INPUT: the ADDRESS is in (DS,BX) and the CARDINAL in (DX)
; OUTPUT: the resulting ADDRESS in (DS,BX).
MOV AX, DX
; add the offsets:
ADD AX, BX
MOV BX, DS
JNC OFF_OK_2
; IF CARRY means: the sum of the offsets gives an overflow,
; so we have to add 1000H to the segment values:
ADD BX, 1000H
JC ADDR_OVF
OFF_OK_2:
;;; Don't make the following shortcut!
;;; It returns a non-normalized address and
;;; therefore the address comparison is slower!
;;; CMP BX, 0F000H
;;; JB ADD_A_C_DONE ; overflow not possible
; check for overflow:
CALL NORM_ADDR
JC ADDR_OVF
ADD_A_C_DONE:
MOV DS, BX
MOV BX, AX
IRET
;---------------------------------------------------------------------------
ADDR_OVF:
; This is the treatment of the overflow
; of an address variable.
; This routine is entered with a JUMP from
; a fast RTS function. Therefore we have to
; save some info for the dump:
CALL SAVE_CPU_INFO
MOV CUR_PROCESS.PD_STATUS, ADDR_OVF_CODE
JMP TERMINATE ; No return!
;---------------------------------------------------------------------------
COMM_SUB_ADDR PROC NEAR
; performs (BX,AX) - DX, and returns result in BX (seg) and AX (off):
CMP AX, DX ; to check, which offset is larger
JAE SUB_OFFSET
; IF BELOW means: the offset to subtract is larger then the offset
; of the address, so we have to borrow as much as we need from the
; segment:
SUB DX, AX
MOV AX, DX ; DX saves the difference
ADD AX, 0FH ; AX:= (AX+15) MOD 16
MOV CL, 4
SHR AX, CL ; AX = number of paragraphs to borrow
SUB BX, AX ; BX = corrected segment value
JB ADDR_OVF
AND DX, 0FH ; normalized offset to subtract
MOV AX, 0
JZ OFF_OK_3 ; the resulting offset is zero
MOV AX, 10H
SUB_OFFSET:
SUB AX, DX
OFF_OK_3:
RET
COMM_SUB_ADDR ENDP
SUB_ADDR:
;========
; GOAL: Subtracts the ADDRESS in (DX,DI) from the
; ADDRESS in (DS,BX). The result is checked
; for overflow and returned in (DS,BX).
MOV AX, BX
MOV BX, DS
MOV DS, DX
MOV DX, DI
CALL COMM_SUB_ADDR
; subtract the segments:
CALL NORM_ONE ; result in (BX,AX)
MOV CX, DS
SUB BX, CX
JB ADDR_OVF
; check for overflow:
CALL NORM_ADDR
JC ADDR_OVF
MOV DS, BX
MOV BX, AX
IRET
SUB_A_C:
;=======
; GOAL: Subtracts the CARDINAL in (DX) from the
; ADDRESS in (DS,BX). The result is checked
; for overflow and returned in (DS,BX).
MOV AX, BX
MOV BX, DS
CALL COMM_SUB_ADDR
; check for overflow:
CALL NORM_ADDR
JC ADDR_OVF
MOV DS, BX
MOV BX, AX
IRET
;---------------------------------------------------------------------------
NORM_ONE PROC NEAR
; Normalizes 'partially' ADDRESS in (BX,AX), result in (BX,AX).
; 'Partially' means: segment is as large as possible, offset
; as small as possible, but the offset might be larger than 15
; (in case of addresses out of range).
MOV DX, AX
AND AX, 0FH
MOV CL, 4
SHR DX, CL
ADD BX, DX ; that's the regular normalization
JC TOO_LARGE_ADDR
RET
TOO_LARGE_ADDR:
MOV CL, 4
INC BX ; add 1 paragraph, since the maximum
; value for the segment is 0FFFFH.
SHL BX, CL ; transform remaining paragraphs in offset
ADD AX, BX ; complete the offset
MOV BX, 0FFFFH ; the highest value for the segment
RET
NORM_ONE ENDP
EQ_ADDR:
;=======
; Compares two ADDRESSes for equality. The values are passed
; in (DS,BX) and (DX,DI) and the result is in BL (0 = FALSE,
; 1 = TRUE). The input values are allowed to be out of the
; legal ADDRESS-range.
; FAST routine. We optimize the path, where the addresses are
; not equal, but have the same segment value. This is the most
; frequent case, when searching elements allocated in the same heap.
; first check, if they differ in the last 4 bits:
MOV AX, BX
AND BL, 0FH ; mask out the last 4 bits
MOV CX, DI
AND CL, 0FH
CMP BL, CL
JNE THEY_ARE_DIFF
; next we check if 1 part of addr is equal:
MOV BX, DS
CMP AX, DI ; compare offsets
JE SAME_OFFSET
CMP BX, DX ; compare segments
JNE DO_NORMALIZE
THEY_ARE_DIFF:
MOV BL, 0 ; value for NOT EQUAL
IRET
SAME_OFFSET:
CMP BX, DX ; compare segments
JNE THEY_ARE_DIFF
THEY_ARE_EQUAL:
MOV BL, 1 ; value for EQUAL
IRET
DO_NORMALIZE:
MOV SI, DX ; second par in (SI,DI)
CALL NORM_ONE
XCHG BX, SI
XCHG AX, DI
CALL NORM_ONE
; now, compare the 2 norm. addresses
CMP BX, SI
JNE THEY_ARE_DIFF
CMP AX, DI
JNE THEY_ARE_DIFF
JMP SHORT THEY_ARE_EQUAL
GT_EQ_ADDR:
;==========
; This routine performs both comparisons, GREATER and
; GREATER or EQUAL of two ADDRESSes (a1 > a2, a1 >= a2).
; The ADDRESS a1 is passed in (DX,DI), a2 in (DS,BX).
; They are allowed to be out of the legal ADDRESS-range.
; The result is in BL (0 = FALSE, 1 = TRUE).
; FAST routine. We optimize the path, where the addresses
; have same segment value, but different offset.
MOV SI, AX ; the function code
MOV AX, BX
MOV BX, DS
; check if segments are equal:
CMP BX, DX
JNE DIFF_SEGMENTS
; segments are equal:
CMP AX, DI ; compare offsets
JA COND_FALSE ; its LESS THAN
JB COND_TRUE ; its GREATER
; they are equal:
CMP SI, GT_ADDR_FCT
JE COND_FALSE
COND_TRUE:
MOV BL, 1
IRET
COND_FALSE:
MOV BL, 0
IRET
DIFF_SEGMENTS:
MOV DS, SI
MOV SI, DX ; a1 is in (SI,DI)
CALL NORM_ONE ; normalize a2
XCHG BX, SI
XCHG AX, DI
CALL NORM_ONE ; normalize a1
; now compare the 2 normalized addresses:
CMP BX, SI ; compare segments
JA COND_TRUE
JB COND_FALSE
; the segments are equal, now we compare the offsets:
; Here we have to distinguish between the comp. GT / GT_EQ:
MOV SI, DS
CMP SI, GT_ADDR_FCT
JNE GT_EQ_TEST
GT_TEST:
CMP AX, DI
JA COND_TRUE
JMP SHORT COND_FALSE
GT_EQ_TEST:
CMP AX, DI
JAE COND_TRUE
JMP SHORT COND_FALSE
;---------------------------------------------------------------------------
CONV_A_C:
;========
; Converts an address in (DS,BX) into a CARDINAL and returns
; it in DX. The result is checked for overflow:
MOV DX, DS
MOV CL, 4
SHL DX, CL ; base * 16
JC BAD_CONVERT
ADD DX, BX ; result = (base * 16) + offset
JC BAD_CONVERT
IRET
BAD_CONVERT:
CALL SAVE_CPU_INFO
;;; JMP SHORT CARD_OVF
;---------------------------------------------------------------------------
CARD_OVF:
;========
; Treats the CARDINAL overflow: generate a P_M_DUMP, set the process
; status to CARD_OVF_CODE and terminates the current program:
MOV CUR_PROCESS.PD_STATUS, CARD_OVF_CODE
JMP TERMINATE ; No return!
;---------------------------------------------------------------------------
INTEGER_OVF:
;===========
; Treats the INTEGER overflow: generate a P_M_DUMP, set the process
; status to INTEGER_OVF_CODE and terminates the current program:
MOV CUR_PROCESS.PD_STATUS, INTEGER_OVF_CODE
JMP TERMINATE ; No return!
;---------------------------------------------------------------------------
RANGE_ERROR:
;===========
; Treats the RANGE ERROR: generate a P_M_DUMP, set the process
; status to RANGE_ERR_CODE and terminates the current program:
MOV CUR_PROCESS.PD_STATUS, RANGE_ERR_CODE
JMP TERMINATE ; No return!
;------------------------------------------------------------------------
PSP_POINTER:
;==========
; Returns a pointer to a static copy of the program segment
; prefix (PSP) for the RTS.
; The address is returned in (CX:BX)
LDS BX, BASE_PAGE_PTR
MOV CX, DS
MOV DS, CUR_PROCESS.PD_DS
IRET
;------------------------------------------------------------------------
;data segment
;NYI DB 'RTS-function not yet implemented: $'
;data ends
NOT_YET:
;=======
; This function can be called by RTS-functions
; that are not yet implemented:
; MOV DX, OFFSET NYI
; CALL WRITE_MSG
; MOV AL, FCT_CODE
; CALL WRITE_BYTE
; CALL WRITE_LN
; MOV DS, CUR_PROCESS.PD_DS
; IRET
MOV CUR_PROCESS.PD_STATUS, ILL_FCT_CODE
JMP TERMINATE
; No Return!
code ends
end