Development Tips

Just a few handy debug methods I’ve developed along the way …

PR: Monitor Stack Values and Program Flow

Forth is definitely ALL about the Stack and visualising the Stack to design code has been, and continues to be my main challenge.

This Stack visualisation method is really simple, yet I use it all the time when designing code and fixing bugs. Used with VIM Mapping, it’s fast and easy to use. I’m still a Forth beginner and I need all the help I can get, so being able to rapidly insert print statements to follow the stack and program flow, plus show nothing is left on the stack has made Forth a lot easier for me.

: pr ( identifier -- identifier print stack ) . .s cr ;           \ A debugging stack printer

Vim Key Mapping

" insert "<linenumber> pr" only in INSERT MODE for Forth debugging
map! _P <C-R>=printf("%d pr", line('.'))<CR>

In Insert mode I move the cursor to the source code line I want the stack printed then I use the key sequence “ _p” and Vim inserts the line number followed by “pr” as in the example below.

PR: Usage Example

\ Program Name: pr-example.fs  for Mecrisp-Stellaris by Matthias Koch and licensed under the GPL
\ This program: 'pr' is a debugging 'print statement' 
\ Hardware: STM32F0 Discovery board
\ Copyright 2017 t.porter <terry@tjporter.com.au> and licensed under the GPL


\ This is the very simple "pr" program. I use it with a VIM map which inserts the program line number before the "pr" as seen in the example below.
 : pr . .s cr ;	 \ print the stack and follow program flow for debugging

\ Example program showing "pr" stack printing interspersed within the "pr-example" program

 :  pr-example cr
   1 pr	 								   
      4 0 do
   2 pr	 
     I 
   3 pr
       dup dup
   4 pr
     ." Loop#" . cr
   5 pr
      +
   6 pr
      drop
      loop
   7 pr
 ;


\ ...................... screenpic .....................
\   pr-example 
\ 
\   1 [0 ] 0 
\   2 [0 ] 0 
\   3 [1 ] 0 0 
\   4 [3 ] 0 0 0 0 
\   Loop#0 
\   5 [2 ] 0 0 0 
\   6 [1 ] 0 0 
\   2 [0 ] 0 
\   3 [1 ] 0 1 
\   4 [3 ] 0 1 1 1 
\   Loop#1 
\   5 [2 ] 0 1 1 
\   6 [1 ] 0 2 
\   2 [0 ] 0 
\   3 [1 ] 0 2 
\   4 [3 ] 0 2 2 2 
\   Loop#2 
\   5 [2 ] 0 2 2 
\   6 [1 ] 0 4 
\   2 [0 ] 0 
\   3 [1 ] 0 3 
\   4 [3 ] 0 3 3 3 
\   Loop#3 
\   5 [2 ] 0 3 3 
\   6 [1 ] 0 6 
\   7 [0 ] 0
\    ok.
\ ...................... screenpic .....................

Balancing The Return Stack

Forth does have a ‘Local Variable’, it’s called The ‘Return Stack’, but it must be balanced or your program will crash.

Note

“Balancing the Return Stack” means making sure that the number of >R equals the number of R> within the same definition. Special care is needed with looping.

Why use the Return Stack to as a Local Variable, why not just use a Global Variable ?

  1. The example Split program below saves 24 Bytes over Split2 and in Embedded, memory is a finite resource and costly to increase.

  2. I prefer Words to be self contained if possible

  3. The challenge of not using Global Variables

Warning

It’s often convenient to be able to put a stack item temporarily in the return stack, but the following rules must be observed because if the Return Stack isn’t balanced, this is very bad and if it doesn’t crash now, it will cause problems later.

  • Data put on the return stack must be taken back within the same word.

  • Data put on the return stack outside a ?DO (DO) … LOOP (+LOOP) cannot be accessed within the loop.

  • Data put on the return stack within a ?DO (DO) … LOOP (+LOOP) must be taken back before leaving the loop.

  • Data cannot be on the return stack when executing I or J in a loop.

Split Program

This program splits a number into digits, and utilises the Return Stack as a local variable.

: split
   >R                      \ Save the input on the Return Stack
   BEGIN
       R> ?DUP 0 > WHILE   \ Fetch from the Return Stack, duplicate because " 0 >" and "/MOD" both use it, then continue WHILE greater than zero. ?DUP is used instead of DUP to prevent a zero being left on the stack after the program has completed.
       10 /MOD >R . cr     \ divide by 10 and save the result on the Return Stack, print each remainder on a new line
   REPEAT
;

\ ------- Split program output ---------- \
123456789 split

9
8
7
6
5
4
3
2
1

Note

At first glance, the Return Stack doesn’t look balanced does it ?

How To Determine if The Return Stack is Balanced

In this case, I used a variable (rb) to tally my Return Stack pushes and pulls. A ‘rb’ total of 0 shows the Return Stack is balanced.

Return Stack Operation

code

Description

Push

>R rb @ 1+ rb !

Add one to ‘rb’

Pull

R> rb @ 1- rb !

Subtract one from ‘rb’

Return Stack Accounting

Every Return Stack ADD is worth 1 and REMOVE is worth -1. A total of 0 shows the Return Stack is balanced, so no visual comparison is required.

0 variable rb

: split-rbalance cr
>R rb @ 1+ rb !
BEGIN
  R> rb @ 1- rb !
  ?DUP 0 > WHILE
  10 /MOD
  >R rb @ 1+ rb !
  .  cr
REPEAT
cr
rb @ 0= if
."  The Return Stack is balanced " cr cr
else
."  UNBALANCED ! The Return Stack is NOT balanced! rb =  " rb @ . cr cr
then
;

Return Stack Accounting Output

123456789 split-rbalance

 9
 8
 7
 6
 5
 4
 3
 2
 1

 The Return Stack is balanced

ok.

Compare Local Variable vs Return Stack code size

Dissasemble Split

Split is 64 bytes

see split

20000398: B500  push { lr }
2000039A: B440  push { r6 }
2000039C: CF40  ldmia r7 { r6 }
2000039E: BC08  pop { r3 }
200003A0: 3F04  subs r7 #4
200003A2: 603E  str r6 [ r7 #0 ]
200003A4: 461E  mov r6 r3
200003A6: 2E00  cmp r6 #0
200003A8: D001  beq 200003AE
200003AA: 3F04  subs r7 #4
200003AC: 603E  str r6 [ r7 #0 ]
200003AE: 2E00  cmp r6 #0
200003B0: CF40  ldmia r7 { r6 }
200003B2: DD11  ble 200003D8
200003B4: 3F04  subs r7 #4
200003B6: 603E  str r6 [ r7 #0 ]
200003B8: 260A  movs r6 #A
200003BA: 2082  movs r0 #82
200003BC: 0180  lsls r0 r0 #6
200003BE: 3039  adds r0 #39
200003C0: 4780  blx r0  --> /mod
200003C2: B440  push { r6 }
200003C4: CF40  ldmia r7 { r6 }
200003C6: 2086  movs r0 #86
200003C8: 01C0  lsls r0 r0 #7
200003CA: 3021  adds r0 #21
200003CC: 4780  blx r0  --> .
200003CE: 2097  movs r0 #97
200003D0: 0180  lsls r0 r0 #6
200003D2: 3033  adds r0 #33
200003D4: 4780  blx r0  --> cr
200003D6: E7E2  b 2000039E
200003D8: BD00  pop { pc }

Alternative Split2 Progran using Variable

0 variable split-1

: split2
    split-1 !
    BEGIN
        split-1 @ ?DUP 0 > WHILE
        10 /MOD split-1 ! . cr
   REPEAT
;

Dissasemble Split2

Split2 is 88 bytes long

see split2

200003B6: 2080  movs r0 #80
200003B8: 0500  lsls r0 r0 #14
200003BA: 30E0  adds r0 #E0
200003BC: 0080  lsls r0 r0 #2
200003BE: 6246  str r6 [ r0 #24 ]
200003C0: CF40  ldmia r7 { r6 }
200003C2: B500  push { lr }
200003C4: 2080  movs r0 #80
200003C6: 0500  lsls r0 r0 #14
200003C8: 30E0  adds r0 #E0
200003CA: 0080  lsls r0 r0 #2
200003CC: 6A43  ldr r3 [ r0 #24 ]
200003CE: 3F04  subs r7 #4
200003D0: 603E  str r6 [ r7 #0 ]
200003D2: 461E  mov r6 r3
200003D4: 2E00  cmp r6 #0
200003D6: D001  beq 200003DC
200003D8: 3F04  subs r7 #4
200003DA: 603E  str r6 [ r7 #0 ]
200003DC: 2E00  cmp r6 #0
200003DE: CF40  ldmia r7 { r6 }
200003E0: DD15  ble 2000040E
200003E2: 3F04  subs r7 #4
200003E4: 603E  str r6 [ r7 #0 ]
200003E6: 260A  movs r6 #A
200003E8: 2082  movs r0 #82
200003EA: 0180  lsls r0 r0 #6
200003EC: 3039  adds r0 #39
200003EE: 4780  blx r0  --> /mod
200003F0: 2080  movs r0 #80
200003F2: 0500  lsls r0 r0 #14
200003F4: 30E0  adds r0 #E0
200003F6: 0080  lsls r0 r0 #2
200003F8: 6246  str r6 [ r0 #24 ]
200003FA: CF40  ldmia r7 { r6 }
200003FC: 2086  movs r0 #86
200003FE: 01C0  lsls r0 r0 #7
20000400: 3021  adds r0 #21
20000402: 4780  blx r0  --> .
20000404: 2097  movs r0 #97
20000406: 0180  lsls r0 r0 #6
20000408: 3033  adds r0 #33
2000040A: 4780  blx r0  --> cr
2000040C: E7DA  b 200003C4
2000040E: BD00  pop { pc }