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 goto
s 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 cycle:
= {
= "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
| }
= {}