Skip to content

Commit fa13648

Browse files
committed
Implement tail calls.
1 parent 62ccefe commit fa13648

File tree

5 files changed

+111
-0
lines changed

5 files changed

+111
-0
lines changed

pkg/forth/basic_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,36 @@ func TestPrimitives(t *testing.T) {
164164
asm: wrapMain("4 0 DO I . LOOP"),
165165
expect: "0 1 2 3 ",
166166
},
167+
{
168+
name: "tail call",
169+
asm: `
170+
\ The point of this isn't to test
171+
\ a forth algorithm, it's to test that the
172+
\ compiler is succesfully able to generate
173+
\ a tail call.
174+
\ If it doesn't, the return stack pointer
175+
\ will overflow on the ulp.
176+
177+
\ countdown from n to 0
178+
: countdown ( n -- )
179+
DUP 0= IF
180+
DROP \ we don't care about the result
181+
ELSE
182+
1-
183+
RECURSE \ tail call recursively!
184+
EXIT \ TODO get this to work without the EXIT
185+
THEN
186+
;
187+
188+
: MAIN
189+
DEPTH u. \ print the depth
190+
5 countdown DEPTH u. \ try after a few recursive calls
191+
2000 countdown DEPTH u. \ crank it up to 11
192+
ESP.DONE
193+
;
194+
`,
195+
expect: "0 0 0 ",
196+
},
167197
}
168198
r := asm.Runner{}
169199
r.SetDefaults()

pkg/forth/cell.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,42 @@ func (c *CellBranch0) IsRecursive(check *WordForth) bool {
332332
func (c *CellBranch0) String() string {
333333
return fmt.Sprintf("Branch0{%p}", c.dest)
334334
}
335+
336+
// Used during an optimization pass to add
337+
// tail calls.
338+
type CellTailCall struct {
339+
dest *WordForth
340+
}
341+
342+
func (c *CellTailCall) Execute(vm *VirtualMachine) error {
343+
return fmt.Errorf("Cannot directly execute a tail call, please file a bug repot")
344+
}
345+
346+
func (c *CellTailCall) AddToList(u *Ulp) error {
347+
// Add the destination to the list.
348+
return c.dest.AddToList(u)
349+
}
350+
351+
func (c *CellTailCall) BuildExecution(u *Ulp) (string, error) {
352+
switch u.compileTarget {
353+
case UlpCompileTargetToken:
354+
return fmt.Sprintf(".int %s + 0x8000", c.dest.Entry.ulpName), nil
355+
case UlpCompileTargetSubroutine:
356+
// put the address after the docol
357+
return fmt.Sprintf("move r2, %s + 1\r\njump r2", c.dest.Entry.ulpName), nil
358+
default:
359+
return "", fmt.Errorf("Unknown compile target %d, please file a bug report", u.compileTarget)
360+
}
361+
}
362+
363+
func (c *CellTailCall) OutputReference(u *Ulp) (string, error) {
364+
return "", fmt.Errorf("Cannot refer to a tail call, please file a bug report")
365+
}
366+
367+
func (c *CellTailCall) IsRecursive(check *WordForth) bool {
368+
return c.dest.IsRecursive(check)
369+
}
370+
371+
func (c *CellTailCall) String() string {
372+
return fmt.Sprintf("TailCall{%s}", c.dest.Entry.Name)
373+
}

pkg/forth/flag.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ type Flag struct {
1616
addedToList bool // This word is already added to the output list.
1717
recursive bool // This Forth word is recursive.
1818
visited bool // This Forth word has already been visited in this optimization pass.
19+
isExit bool // This assembly word is the EXIT word.
1920
}

pkg/forth/primitive.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,9 @@ func PrimitiveSetup(vm *VirtualMachine) error {
10581058
},
10591059
{
10601060
name: "EXIT",
1061+
flag: Flag{
1062+
isExit: true,
1063+
},
10611064
goFunc: func(vm *VirtualMachine, entry *DictionaryEntry) error {
10621065
ret, err := vm.ReturnStack.Pop()
10631066
if err != nil {

pkg/forth/ulpBuild.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ func (u *Ulp) optimize() error {
207207
if err != nil {
208208
return errors.Join(fmt.Errorf("could not tag recursion during optimization"), err)
209209
}
210+
err = u.putTailCalls()
211+
if err != nil {
212+
return errors.Join(fmt.Errorf("could not create tail calls, please file a bug report"), err)
213+
}
210214
return nil
211215
}
212216

@@ -228,6 +232,40 @@ func (u *Ulp) tagRecursion() error {
228232
return nil
229233
}
230234

235+
// Put tail calls as an optimization.
236+
func (u *Ulp) putTailCalls() error {
237+
for _, w := range u.forthWords {
238+
length := len(w.Cells) - 1
239+
for i := 0; i < length; i++ {
240+
// check if the first cell is a forth word
241+
firstAddress, ok := w.Cells[i].(CellAddress)
242+
if !ok {
243+
continue
244+
}
245+
word, ok := firstAddress.Entry.Word.(*WordForth)
246+
if !ok {
247+
continue
248+
}
249+
// check if the second cell is an EXIT
250+
secondAddress, ok := w.Cells[i+1].(CellAddress)
251+
if !ok {
252+
continue
253+
}
254+
if !secondAddress.Entry.Flag.isExit {
255+
continue
256+
}
257+
// replace both cells with the tail call!
258+
tailCall := CellTailCall{word} // create the tail call
259+
w.Cells[i] = &tailCall // replace the word
260+
before := w.Cells[:i+1] // get the cells before, including the tail call
261+
after := w.Cells[i+2:] // get the cells after, excluding the exit
262+
w.Cells = append(before, after...) // recreate the list
263+
length -= 1 // shift the length
264+
}
265+
}
266+
return nil
267+
}
268+
231269
func (u *Ulp) clearVisited() {
232270
for _, w := range u.forthWords {
233271
w.Entry.ClearVisited()

0 commit comments

Comments
 (0)