1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2024-06-18 03:29:32 +00:00
SixtyPical/tests/SixtyPical Fallthru.md

4.9 KiB

SixtyPical Fallthru

This is a test suite, written in Falderal format, for SixtyPical's ability to detect which routines make tail calls to other routines, and thus can be re-arranged to simply "fall through" to them.

The theory is as follows.

SixtyPical supports a goto, but it can only appear in tail position. If a routine r1 ends with a unique goto to a fixed routine r2 it is said to potentially fall through to r2.

A unique goto means that there are not multiple different gotos in tail position (which can happen if, for example, an if is the last thing in a routine, and each branch of that if ends with a different goto.)

A fixed routine means, a routine which is known at compile time, not a goto through a vector.

Consider the set R of all routines in the program.

Every routine r1 ∈ R either potentially falls through to a single routine r2 ∈ R (r2 ≠ r1) or it does not potentially fall through to any routine. We can say out(r1) = {r2} or out(r1) = ∅.

Every routine r ∈ R in this set also has a set of zero or more routines from which it is potentially falled through to by. Call this in(r). It is the case that out(r1) = {r2} → r1 ∈ in(r2).

We can trace out the connections by following the in- or our- sets of a given routine. Because each routine potentially falls through to only a single routine, the structures we find will be tree-like, not DAG-like.

But they do permit cycles.

So, we first break those cycles. We will be left with out() sets which are disjoint trees, i.e. if r1 ∈ in(r2), then r1 ∉ in(r3) for all r3 ≠ r2.

We then follow an algorithm something like this. Treat R as a mutable set and start with an empty list L. Then,

  • Pick a routine r from R where out(r) = ∅.
  • Find the longest chain of routines r1,r2,...rn in R where out(r1) = {r2}, out(r2} = {r3}, ... out(rn-1) = {rn}, and rn = r.
  • Remove (r1,r2,...,rn) from R and append them to L in that order. Mark (r1,r2,...rn-1) as "will have their final goto removed."
  • Repeat until R is empty.

When times comes to generate code, generate it in the order given by L.

-> Functionality "Dump fallthru info for SixtyPical program" is implemented by
-> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)"

-> Tests for functionality "Dump fallthru info for SixtyPical program"

A single routine, obviously, falls through to nothing and has nothing fall through to it.

| define main routine
| {
| }
= {}

If main does a goto foo, then it can fall through to foo.

| define foo routine trashes a, z, n
| {
|     ld a, 0
| }
| 
| define main routine trashes a, z, n
| {
|     goto foo
| }
= {
=     "foo": [
=         "main"
=     ]
= }

More than one routine can fall through to a routine.

If main does a goto foo, then it can fall through to foo.

| define foo routine trashes a, z, n
| {
|     ld a, 0
| }
| 
| define bar routine trashes a, z, n
| {
|     ld a, 0
|     goto foo
| }
| 
| define main routine trashes a, z, n
| {
|     goto foo
| }
= {
=     "foo": [
=         "bar", 
=         "main"
=     ]
= }

There is nothing stopping two routines from tail-calling each other, but we will only be able to make one of them, at most, fall through to the other.

| define foo routine trashes a, z, n
| {
|     ld a, 0
|     goto bar
| }
| 
| define bar routine trashes a, z, n
| {
|     ld a, 0
|     goto foo
| }
| 
| define main routine trashes a, z, n
| {
| }
= {
=     "bar": [
=         "foo"
=     ], 
=     "foo": [
=         "bar"
=     ]
= }
= *** cycles found:
= [
=     "bar", 
=     "foo"
= ]
= *** after breaking cycles:
= {
=     "bar": [
=         "foo"
=     ]
= }

If a routine does two tail calls (which is possible because they can be in different branches of an if) it cannot fall through to another routine.

| define foo routine trashes a, z, n
| {
|     ld a, 0
| }
| 
| define bar routine trashes a, z, n
| {
|     ld a, 0
| }
| 
| define main routine inputs z trashes a, z, n
| {
|     if z {
|         goto foo
|     } else {
|         goto bar
|     }
| }
= {}

Similarly, a tail call to a vector can't be turned into a fallthru, because we don't necessarily know what actual routine the vector contains.

| vector routine trashes a, z, n
|   vec
| 
| define foo routine trashes a, z, n
| {
|     ld a, 0
| }
| 
| define bar routine trashes a, z, n
| {
|     ld a, 0
| }
| 
| define main routine outputs vec trashes a, z, n
| {
|     copy bar, vec
|     goto vec
| }
= {}