;************************************************************* ; ; 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