diff --git a/README.md b/README.md index a727eb2..ab88744 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,6 @@ See the [TUTORIALS](docs/TUTORIALS.md) for detailed download and flashing inform # TODO -- [ ] Implement bounds checks for stacks and dictionary -- [ ] Add example Forth code to turn it into a "real" Forth (ex: `[`, `]`, `branch`, etc) - [ ] Code cleanup and optimizations # Contributing @@ -45,6 +43,14 @@ Please create a pull-request or [open an issue](https://github.com/aw/picolisp-k # Changelog +## 0.3 (TIB) + + * Implement bounds checks for stacks + * Implement bounds checks for user dictionary + * Add better error messages + * Add detailed documentation in [docs](docs/) + * Add `djb2.c` to generate a word's hash locally + ## 0.2 (2023-01-10) * Fix issue #9 - Handling of carriage return diff --git a/docs/TUTORIALS.md b/docs/TUTORIALS.md index d325197..87cde4f 100644 --- a/docs/TUTORIALS.md +++ b/docs/TUTORIALS.md @@ -87,7 +87,7 @@ $ make Additional build options are explained in the [HOWTO](HOWTO.md) section. -The firmware file is called `fiveforths.bin` and is **under 2 KBytes** as of _release v0.1_ since _January 08, 2023_. +The firmware file is called `fiveforths.bin` and is **slightly over 2 KBytes** as of _release v0.3_ since _January 16, 2023_. ### Flash it diff --git a/src/01-variables-constants.s b/src/01-variables-constants.s index 84b128b..03d39f5 100644 --- a/src/01-variables-constants.s +++ b/src/01-variables-constants.s @@ -2,7 +2,7 @@ # Variables and constants ## -.equ FORTH_VERSION, 2 +.equ FORTH_VERSION, 3 ## # Memory map diff --git a/src/02-macros.s b/src/02-macros.s index 90d2da1..efd293a 100644 --- a/src/02-macros.s +++ b/src/02-macros.s @@ -18,12 +18,18 @@ # push register to top of stack and move DSP .macro PUSH reg + li t0, RSP_TOP+(1*CELL) # load address of top of DSP with -4 offset (needs 1 CELL) + blt sp, t0, err_overflow # jump to error handler if stack overflow + sw \reg, -CELL(sp) # store the value in the register to the top of the DSP addi sp, sp, -CELL # move the DSP down by 1 cell .endm # push variable to top of stack .macro PUSHVAR var + li t0, RSP_TOP+(1*CELL) # load address of top of DSP with -4 offset (needs 1 CELL) + blt sp, t0, err_overflow # jump to error handler if stack overflow + li t0, \var # load variable into temporary sw t0, -CELL(sp) # store the variable value to the top of the DSP addi sp, sp, -CELL # move the DSP down by 1 cell @@ -31,12 +37,18 @@ # push register to return stack .macro PUSHRSP reg + li t0, RSP_TOP+(1*CELL) # load address of top of RSP with -4 offset (needs 1 CELL) + blt s2, t0, err_overflow # jump to error handler if stack overflow + sw \reg, -CELL(s2) # store value from register into RSP addi s2, s2, -CELL # decrement RSP by 1 cell .endm # pop top of return stack to register .macro POPRSP reg + li t0, RSP_TOP-(1*CELL) # load address of top of RSP with -4 offset (needs 1 CELL) + bgt s2, t0, err_underflow # jump to error handler if stack underflow + lw \reg, 0(s2) # load value from RSP into register addi s2, s2, CELL # increment RSP by 1 cell .endm @@ -67,3 +79,13 @@ li t0, \char # load character into temporary beq a0, t0, \dest # jump to the destination if the char matches .endm + +# print a message +.macro print_error name, size, jump + .balign CELL + err_\name : + la a1, msg_\name # load string message + addi a2, a1, \size # load string length + call uart_print # call uart print function + j \jump # jump when print returns +.endm diff --git a/src/05-internal-functions.s b/src/05-internal-functions.s index e9ccae2..677baad 100644 --- a/src/05-internal-functions.s +++ b/src/05-internal-functions.s @@ -112,7 +112,7 @@ lookup_error: # check the STATE li t0, STATE # load the address of the STATE variable into temporary lw t0, 0(t0) # load the current state into a temporary - beqz t0, error # if in execute mode (STATE = 0), jump to error handler to reset + beqz t0, err_error # if in execute mode (STATE = 0), jump to error handler to reset # update HERE since we're in compile mode li t0, HERE # load HERE variable into temporary @@ -123,6 +123,6 @@ lookup_error: lw t1, 0(t2) # load LATEST variable value into temporary sw t1, 0(t0) # store LATEST word into LATEST variable - j error # jump to error handler + j err_error # jump to error handler lookup_done: ret diff --git a/src/07-error-handling.s b/src/07-error-handling.s index 259ac05..02ca7a5 100644 --- a/src/07-error-handling.s +++ b/src/07-error-handling.s @@ -2,30 +2,20 @@ # Error handling ## -.balign CELL -# print an error message to the UART -error: - la a1, msg_error # load string message - addi a2, a1, 4 # load string length - call uart_print # call uart print function - j reset # jump to reset the stack pointers, variables, etc before jumping to the interpreter - -.balign CELL -# print an OK message to the UART -ok: - la a1, msg_ok # load string message - addi a2, a1, 6 # load string length - call uart_print # call uart print function - j tib_init # jump to reset the terminal input buffer before jumping to the interpreter - -.balign CELL -# print a REBOOTING message to the UART -reboot: - la a1, msg_reboot # load string message - addi a2, a1, 12 # load string length - call uart_print # call uart print function - j _start # reboot when print returns +print_error error, 4, reset +print_error ok, 6, tib_init +print_error reboot, 16, _start +print_error tib, 14, reset +print_error mem, 16, reset +print_error token, 14, reset +print_error underflow, 20, reset +print_error overflow, 20, reset msg_error: .ascii " ?\n" msg_ok: .ascii " ok\n" -msg_reboot: .ascii " rebooting\n" +msg_reboot: .ascii " ok rebooting\n" +msg_tib: .ascii " ? tib full\n" +msg_mem: .ascii " ? memory full\n" +msg_token: .ascii " ? big token\n" +msg_underflow: .ascii " ? stack underflow\n" +msg_overflow: .ascii " ? stack overflow\n" diff --git a/src/08-forth-primitives.s b/src/08-forth-primitives.s index f5d56e9..758580a 100644 --- a/src/08-forth-primitives.s +++ b/src/08-forth-primitives.s @@ -6,7 +6,7 @@ # reboot ( -- ) # Reboot the entire system and initialize memory defcode "reboot", 0x06266b70, REBOOT, NULL - j reboot # jump to reboot + j err_reboot # jump to reboot # @ ( addr -- x ) Fetch memory at addr defcode "@", 0x0102b5e5, FETCH, REBOOT @@ -17,6 +17,9 @@ defcode "@", 0x0102b5e5, FETCH, REBOOT # ! ( x addr -- ) Store x at addr defcode "!", 0x0102b5c6, STORE, FETCH + li t0, DSP_TOP-(2*CELL) # load address of top of DSP with -8 offset (needs 2 CELLs) + bgt sp, t0, err_underflow # jump to error handler if stack underflow + lw t0, 0(sp) # load the DSP value (x) into temporary lw t1, CELL(sp) # load the DSP value (addr) into temporary sw t0, 0(t1) # store x into addr @@ -43,6 +46,9 @@ defcode "0=", 0x025970b2, ZEQU, RSPFETCH # + ( x1 x2 -- n ) Add the two values at the top of the stack defcode "+", 0x0102b5d0, ADD, ZEQU + li t0, DSP_TOP-(2*CELL) # load address of top of DSP with -8 offset (needs 2 CELLs) + bgt sp, t0, err_underflow # jump to error handler if stack underflow + POP t0 # pop DSP value (x1) into temporary lw t1, 0(sp) # load DSP value (x2) into temporary add t0, t0, t1 # add the two values @@ -51,6 +57,9 @@ defcode "+", 0x0102b5d0, ADD, ZEQU # nand ( x1 x2 -- n ) Bitwise NAND the two values at the top of the stack defcode "nand", 0x049b0c66, NAND, ADD + li t0, DSP_TOP-(2*CELL) # load address of top of DSP with -8 offset (needs 2 CELLs) + bgt sp, t0, err_underflow # jump to error handler if stack underflow + POP t0 # pop DSP value (x1) into temporary lw t1, 0(sp) # load DSP value (x2) into temporary and t0, t0, t1 # perform bitwise AND of the two values @@ -130,9 +139,9 @@ defcode ":", 0x0102b5df, COLON, LATEST sw t0, 0(t3) # move TOIN to process the next word in the TIB # bounds checks on token size - beqz a1, ok # ok if token size is 0 + beqz a1, err_ok # ok if token size is 0 li t0, 32 # load max token size (2^5 = 32) in temporary - bgtu a1, t0, error # error if token size is greater than 32 + bgtu a1, t0, err_token # error if token size is greater than 32 call djb2_hash # hash the token @@ -151,8 +160,8 @@ defcode ":", 0x0102b5df, COLON, LATEST # bounds check on new word memory location addi t4, t2, 3*CELL # prepare to move the HERE pointer to the end of the word - li t5, PAD # load out of bounds memory address (PAD) - bgt t4, t5, error # error if the memory address is out of bounds + li t5, PAD-4 # load out of bounds memory address (PAD) + bgt t4, t5, err_mem # error if the memory address is out of bounds # update LATEST variable sw t2, 0(t1) # store the current value of HERE into the LATEST variable @@ -192,13 +201,14 @@ defcode ";", 0x8102b5e0, SEMI, COLON # update HERE variable li t0, HERE # copy the memory address of HERE into temporary lw t2, 0(t0) # load the HERE value into temporary - la t1, code_EXIT # load the codeword address into temporary - sw t1, 0(t2) # store the codeword address into HERE # bounds check on the exit memory location + li t3, PAD-4 # load out of bounds memory address (PAD) + bgt t2, t3, compile_error # error if the memory address is out of bounds + + la t1, code_EXIT # load the codeword address into temporary + sw t1, 0(t2) # store the codeword address into HERE addi t2, t2, CELL # prepare to move the HERE pointer by 1 CELL - li t3, PAD # load out of bounds memory address (PAD) - bgt t2, t3, error # error if the memory address is out of bounds # move HERE pointer sw t2, 0(t0) # store the new address of HERE into the HERE variable diff --git a/src/09-interpreter.s b/src/09-interpreter.s index 3b3d940..4f4ac25 100644 --- a/src/09-interpreter.s +++ b/src/09-interpreter.s @@ -37,7 +37,7 @@ skip_send: interpreter_tib: # add the character to the TIB li t4, TIB_TOP # load TIB_TOP memory address - bge a1, t4, error # error if the terminal input buffer is full # FIXME: handle this better + bge a1, t4, err_tib # error if the terminal input buffer is full sb a0, 0(a1) # store the character from W register in the TIB addi a1, a1, 1 # increment TOIN value by 1 li t0, CHAR_NEWLINE # load newline into temporary @@ -82,9 +82,9 @@ process_token: sw t0, 0(t3) # move TOIN to process the next word in the TIB # bounds checks on token size - beqz a1, ok # ok if token size is 0 + beqz a1, err_ok # ok if token size is 0 li t0, 32 # load max token size (2^5 = 32) in temporary - bgtu a1, t0, error # error if token size is greater than 32 + bgtu a1, t0, err_token # error if token size is greater than 32 # check if the token is a number mv t5, a0 # save a0 temporarily @@ -131,22 +131,46 @@ compile: addi t0, a1, 2*CELL # increment the address of the found word by 8 to get the codeword address li t1, HERE # load HERE variable into temporary lw t2, 0(t1) # load HERE value into temporary + + # bounds check on codeword memory location + li t3, PAD-4 # load out of bounds memory address (PAD) + bgt t2, t3, compile_error # error if the memory address is out of bounds + sw t0, 0(t2) # write the address of the codeword to the current definition addi t0, t2, CELL # increment HERE by 4 sw t0, 0(t1) # store new HERE address compile_done: j process_token +compile_error: + li t0, HERE # load HERE variable into temporary + li t1, LATEST # load LATEST variable into temporary + lw t2, 0(t1) # load the address of the previous word into temporary (LATEST) + + # update HERE + sw t2, 0(t0) # store the address of LATEST back into HERE + + # update LATEST + lw t0, 0(t2) # load LATEST variable value into temporary + sw t0, 0(t1) # store LATEST word into LATEST variable + + j err_mem # jump to error handler push_number: # push to stack if STATE = 0 (execute) li t1, STATE # load the address of the STATE variable into temporary lw t1, 0(t1) # load the current state into a temporary beqz t1, push_stack # if in execute mode, push the number to the stack + # push to memory if STATE = 1 (compile) la t0, code_LIT # load the codeword address of LIT into temporary li t1, HERE # load HERE variable into temporary lw t2, 0(t1) # load HERE value into temporary + + # bounds check on codeword memory location + li t3, PAD-8 # load out of bounds memory address (PAD) + bgt t2, t3, compile_error # error if the memory address is out of bounds + sw t0, 0(t2) # write the codeword address of LIT to memory (HERE) sw a0, 4(t2) # write the number from W to the next memory cell (HERE+4) addi t0, t2, 2*CELL # increment HERE by 2 CELLs