2018-03-29 15:44:06 +00:00
|
|
|
|
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.
|
|
|
|
|
|
2018-04-04 09:15:06 +00:00
|
|
|
|
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.
|
|
|
|
|
|
2018-04-05 08:57:14 +00:00
|
|
|
|
Consider the set R of all available routines in the program.
|
2018-04-04 09:15:06 +00:00
|
|
|
|
|
2018-04-05 08:57:14 +00:00
|
|
|
|
Every routine either potentially falls through to a single other routine
|
|
|
|
|
or it does not potentially fall through to any routine.
|
2018-04-04 09:15:06 +00:00
|
|
|
|
|
2018-04-05 08:57:14 +00:00
|
|
|
|
More formally, we can say
|
2018-04-04 09:15:06 +00:00
|
|
|
|
|
2018-04-05 13:10:04 +00:00
|
|
|
|
> fall : R → R ∪ {nil}, fall(r) ≠ r
|
2018-04-04 09:15:06 +00:00
|
|
|
|
|
2018-04-05 08:57:14 +00:00
|
|
|
|
where `nil` is an atom that represents no routine.
|
2018-04-04 09:15:06 +00:00
|
|
|
|
|
2018-04-05 08:57:14 +00:00
|
|
|
|
Now consider an operation chain() vaguely similar to a transitive closure
|
|
|
|
|
on fall(). Starting with r, we construct a list of r, fall(r),
|
|
|
|
|
fall(fall(r)), ... with the following restrictions:
|
2018-04-04 14:09:48 +00:00
|
|
|
|
|
2018-04-05 08:57:14 +00:00
|
|
|
|
- we stop when we reach `nil` (because fall(`nil`) is not defined)
|
|
|
|
|
- we stop when we see an element that is not in R.
|
|
|
|
|
- we stop when we see an element that we have already added to the
|
|
|
|
|
list (this is to prevent infinite lists due to cycles.)
|
2018-04-04 09:15:06 +00:00
|
|
|
|
|
2018-04-05 08:57:14 +00:00
|
|
|
|
With these definitions, our algorithm is something like this.
|
2018-04-04 09:15:06 +00:00
|
|
|
|
|
2018-04-05 08:57:14 +00:00
|
|
|
|
Treat R as a mutable set and start with an empty list of lists L. Then,
|
|
|
|
|
|
|
|
|
|
- For all r ∈ R, find all chain(r).
|
|
|
|
|
- Pick a longest such chain. Call it C.
|
|
|
|
|
- Append C to L.
|
|
|
|
|
- Remove all elements occurring in C, from R.
|
|
|
|
|
- Repeat until R is empty.
|
2018-04-04 09:15:06 +00:00
|
|
|
|
|
|
|
|
|
When times comes to generate code, generate it in the order given by L.
|
2018-04-05 08:57:14 +00:00
|
|
|
|
In addition, each sublist in L represents a number of routines to
|
|
|
|
|
generate; all except the final routine in such a sublist need not have
|
|
|
|
|
any jump instruction generated for its final `goto`.
|
|
|
|
|
|
|
|
|
|
The tests in this document test against the list L.
|
|
|
|
|
|
|
|
|
|
Note that this optimization is a feature of the SixtyPical's reference
|
|
|
|
|
compiler, not the language. So an implementation is not required
|
|
|
|
|
to pass these tests to be considered an implementation of SixtyPical.
|
2018-04-04 09:15:06 +00:00
|
|
|
|
|
2018-03-29 15:44:06 +00:00
|
|
|
|
[Falderal]: http://catseye.tc/node/Falderal
|
|
|
|
|
|
2018-04-04 10:54:50 +00:00
|
|
|
|
-> 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)"
|
2018-03-29 15:44:06 +00:00
|
|
|
|
|
2018-04-05 09:52:14 +00:00
|
|
|
|
-> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by
|
2018-04-23 12:18:01 +00:00
|
|
|
|
-> shell command "bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
2018-04-05 09:52:14 +00:00
|
|
|
|
|
2018-04-04 10:54:50 +00:00
|
|
|
|
-> Tests for functionality "Dump fallthru info for SixtyPical program"
|
2018-03-29 15:44:06 +00:00
|
|
|
|
|
|
|
|
|
A single routine, obviously, falls through to nothing and has nothing fall
|
|
|
|
|
through to it.
|
|
|
|
|
|
|
|
|
|
| define main routine
|
|
|
|
|
| {
|
|
|
|
|
| }
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= [
|
|
|
|
|
= [
|
|
|
|
|
= "main"
|
|
|
|
|
= ]
|
|
|
|
|
= ]
|
2018-03-29 15:44:06 +00:00
|
|
|
|
|
2018-04-06 12:24:04 +00:00
|
|
|
|
If `main` does a `goto foo`, then it can fall through to `foo`.
|
2018-03-29 15:44:06 +00:00
|
|
|
|
|
|
|
|
|
| define foo routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 0
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define main routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| goto foo
|
|
|
|
|
| }
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= [
|
|
|
|
|
= [
|
2018-04-04 16:05:48 +00:00
|
|
|
|
= "main",
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= "foo"
|
|
|
|
|
= ]
|
|
|
|
|
= ]
|
2018-03-29 15:44:06 +00:00
|
|
|
|
|
2018-04-04 16:05:48 +00:00
|
|
|
|
More than one routine can fall through to a routine. We pick one
|
|
|
|
|
of them to fall through, when selecting the order of routines.
|
2018-03-29 15:44:06 +00:00
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
| }
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= [
|
|
|
|
|
= [
|
2018-04-05 08:57:14 +00:00
|
|
|
|
= "main",
|
|
|
|
|
= "foo"
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= ],
|
|
|
|
|
= [
|
2018-04-05 08:57:14 +00:00
|
|
|
|
= "bar"
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= ]
|
|
|
|
|
= ]
|
2018-03-29 15:44:06 +00:00
|
|
|
|
|
2018-04-06 12:24:04 +00:00
|
|
|
|
Because `main` is always serialized first (so that the entry
|
|
|
|
|
point of the entire program appears at the beginning of the code),
|
|
|
|
|
nothing ever falls through to `main`.
|
|
|
|
|
|
|
|
|
|
| define foo routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 0
|
|
|
|
|
| goto main
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define main routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 1
|
|
|
|
|
| }
|
|
|
|
|
= [
|
|
|
|
|
= [
|
|
|
|
|
= "main"
|
|
|
|
|
= ],
|
|
|
|
|
= [
|
|
|
|
|
= "foo"
|
|
|
|
|
= ]
|
|
|
|
|
= ]
|
|
|
|
|
|
2018-03-29 15:44:06 +00:00
|
|
|
|
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
|
|
|
|
|
| {
|
|
|
|
|
| }
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= [
|
|
|
|
|
= [
|
2018-04-04 16:55:59 +00:00
|
|
|
|
= "main"
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= ],
|
|
|
|
|
= [
|
2018-04-05 08:57:14 +00:00
|
|
|
|
= "bar",
|
|
|
|
|
= "foo"
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= ]
|
|
|
|
|
= ]
|
2018-03-29 15:44:06 +00:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
| }
|
|
|
|
|
| }
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= [
|
|
|
|
|
= [
|
2018-04-04 16:55:59 +00:00
|
|
|
|
= "main"
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= ],
|
|
|
|
|
= [
|
2018-04-04 16:55:59 +00:00
|
|
|
|
= "bar"
|
2018-04-04 16:49:50 +00:00
|
|
|
|
= ],
|
|
|
|
|
= [
|
2018-04-04 16:55:59 +00:00
|
|
|
|
= "foo"
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= ]
|
|
|
|
|
= ]
|
2018-03-29 15:44:06 +00:00
|
|
|
|
|
2018-04-06 13:08:09 +00:00
|
|
|
|
If, however, they are the same goto, one can be optimized away.
|
|
|
|
|
|
|
|
|
|
| define foo routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 0
|
|
|
|
|
| if z {
|
|
|
|
|
| ld a, 1
|
|
|
|
|
| goto bar
|
|
|
|
|
| } else {
|
|
|
|
|
| ld a, 2
|
|
|
|
|
| goto bar
|
|
|
|
|
| }
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define bar routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 255
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define main routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| }
|
|
|
|
|
= [
|
|
|
|
|
= [
|
|
|
|
|
= "main"
|
|
|
|
|
= ],
|
|
|
|
|
= [
|
|
|
|
|
= "foo",
|
|
|
|
|
= "bar"
|
|
|
|
|
= ]
|
|
|
|
|
= ]
|
|
|
|
|
|
2018-03-29 15:44:06 +00:00
|
|
|
|
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
|
|
|
|
|
| }
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= [
|
|
|
|
|
= [
|
2018-04-04 16:55:59 +00:00
|
|
|
|
= "main"
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= ],
|
|
|
|
|
= [
|
2018-04-04 16:55:59 +00:00
|
|
|
|
= "bar"
|
2018-04-04 16:49:50 +00:00
|
|
|
|
= ],
|
|
|
|
|
= [
|
2018-04-04 16:55:59 +00:00
|
|
|
|
= "foo"
|
2018-04-04 15:22:14 +00:00
|
|
|
|
= ]
|
|
|
|
|
= ]
|
2018-04-05 09:52:14 +00:00
|
|
|
|
|
2018-04-06 12:24:04 +00:00
|
|
|
|
Our algorithm might not be strictly optimal, but it does a good job.
|
|
|
|
|
|
|
|
|
|
| define r1 routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 0
|
|
|
|
|
| goto r2
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define r2 routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 0
|
|
|
|
|
| goto r3
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define r3 routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 0
|
|
|
|
|
| goto r4
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define r4 routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 0
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define r5 routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 0
|
|
|
|
|
| goto r6
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define r6 routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 0
|
|
|
|
|
| goto r3
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define main routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| goto r1
|
|
|
|
|
| }
|
|
|
|
|
= [
|
|
|
|
|
= [
|
|
|
|
|
= "main",
|
|
|
|
|
= "r1",
|
|
|
|
|
= "r2",
|
|
|
|
|
= "r3",
|
|
|
|
|
= "r4"
|
|
|
|
|
= ],
|
|
|
|
|
= [
|
|
|
|
|
= "r5",
|
|
|
|
|
= "r6"
|
|
|
|
|
= ]
|
|
|
|
|
= ]
|
|
|
|
|
|
2018-04-05 09:52:14 +00:00
|
|
|
|
-> Tests for functionality "Compile SixtyPical program with fallthru optimization"
|
|
|
|
|
|
|
|
|
|
Basic test for actually applying this optimization when compiling SixtyPical programs.
|
|
|
|
|
|
|
|
|
|
| define foo routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 0
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define bar routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 255
|
|
|
|
|
| goto foo
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define main routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| goto foo
|
|
|
|
|
| }
|
2018-04-05 13:10:04 +00:00
|
|
|
|
= $080D LDA #$00
|
|
|
|
|
= $080F RTS
|
|
|
|
|
= $0810 LDA #$FF
|
|
|
|
|
= $0812 JMP $080D
|
2018-04-06 13:08:09 +00:00
|
|
|
|
|
|
|
|
|
It can optimize out one of the `goto`s if they are the same.
|
|
|
|
|
|
|
|
|
|
| define foo routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 0
|
|
|
|
|
| if z {
|
|
|
|
|
| ld a, 1
|
|
|
|
|
| goto bar
|
|
|
|
|
| } else {
|
|
|
|
|
| ld a, 2
|
|
|
|
|
| goto bar
|
|
|
|
|
| }
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define bar routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 255
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define main routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| }
|
|
|
|
|
= $080D RTS
|
|
|
|
|
= $080E LDA #$00
|
|
|
|
|
= $0810 BNE $0817
|
|
|
|
|
= $0812 LDA #$01
|
|
|
|
|
= $0814 JMP $0819
|
|
|
|
|
= $0817 LDA #$02
|
|
|
|
|
= $0819 LDA #$FF
|
|
|
|
|
= $081B RTS
|
|
|
|
|
|
|
|
|
|
It cannot optimize out the `goto`s if they are different.
|
|
|
|
|
|
|
|
|
|
Note, this currently produces unfortunately unoptimized code,
|
|
|
|
|
because generating code for the "true" branch of an `if` always
|
|
|
|
|
generates a jump out of the `if`, even if the last instruction
|
|
|
|
|
in the "true" branch is a `goto`.
|
|
|
|
|
|
|
|
|
|
| define foo routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 0
|
|
|
|
|
| if z {
|
|
|
|
|
| ld a, 1
|
|
|
|
|
| goto bar
|
|
|
|
|
| } else {
|
|
|
|
|
| ld a, 2
|
|
|
|
|
| goto main
|
|
|
|
|
| }
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define bar routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| ld a, 255
|
|
|
|
|
| }
|
|
|
|
|
|
|
|
|
|
|
| define main routine trashes a, z, n
|
|
|
|
|
| {
|
|
|
|
|
| }
|
|
|
|
|
= $080D RTS
|
|
|
|
|
= $080E LDA #$FF
|
|
|
|
|
= $0810 RTS
|
|
|
|
|
= $0811 LDA #$00
|
|
|
|
|
= $0813 BNE $081D
|
|
|
|
|
= $0815 LDA #$01
|
|
|
|
|
= $0817 JMP $080E
|
|
|
|
|
= $081A JMP $0822
|
|
|
|
|
= $081D LDA #$02
|
|
|
|
|
= $081F JMP $080D
|