Dave's nasmjf Dev Log 11

Created: 2022-07-22

nasmjf home

This is an entry in my developer’s log series written between December 2021 and August 2022 (started project in September). I wrote these as I completed my port of the JONESFORTH assembly language Forth interpreter.

← Previous 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Next →
    I find it hard to believe that I've discovered a
    bug in JonesFORTH, but it seems to me that my fix
    of simply removing FETCH from colon so that the
    word header that receives the HIDDEN flag is the
    one being compiled, not the previous one in the
    linked list (dictionary).

        !!!!!!!!!!!!!!!!!!!! Update !!!!!!!!!!!!!!!!!!!!
        ! In log19.txt, I realize that my variable     !
        ! handling is wrong. Variables should leave    !
        ! their addresses on the stack, not their      !
        ! values! We need FETCH to get the value from  !
        ! the address!                                 !
        !!!!!!!!!!!!!!!!!!!! Update !!!!!!!!!!!!!!!!!!!!


    So unless I discover otherwise, I've changed it
    from


        dd LATEST, FETCH, HIDDEN ; Make the word hidden while it's being compiled.

    to

        dd LATEST, HIDDEN ; Make the word hidden while it's being compiled.

(gdb) break code_LATEST
Breakpoint 2 at 0x804939d: file nasmjf.asm, line 772.
(gdb) c
Continuing.
: FIVE 5 ;

Breakpoint 2, code_LATEST () at nasmjf.asm:772
772             push dword [var_%4]
27          lodsd     ; NEXT: Load from memory into eax, inc esi to point to next word.
28          jmp [eax] ; Jump to whatever code we're now pointing at.
code_HIDDEN () at nasmjf.asm:636
636         pop edi                 ; Dictionary entry, first byte is link
637         add edi, 4              ; Move to name/flags byte.

    If my fix works, then the word "FIVE" that is being compiled
    will be the one we're hiding until compilation is complete:

(gdb) x/bx $edi
0x804e004:      0x04
(gdb) x/bt $edi
0x804e004:      00000100
(gdb) x/4c $edi+1
0x804e005:      70 'F'  73 'I'  86 'V'  69 'E'

    Yup, looks good! And the hidden flag?

638         xor [edi], word F_HIDDEN  ; Toggle the HIDDEN bit in place.
(gdb) x/bt $edi
0x804e004:      00100100

    Looks good also.

    Next, let's implement BRANCH!
    Turns out, it's another crazy simple "hack" with
    the esi register (like LIT), this time, adding an
    offset to esi itself to change which word NEXT
    should execute next.

(gdb) break code_BRANCH
Breakpoint 2 at 0x804904e: file nasmjf.asm, line 237.
(gdb) c
Continuing.
: FIVE 5 ;

Breakpoint 2, code_BRANCH () at nasmjf.asm:237
237         add esi, [esi]          ; add the offset to the instruction pointer

    The line above is the entirety of BRANCH!
    So before that line executes, esi should point
    to the next "word" in the definition of QUIT, which
    is actually not a word at all, but an offset to
    add to the current value of esi itself.

    So we should see QUIT + <some offset> in esi

(gdb) info sym $esi
QUIT + 20 in section .data of /home/dave/nasmjf/nasmjf

    And the value at esi should be our negative offset, -8.

(gdb) p *$esi
$2 = -8

    What will -8 branch to? Here's the entire definition
    of QUIT:

        DEFWORD "QUIT",4,0,QUIT
        dd R0           ; push R0 (addr of top of return stack)
        dd RSPSTORE     ; store R0 in return stack pointer (ebp)
        dd INTERPRET    ; interpret the next word
        dd BRANCH,-8    ; and loop (indefinitely)

    esi was pointing at the "double" (4 bytes) containing
    the value -8. Every word linked here is also 4 bytes.

        esi   points to -8          (QUIT + 20)
        esi-4 points to BRANCH      (QUIT + 16)
        esi-8 points to INTERPRET   (QUIT + 12)

    So BRANCH here makes a conditionless loop over INTERPRET.

(gdb) info sym $esi
QUIT + 12 in section .data of /home/dave/nasmjf/nasmjf
27          lodsd     ; NEXT: Load from memory into eax, inc esi to point to next word.
28          jmp [eax] ; Jump to whatever code we're now pointing at.
code_INTERPRET () at nasmjf.asm:244
244         call _WORD              ; Returns %ecx = length, %edi = pointer to word.

    This is getting exciting. So we shoud be able to compile
    a word and then execute it. Let's see if my "FIVE" literal
    works:

(gdb) c
Continuing.
FIVE
PARSE ERROR: FIVE

    Oh no! It didn't find FIVE. So something in the
    compilation of ": FIVE 5 ;" isn't quite right yet.

    Wait a dang second.

    I've removed the FETCH so it's no longer setting the
    wrong word header to hidden during compilation. But
    I didn't make the same change in SEMICOLON (;) to
    unhide the correct word!

        dd LATEST, FETCH, HIDDEN ; Unhide word now that it's been compiled.

    Removing the FETCH so we have

        dd LATEST, HIDDEN ; Unhide word now that it's been compiled.

    and let's see what happens.

(gdb) break code_INTERPRET
Breakpoint 2, code_INTERPRET () at nasmjf.asm:244
(gdb) c
Continuing.
: FIVE 5 ;

Breakpoint 2, code_INTERPRET () at nasmjf.asm:244
(gdb) c
Continuing.
FIVE

Breakpoint 2, code_INTERPRET () at nasmjf.asm:244
(gdb) p/x $esp
$3 = 0xbffff98c
(gdb) x/x $esp
0xbffff98c:     0x00000005

    Using my compiled word FIVE put a 5 on the stack!
    It works!

    I think I've earned a bit of fun, so the next thing
    I'll implement is EMIT, which will let me actually
    display characters on the screen!
← Previous 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Next →