1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-07-22 05:20:19 +10:00
collapseos/apps/zasm/symbol.asm
Clanmaster21 cca3157c66 addHL and subHL affect flags, and are smaller (#30)
* addHL and subHL affect flags, and are smaller

Most importantly, addHL and subHL now affect the flags as you would expect from a 16 bit addition/subtraction. This seems like it'd be preferred behaviour, however I realise any code relying on it not affecting flags would break. One byte saved in addHL, and two bytes saved in subHL. Due to the branching nature of the original code, it's difficult to compare speeds, subHL is either 1 or 6 cycles faster depending on branching, and addHL is between -1 and 3 cycles faster. If the chance of a carry is 50%, addHL is expected to be a cycle faster, but for a chance of carry below 25% (so a < 0x40) this will be up to a cycle slower.

* Update core.asm

* Reworked one use of addHL

By essentially inlining both addHL and cpHLDE, 100 cycles are saved, but due to the registers not needing preserving, a byte is saved too.

* Corrected spelling error in comment

* Reworked second use of addHL

43 cycles saved, and no more addHL in critical loops. No bytes saved or used.

* Fixed tabs and spacing, and made a comment clearer.

* Clearer comments

* Adopted push/pop notation
2019-10-17 16:45:27 -04:00

270 lines
6.2 KiB
NASM

; Manages both constants and labels within a same namespace and registry.
;
; Local Labels
;
; Local labels during the "official" first pass are ignored. To register them
; in the global registry during that pass would be wasteful in terms of memory.
;
; What we do instead is set up a separate register for them and have a "second
; first pass" whenever we encounter a new context. That is, we wipe the local
; registry, parse the code until the next global symbol (or EOF), then rewind
; and continue second pass as usual.
; *** Constants ***
; Size of each record in registry
.equ SYM_RECSIZE 3
.equ SYM_REGSIZE ZASM_REG_BUFSZ+1+ZASM_REG_MAXCNT*SYM_RECSIZE
.equ SYM_LOC_REGSIZE ZASM_LREG_BUFSZ+1+ZASM_LREG_MAXCNT*SYM_RECSIZE
; *** Variables ***
; A registry has three parts: record count (byte) record list and names pool.
; A record is a 3 bytes structure:
; 1b - name length
; 2b - value associated to symbol
;
; We know we're at the end of the record list when we hit a 0-length one.
;
; The names pool is a list of strings, not null-terminated, associated with
; the value.
;
; It is assumed that the registry is aligned in memory in that order:
; names pool, rec count, reclist
; Global labels registry
.equ SYM_GLOB_REG SYM_RAMSTART
.equ SYM_LOC_REG SYM_GLOB_REG+SYM_REGSIZE
.equ SYM_CONST_REG SYM_LOC_REG+SYM_LOC_REGSIZE
.equ SYM_RAMEND SYM_CONST_REG+SYM_REGSIZE
; *** Registries ***
; A symbol registry is a 5 bytes record with points to the name pool then the
; records list of the register and then the max record count.
SYM_GLOBAL_REGISTRY:
.dw SYM_GLOB_REG, SYM_GLOB_REG+ZASM_REG_BUFSZ
.db ZASM_REG_MAXCNT
SYM_LOCAL_REGISTRY:
.dw SYM_LOC_REG, SYM_LOC_REG+ZASM_LREG_BUFSZ
.db ZASM_LREG_MAXCNT
SYM_CONST_REGISTRY:
.dw SYM_CONST_REG, SYM_CONST_REG+ZASM_REG_BUFSZ
.db ZASM_REG_MAXCNT
; *** Code ***
symInit:
ld ix, SYM_GLOBAL_REGISTRY
call symClear
ld ix, SYM_LOCAL_REGISTRY
call symClear
ld ix, SYM_CONST_REGISTRY
jp symClear
; Sets Z according to whether label in (HL) is local (starts with a dot)
symIsLabelLocal:
ld a, '.'
cp (hl)
ret
symRegisterGlobal:
push ix
ld ix, SYM_GLOBAL_REGISTRY
call symRegister
pop ix
ret
symRegisterLocal:
push ix
ld ix, SYM_LOCAL_REGISTRY
call symRegister
pop ix
ret
symRegisterConst:
push ix
ld ix, SYM_CONST_REGISTRY
call symRegister
pop ix
ret
; Register label in (HL) (minus the ending ":") into the symbol registry in IX
; and set its value in that registry to the value specified in DE.
; If successful, Z is set. Otherwise, Z is unset and A is an error code (ERR_*).
symRegister:
push hl ; --> lvl 1. it's the symbol to add
call _symIsFull
jr z, .outOfMemory
; First, let's get our strlen
call strlen
ld c, a ; save that strlen for later
call _symFind
jr z, .duplicateError
; Is our new name going to make us go out of bounds?
push hl ; --> lvl 2
push de ; --> lvl 3
ld d, 0
ld e, c
add hl, de ; if carry set here, sbc will carry too
ld e, (ix+2) ; DE --> pointer to record list, which is also
ld d, (ix+3) ; the end of names pool
; DE --> names end
sbc hl, de ; compares hl and de destructively
pop de ; <-- lvl 3
pop hl ; <-- lvl 2
jr nc, .outOfMemory ; HL >= DE
; Success. At this point, we have:
; HL -> where we want to add the string
; IY -> target record where the value goes
; DE -> value to register
; SP -> string to register
; Let's start with the record
ld (iy), c ; strlen
ld (iy+1), e
ld (iy+2), d
; Good! now, the string. Destination is in HL, source is in SP
ex de, hl ; dest is in DE
pop hl ; <-- lvl 1. string to register
; Copy HL into DE until we reach null char
call strcpyM
; Last thing: increase record count
ld l, (ix+2)
ld h, (ix+3)
inc (hl)
xor a ; sets Z
ret
.outOfMemory:
pop hl ; <-- lvl 1
ld a, ERR_OOM
jp unsetZ
.duplicateError:
pop hl ; <-- lvl 1
ld a, ERR_DUPSYM
jp unsetZ ; return
; Assuming that IX points to a registry, find name HL in its names and make IY
; point to the corresponding record. If it doesn't find anything, IY will
; conveniently point to the next record after the last, and HL to the next
; name insertion point.
; If we find something, Z is set, otherwise unset.
_symFind:
push de
push bc
call strlen
ld c, a ; save strlen
ex de, hl ; easier if needle is in DE
; IY --> records
ld l, (ix+2)
ld h, (ix+3)
; first byte is count
ld b, (hl)
inc hl ; first record
push hl \ pop iy
; HL --> names
ld l, (ix)
ld h, (ix+1)
; do we have an empty reclist?
xor a
cp b
jr z, .nothing ; zero count? nothing
.loop:
ld a, (iy) ; name len
cp c
jr nz, .skip ; different strlen, can't possibly match. skip
call strncmp
jr z, .end ; match! Z already set, IY and HL placed.
.skip:
; ok, next!
push de ; --> lvl 1
ld de, 0x0003
add iy, de ; faster and shorter than three inc's
ld e, (iy-3) ; offset is also compulsory, so no extra bytes used
; (iy-3) holds the name length of the string just processed
add hl, de ; advance HL by (iy-3) characters
pop de ; <-- lvl 1
djnz .loop
; end of the chain, nothing found
.nothing:
call unsetZ
.end:
pop bc
pop de
ret
; For a given symbol name in (HL), find it in the appropriate symbol register
; and return its value in DE. If (HL) is a local label, the local register is
; searched. Otherwise, the global one. It is assumed that this routine is
; always called when the global registry is selected. Therefore, we always
; reselect it afterwards.
symFindVal:
push ix
call symIsLabelLocal
jr z, .local
; global. Let's try labels first, then consts
push hl ; --> lvl 1. we'll need it again if not found.
ld ix, SYM_GLOBAL_REGISTRY
call _symFind
pop hl ; <-- lvl 1
jr z, .found
ld ix, SYM_CONST_REGISTRY
call _symFind
jr nz, .end
.found:
; Found! let's fetch value
ld e, (iy+1)
ld d, (iy+2)
jr .end
.local:
ld ix, SYM_LOCAL_REGISTRY
call _symFind
jr z, .found
; continue to end
.end:
pop ix
ret
; Clear registry at IX
symClear:
push af
push hl
ld l, (ix+2)
ld h, (ix+3)
; HL --> reclist count
xor a
ld (hl), a
pop hl
pop af
ret
; Returns whether register in IX has reached its capacity.
; Sets Z if full, unset if not.
_symIsFull:
push hl
ld l, (ix+2)
ld h, (ix+3)
ld l, (hl) ; record count
ld a, (ix+4) ; max record count
cp l
pop hl
ret