- The LBRANCH and LCBRANCH directives create different instruction sequences depending on the jump distance. So the code size can shrink during the first pass when the jump distance can be determined (when the label that is the jump destination is parsed). In the long form, LOADREL/JUMP is used, which might or might not need 2 bytes of padding. With this bugfix, the padding is always added, either before or after the LOADREL indirect operand, so that the code size does not change depending on the padding required. Otherwise the code might shrink further on the second pass because a LBRANCH/LCBRANCH instruction no longer needs padding due to an earlier code size change.
1973 lines
46 KiB
ArmAsm
1973 lines
46 KiB
ArmAsm
; Copyright 2021-2024 Sebastian Lederer. See the file LICENSE.md for details
|
|
.EQU LOADER_START 4096
|
|
|
|
.EQU STRM_FS 16
|
|
.EQU STRM_PTR 0
|
|
.EQU STRM_IDX 4
|
|
.EQU STRM_LEN 8
|
|
.EQU STRM_DELTA 12
|
|
|
|
; copy bytes inside a pascal string, moving up
|
|
; args: string ptr, index, length, delta
|
|
STRMOVEUP:
|
|
FPADJ -STRM_FS
|
|
STORE STRM_DELTA
|
|
STORE STRM_LEN
|
|
STORE STRM_IDX
|
|
STORE STRM_PTR
|
|
|
|
|
|
LOADCP STRM_CHKARGS
|
|
CALL
|
|
CBRANCH.Z STRMU_XT
|
|
|
|
; args are ok, call _BMOVEUP
|
|
; offset
|
|
LOAD STRM_DELTA
|
|
; src
|
|
LOAD STRM_IDX
|
|
DEC 1 ; adjust for base 1
|
|
LOAD STRM_PTR
|
|
INC 8 ; skip string header
|
|
ADD
|
|
; count
|
|
LOAD STRM_LEN
|
|
|
|
LOADCP _BMOVEUP
|
|
CALL
|
|
STRMU_XT:
|
|
FPADJ STRM_FS
|
|
RET
|
|
|
|
; check args for STRMOVEUP and STRMOVEDOWN
|
|
; shares stack frame with caller
|
|
; returns 0 if args are invalid
|
|
STRM_CHKARGS: ; check if length or index are negative
|
|
LOAD STRM_LEN
|
|
LOADC 0
|
|
CMP LE
|
|
CBRANCH STRMC_XT
|
|
|
|
LOAD STRM_IDX
|
|
LOADC 0
|
|
CMP LE
|
|
CBRANCH STRMC_XT
|
|
|
|
LOAD STRM_DELTA
|
|
LOADC 1
|
|
CMP LT
|
|
CBRANCH STRMC_XT
|
|
|
|
; check if index + len + delta < string end
|
|
LOAD STRM_PTR
|
|
LOADI ; get string size
|
|
|
|
LOAD STRM_IDX
|
|
LOAD STRM_DELTA
|
|
ADD
|
|
LOAD STRM_LEN
|
|
ADD
|
|
DEC 1 ; adjust for base 1
|
|
|
|
CMP LT
|
|
CBRANCH STRMC_XT
|
|
|
|
LOADC 1
|
|
RET
|
|
STRMC_XT:
|
|
LOADC 0
|
|
RET
|
|
|
|
; copy bytes inside a pascal string, moving down
|
|
; index is the destination, index + delta the source
|
|
; args: string ptr, index, length, delta
|
|
STRMOVEDOWN:
|
|
FPADJ -STRM_FS
|
|
STORE STRM_DELTA
|
|
STORE STRM_LEN
|
|
STORE STRM_IDX
|
|
STORE STRM_PTR
|
|
|
|
LOADCP STRM_CHKARGS
|
|
CALL
|
|
CBRANCH.Z STRMD_XT
|
|
|
|
; args are ok, call _BMOVEDOWN
|
|
; offset
|
|
LOAD STRM_DELTA
|
|
; src
|
|
LOAD STRM_IDX
|
|
DEC 1 ; adjust for base 1
|
|
LOAD STRM_PTR
|
|
INC 8 ; skip string header
|
|
ADD
|
|
; count
|
|
LOAD STRM_LEN
|
|
|
|
LOADCP _BMOVEDOWN
|
|
CALL
|
|
STRMD_XT:
|
|
FPADJ STRM_FS
|
|
RET
|
|
|
|
; copy bytes from src to src + offset, moving
|
|
; upwards. the copied byte ranges may overlap.
|
|
; count and offset must be > 0
|
|
; args: offset, src, count
|
|
.EQU BMU_COUNT 0
|
|
.EQU BMU_SRC 4
|
|
.EQU BMU_OFFS 8
|
|
.EQU BMU_FS 12
|
|
_BMOVEUP:
|
|
FPADJ -BMU_FS
|
|
DUP
|
|
STORE BMU_COUNT
|
|
DEC 1
|
|
ADD ; store end of source range
|
|
STORE BMU_SRC
|
|
STORE BMU_OFFS
|
|
|
|
; copy bytes from end to start, descending
|
|
LOAD BMU_COUNT ; keep count on stack for the loop
|
|
_BMU_L:
|
|
|
|
LOAD BMU_SRC
|
|
LOAD BMU_OFFS
|
|
ADD ; dest addr = src + offs
|
|
LOADI.S1.X2Y ; [ dest, word ]
|
|
OVER ; [ dest, word, dest ]
|
|
LOADC $FF ; [ dest, word, dest, $FF]
|
|
BPLC
|
|
NOT ; [ dest, word, mask ]
|
|
AND ; [ dest, masked word ]
|
|
|
|
OVER ; [ dest, masked, dest ]
|
|
LOAD BMU_SRC ; [ dest, masked, dest, src ]
|
|
LOADI.S1.X2Y ; [ dest, masked, dest, src, src word ]
|
|
BSEL ; [ dest, masked, dest, src byte ]
|
|
BPLC ; [ dest, masked, dest byte ]
|
|
OR ; [ dest, dest word ]
|
|
STOREI
|
|
DROP
|
|
|
|
DEC 1
|
|
DUP
|
|
CBRANCH.Z _BMU_X
|
|
LOAD BMU_SRC
|
|
DEC 1
|
|
STORE BMU_SRC
|
|
BRANCH _BMU_L
|
|
_BMU_X:
|
|
DROP
|
|
FPADJ BMU_FS
|
|
RET
|
|
|
|
; copy bytes from dest + offset to dest, moving
|
|
; downwards. the copied byte ranges may overlap.
|
|
; count and offset must be > 0
|
|
; args: offset, dest, count
|
|
.EQU BMD_COUNT 0
|
|
.EQU BMD_DEST 4
|
|
.EQU BMD_OFFS 8
|
|
.EQU BMD_FS 12
|
|
_BMOVEDOWN:
|
|
FPADJ -BMD_FS
|
|
STORE BMD_COUNT
|
|
STORE BMD_DEST
|
|
STORE BMD_OFFS
|
|
|
|
; copy bytes from start to end
|
|
LOAD BMU_COUNT ; keep count on stack for the loop
|
|
_BMD_L:
|
|
LOAD BMD_DEST ; [ dest ]
|
|
LOADI.S1.X2Y ; [ dest, dest word ]
|
|
OVER ; [ dest, word, dest ]
|
|
LOADC $FF
|
|
BPLC
|
|
NOT ; [ dest, word, mask ]
|
|
AND ; [ dest, masked word ]
|
|
OVER ; [ dest, masked word, dest ]
|
|
|
|
DUP
|
|
LOAD BMD_OFFS
|
|
ADD ; src addr = dest + offs
|
|
LOADI.S1.X2Y ; get source word
|
|
BSEL ; get source byte
|
|
; stack now: [ dest, masked word, dest, source byte ]
|
|
BPLC ; [ dest, masked word, rotated byte ]
|
|
OR ; [ dest, new word ]
|
|
STOREI
|
|
DROP
|
|
|
|
DEC 1
|
|
DUP
|
|
CBRANCH.Z _BMD_X
|
|
LOAD BMD_DEST
|
|
INC 1
|
|
STORE BMD_DEST
|
|
BRANCH _BMD_L
|
|
_BMD_X:
|
|
DROP
|
|
FPADJ BMD_FS
|
|
RET
|
|
|
|
; copy bytes between two arrays of words
|
|
; no bounds check is performed
|
|
; parameters: [ dest, destoffset, src, srcoffset, length ]
|
|
.EQU CPB_LEN 0
|
|
.EQU CPB_DPTR 4
|
|
.EQU CPB_SPTR 8
|
|
.EQU CPB_FS 12
|
|
COPYBUF:
|
|
FPADJ -CPB_FS
|
|
STORE CPB_LEN
|
|
ADD ; add src and srcoffset
|
|
STORE CPB_SPTR
|
|
ADD ; add dest and destoffset
|
|
STORE CPB_DPTR
|
|
|
|
; check if source ptr and dest prtr
|
|
; are word-aligned
|
|
LOAD CPB_SPTR
|
|
LOAD CPB_DPTR
|
|
OR
|
|
LOADC 3
|
|
AND
|
|
CBRANCH COPYBYTES ; if not, continue with COPYBYTES
|
|
|
|
COPYBUF_1:
|
|
LOAD CPB_LEN ; check if length is smaller than
|
|
LOADC 4 ; word size
|
|
CMP LT
|
|
CBRANCH COPYBYTES ; if yes, continue with COPYBYTES
|
|
|
|
; else copy whole words
|
|
LOAD CPB_DPTR
|
|
LOAD CPB_SPTR
|
|
LOAD CPB_LEN
|
|
SHR ; COPYWORDS needs number of words, not bytes
|
|
SHR ; so divide by 4
|
|
LOADCP _COPYWORDS
|
|
CALL
|
|
|
|
; calculate remaining bytes
|
|
LOAD CPB_LEN
|
|
DUP ; duplicate for offset calculation below
|
|
LOADC 3
|
|
AND ; get lower two bits for remaining byte count
|
|
DUP
|
|
CBRANCH.Z COPYBUF_XT ; if zero remaining, exit
|
|
STORE CPB_LEN ; store as new length
|
|
LOADC ~3 ; calculate length rounded down to word size
|
|
AND ; by setting lower two bits to zero
|
|
DUP ; duplicate it to calculate two offsets
|
|
LOAD CPB_DPTR
|
|
ADD
|
|
STORE CPB_DPTR ; move dest ptr to remaining bytes
|
|
LOAD CPB_SPTR
|
|
ADD
|
|
STORE CPB_SPTR ; move src ptr to remaining bytes
|
|
BRANCH COPYBYTES
|
|
|
|
COPYBUF_XT:
|
|
DROP ; cleanup stack
|
|
DROP
|
|
FPADJ CPB_FS
|
|
RET
|
|
|
|
; Copy single bytes, branch from COPYBUF
|
|
COPYBYTES:
|
|
LOAD CPB_LEN ; put counter on stack
|
|
COPYBYTES_L:
|
|
DUP ; check if remaining length is 0
|
|
CBRANCH.Z COPYBYTES_XT ; exit loop if true
|
|
LOAD CPB_DPTR ; load dest ptr for SETSTRINGCHAR below
|
|
INC.S1.X2Y 1 ; increment dest ptr and keep old value
|
|
STORE CPB_DPTR ; store incremented dest ptr, old value is now ToS
|
|
LOAD CPB_SPTR ; load src ptr
|
|
INC.S1.X2Y 1 ; increment src ptr and keep old value
|
|
STORE CPB_SPTR ; store incremented src ptr, old value is now ToS
|
|
LOADI.S1.X2Y ; load source word, keep addr
|
|
BSEL ; select byte from word via addr
|
|
LOADCP _SETSTRINGCHAR ; put byte into destination
|
|
CALL
|
|
DEC 1 ; decrement counter
|
|
BRANCH COPYBYTES_L
|
|
COPYBYTES_XT:
|
|
DROP ; remove counter
|
|
FPADJ CPB_FS
|
|
RET
|
|
|
|
; helper routine to read a char from a file
|
|
; parameters: [ pointer to file record ]
|
|
READFSCHAR:
|
|
LOADCP READFSCHARBUF
|
|
LOADC 1 ; length arg
|
|
LOADCP READFS
|
|
CALL
|
|
|
|
LOADC 0 ; byte address for BSEL
|
|
LOADCP READFSCHARBUF
|
|
LOADI
|
|
|
|
BSEL ; get the most significant byte
|
|
RET
|
|
|
|
READFSCHARBUF:
|
|
.WORD 0
|
|
|
|
; helper routine to write a byte to a file
|
|
; parameters: [ pointer to file record, char value ]
|
|
WRITEFSCHAR:
|
|
BROT ; rotate char value to MSB
|
|
BROT
|
|
BROT
|
|
LOADCP WRITEFSCHARBUF
|
|
SWAP
|
|
STOREI ; store rotated value to buffer
|
|
; keep address on stack (WRITEFSCHARBUF)
|
|
LOADC 1 ; length arg
|
|
LOADCP WRITEFS
|
|
JUMP ; save one RET
|
|
|
|
WRITEFSCHARBUF:
|
|
.WORD 0
|
|
|
|
; helper routine to write a string to a file
|
|
; parameters: pointer to file record, string pointer
|
|
WRITEFSSTRING:
|
|
DUP ; duplicate string ptr
|
|
INC 8 ; skip string header
|
|
SWAP ; put original string ptr on ToS
|
|
LOADI ; load length from string header
|
|
; stack is now [ ptr to file, string ptr+8, length ]
|
|
LOADCP WRITEFS
|
|
JUMP
|
|
; write a number of words to a channel
|
|
; parameters: [ ptr to file record, word pointer, word count ]
|
|
WRITECHANWORDS:
|
|
; we ignore the file record since there is only
|
|
; one device for now
|
|
DUP
|
|
CBRANCH.Z WRITECHANW_XT ; if count is zero, exit
|
|
DEC 1 ; decrement counter
|
|
SWAP ; swap counter and word ptr
|
|
LOADI.S1.X2Y ; load word, keep addr
|
|
LOADCP CONOUTW
|
|
CALL
|
|
INC 4 ; increment addr
|
|
SWAP ; swap back word ptr and counter
|
|
BRANCH WRITECHANWORDS
|
|
WRITECHANW_XT:
|
|
DROP
|
|
DROP
|
|
DROP
|
|
RET
|
|
|
|
|
|
; read a number of words from a channel
|
|
; parameters: [ ptr to file record, destination pointer, word count ]
|
|
READCHANWORDS:
|
|
; we ignore the file record since there is only
|
|
; one device for now
|
|
DUP
|
|
CBRANCH.Z WRITECHANW_XT ; if count is zero, exit
|
|
DEC 1 ; decrement counter
|
|
SWAP ; swap counter and word ptr
|
|
LOADCP CONINW ; read four bytes
|
|
CALL
|
|
STOREI 4 ; store and post-increment
|
|
SWAP ; swap back word ptr and counter
|
|
BRANCH READCHANWORDS
|
|
READCHANW_XT:
|
|
DROP
|
|
DROP
|
|
DROP
|
|
RET
|
|
|
|
; --- check if a string is already initialized
|
|
; parameters: addr
|
|
; returns: 1 if it is initialized, 0 otherwise
|
|
_STRINGISINITED:
|
|
LOADI.S1.X2Y ; load length field, keep addr
|
|
CBRANCH.NZ STRIN_XT1 ; if not zero, it is already inited
|
|
INC 4 ; increment addr for max length field
|
|
LOADI
|
|
CBRANCH.NZ STRIN_XT2 ; if not zero, already initialized
|
|
LOADC 0 ; return 0
|
|
RET
|
|
STRIN_XT1:
|
|
DROP ; remove addr
|
|
STRIN_XT2:
|
|
LOADC 1 ; return 1
|
|
RET
|
|
|
|
; --- initialise a string if it is not already initialized
|
|
; parameters: [ length, addr ]
|
|
_INITSTRING:
|
|
DUP
|
|
LOADCP _STRINGISINITED
|
|
CALL ; check if string is already initialized
|
|
CBRANCH.Z _INITSTRINGF
|
|
DROP ; if yes, drop args and return immediately
|
|
DROP
|
|
RET
|
|
|
|
; --- initialise a string, do not check if it is already
|
|
; initialized.
|
|
; parameters: [ length, addr ]
|
|
_INITSTRINGF:
|
|
LOADC 0 ; [ length, addr, 0 ]
|
|
STOREI 4 ; set length to 0, store with post-increment [ length, addr + 4 ]
|
|
SWAP ; swap addr and length [ addr + 4, length ]
|
|
STOREI 4; store max length, post-increment addr by 4 [ addr + 8 ]
|
|
LOADC 0 ; [addr + 8, 0 ]
|
|
STOREI ; zero first word [ addr + 8 ]
|
|
DROP ; drop addr from STOREI
|
|
RET
|
|
|
|
; --- initialise string from another
|
|
; parameters: [ dest, src, length ]
|
|
_INITSTRINGFROM:
|
|
FPADJ -4
|
|
STORE 0 ; [ dest, src ]
|
|
OVER ; [ dest, src, dest ]
|
|
LOAD 0 ; [ dest, src, dest, length ]
|
|
SWAP ; [ dest, src, length, dest ]
|
|
LOADCP _INITSTRINGF
|
|
CALL ; [ dest, src ]
|
|
LOADCP _COPYSTRING
|
|
CALL ; [ ]
|
|
FPADJ 4
|
|
RET
|
|
|
|
; --- copy a pascal string to another
|
|
; parameters: [ dest, src ]
|
|
.EQU CPSTR_DST 0
|
|
.EQU CPSTR_SRC 4
|
|
.EQU CPSTR_DSTMAX 8
|
|
.EQU CPSTR_SRCLEN 12
|
|
.EQU CPSTR_FS 16
|
|
_COPYSTRING:
|
|
FPADJ -CPSTR_FS
|
|
SWAP ; get dest addr first
|
|
DUP ; dup pointer for LOADI
|
|
STORE CPSTR_DST ; destination pointer
|
|
INC 4 ; increment pointer
|
|
LOADI ; load max length of destination
|
|
STORE CPSTR_DSTMAX ; and store into local var
|
|
DUP ; dup src pointer for LOADI
|
|
STORE CPSTR_SRC ; source pointer
|
|
LOADI ; load length of source
|
|
DUP ; duplicate length for CMPU below
|
|
STORE CPSTR_SRCLEN ; and store into local var
|
|
LOAD CPSTR_DSTMAX ; if dest max size is zero,
|
|
CBRANCH.NZ CPSTR_0 ; throw a runtime error
|
|
LOADCP _ERRMSG_CONSTWR
|
|
LOADCP _RUNTIME_ERR
|
|
JUMP
|
|
CPSTR_0:
|
|
LOAD CPSTR_DSTMAX
|
|
; if dest.max_length < src.length, use dest.max_length as src.length
|
|
CMPU LE ; src.length <= dest.max_length?
|
|
CBRANCH CPSTR_1
|
|
LOAD CPSTR_DSTMAX
|
|
STORE CPSTR_SRCLEN
|
|
CPSTR_1:
|
|
; set dest.length to src.length
|
|
LOAD CPSTR_DST ; load dst addr
|
|
DUP ; dup for INC/STORE below
|
|
LOAD CPSTR_SRCLEN
|
|
STOREI ; store src length to dst length
|
|
DROP ; drop addr from STOREI
|
|
INC 8 ; skip length and max length words
|
|
STORE CPSTR_DST
|
|
LOAD CPSTR_SRC ; skip length and max length words
|
|
INC 8
|
|
STORE CPSTR_SRC
|
|
|
|
; copy ((src.length+3) div 4) words from src to dest
|
|
LOAD CPSTR_SRCLEN ; calculate number of words
|
|
LOADC 3
|
|
ADD
|
|
SHR
|
|
SHR
|
|
CPSTR_L1:
|
|
DUP ; duplicate word counter
|
|
CBRANCH.Z CPSTR_LE ; if it is zero, exit loop
|
|
LOAD CPSTR_DST ; load dst addr for STOREI below
|
|
LOAD CPSTR_SRC ; load src addr
|
|
LOADI ; load value
|
|
STOREI 4 ; store value, increment dst addr
|
|
STORE CPSTR_DST ; store new dst addr
|
|
LOAD CPSTR_SRC
|
|
INC 4 ; increment src addr
|
|
STORE CPSTR_SRC ; and store it
|
|
DEC 1 ; decrement word counter
|
|
BRANCH CPSTR_L1
|
|
CPSTR_LE:
|
|
; mask bytes in last word depending on length modulo 4:
|
|
; 0: $00000000
|
|
; 1: $FF000000
|
|
; 2: $FFFF0000
|
|
; 3: $FFFFFF00
|
|
LOAD CPSTR_DST
|
|
DEC 4 ; get pointer to last word
|
|
LOADI.X2Y.S1 ; load value of last word, keep addr
|
|
LOAD CPSTR_SRCLEN ; length of string
|
|
LOADC 3
|
|
AND ; modulo 4
|
|
SHL 2 ; *4
|
|
LOADCP CPSTR_MASK ; + addr of mask table
|
|
ADD
|
|
LOADI ; load mask value
|
|
AND
|
|
STOREI ; store masked value
|
|
DROP ; remove addr from STOREI
|
|
DROP ; drop word counter
|
|
CPSTR_XT:
|
|
FPADJ CPSTR_FS
|
|
RET
|
|
CPSTR_MASK:
|
|
.WORD $FFFFFFFF, $FF000000, $FFFF0000, $FFFFFF00
|
|
|
|
.CPOOL
|
|
|
|
; --- append a pascal string to another
|
|
; parameters: [ dest, src ]
|
|
_APPENDSTRING:
|
|
.EQU APSTR_SRCLEN 0
|
|
.EQU APSTR_DSTLEN 4
|
|
.EQU APSTR_SRC 8
|
|
.EQU APSTR_DST 12
|
|
.EQU APSTR_DSTMAX 16
|
|
.EQU APSTR_FS 20
|
|
FPADJ -APSTR_FS
|
|
LOADI.X2Y.S1 ; load src length, keep addr
|
|
STORE APSTR_SRCLEN ; store it to local var
|
|
INC 8 ; increment addr to point to first char/word
|
|
STORE APSTR_SRC ; store it to local var
|
|
LOADI.X2Y.S1 ; load dest length, keep addr
|
|
STORE APSTR_DSTLEN ; store it to local var
|
|
INC 4 ; increment addr to point to dest max length
|
|
LOADI.X2Y.S1 ; load it, keep addr
|
|
STORE APSTR_DSTMAX ; store it to local var
|
|
INC 4 ; increment addr to point to first char/word
|
|
STORE APSTR_DST ; store it to local var
|
|
|
|
LOAD APSTR_DSTMAX ; if dest max size is zero,
|
|
CBRANCH.NZ APSTR_0 ; throw a runtime error
|
|
LOADCP _ERRMSG_CONSTWR
|
|
LOADCP _RUNTIME_ERR
|
|
JUMP
|
|
|
|
APSTR_0:
|
|
; if src.length + dest.length <= dest.maxlength
|
|
; use src.length as number of characters
|
|
; else
|
|
; use dest.maxlength - dest.length as number of characters
|
|
|
|
LOAD APSTR_SRCLEN ; char counter
|
|
DUP
|
|
LOAD APSTR_DSTLEN
|
|
ADD
|
|
LOAD APSTR_DSTMAX
|
|
CMPU LE
|
|
CBRANCH APSTR_1
|
|
DROP ; drop char counter
|
|
LOAD APSTR_DSTMAX
|
|
LOAD APSTR_DSTLEN
|
|
SUB ; calculate dest.maxlength - dest.length as char counter
|
|
APSTR_1:
|
|
; set dest.length to dest.length + number of characters
|
|
LOAD APSTR_DST
|
|
DEC 8 ; get pointer to dest.length for STOREI
|
|
OVER ; duplicate char counter
|
|
LOAD APSTR_DSTLEN
|
|
ADD ; dest.length = dest.length + char counter
|
|
STOREI
|
|
DROP ; drop STOREI addr, char counter is now on top of stack
|
|
; set dest pointer to start + dest.length
|
|
LOAD APSTR_DST
|
|
LOAD APSTR_DSTLEN
|
|
ADD
|
|
STORE APSTR_DST
|
|
APSTR_L0:
|
|
; while number of characters > 0:
|
|
DUP ; duplicate number of remaining characters
|
|
CBRANCH.Z APSTR_3 ; if zero, exit loop
|
|
DEC 1 ; decrement char counter
|
|
|
|
; load src value, byte select by src addr
|
|
LOAD APSTR_SRC
|
|
LOADI.X2Y.S1
|
|
BSEL
|
|
LOAD APSTR_DST ; load dest pointer
|
|
LOADCP APPENDCHAR_U
|
|
CALL
|
|
|
|
; increment src and dest pointer
|
|
LOAD APSTR_DST
|
|
INC 1
|
|
STORE APSTR_DST
|
|
LOAD APSTR_SRC
|
|
INC 1
|
|
STORE APSTR_SRC
|
|
BRANCH APSTR_L0
|
|
APSTR_3:
|
|
DROP ; remove char counter
|
|
FPADJ APSTR_FS
|
|
RET
|
|
|
|
; write four bytes from a word to console, msb first
|
|
; parameters: [ word value ]
|
|
CONOUTW:
|
|
BROT ; rotate msb to lsb
|
|
DUP
|
|
LOADC 255 ; mask out all other bytes
|
|
AND
|
|
LOADCP CONOUT
|
|
CALL
|
|
|
|
BROT ; rotate msb to lsb
|
|
DUP
|
|
LOADC 255
|
|
AND
|
|
LOADCP CONOUT
|
|
CALL
|
|
|
|
BROT ; rotate msb to lsb
|
|
DUP
|
|
LOADC 255
|
|
AND
|
|
LOADCP CONOUT
|
|
CALL
|
|
|
|
BROT ; rotate msb to lsb
|
|
LOADC 255
|
|
AND
|
|
LOADCP CONOUT
|
|
CALL
|
|
|
|
RET
|
|
|
|
; read four bytes from console to a word
|
|
; returns word on estack
|
|
CONINW:
|
|
LOADCP CONIN
|
|
CALL
|
|
|
|
BROT
|
|
LOADCP CONIN
|
|
CALL
|
|
OR
|
|
|
|
BROT
|
|
LOADCP CONIN
|
|
CALL
|
|
OR
|
|
|
|
BROT
|
|
LOADCP CONIN
|
|
CALL
|
|
OR
|
|
RET
|
|
|
|
; put a byte-sized character into a word.
|
|
; this is a utility route for APPENDSTRING, no bounds checking is done.
|
|
; bytes in the word after this character are cleared.
|
|
; example: destination word is 'ABCD', addr points to word addr +1,
|
|
; character is 'X', result: word contains 'AX\0\0'
|
|
; parameters [ character, byte pointer ]
|
|
APPENDCHAR_U:
|
|
FPADJ -4
|
|
DUP
|
|
STORE 0
|
|
SWAP ; [ ptr, char ]
|
|
BPLC ; [ rotated char ]
|
|
LOAD 0 ; [ rot char, ptr ]
|
|
LOADI ; load word value
|
|
LOAD 0 ; calculate mask value by addr modulo 4
|
|
LOADC 3
|
|
AND
|
|
SHL 2
|
|
LOADCP _APPENDMASK
|
|
ADD
|
|
LOADI ; load mask
|
|
AND ; mask word
|
|
|
|
OR ; OR masked dest value with rotated char value
|
|
LOAD 0 ; get word addr again
|
|
SWAP ; swap for STOREI
|
|
STOREI
|
|
DROP ; remove addr from STOREI
|
|
FPADJ 4
|
|
RET
|
|
_APPENDMASK: .WORD $00000000, $FF000000, $FFFF0000, $FFFFFF00
|
|
|
|
; Append a character to a pascal string.
|
|
; parameters: [ string ptr, char value ]
|
|
; Does a bounds check, so it can be called from Pascal.
|
|
APPENDCHAR:
|
|
SWAP ; put string ptr first
|
|
LOADI.S1.X2Y ; get current length, keep ptr
|
|
OVER
|
|
INC 4
|
|
LOADI ; get maximum length
|
|
CMPU LT ; if current length > max length, we can add a character
|
|
CBRANCH.NZ APPENDCHAR_1
|
|
|
|
;if not, check for attempt to write to a string constant
|
|
; (which has max length 0)
|
|
NIP ; we don't need the char value anymore, the string ptr is ToS
|
|
INC 4 ; last time we use str ptr, so no DUP
|
|
LOADI ; get max length
|
|
CBRANCH.NZ APPENDCHAR_0
|
|
|
|
LOADCP _ERRMSG_CONSTWR
|
|
LOADCP _RUNTIME_ERR
|
|
JUMP
|
|
APPENDCHAR_0:
|
|
; if the string is already at max length,
|
|
; we do nothing, analogous to the + operator
|
|
RET
|
|
|
|
APPENDCHAR_1: ; on stack here: [ char value, str ptr ]
|
|
; calculate byte ptr from str ptr
|
|
LOADI.S1.X2Y ; get length, keep str ptr
|
|
INC 1 ; increase length by 1
|
|
STOREI ; store new length
|
|
LOADI.S1.X2Y ; load it again; keep ptr
|
|
ADD ; add length to str ptr
|
|
INC 8-1 ; byte ptr starts at 0, so adjust for header size - 1
|
|
LOADCP APPENDCHAR_U
|
|
JUMP
|
|
|
|
; compare two pascal strings for equality.
|
|
; the last word must be padded with null bytes
|
|
; if string length is not divisible by 4
|
|
; (which is ensured by _INITSTRING/APPENDSTRING).
|
|
; parameters: [ string1, string2 ]
|
|
; returns: 0 if not equal, 1 if equal
|
|
_CMPSTRING:
|
|
.EQU CMPSTR_S1 0
|
|
.EQU CMPSTR_S2 4
|
|
.EQU CMPSTR_FS 8
|
|
FPADJ -CMPSTR_FS
|
|
STORE CMPSTR_S2 ; store s2 string pointer
|
|
STORE CMPSTR_S1 ; store s1 string pointer
|
|
LOAD CMPSTR_S1
|
|
LOADI ; load s1.length
|
|
LOAD CMPSTR_S2
|
|
LOADI ; load s2.length
|
|
CMPU NE ; if string lengths are not the same,
|
|
CBRANCH CMPSTR_XT_NE1 ; strings are not equal
|
|
|
|
LOAD CMPSTR_S1
|
|
LOADI.X2Y.S1 ; load s1 length again, keep addr
|
|
INC 3 ; calculate number of words to compare:
|
|
SHR ; count = (length + 3) div 4
|
|
SHR
|
|
SWAP ; put addr on top
|
|
INC 8 ; increment addr to skip length fields
|
|
STORE CMPSTR_S1 ; store new addr
|
|
LOAD CMPSTR_S2 ; skip length fields for s2 pointer too
|
|
INC 8
|
|
STORE CMPSTR_S2
|
|
; word counter is now on top of stack
|
|
CMPSTR_L0:
|
|
DUP ; if word counter is zero, strings are equal
|
|
CBRANCH.Z CMPSTR_XT_EQ
|
|
LOAD CMPSTR_S1 ; load current word of s1 and s2
|
|
LOADI
|
|
LOAD CMPSTR_S2
|
|
LOADI
|
|
CMPU NE
|
|
CBRANCH CMPSTR_XT_NE ; if not equal, strings are not equal
|
|
LOAD CMPSTR_S1 ; increment s1 pointer
|
|
INC 4
|
|
STORE CMPSTR_S1
|
|
LOAD CMPSTR_S2 ; increment s2 pointer
|
|
INC 4
|
|
STORE CMPSTR_S2
|
|
DEC 1 ; decrement word counter
|
|
BRANCH CMPSTR_L0 ; loop
|
|
CMPSTR_XT_EQ:
|
|
DROP ; remove word counter
|
|
LOADC 1 ; return 1
|
|
BRANCH CMPSTR_XT
|
|
CMPSTR_XT_NE:
|
|
DROP ; remove word counter
|
|
CMPSTR_XT_NE1:
|
|
LOADC 0 ; return 0
|
|
CMPSTR_XT:
|
|
FPADJ CMPSTR_FS
|
|
RET
|
|
|
|
; compare two pascal strings lexicographically
|
|
; parameters: [ pointer to string a, pointer to string b ]
|
|
; returns: -1 if a < b, 0 if a = b, 1 if a > b
|
|
; NOTE: does not really do a lexicographic comparison, but
|
|
; a byte-wise numeric comparison. this works for ascii
|
|
; but not for other characters like umlauts etc.
|
|
; requires that the unused bytes in the last word
|
|
; are set to zero (which is guaranteed by _INITSTRING
|
|
; and APPENDSTRING).
|
|
|
|
.EQU CMPSTRL_A_COUNT 0
|
|
.EQU CMPSTRL_B_COUNT 4
|
|
.EQU CMPSTRL_A 8
|
|
.EQU CMPSTRL_B 12
|
|
.EQU CMPSTRL_OFFS 16
|
|
.EQU CMPSTRL_RESULT 20
|
|
.EQU CMPSTRL_FS 24
|
|
_CMPSTRINGL:
|
|
FPADJ -CMPSTRL_FS
|
|
STORE CMPSTRL_B
|
|
STORE CMPSTRL_A
|
|
LOAD CMPSTRL_A ; load ptr to A
|
|
LOADI ; get length field
|
|
INC 3 ; add wordsize - 1
|
|
SHR
|
|
SHR ; divide by 4 to get length in words
|
|
STORE CMPSTRL_A_COUNT
|
|
LOAD CMPSTRL_B ; load ptr to B
|
|
LOADI ; get length field
|
|
INC 3 ; add wordsize - 1
|
|
SHR
|
|
SHR ; divide by 4 to get length in words
|
|
STORE CMPSTRL_B_COUNT
|
|
|
|
LOADC 8
|
|
STORE CMPSTRL_OFFS ; offset for both strings, start after string header
|
|
|
|
CMPSTRL_L: ; loop start
|
|
LOAD CMPSTRL_A_COUNT
|
|
CBRANCH.NZ CMPSTRL_1 ; if word count A if zero:
|
|
LOAD CMPSTRL_B_COUNT
|
|
CBRANCH.NZ CMPSTRL_1A ; and word count B is zero,
|
|
LOADC 0
|
|
BRANCH CMPSTRL_XT ; strings are equal
|
|
CMPSTRL_1A:
|
|
; word count A is zero, word count B is not zero: A < B
|
|
LOADC -1
|
|
BRANCH CMPSTRL_XT
|
|
CMPSTRL_1:
|
|
LOAD CMPSTRL_B_COUNT
|
|
CBRANCH.NZ CMPSTRL_2 ; if word count B is zero (and word count A cannot be zero here)
|
|
LOADC 1
|
|
BRANCH CMPSTRL_XT ; A > B
|
|
CMPSTRL_2: ; both strings have remaining words to compare here
|
|
LOAD CMPSTRL_OFFS
|
|
LOAD CMPSTRL_A ; word address = A ptr + offset
|
|
ADD
|
|
LOADI ; load word from A
|
|
LOAD CMPSTRL_OFFS ; word address = B ptr + offset
|
|
LOAD CMPSTRL_B
|
|
ADD
|
|
LOADI ; load word from B
|
|
OVER
|
|
OVER ; duplicate both words
|
|
CMPU LT ; check if word from A < word from B
|
|
CBRANCH.Z CMPSTRL_2A
|
|
DROP
|
|
DROP
|
|
LOADC -1 ; result code for A < B
|
|
BRANCH CMPSTRL_XT
|
|
CMPSTRL_2A:
|
|
CMPU GT ; check if word from A > word from B
|
|
CBRANCH CMPSTRL_2B
|
|
; nope, they are equal
|
|
; continue loop: decrease counters, increase offset
|
|
LOAD CMPSTRL_A_COUNT
|
|
DEC 1
|
|
STORE CMPSTRL_A_COUNT
|
|
LOAD CMPSTRL_B_COUNT
|
|
DEC 1
|
|
STORE CMPSTRL_B_COUNT
|
|
LOAD CMPSTRL_OFFS
|
|
INC 4
|
|
STORE CMPSTRL_OFFS
|
|
BRANCH CMPSTRL_L
|
|
CMPSTRL_2B:
|
|
LOADC 1 ; result code for A > B
|
|
CMPSTRL_XT:
|
|
FPADJ CMPSTRL_FS
|
|
RET
|
|
|
|
.CPOOL
|
|
|
|
; create a string from a character
|
|
; requires a pointer to a buffer of 12 bytes (minimum string buffer size)
|
|
; parameters: [ char value, pointer to string buffer ]
|
|
_CHARTOSTRING:
|
|
LOADC 1
|
|
STOREI 4 ; store 1 to length field, post-increment addr
|
|
LOADC 1
|
|
STOREI 4 ; store 1 to max length field, post-increment addr
|
|
SWAP ; swap addr and char value, char is now on ToS
|
|
LOADC 255 ; mask lsb of word
|
|
AND
|
|
BROT ; rotate lsb to msb
|
|
BROT
|
|
BROT
|
|
STOREI ; store it
|
|
DROP
|
|
RET
|
|
|
|
; convert string and index to a byte pointer.
|
|
; does a bounds check.
|
|
; parameters [ str ptr, char index ]
|
|
; returns: byte ptr
|
|
_INDEXSTRING:
|
|
DEC 1 ; adjust to base 0, string indices start at 1
|
|
OVER ; [ str, index, str ]
|
|
LOADI ; [ str, index, length ]
|
|
CMPU.S0 GE ; [ str, index, cmp ]
|
|
CBRANCH INDEXSTRING_ERR ; [ str, index ]
|
|
INC 8 ; account for string header
|
|
ADD ; add index to ptr to get byte ptr
|
|
RET
|
|
INDEXSTRING_ERR:
|
|
DROP
|
|
DROP
|
|
LOADCP _ERRMSG_STRIDX
|
|
LOADCP _RUNTIME_ERR
|
|
JUMP
|
|
|
|
; set a char at a specific index in a string.
|
|
; expects a byte pointer and a char value.
|
|
; the byte pointer contains the byte address in bits 0-1.
|
|
; no bounds check is performed.
|
|
; parameters: [ byte ptr, char value ]
|
|
_SETSTRINGCHAR:
|
|
BPLC.S0 ; [ ptr, rotated char ]
|
|
OVER ; [ ptr, rot char, ptr ]
|
|
LOADC $FF ;[ptr, rot char, ptr, $FF]
|
|
BPLC.S0 ; [ ptr, rot char, ptr, ~mask ]
|
|
NOT ; [ ptr, rot char, ptr, mask ]
|
|
SWAP ; [ ptr, rot char, mask, ptr ]
|
|
LOADI ; [ ptr, rot char, mask, word ]
|
|
AND ; [ ptr, rot char, masked word ]
|
|
OR ; [ ptr, new word ]
|
|
STOREI ; [ ptr ]
|
|
DROP
|
|
RET
|
|
|
|
; convert string to char
|
|
; string must have length of 1, otherwise a runtime error occurs
|
|
; parameters [ pointer to string ]
|
|
; returns: char value
|
|
_STRINGTOCHAR:
|
|
DUP ; dup addr for later
|
|
LOADI ; load string length
|
|
LOADC 1 ; compare with 1
|
|
CMPU EQ
|
|
CBRANCH STRINGTOCHAR_1
|
|
DROP
|
|
LOADCP _ERRMSG_STR2CHAR ; if not 1, issue runtime error
|
|
LOADCP _RUNTIME_ERR
|
|
JUMP
|
|
STRINGTOCHAR_1:
|
|
INC 8 ; increment addr to skip string header
|
|
LOADI ; load first string word
|
|
BROT ; rotate msb byte to lsb
|
|
; which is the first char/byte of the string.
|
|
; since the last string word is always zero-padded,
|
|
; we dont need to mask the byte
|
|
RET
|
|
|
|
; set the current length of a pascal string.
|
|
; must be less or equal than the maximum length of the string.
|
|
; if the new length is larger than the old length, the string
|
|
; is padded with zero bytes.
|
|
; parameters: [ pointer to string, new length ]
|
|
.EQU SETSTRL_NEWLEN 0
|
|
.EQU SETSTRL_PTR 4
|
|
.EQU SETSTRL_FS 8
|
|
_SETSTRINGLENGTH:
|
|
FPADJ -SETSTRL_FS
|
|
DUP
|
|
STORE SETSTRL_NEWLEN
|
|
SWAP ; [ newlen, ptr ]
|
|
DUP ; [ newlen, ptr, ptr ]
|
|
STORE SETSTRL_PTR ; [ newlen, ptr ]
|
|
INC 4 ; skip to max length field [ newlen, ptr+4 ]
|
|
LOADI ; load max length [ newlen, maxlen ]
|
|
CMPU.S1.X2Y GE ; new length > max_length? [ newlen, maxlen, cmp_result ]
|
|
CBRANCH.Z SETSTRL_0_0 ; if not, skip the following [ newlen, maxlen ]
|
|
; if it is, clamp new length to max length
|
|
NIP ; [ maxlen ]
|
|
DUP ; [ maxlen, maxlen ]
|
|
STORE SETSTRL_NEWLEN ; store maxlen as newlen [ ]
|
|
BRANCH SETSTRL_0
|
|
SETSTRL_0_0:
|
|
DROP ; remove maxlen
|
|
SETSTRL_0: ; newlen is on stack here
|
|
LOAD SETSTRL_PTR ; load ptr to string, size field
|
|
; stack is now: [ newlen, ptr ]
|
|
; loop from addr + old_length - 1
|
|
; to addr + new_length - 1:
|
|
; SETSTRINGCHAR(a, chr(0))
|
|
LOADI ; load current length [ newlen, oldlen ]
|
|
CMPU LE ; new length <= old length?
|
|
CBRANCH SETSTRL_1 ; if yes, skip padding
|
|
|
|
; string is being expanded, need to pad with zero bytes
|
|
LOAD SETSTRL_NEWLEN ; load new length
|
|
LOAD SETSTRL_PTR ; load string pointer yet again
|
|
LOADI ; load current(old) length
|
|
SUB ; calculate length difference (old - new)
|
|
; this is out char counter
|
|
LOAD SETSTRL_PTR ; load string pointer yet again
|
|
LOADI.S1.X2Y ; load old string length
|
|
ADD ; add to pointer
|
|
INC 8 ; adjust for string header
|
|
|
|
SETSTRL_0_L:
|
|
DUP ; duplicate pointer
|
|
LOADC 0 ; zero arg
|
|
LOADCP _SETSTRINGCHAR
|
|
CALL
|
|
|
|
INC 1 ; increment pointer
|
|
SWAP ; swap pointer and counter
|
|
DEC 1 ; decrement counter
|
|
DUP
|
|
CBRANCH.Z SETSTRL_1_0 ; if counter is zero, end loop
|
|
SWAP ; swap pointer and counter again
|
|
BRANCH SETSTRL_0_L
|
|
SETSTRL_1_0:
|
|
DROP
|
|
DROP ; remove pointer and counter
|
|
SETSTRL_1: ; pad last word of string with zero bytes
|
|
LOAD SETSTRL_PTR
|
|
INC 8 ; get pointer to first word
|
|
LOAD SETSTRL_NEWLEN
|
|
ADD ; get pointer to last word
|
|
LOADI.S1.X2Y ; load word, keep addr
|
|
OVER ; duplicate addr
|
|
LOADC 3
|
|
AND ; addr modulo 4
|
|
SHL 2 ; * 4 = offset into table borrowed from COPYSTRING
|
|
LOADCP CPSTR_MASK
|
|
ADD ; add up to addr of mask
|
|
LOADI ; load mask
|
|
; now on stack: [ word ptr, word, word mask ]
|
|
AND ; apply mask [ word ptr, word AND mask ]
|
|
STOREI ; store new word
|
|
DROP ; remove addr from STOREI
|
|
|
|
SETSTRL_XT2:
|
|
LOAD SETSTRL_PTR ; store new length in the length field
|
|
LOAD SETSTRL_NEWLEN ; of the string
|
|
STOREI
|
|
DROP
|
|
|
|
FPADJ SETSTRL_FS
|
|
RET
|
|
|
|
; check if a char value is contained
|
|
; within a string.
|
|
; parameters: [ value, ptr to string ]
|
|
; returns: nonzero if string contains value, 0 if not
|
|
_ISCHARINSTRING:
|
|
FPADJ -4
|
|
SWAP
|
|
STORE 0 ; store value to find
|
|
LOADI.S1.X2Y ; load string length
|
|
SWAP ; [ length, ptr ]
|
|
INC 8 ; skip string header
|
|
SWAP ; [ ptr+8, length ]
|
|
ISCHIS_L:
|
|
DUP ; check if bytes remaining
|
|
CBRANCH.Z ISCHIS_XT0 ; if zero, exit
|
|
SWAP ; [ length, ptr+n ]
|
|
DUP ; [ length, ptr+n, ptr+n ]
|
|
LOADI.S1.X2Y ; [ length, ptr+n, ptr+n, word ]
|
|
BSEL ; [ length, ptr+n, char ]
|
|
LOAD 0
|
|
CMP EQ ; compare with char value
|
|
CBRANCH ISCHIS_FND ; found it
|
|
; [ length, ptr+n ]
|
|
INC 1 ; increment byte ptr
|
|
SWAP ; [ ptr+n, length ]
|
|
DEC 1 ; decrement counter
|
|
BRANCH ISCHIS_L
|
|
ISCHIS_FND:
|
|
LOADC 1
|
|
BRANCH ISCHIS_XT
|
|
ISCHIS_XT0:
|
|
LOADC 0
|
|
ISCHIS_XT:
|
|
NIP ; remove ptr
|
|
NIP ; and counter, keeping the result code
|
|
FPADJ 4
|
|
RET
|
|
|
|
; check if an integer value is contained
|
|
; within an array of integers
|
|
; parameters: [ value, ptr to array, size in words ]
|
|
; returns: 1 if array contains value, 0 if not
|
|
_ISINTINARRAY:
|
|
FPADJ -4
|
|
SWAP ; swap size and array
|
|
STORE 0 ; store ptr
|
|
ISINTINA_L:
|
|
DUP ; duplicate word counter
|
|
CBRANCH.Z ISINTINA_XT0 ; if counter is zero, exit
|
|
OVER ; copy value to top of stack
|
|
LOAD 0 ; load ptr
|
|
INC.S1.X2Y 4 ; increment ptr, keep old value on stack
|
|
STORE 0 ; store new ptr
|
|
LOADI ; load array element via old ptr
|
|
CMP NE ; compare value and array element
|
|
CBRANCH ISINTINA_1 ; if not equal, skip the following
|
|
DROP
|
|
DROP ; drop counter and value
|
|
LOADC 1 ; we found our value, load 1 as return value
|
|
BRANCH ISINTINA_XT ; and branch to exit
|
|
ISINTINA_1:
|
|
DEC 1 ; decrease counter
|
|
BRANCH ISINTINA_L ; and loop
|
|
ISINTINA_XT0:
|
|
DROP ; drop counter and value
|
|
DROP
|
|
LOADC 0 ; return 0
|
|
ISINTINA_XT:
|
|
FPADJ 4
|
|
RET
|
|
|
|
; Set a bit in a word. The bit is specified by index (0-31)
|
|
; where 0 indicates the lsb and 31 the msb.
|
|
; parameters: [ word, bit number ]
|
|
; returns: word with bit set
|
|
_SETBIT:
|
|
SHL 2 ; bit number * 4 = offset into table
|
|
LOADCP _BITTABLE
|
|
ADD
|
|
LOADI ; get bit value
|
|
OR ; combine original value and shifted bit
|
|
RET
|
|
|
|
; Clear a bit in a word. The bit is specified by index (0-31)
|
|
; where 0 indicates the lsb and 31 the msb.
|
|
; parameters: [ word, bit number ]
|
|
; returns: word with bit cleared
|
|
_CLEARBIT:
|
|
SHL 2 ; bit number * 4 = offset into table
|
|
LOADCP _BITTABLE
|
|
ADD
|
|
LOADI ; get bit value
|
|
NOT ; invert mask
|
|
AND ; combine original value and shifted bit
|
|
RET
|
|
|
|
; test if a bit is set a word. The bit is specified by index (0-31)
|
|
; where 0 indicates the lsb and 31 the msb.
|
|
; parameters: [ bit number, word ]
|
|
; returns: 1 if bit is set, 0 otherwise
|
|
_TESTBIT:
|
|
SWAP
|
|
SHL 2 ; bit number * 4 = offset into table
|
|
LOADCP _BITTABLE
|
|
ADD
|
|
LOADI ; get bit value
|
|
AND ; combine original value and shifted bit
|
|
LOADC 0
|
|
CMP NE ; if not zero, return 1
|
|
RET
|
|
|
|
; Convert an integer array to a 32-bit set, by
|
|
; iterating over all array values and calling SETBIT.
|
|
; parameters: [ array ptr, array size in words ]
|
|
; returns: set value as a single word
|
|
_ARRAYTOSET:
|
|
FPADJ -4 ; local var offset 0 is the result value
|
|
LOADC 0
|
|
STORE 0 ; clear result value
|
|
ARRAYTOSET_L:
|
|
DUP ; duplicate word counter
|
|
CBRANCH.Z ARRAYTOSET_X ; if zero, we are done
|
|
SWAP ; swap counter and ptr, ptr is on ToS
|
|
LOADI.S1.X2Y ; load value from array, keep ptr
|
|
LOADC 31
|
|
AND ; clamp bit number to 31
|
|
LOAD 0 ; load result value
|
|
SWAP ; SETBIT wants [ value, bit number ]
|
|
LOADCP _SETBIT ; set the bit
|
|
CALL
|
|
STORE 0 ; store result
|
|
INC 4 ; increment ptr
|
|
SWAP ; swap ptr and counter again
|
|
DEC 1 ; decrement counter
|
|
BRANCH ARRAYTOSET_L ; and loop
|
|
ARRAYTOSET_X:
|
|
DROP ; remove word counter
|
|
DROP ; remove ptr
|
|
LOAD 0 ; load result
|
|
FPADJ 4
|
|
RET
|
|
|
|
_BITTABLE:
|
|
.WORD %00000000000000000000000000000001
|
|
.WORD %00000000000000000000000000000010
|
|
.WORD %00000000000000000000000000000100
|
|
.WORD %00000000000000000000000000001000
|
|
.WORD %00000000000000000000000000010000
|
|
.WORD %00000000000000000000000000100000
|
|
.WORD %00000000000000000000000001000000
|
|
.WORD %00000000000000000000000010000000
|
|
.WORD %00000000000000000000000100000000
|
|
.WORD %00000000000000000000001000000000
|
|
.WORD %00000000000000000000010000000000
|
|
.WORD %00000000000000000000100000000000
|
|
.WORD %00000000000000000001000000000000
|
|
.WORD %00000000000000000010000000000000
|
|
.WORD %00000000000000000100000000000000
|
|
.WORD %00000000000000001000000000000000
|
|
.WORD %00000000000000010000000000000000
|
|
.WORD %00000000000000100000000000000000
|
|
.WORD %00000000000001000000000000000000
|
|
.WORD %00000000000010000000000000000000
|
|
.WORD %00000000000100000000000000000000
|
|
.WORD %00000000001000000000000000000000
|
|
.WORD %00000000010000000000000000000000
|
|
.WORD %00000000100000000000000000000000
|
|
.WORD %00000001000000000000000000000000
|
|
.WORD %00000010000000000000000000000000
|
|
.WORD %00000100000000000000000000000000
|
|
.WORD %00001000000000000000000000000000
|
|
.WORD %00010000000000000000000000000000
|
|
.WORD %00100000000000000000000000000000
|
|
.WORD %01000000000000000000000000000000
|
|
.WORD %10000000000000000000000000000000
|
|
|
|
.CPOOL
|
|
|
|
.EQU _HEAP_HDR_SZ 8
|
|
.EQU _HEAP_MIN_SZ 32
|
|
; MEM_ALLOC
|
|
; allocate a chunk of memory
|
|
; parameters: [ size in bytes ]
|
|
; returns: starting address of allocated memory, or 0 if
|
|
; no more space is available
|
|
.EQU MA_REQD_SIZE 0
|
|
.EQU MA_CURCHUNK 4
|
|
.EQU MA_LASTCHUNK 8
|
|
.EQU MA_CURCHUNK_SIZE 12
|
|
.EQU MA_NEWCHUNK 16
|
|
.EQU MA_FS 20
|
|
_MEM_ALLOC:
|
|
FPADJ -MA_FS
|
|
; round up requested size
|
|
LOADC _HEAP_HDR_SZ
|
|
ADD
|
|
LOADC _HEAP_MIN_SZ -1
|
|
ADD
|
|
LOADC ~_HEAP_MIN_SZ + 1
|
|
AND
|
|
STORE MA_REQD_SIZE
|
|
|
|
;LOAD MA_REQD_SIZE
|
|
;LOADCP PRINTDEC
|
|
;CALL
|
|
;LOADCP NEWLINE
|
|
;CALL
|
|
|
|
; get current alloc pointer (points to last previously seen free chunk)
|
|
LOADCP _HEAP_CUR
|
|
LOADI
|
|
STORE MA_CURCHUNK
|
|
MEM_ALLOC_L0:
|
|
; get next free chunk
|
|
LOAD MA_CURCHUNK
|
|
DUP
|
|
STORE MA_LASTCHUNK ; last chunk = cur chunk
|
|
LOADI ; load next ptr
|
|
DUP ; dup ptr for accessing the size
|
|
STORE MA_CURCHUNK ; cur chunk = cur chunk^.next
|
|
INC 4 ; skip to size field
|
|
LOADI ; load it
|
|
STORE MA_CURCHUNK_SIZE ; and store to local var
|
|
|
|
;LOADC ':'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
|
|
;LOAD MA_CURCHUNK
|
|
;LOADCP PRINTHEXW
|
|
;CALL
|
|
|
|
;LOADCP 's'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
|
|
;LOAD MA_CURCHUNK_SIZE
|
|
;LOADCP PRINTHEXW
|
|
;CALL
|
|
|
|
;LOADCP '?'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
|
|
;LOAD MA_REQD_SIZE
|
|
;LOADCP PRINTHEXW
|
|
;CALL
|
|
|
|
;LOADCP NEWLINE
|
|
;CALL
|
|
|
|
; if requested size is less or equal than chunk size, jump to allocation
|
|
LOAD MA_REQD_SIZE
|
|
LOAD MA_CURCHUNK_SIZE
|
|
CMPU LE
|
|
CBRANCH MEM_ALLOC_A
|
|
; if back to where we started, return failure
|
|
LOADCP _HEAP_CUR
|
|
LOADI
|
|
LOAD MA_CURCHUNK
|
|
CMPU NE
|
|
CBRANCH MEM_ALLOC_L0 ; else, go to next chunk
|
|
|
|
;LOADC 'e'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
|
|
; TODO: grow heap and add new chunk, check for
|
|
; collision with downward growing user stack (FP)
|
|
|
|
LOADC 0
|
|
BRANCH MEM_ALLOC_XT
|
|
|
|
; allocation:
|
|
MEM_ALLOC_A:
|
|
LOAD MA_CURCHUNK
|
|
STORE MA_NEWCHUNK
|
|
|
|
LOADCP _HEAP_CUR ; remember last chunk
|
|
LOAD MA_LASTCHUNK ; for next invocation
|
|
STOREI
|
|
DROP
|
|
; split chunk:
|
|
; skip if requested_size = chunk size
|
|
LOAD MA_CURCHUNK_SIZE
|
|
LOAD MA_REQD_SIZE
|
|
CMPU EQ
|
|
CBRANCH MEM_ALLOC_A0
|
|
; new chunk is head portion of current chunk
|
|
; new chunk size = requested size
|
|
LOAD MA_NEWCHUNK
|
|
INC 4 ; skip zo size field
|
|
LOAD MA_REQD_SIZE
|
|
STOREI ; store size
|
|
DROP
|
|
|
|
;LOADC '|'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
|
|
; current chunk is the split off free chunk
|
|
; current chunk start += requested size
|
|
; current chunk size -= requested size
|
|
LOAD MA_CURCHUNK_SIZE
|
|
LOAD MA_REQD_SIZE
|
|
SUB ; new size = old size - requested
|
|
LOAD MA_CURCHUNK
|
|
LOADI ; load old next ptr
|
|
|
|
LOAD MA_CURCHUNK
|
|
LOAD MA_REQD_SIZE
|
|
ADD ; new addr of current chunk = old addr + requested size
|
|
DUP
|
|
STORE MA_CURCHUNK
|
|
; the stack at this point: [ new size, old next ptr, cur chunk addr ]
|
|
; now write new header
|
|
SWAP ; swap next ptr and addr
|
|
STOREI 4 ; store next ptr to addr, leave addr + 4
|
|
SWAP ; swap addr and size
|
|
STOREI ; store size
|
|
DROP
|
|
|
|
; previous chunk next pointer = current chunk pointer
|
|
LOAD MA_LASTCHUNK
|
|
LOAD MA_CURCHUNK
|
|
STOREI
|
|
DROP
|
|
BRANCH MEM_ALLOC_A1
|
|
|
|
MEM_ALLOC_A0:
|
|
;LOADC 'O'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
|
|
; if chunk was not split: previous chunk next ptr = current chunk next ptr
|
|
LOAD MA_LASTCHUNK
|
|
LOAD MA_CURCHUNK
|
|
LOADI
|
|
STOREI
|
|
DROP
|
|
|
|
MEM_ALLOC_A1:
|
|
; return new chunk pointer + header size
|
|
LOAD MA_NEWCHUNK
|
|
INC _HEAP_HDR_SZ
|
|
|
|
MEM_ALLOC_XT:
|
|
FPADJ MA_FS
|
|
RET
|
|
|
|
; MEM_FREE
|
|
; free a chunk of memory which was allocated by MEM_ALLOC
|
|
; parameters: [ address previously returned by MEM_ALLOC ]
|
|
.EQU MF_FREECHUNK 0
|
|
.EQU MF_CURCHUNK 4
|
|
.EQU MF_NEXTCHUNK 8
|
|
.EQU MF_CURCHUNK_END 12
|
|
.EQU MF_FS 16
|
|
_MEM_FREE:
|
|
FPADJ -MF_FS
|
|
DEC _HEAP_HDR_SZ
|
|
DUP ; dup for comparison below [ ptr, ptr ]
|
|
DUP ; dup for other comparison below [ ptr, ptr, ptr ]
|
|
STORE MF_FREECHUNK ; to-be-freed chunk pointer = address - header size [ptr, ptr ]
|
|
LOADCP _HEAP_ANCHOR ; [ ptr, ptr, anchor ]
|
|
CMP GT ; [ ptr , cmp ]
|
|
CBRANCH MEM_FREE_L ; [ ptr ]
|
|
|
|
DUP
|
|
LOADCP PRINTHEXW
|
|
CALL
|
|
|
|
DROP
|
|
|
|
LOADCP _ERRMSG_MEMFREE
|
|
LOADCP _RUNTIME_ERR
|
|
JUMP
|
|
MEM_FREE_L:
|
|
LOADCP _HEAP_CUR
|
|
LOADI ; load current heap pointer
|
|
DUP ; dup for comparison below
|
|
STORE MF_CURCHUNK
|
|
; on the stack now: [ freechunk, heap_cur ]
|
|
CMPU GE
|
|
CBRANCH MEM_FREE_L0
|
|
LOADCP _HEAP_ANCHOR
|
|
STORE MF_CURCHUNK
|
|
MEM_FREE_L0:
|
|
; get current alloc pointer (points to last seen free chunk)
|
|
LOAD MF_CURCHUNK
|
|
DUP
|
|
DUP
|
|
INC 4 ; skip curchunk ptr to size field
|
|
LOADI ; load size
|
|
ADD ; add to curchunk ptr to get end of chunk
|
|
STORE MF_CURCHUNK_END
|
|
LOADI ; get next chunk pointer
|
|
STORE MF_NEXTCHUNK
|
|
|
|
;LOADC 'N'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
;LOAD MF_NEXTCHUNK
|
|
;LOADCP PRINTHEXW
|
|
;CALL
|
|
;LOADC 'C'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
;LOAD MF_CURCHUNK
|
|
;LOADCP PRINTHEXW
|
|
;CALL
|
|
;LOADC 'F'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
;LOAD MF_FREECHUNK
|
|
;LOADCP PRINTHEXW
|
|
;CALL
|
|
;LOADCP NEWLINE
|
|
;CALL
|
|
|
|
; if next ptr < to-be-freed, skip to next chunk
|
|
LOAD MF_NEXTCHUNK
|
|
LOAD MF_FREECHUNK
|
|
CMPU LT
|
|
CBRANCH MEM_FREE_CT1
|
|
|
|
MEM_FREE_INS:
|
|
LOAD MF_FREECHUNK
|
|
LOAD MF_NEXTCHUNK
|
|
CMP NE
|
|
CBRANCH MEM_FREE_INS1
|
|
|
|
LOADCP _ERRMSG_MEMDFREE
|
|
LOADCP _RUNTIME_ERR
|
|
JUMP
|
|
|
|
MEM_FREE_INS1:
|
|
|
|
; to-be-freed chunk next ptr = next chunk
|
|
LOAD MF_FREECHUNK
|
|
LOAD MF_NEXTCHUNK
|
|
STOREI
|
|
DROP
|
|
|
|
; current chunk next ptr = to-be-freed chunk
|
|
LOAD MF_CURCHUNK
|
|
LOAD MF_FREECHUNK
|
|
STOREI
|
|
DROP
|
|
|
|
;LOADC 'i'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
|
|
; merge down if needed
|
|
; if current chunk end = to-be-freed chunk start:
|
|
; current chunk size += to-be-freed size
|
|
; to-be-freed chunk = current chunk
|
|
; current chunk next = next
|
|
LOAD MF_CURCHUNK_END
|
|
LOAD MF_FREECHUNK
|
|
CMPU NE
|
|
CBRANCH MEM_FREE_CT
|
|
|
|
;LOADC 'v'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
|
|
LOAD MF_CURCHUNK
|
|
INC 4
|
|
DUP ; dup addr of cur chunk size for STOREI below
|
|
LOADI ; get cur chunk size
|
|
LOAD MF_FREECHUNK
|
|
INC 4
|
|
LOADI ; get to-be-freed chunk size
|
|
ADD
|
|
STOREI ; store new cur chunk size
|
|
DROP
|
|
|
|
LOAD MF_CURCHUNK ; set current chunk next pointer again
|
|
LOAD MF_NEXTCHUNK
|
|
STOREI ; store and reuse addr
|
|
STORE MF_FREECHUNK ; to-be-freed chunk becomes current chunk
|
|
|
|
; merge up if needed
|
|
; if to-be-freed chunk end = next chunk start:
|
|
; to-be-freed-chunk size += next chunk size
|
|
; to-be-freed next ptr = next chunk next ptr
|
|
MEM_FREE_CT:
|
|
LOAD MF_FREECHUNK
|
|
DUP
|
|
INC 4
|
|
LOADI
|
|
ADD ; calculate to-be-freed chunk end
|
|
LOAD MF_NEXTCHUNK
|
|
CMPU NE
|
|
CBRANCH MEM_FREE_CT0
|
|
|
|
;LOADC '^'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
|
|
LOAD MF_FREECHUNK ; store next chunk next ptr to to-be-freed next ptr
|
|
LOAD MF_NEXTCHUNK
|
|
LOADI
|
|
STOREI 4 ; store and post-increment addr to to-be-freed size field
|
|
LOADI.S1.X2Y ; load size and keep addr
|
|
LOAD MF_NEXTCHUNK
|
|
INC 4
|
|
LOADI ; get next chunk size
|
|
ADD
|
|
STOREI
|
|
DROP
|
|
|
|
MEM_FREE_CT0:
|
|
|
|
BRANCH MEM_FREE_XT
|
|
|
|
MEM_FREE_CT1:
|
|
;LOADC '>'
|
|
;LOADCP CONOUT
|
|
;CALL
|
|
; if we are at the end of the list, insert there
|
|
LOADCP _HEAP_ANCHOR
|
|
LOAD MF_NEXTCHUNK
|
|
CMPU EQ
|
|
CBRANCH MEM_FREE_INS
|
|
|
|
; move to next chunk
|
|
LOAD MF_NEXTCHUNK
|
|
STORE MF_CURCHUNK
|
|
BRANCH MEM_FREE_L0
|
|
|
|
MEM_FREE_XT:
|
|
; reset current heap pointer because
|
|
; a merge might have invalidated it
|
|
LOADCP _HEAP_CUR
|
|
LOADCP _HEAP_ANCHOR
|
|
STOREI
|
|
DROP
|
|
|
|
FPADJ MF_FS
|
|
RET
|
|
|
|
; MEM_INIT
|
|
; Initialize dynamic memory, user stack and return stack.
|
|
; Since the return stack is no longer valid afterwards, directly
|
|
; jumps to _MAIN instead of using RET.
|
|
|
|
; parameters: [ start of heap address ]
|
|
_MEM_INIT:
|
|
; initialize anchor chunk with start of heap address
|
|
; and heap size - header size
|
|
LOADCP _HEAP_CUR ; load addr of _HEAP_CUR for STOREI below
|
|
LOADCP _HEAP_ANCHOR
|
|
DUP
|
|
DUP ; anchor points to itself at first
|
|
STOREI 4
|
|
LOADC 0 ; store size 0
|
|
STOREI
|
|
DROP
|
|
STOREI ; store _HEAP_ANCHOR to _HEAP_CUR
|
|
DROP
|
|
|
|
LOADCP _HEAP_SZ_PTR ; load the value of the heap size from
|
|
LOADI ; the program header
|
|
|
|
; set user stack pointer to heap start + heap size + stack size
|
|
OVER ; [ start, size, start ]
|
|
OVER ; [ start, size, start, size ]
|
|
ADD ; [ start, size, start+size ]
|
|
LOADCP _STACK_SZ_PTR
|
|
LOADI
|
|
ADD ; add user stack size to get new FP value [ start, size, start+heapsize+stacksize]
|
|
STOREREG FP
|
|
|
|
LOADREG FP
|
|
INC 4
|
|
STOREREG RP ; set RP to start right after user stack
|
|
; this trashes the previous return stack,
|
|
; so we cannot use RET at the end of MEM_INIT
|
|
|
|
; set chunk header
|
|
OVER ; [ start, size, start ]
|
|
LOADC 0 ; [ start, size, start, 0 ] ; set next chunk ptr
|
|
STOREI 4 ; [ start, size , start + 4 ] ; to zero or we get an error in _MEM_FREE
|
|
SWAP ; [ start, start + 4, size ]
|
|
STOREI ; store the size
|
|
DROP ; [ start ]
|
|
|
|
LOADC _HEAP_HDR_SZ
|
|
ADD ; adjust chunk address for header
|
|
|
|
LOADCP _MEM_FREE ; add chunk to free list
|
|
CALL
|
|
|
|
; get address of _MAIN from program header
|
|
LOADCP _MAIN_PTR
|
|
LOADI
|
|
JUMP
|
|
|
|
; allocate a string with MEM_ALLOC.
|
|
; the string is also initialized.
|
|
; parameters: [ max length ]
|
|
; returns: pointer to allocated string, or zero if
|
|
; no heap space available
|
|
|
|
_STRING_ALLOC:
|
|
DUP ; [ length, length ]
|
|
INC 8 ; adjust length for string header
|
|
LOADCP _MEM_ALLOC
|
|
CALL ; [ length, addr ]
|
|
DUP ; [ length, addr, addr ]
|
|
CBRANCH.Z STRING_ALLOC_E ; [ length, addr ]
|
|
SWAP ; [ addr, length ]
|
|
OVER ; [ addr, length, addr ]
|
|
LOADCP _INITSTRINGF ; set max string length (forced)
|
|
CALL ; [ addr ]
|
|
RET
|
|
STRING_ALLOC_E:
|
|
SWAP
|
|
DROP
|
|
RET
|
|
|
|
.CPOOL
|
|
|
|
LENGTH:
|
|
LOADI
|
|
RET
|
|
|
|
MAXLENGTH:
|
|
INC 4
|
|
LOADI
|
|
RET
|
|
|
|
; issue a runtime error if pointer is zero
|
|
_CHECK_ALLOC:
|
|
;TODO: check for heap overrun by user stack
|
|
|
|
;DUP
|
|
;LOADCP _CHECK_CHUNK ; check for corrupted free list
|
|
;CALL
|
|
|
|
DUP
|
|
CBRANCH.NZ _CHECK_ALLOC_OK
|
|
LOADCP _ERRMSG_MEMALLOC
|
|
LOADCP _RUNTIME_ERR
|
|
JUMP
|
|
_CHECK_ALLOC_OK:
|
|
RET
|
|
|
|
; print the free list for debugging
|
|
MEM_DUMP:
|
|
LOADC 35
|
|
LOADCP CONOUT
|
|
CALL
|
|
LOADCP _HEAP_ANCHOR
|
|
DUP
|
|
LOADCP PRINTHEXW
|
|
CALL
|
|
LOADC '^'
|
|
LOADCP CONOUT
|
|
CALL
|
|
LOADCP _HEAP_CUR
|
|
LOADI
|
|
LOADCP PRINTHEXW
|
|
CALL
|
|
LOADCP NEWLINE
|
|
CALL
|
|
MEM_DUMP_L0:
|
|
DUP ; dup cur ptr for later
|
|
DUP ; dup cur ptr for printing
|
|
LOADCP PRINTHEXW
|
|
CALL
|
|
LOADC ' '
|
|
LOADCP CONOUT
|
|
CALL
|
|
INC 4
|
|
LOADI
|
|
LOADCP PRINTHEXW
|
|
CALL
|
|
LOADC '>'
|
|
LOADCP CONOUT
|
|
CALL
|
|
LOADI ; load next ptr
|
|
DUP
|
|
LOADCP PRINTHEXW
|
|
CALL
|
|
LOADCP NEWLINE
|
|
CALL
|
|
DUP ; dup for comparison
|
|
LOADCP _HEAP_ANCHOR
|
|
CMPU NE ; if next ptr is anchor, we are done
|
|
CBRANCH MEM_DUMP_L0
|
|
DROP
|
|
RET
|
|
|
|
; check if a pointer is part of the free list
|
|
; args: pointer returned by MEM_ALLOC
|
|
; throws runtime error if the pointer is found
|
|
_CHECK_CHUNK:
|
|
FPADJ -4
|
|
LOADC _HEAP_HDR_SZ ; adjust for header
|
|
SUB
|
|
STORE 0 ; store chunk addr in local var
|
|
|
|
LOADCP _HEAP_ANCHOR ; start loop with anchor address
|
|
CHK_CH_L:
|
|
DUP ; current chunk addr is on stack
|
|
LOADI ; load next ptr
|
|
LOAD 0 ; load addr to be checked
|
|
CMP EQ ; if equal, error
|
|
CBRANCH.NZ CHK_ERR
|
|
|
|
LOADI ; load next ptr again
|
|
LOADCP _HEAP_ANCHOR
|
|
CMP.S0 EQ ; compare with anchor, keep ptr on stack
|
|
CBRANCH.Z CHK_CH_L ; in not equal, loop
|
|
|
|
DROP ; remove current chunk ptr
|
|
|
|
FPADJ 4
|
|
RET
|
|
CHK_ERR:
|
|
LOAD 0
|
|
LOADCP PRINTHEXW
|
|
CALL
|
|
LOADCP NEWLINE
|
|
CALL
|
|
|
|
LOADCP MEM_DUMP
|
|
CALL
|
|
; remove one return stack entry
|
|
; for reporting the correct PC
|
|
; since _CHECK_CHUNK is
|
|
; always called by another checking
|
|
; routine (e.g. _CHECK_ALLOC)
|
|
LOADREG RP
|
|
DEC 4
|
|
STOREREG RP
|
|
|
|
LOADCP _ERRMSG_MEMBROKEN
|
|
LOADCP _RUNTIME_ERR
|
|
JUMP
|
|
|
|
; array bounds check
|
|
; parameters [ index, array size ]
|
|
; throws a runtime error when not inside bounds
|
|
_BOUNDSCHECK:
|
|
CMPU LT
|
|
CBRANCH _BOUNDSCHECK_OK
|
|
LOADCP _ERRMSG_ARRAY_OOB
|
|
LOADCP _RUNTIME_ERR
|
|
JUMP
|
|
_BOUNDSCHECK_OK:
|
|
RET
|
|
|
|
; subrange check
|
|
; parameters: [ value, min, max ]
|
|
; throws runtime error when not inside bounds
|
|
_RANGECHECK:
|
|
FPADJ -4
|
|
STORE 0 ; store max value
|
|
OVER ; dup value, stack is now [ v, min, v ]
|
|
CMP GT ; if min is greater than value
|
|
CBRANCH _RANGE_ERR ; then it is a runtime error
|
|
LOAD 0 ; load max again, stack is now [ v, max ]
|
|
CMP GT ; if value is greater than max
|
|
CBRANCH _RANGE_ERR ; then it is a runtime error
|
|
FPADJ 4
|
|
RET
|
|
_RANGE_ERR:
|
|
LOADCP _ERRMSG_RANGE
|
|
LOADCP _RUNTIME_ERR
|
|
JUMP
|
|
|
|
; enum range check
|
|
; parameters: [ value, max ]
|
|
; throws runtime error when outside bounds
|
|
_ENUMCHECK:
|
|
CMPU GT
|
|
CBRANCH _ENUM_ERR
|
|
RET
|
|
_ENUM_ERR:
|
|
LOADCP _ERRMSG_ENUM
|
|
LOADCP _RUNTIME_ERR
|
|
JUMP
|
|
|
|
HALT:
|
|
LOADCP PTERM
|
|
JUMP
|
|
|
|
; Show runtime error, to be called from Pascal.
|
|
; The string must end in a null byte, for example
|
|
; by having a greater maximum string length than
|
|
; actual length, or by adding a chr(0) at the end.
|
|
; parameters: [ ptr to pascal string ]
|
|
RUNTIMEERROR:
|
|
INC 8 ; skip string header
|
|
; fall through to _RUNTIME_ERR
|
|
|
|
; show runtime error message and abort program
|
|
; args: error message
|
|
_RUNTIME_ERR:
|
|
LOADCP _ERRMSG_1
|
|
LOADCP PRINTLINE
|
|
CALL
|
|
LOADREG RP
|
|
LOADI
|
|
LOADCP PRINTHEXW
|
|
CALL
|
|
LOADCP NEWLINE
|
|
CALL
|
|
LOADCP PRINTLINE
|
|
CALL
|
|
LOADCP NEWLINE
|
|
CALL
|
|
|
|
LOADCP PTERM
|
|
JUMP
|
|
|
|
.EQU _ESP_EMPTY 0
|
|
|
|
_CLEARESTACK:
|
|
LOADREG ESP
|
|
LOADC _ESP_EMPTY
|
|
CMP EQ
|
|
CBRANCH _CLEARESTACK_XT
|
|
DROP
|
|
BRANCH _CLEARESTACK
|
|
_CLEARESTACK_XT:
|
|
RET
|
|
|
|
; Terminate program: clear estack and
|
|
; jump to coreloader
|
|
PTERM:
|
|
LOADCP _CLEARESTACK
|
|
CALL
|
|
LOADCP LOADER_START
|
|
JUMP
|
|
|
|
; Return an integer representation
|
|
; of a real(float32) number.
|
|
; This is our native format, so
|
|
; we do not have to do anything.
|
|
ENCODEFLOAT32:
|
|
RET
|
|
|
|
.CPOOL
|
|
|
|
_HEAP_START: .WORD 0
|
|
_HEAP_MAX: .WORD 0
|
|
_HEAP_CUR: .WORD 0
|
|
_HEAD_FIRSTF: .WORD 0
|
|
_HEAP_ANCHOR: .WORD 0,0
|
|
|
|
_ERRMSG_1: .BYTE 13,10,"Runtime error at PC ",0
|
|
_ERRMSG_ARRAY_OOB: .BYTE "Array index out of bounds",0
|
|
_ERRMSG_CONSTWR: .BYTE "Write to constant string",0
|
|
_ERRMSG_STR2CHAR: .BYTE "Invalid conversion from string to char",0
|
|
_ERRMSG_STRIDX: .BYTE "String index out of bounds",0
|
|
_ERRMSG_RANGE: .BYTE "Range check",0
|
|
_ERRMSG_ENUM: .BYTE "Invalid enum value",0
|
|
_ERRMSG_MEMFREE: .BYTE "Invalid pointer in dispose",0
|
|
_ERRMSG_MEMDFREE: .BYTE "Chunk already disposed",0
|
|
_ERRMSG_MEMALLOC: .BYTE "Out of heap space",0
|
|
_ERRMSG_MEMBROKEN: .BYTE "Heap corrupted",0
|
|
NEWLINESTR:
|
|
.WORD 2,0
|
|
.BYTE 13,10
|
|
|
|
.CPOOL
|
|
|
|
%export _APPENDSTRING
|
|
%export _ARRAYTOSET
|
|
%export _BOUNDSCHECK
|
|
%export _CHARTOSTRING
|
|
%export _CHECK_ALLOC
|
|
%export _CLEARBIT
|
|
%export _CLEARMEM
|
|
%export _CMPSTRING
|
|
%export _CMPSTRINGL
|
|
%export _CMPWORDS
|
|
%export _COPYSTRING
|
|
%export _COPYWORDS
|
|
%export _ENUMCHECK
|
|
%export _INDEXSTRING
|
|
%export _INITSTRING
|
|
%export _INITSTRINGF
|
|
%export _INITSTRINGFROM
|
|
%export _ISCHARINSTRING
|
|
%export _ISINTINARRAY
|
|
%export _MEM_ALLOC
|
|
%export _MEM_INIT
|
|
%export _MEM_FREE
|
|
%export _RANGECHECK
|
|
%export _RUNTIME_ERR
|
|
%export _SETBIT
|
|
%export _SETSTRINGCHAR
|
|
%export _SETSTRINGLENGTH
|
|
%export _STRINGTOCHAR
|
|
%export _STRING_ALLOC
|
|
%export _TESTBIT
|
|
%export MEM_DUMP
|