mirror of
https://github.com/catseye/SixtyPical.git
synced 2025-04-07 23:37:23 +00:00
commit
4b539930bd
27
.circleci/config.yml
Normal file
27
.circleci/config.yml
Normal file
@ -0,0 +1,27 @@
|
||||
# Python CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-python/ for more details
|
||||
#
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/python:3.6.1
|
||||
|
||||
working_directory: ~/SixtyPical
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: install dependencies
|
||||
command: |
|
||||
echo "hi"
|
||||
git clone https://github.com/catseye/Falderal
|
||||
git clone https://github.com/catseye/dcc6502
|
||||
(cd dcc6502 && make)
|
||||
|
||||
- run:
|
||||
name: run tests
|
||||
command: |
|
||||
PATH=dcc6502:Falderal/bin:$PATH ./test.sh
|
26
HISTORY.md
26
HISTORY.md
@ -1,6 +1,32 @@
|
||||
History of SixtyPical
|
||||
=====================
|
||||
|
||||
0.21
|
||||
----
|
||||
|
||||
* A source file can be included in another source file
|
||||
by means of the `include` directive.
|
||||
* A routine can be declared `preserved`, which prevents a
|
||||
compiler from omitting it from the final executable, even
|
||||
if it determines it is not called by any other routine.
|
||||
* The reference implementation constructs a callgraph and
|
||||
determines the set of routines which are not reachable
|
||||
(directly or indirectly) from `main`, with an eye to
|
||||
omitting them from the final executable.
|
||||
* Added `--prune-unreachable-routines` option, which causes
|
||||
the compiler to in fact omit routines determined to be
|
||||
unreachable as described above.
|
||||
* Added `--include-path` option, which configures the list
|
||||
of directories that are searched when a source file is
|
||||
included with the `include` directive.
|
||||
* Code generation now performs modest peephole optimization
|
||||
at the end of each routine. This results in better code
|
||||
generation for constructs in tail position, notably
|
||||
tail optimization of `calls`, but also for `goto`s and
|
||||
`if` blocks at the end of a routine.
|
||||
* Began collecting architecture-specific and portable
|
||||
include-files for a nascent "standard library".
|
||||
|
||||
0.20
|
||||
----
|
||||
|
||||
|
11
README.md
11
README.md
@ -1,10 +1,12 @@
|
||||
SixtyPical
|
||||
==========
|
||||
|
||||
_Version 0.20. Work-in-progress, everything is subject to change._
|
||||
_Version 0.21. Work-in-progress, everything is subject to change._
|
||||
|
||||
**SixtyPical** is a [low-level](#low-level) programming language
|
||||
supporting a sophisticated [static analysis](#static-analysis).
|
||||
**SixtyPical** brings [extended static checking][] to the [6502][].
|
||||
|
||||
SixtyPical is a [low-level](#low-level) programming language
|
||||
supporting some advanced [static analysis](#static-analysis) methods.
|
||||
Its reference compiler can generate [efficient code](#efficient-code) for
|
||||
several 6502-based [target platforms](#target-platforms) while catching many
|
||||
common mistakes at compile-time, reducing the time spent in debugging.
|
||||
@ -114,6 +116,7 @@ In order to run the tests for compilation, [dcc6502][] needs to be installed.
|
||||
* [Literate test suite for SixtyPical analysis (control flow)](tests/SixtyPical%20Control%20Flow.md)
|
||||
* [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
|
||||
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
|
||||
* [Literate test suite for SixtyPical callgraph construction](tests/SixtyPical%20Callgraph.md)
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
@ -124,7 +127,9 @@ Documentation
|
||||
* [Output formats supported by `sixtypical`](doc/Output%20Formats.md)
|
||||
* [TODO](TODO.md)
|
||||
|
||||
[6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502
|
||||
[MOS Technology 6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502
|
||||
[extended static checking]: https://en.wikipedia.org/wiki/Extended_static_checking
|
||||
[effect system]: https://en.wikipedia.org/wiki/Effect_system
|
||||
[abstractly interprets]: https://en.wikipedia.org/wiki/Abstract_interpretation
|
||||
[calling conventions]: https://en.wikipedia.org/wiki/Calling_convention
|
||||
|
57
TODO.md
57
TODO.md
@ -31,14 +31,6 @@ For goodness sake, let the programmer say `'A'` instead of `65`.
|
||||
Not all computers think `'A'` should be `65`. Allow the character set to be
|
||||
mapped. Probably copy what Ophis does.
|
||||
|
||||
### "Include" directives
|
||||
|
||||
Search a searchlist of include paths. And use them to make libraries of routines.
|
||||
|
||||
One such library routine might be an `interrupt routine` type for various architectures.
|
||||
Since "the supervisor" has stored values on the stack, we should be able to trash them
|
||||
with impunity, in such a routine.
|
||||
|
||||
### Pointers into non-byte tables
|
||||
|
||||
Right now you cannot get a pointer into a non-byte (for instance, word or vector) table.
|
||||
@ -63,6 +55,14 @@ What happens if a routine calls itself, directly or indirectly? Many
|
||||
constraints might be violated in this case. We should probably disallow
|
||||
recursion by default. (Which means assembling the callgraph in all cases.)
|
||||
|
||||
However note, it's okay for a routine to goto itself. It's a common
|
||||
pattern for implementing a state machine, for a routine to tail-goto a
|
||||
vector, which might contain the address of the same routine.
|
||||
|
||||
The problems only come up, I think, when a routine calls itself re-entrantly.
|
||||
|
||||
So the callgraph would need to distinguish between these two cases.
|
||||
|
||||
### Analyze memory usage
|
||||
|
||||
If you define two variables that occupy the same address, an analysis error ought
|
||||
@ -83,36 +83,31 @@ This is not just an impressive trick -- in the presence of local pointers, which
|
||||
use up a word in zero-page, which we consider a precious resource, it allow those
|
||||
zero-page locations to be re-used.
|
||||
|
||||
### Tail-call optimization
|
||||
|
||||
If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
|
||||
if the block is in tail position. The constraints should iron out the same both ways.
|
||||
|
||||
As long as the routine has consistent type context every place it exits, that should be fine.
|
||||
|
||||
### Branch optimization in `if`
|
||||
|
||||
Currently the `if` generator is not smart enough to avoid generating silly
|
||||
jump instructions. (See the Fallthru tests.) Improve it.
|
||||
|
||||
### Dead code removal
|
||||
|
||||
Once we have a call graph we can omit routines that we're sure aren't called.
|
||||
|
||||
This would let us use include-files and standard-libraries nicely: any
|
||||
routines they define, but that you don't use, don't get included.
|
||||
|
||||
Analyzing the set of possible routines that a vector can take on would help
|
||||
this immensely.
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
|
||||
### Line numbers in analysis error messages
|
||||
### Filename and line number in analysis error messages
|
||||
|
||||
For analysis errors, there is a line number, but it's the line of the routine
|
||||
after the routine in which the analysis error occurred. Fix this.
|
||||
|
||||
### Better selection of options
|
||||
|
||||
`-O` should turn on the standard optimizations.
|
||||
|
||||
There should maybe be a flag to turn off tail-call optimization.
|
||||
|
||||
Some options should automatically add the appropriate architecture include
|
||||
directory to the path.
|
||||
|
||||
Distribution
|
||||
------------
|
||||
|
||||
### Demo game
|
||||
|
||||
Seems you're not be able to get killed unless you go off the top or bottom of
|
||||
the screen? In particular, you cannot collide with a bar?
|
||||
|
||||
Blue-skying
|
||||
-----------
|
||||
|
||||
|
@ -17,23 +17,23 @@ from tempfile import NamedTemporaryFile
|
||||
import traceback
|
||||
|
||||
from sixtypical.symtab import SymbolTable
|
||||
from sixtypical.parser import Parser, merge_programs
|
||||
from sixtypical.parser import Parser, load_program, merge_programs
|
||||
from sixtypical.analyzer import Analyzer
|
||||
from sixtypical.callgraph import construct_callgraph, prune_unreachable_routines
|
||||
from sixtypical.outputter import outputter_class_for
|
||||
from sixtypical.compiler import Compiler
|
||||
|
||||
|
||||
def process_input_files(filenames, options):
|
||||
symtab = SymbolTable()
|
||||
include_path = options.include_path.split(':')
|
||||
|
||||
programs = []
|
||||
|
||||
for filename in options.filenames:
|
||||
text = open(filename).read()
|
||||
parser = Parser(symtab, text, filename)
|
||||
program = load_program(filename, symtab, include_path, include_file=False)
|
||||
if options.debug:
|
||||
print(symtab)
|
||||
program = parser.program()
|
||||
programs.append(program)
|
||||
|
||||
if options.parse_only:
|
||||
@ -47,25 +47,24 @@ def process_input_files(filenames, options):
|
||||
analyzer.analyze_program(program)
|
||||
finally:
|
||||
if options.dump_exit_contexts:
|
||||
sys.stdout.write(json.dumps(analyzer.exit_contexts_map, indent=4, sort_keys=True, separators=(',', ':')))
|
||||
sys.stdout.write(json.dumps(analyzer.exit_contexts_map, indent=4, sort_keys=True, separators=(',', ': ')))
|
||||
sys.stdout.write("\n")
|
||||
|
||||
callgraph = construct_callgraph(program)
|
||||
if options.dump_callgraph:
|
||||
sys.stdout.write(json.dumps(callgraph, indent=4, sort_keys=True, separators=(',', ': ')))
|
||||
if options.prune_unreachable_routines:
|
||||
program = prune_unreachable_routines(program, callgraph)
|
||||
|
||||
compilation_roster = None
|
||||
if options.optimize_fallthru:
|
||||
from sixtypical.fallthru import FallthruAnalyzer
|
||||
|
||||
def dump(data, label=None):
|
||||
if not options.dump_fallthru_info:
|
||||
return
|
||||
if label:
|
||||
sys.stdout.write("*** {}:\n".format(label))
|
||||
sys.stdout.write(json.dumps(data, indent=4, sort_keys=True, separators=(',', ':')))
|
||||
sys.stdout.write("\n")
|
||||
|
||||
fa = FallthruAnalyzer(symtab, debug=options.debug)
|
||||
fa.analyze_program(program)
|
||||
compilation_roster = fa.serialize()
|
||||
dump(compilation_roster)
|
||||
if options.dump_fallthru_info:
|
||||
sys.stdout.write(json.dumps(compilation_roster, indent=4, sort_keys=True, separators=(',', ': ')))
|
||||
|
||||
if options.analyze_only or (options.output is None and not options.run_on):
|
||||
return
|
||||
@ -139,6 +138,12 @@ if __name__ == '__main__':
|
||||
"Default: raw."
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
"--include-path", "-I", type=str, metavar='PATH', default='.',
|
||||
help="A colon-separated list of directories in which to look for "
|
||||
"files which are included during `include` directives."
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
"--analyze-only",
|
||||
action="store_true",
|
||||
@ -161,6 +166,17 @@ if __name__ == '__main__':
|
||||
action="store_true",
|
||||
help="Dump the ordered fallthru map, in JSON, to stdout after analyzing the program."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--prune-unreachable-routines",
|
||||
action="store_true",
|
||||
help="Omit code for unreachable routines (as determined by the callgraph) "
|
||||
"from the final output."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--dump-callgraph",
|
||||
action="store_true",
|
||||
help="Dump the call graph, in JSON, to stdout after analyzing the program."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--parse-only",
|
||||
action="store_true",
|
||||
@ -185,7 +201,7 @@ if __name__ == '__main__':
|
||||
argparser.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version="%(prog)s 0.20"
|
||||
version="%(prog)s 0.21"
|
||||
)
|
||||
|
||||
options, unknown = argparser.parse_known_args(sys.argv[1:])
|
||||
|
29
eg/README.md
29
eg/README.md
@ -3,35 +3,18 @@ in subdirectories by machine architecture.
|
||||
|
||||
### rudiments
|
||||
|
||||
In the [rudiments](rudiments/) directory are programs which are not for
|
||||
any particular machine, but meant to demonstrate the features of SixtyPical.
|
||||
Some are meant to fail and produce an error message. Others can run on
|
||||
any architecture where there is a routine at 65490 which outputs the value
|
||||
of the accumulator as an ASCII character.
|
||||
In the [rudiments](rudiments/) directory are programs which are
|
||||
meant to demonstrate the elementary features of SixtyPical, and
|
||||
to serve as manual integration test cases. See
|
||||
[the README in that directory](rudiments/README.md) for details.
|
||||
|
||||
### c64
|
||||
|
||||
In the [c64](c64/) directory are programs that run on the Commodore 64.
|
||||
The directory itself contains some simple demos, for example
|
||||
[hearts.60p](c64/hearts.60p), while there are subdirectories for more
|
||||
elaborate demos:
|
||||
|
||||
* [demo-game](c64/demo-game/): a little game-like program written as a
|
||||
"can we write something you'd see in practice?" test case for SixtyPical.
|
||||
|
||||
* [ribos](c64/ribos/): a well-commented example of a C64 raster interrupt
|
||||
routine. Originally written with the P65 assembler (which has since
|
||||
been reborn as [Ophis][]).
|
||||
|
||||
The second version of Ribos has been translated to SixtyPical.
|
||||
|
||||
* [petulant](c64/petulant/): "The PETulant Cursor", a tiny (44 bytes)
|
||||
"display hack". Originally written in the late 80's. Rewritten with
|
||||
the P65 assembler (now Ophis) and re-released on April 1st, 2008 (a
|
||||
hint as to its nature).
|
||||
|
||||
Translated to SixtyPical (in 2018), after adding some optimizations
|
||||
to the SixtyPical compiler, the resulting executable is still 44 bytes!
|
||||
elaborate demos, like the flagship demo game. See
|
||||
[the README in that directory](c64/README.md) for details.
|
||||
|
||||
### vic20
|
||||
|
||||
|
@ -1,5 +1,23 @@
|
||||
This directory contains SixtyPical example programs
|
||||
specifically for the Commodore 64.
|
||||
|
||||
See the [README in the parent directory](../README.md) for
|
||||
more information on these example programs.
|
||||
There are subdirectories for more elaborate demos:
|
||||
|
||||
* [demo-game](demo-game/): a little game-like program written as a
|
||||
"can we write something you'd see in practice?" test case for SixtyPical.
|
||||
|
||||
* [ribos](ribos/): a well-commented example of a C64 raster interrupt
|
||||
routine. Originally written with the P65 assembler (which has since
|
||||
been reborn as [Ophis][]).
|
||||
|
||||
The second version of Ribos has been translated to SixtyPical.
|
||||
|
||||
* [petulant](petulant/): "The PETulant Cursor", a tiny (44 bytes)
|
||||
"display hack". Originally written in the late 80's. Rewritten with
|
||||
the P65 assembler (now Ophis) and re-released on April 1st, 2008 (a
|
||||
hint as to its nature).
|
||||
|
||||
Translated to SixtyPical (in 2018), after adding some optimizations
|
||||
to the SixtyPical compiler, the resulting executable is still 44 bytes!
|
||||
|
||||
[Ophis]: http://michaelcmartin.github.io/Ophis/
|
||||
|
@ -2,6 +2,8 @@
|
||||
// * Demo Game for SixtyPical *
|
||||
// ****************************
|
||||
|
||||
include "joystick.60p"
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Type Definitions
|
||||
// ----------------------------------------------------------------
|
||||
@ -56,7 +58,6 @@ byte vic_border @ 53280
|
||||
byte vic_bg @ 53281
|
||||
byte table[2048] screen @ 1024
|
||||
byte table[2048] colormap @ 55296
|
||||
byte joy2 @ $dc00
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Global Variables
|
||||
@ -69,7 +70,6 @@ word pos
|
||||
word new_pos
|
||||
|
||||
word table[256] actor_delta
|
||||
word delta
|
||||
|
||||
byte player_died
|
||||
|
||||
@ -103,71 +103,6 @@ vector game_state_routine
|
||||
// Utility Routines
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
define read_stick routine
|
||||
inputs joy2
|
||||
outputs delta
|
||||
trashes a, x, z, n
|
||||
{
|
||||
ld x, joy2
|
||||
ld a, x
|
||||
and a, 1 // up
|
||||
if z {
|
||||
copy $ffd8, delta // -40
|
||||
} else {
|
||||
ld a, x
|
||||
and a, 2 // down
|
||||
if z {
|
||||
copy word 40, delta
|
||||
} else {
|
||||
ld a, x
|
||||
and a, 4 // left
|
||||
if z {
|
||||
copy $ffff, delta // -1
|
||||
} else {
|
||||
ld a, x
|
||||
and a, 8 // right
|
||||
if z {
|
||||
copy word 1, delta
|
||||
} else {
|
||||
copy word 0, delta
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// You can repeatedly (i.e. as part of actor logic or an IRQ handler)
|
||||
// call this routine.
|
||||
// Upon return, if carry is set, the button was pressed then released.
|
||||
|
||||
define check_button routine
|
||||
inputs joy2
|
||||
outputs c
|
||||
trashes a, z, n
|
||||
static byte button_down : 0
|
||||
{
|
||||
ld a, button_down
|
||||
if z {
|
||||
ld a, joy2
|
||||
and a, $10
|
||||
if z {
|
||||
ld a, 1
|
||||
st a, button_down
|
||||
}
|
||||
st off, c
|
||||
} else {
|
||||
ld a, joy2
|
||||
and a, $10
|
||||
if not z {
|
||||
ld a, 0
|
||||
st a, button_down
|
||||
st on, c
|
||||
} else {
|
||||
st off, c
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define clear_screen routine
|
||||
outputs screen, colormap
|
||||
trashes a, y, c, n, z
|
||||
@ -425,7 +360,7 @@ define game_state_game_over game_state_routine
|
||||
// * Main Game Loop Driver *
|
||||
// *************************
|
||||
|
||||
define our_cinv game_state_routine
|
||||
define our_cinv preserved game_state_routine
|
||||
{
|
||||
goto dispatch_game_state
|
||||
}
|
||||
|
23
eg/c64/demo-game/run.sh
Executable file
23
eg/c64/demo-game/run.sh
Executable file
@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script builds and runs the demo game. You need
|
||||
# the VICE emulatore installed, in particular VICE's x64.
|
||||
|
||||
# You might want a `vicerc` file like the following:
|
||||
# [C64]
|
||||
# VICIIDoubleScan=0
|
||||
# VICIIDoubleSize=0
|
||||
# KeySet1NorthWest=0
|
||||
# KeySet1North=273
|
||||
# KeySet1NorthEast=0
|
||||
# KeySet1East=275
|
||||
# KeySet1SouthEast=0
|
||||
# KeySet1South=274
|
||||
# KeySet1SouthWest=0
|
||||
# KeySet1West=276
|
||||
# KeySet1Fire=306
|
||||
# KeySetEnable=1
|
||||
# JoyDevice1=0
|
||||
# JoyDevice2=2
|
||||
|
||||
../../../bin/sixtypical --run-on x64 -I ../../../include/c64/ demo-game.60p
|
15
eg/c64/joystick-demo.60p
Normal file
15
eg/c64/joystick-demo.60p
Normal file
@ -0,0 +1,15 @@
|
||||
include "joystick.60p"
|
||||
|
||||
word screen @ 1024
|
||||
|
||||
define main routine
|
||||
inputs joy2
|
||||
outputs delta
|
||||
trashes a, x, z, n, screen
|
||||
{
|
||||
repeat {
|
||||
call read_stick
|
||||
copy delta, screen
|
||||
ld a, 1
|
||||
} until z
|
||||
}
|
@ -2,16 +2,16 @@ This directory contains example sources which demonstrate
|
||||
the rudiments of SixtyPical.
|
||||
|
||||
Examples that are meant to fail and produce an error message
|
||||
are in the `errorful/` subdirectory.
|
||||
when being compiled are in the `errorful/` subdirectory.
|
||||
|
||||
These files are intended to be architecture-agnostic.
|
||||
For the ones that do produce output, an appropriate source
|
||||
under `support/` should be included first, so that system entry
|
||||
points such as `chrout` are defined. In addition, some of these
|
||||
programs use "standard" support modules, so those should be included
|
||||
first too. For example:
|
||||
The other sources are portable across architectures. They use
|
||||
`include` directives to bring in architecture-dependent libraries
|
||||
to produce output. Libraries for such are provided in the
|
||||
architecture-specific subdirectories of the `include` directory
|
||||
in the root directory of this repository; make sure it is on the
|
||||
compiler's include search path. For example:
|
||||
|
||||
sixtypical --run-on=x64 support/c64.60p support/stdlib.60p vector-table.60p
|
||||
sixtypical --run-on=x64 -I../../include/c64/:../../include/stdlib/ vector-table.60p
|
||||
|
||||
`chrout` is a routine with outputs the value of the accumulator
|
||||
as an ASCII character, disturbing none of the other registers,
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print YY
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
word score
|
||||
|
||||
define main routine
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print Y
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
byte table[2048] buf
|
||||
pointer ptr @ 254
|
||||
byte foo
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print AA
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
define print routine
|
||||
trashes a, z, n
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print ENGGL
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
byte b
|
||||
|
||||
define main routine
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print ENGGL
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
word w1
|
||||
|
||||
define main routine
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print ENGGL
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
word w1
|
||||
word w2
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Demonstrates vector tables.
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print YN
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
define main routine
|
||||
trashes a, x, y, z, n, c, v
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print YA
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
define main routine
|
||||
trashes a, x, y, z, n, c, v
|
||||
{
|
||||
|
@ -1,3 +1,8 @@
|
||||
// should fail analysis with an UnmeaningfulReadError
|
||||
// because adding 4 to the accumulator reads the carry
|
||||
// but the carry has neither been provided as input
|
||||
// nor set to anything in particular by this routine.
|
||||
|
||||
define add_four routine
|
||||
inputs a
|
||||
outputs a
|
||||
|
@ -1,3 +1,7 @@
|
||||
// should fail analysis with a RangeExceededError
|
||||
// because the index is detected to fall outside the
|
||||
// allowable range of the table it is indexing.
|
||||
|
||||
byte table[8] message : "WHAT?"
|
||||
|
||||
define main routine
|
||||
|
@ -1,9 +1,14 @@
|
||||
vector vec
|
||||
// should fail analysis with a ConstantConstraintError
|
||||
// because it cannot copy the address of `foo` into `vec`
|
||||
// because it has incompatible constraints.
|
||||
|
||||
vector routine
|
||||
inputs y
|
||||
outputs y
|
||||
trashes z, n
|
||||
vec
|
||||
|
||||
routine foo
|
||||
define foo routine
|
||||
inputs x
|
||||
outputs x
|
||||
trashes z, n
|
||||
@ -11,7 +16,7 @@ routine foo
|
||||
inc x
|
||||
}
|
||||
|
||||
routine main
|
||||
define main routine
|
||||
inputs foo
|
||||
outputs vec
|
||||
trashes a, z, n
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Include `support/${PLATFORM}.60p` and `support/stdlib.60p` before this source
|
||||
// Should print 01
|
||||
|
||||
include "chrout.60p"
|
||||
include "prbyte.60p"
|
||||
|
||||
byte lives
|
||||
|
||||
define main routine
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print AB
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
define bar routine trashes a, z, n {
|
||||
ld a, 66
|
||||
call chrout
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
define main routine
|
||||
trashes a, y, z, n, c
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print AB
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
byte foo
|
||||
|
||||
define print routine
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print H (being ASCII 72 = 8 * 9)
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
// Increase y by 7, circuitously
|
||||
//
|
||||
define foo routine
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print A
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
define main routine
|
||||
inputs a
|
||||
trashes a, z, n
|
||||
|
@ -1,7 +1,9 @@
|
||||
// Demonstrates vector tables.
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print AABAB
|
||||
|
||||
// Demonstrates vector tables.
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
vector routine
|
||||
trashes a, z, n
|
||||
print
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print AB
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
vector routine
|
||||
trashes a, z, n
|
||||
print
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print YY
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
word one
|
||||
word table[256] many
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
word screen @ 1024
|
||||
byte joy2 @ $dc00
|
||||
|
||||
word delta
|
||||
|
||||
// Read the joystick and compute the delta it represents
|
||||
// in a row-based 40-column grid like the C64's screen.
|
||||
|
||||
define read_stick routine
|
||||
inputs joy2
|
||||
outputs delta
|
||||
@ -36,14 +38,34 @@ define read_stick routine
|
||||
}
|
||||
}
|
||||
|
||||
define main routine
|
||||
// You can repeatedly (i.e. as part of actor logic or an IRQ handler)
|
||||
// call this routine.
|
||||
// Upon return, if carry is set, the button was pressed then released.
|
||||
|
||||
define check_button routine
|
||||
inputs joy2
|
||||
outputs delta
|
||||
trashes a, x, z, n, screen
|
||||
outputs c
|
||||
trashes a, z, n
|
||||
static byte button_down : 0
|
||||
{
|
||||
repeat {
|
||||
call read_stick
|
||||
copy delta, screen
|
||||
ld a, 1
|
||||
} until z
|
||||
ld a, button_down
|
||||
if z {
|
||||
ld a, joy2
|
||||
and a, $10
|
||||
if z {
|
||||
ld a, 1
|
||||
st a, button_down
|
||||
}
|
||||
st off, c
|
||||
} else {
|
||||
ld a, joy2
|
||||
and a, $10
|
||||
if not z {
|
||||
ld a, 0
|
||||
st a, button_down
|
||||
st on, c
|
||||
} else {
|
||||
st off, c
|
||||
}
|
||||
}
|
||||
}
|
@ -141,17 +141,23 @@ class Analyzer(object):
|
||||
def analyze_program(self, program):
|
||||
assert isinstance(program, Program)
|
||||
for routine in program.routines:
|
||||
context = self.analyze_routine(routine)
|
||||
routine.called_routines = set()
|
||||
context, type_ = self.analyze_routine(routine)
|
||||
if type_:
|
||||
routine.routine_type = type_
|
||||
routine.encountered_gotos = list(context.encountered_gotos()) if context else []
|
||||
routine.called_routines = list(routine.called_routines)
|
||||
|
||||
def analyze_routine(self, routine):
|
||||
assert isinstance(routine, Routine)
|
||||
type_ = self.get_type_for_name(routine.name)
|
||||
|
||||
if routine.block is None:
|
||||
# it's an extern, that's fine
|
||||
return None
|
||||
return None, type_
|
||||
|
||||
self.current_routine = routine
|
||||
type_ = self.get_type_for_name(routine.name)
|
||||
|
||||
context = AnalysisContext(self.symtab, routine, type_.inputs, type_.outputs, type_.trashes)
|
||||
|
||||
# register any local statics as already-initialized
|
||||
@ -209,7 +215,7 @@ class Analyzer(object):
|
||||
|
||||
self.exit_contexts = None
|
||||
self.current_routine = None
|
||||
return context
|
||||
return context, type_
|
||||
|
||||
def analyze_block(self, block, context):
|
||||
assert isinstance(block, Block)
|
||||
@ -512,16 +518,19 @@ class Analyzer(object):
|
||||
raise NotImplementedError(opcode)
|
||||
|
||||
def analyze_call(self, instr, context):
|
||||
type = self.get_type(instr.location)
|
||||
if not isinstance(type, (RoutineType, VectorType)):
|
||||
type_ = self.get_type(instr.location)
|
||||
if not isinstance(type_, (RoutineType, VectorType)):
|
||||
raise TypeMismatchError(instr, instr.location.name)
|
||||
if isinstance(type, VectorType):
|
||||
type = type.of_type
|
||||
for ref in type.inputs:
|
||||
|
||||
self.current_routine.called_routines.add((instr.location, type_))
|
||||
|
||||
if isinstance(type_, VectorType):
|
||||
type_ = type_.of_type
|
||||
for ref in type_.inputs:
|
||||
context.assert_meaningful(ref)
|
||||
for ref in type.outputs:
|
||||
for ref in type_.outputs:
|
||||
context.set_written(ref)
|
||||
for ref in type.trashes:
|
||||
for ref in type_.trashes:
|
||||
context.assert_writeable(ref)
|
||||
context.set_touched(ref)
|
||||
context.set_unmeaningful(ref)
|
||||
@ -533,6 +542,8 @@ class Analyzer(object):
|
||||
if not isinstance(type_, (RoutineType, VectorType)):
|
||||
raise TypeMismatchError(instr, location.name)
|
||||
|
||||
self.current_routine.called_routines.add((instr.location, type_))
|
||||
|
||||
# assert that the dest routine's inputs are all initialized
|
||||
if isinstance(type_, VectorType):
|
||||
type_ = type_.of_type
|
||||
|
68
src/sixtypical/callgraph.py
Normal file
68
src/sixtypical/callgraph.py
Normal file
@ -0,0 +1,68 @@
|
||||
from sixtypical.ast import Program
|
||||
from sixtypical.model import RoutineType, VectorType
|
||||
|
||||
|
||||
def find_routines_matching_type(program, type_):
|
||||
"""Return the subset of routines of the program whose value
|
||||
may be assigned to a location of the given type_ (typically
|
||||
a vector)."""
|
||||
return [r for r in program.routines if RoutineType.executable_types_compatible(r.routine_type, type_)]
|
||||
|
||||
|
||||
def mark_as_reachable(graph, routine_name):
|
||||
node = graph[routine_name]
|
||||
if node.get('reachable', False):
|
||||
return
|
||||
node['reachable'] = True
|
||||
for next_routine_name in node['potentially-calls']:
|
||||
mark_as_reachable(graph, next_routine_name)
|
||||
|
||||
|
||||
def construct_callgraph(program):
|
||||
graph = {}
|
||||
|
||||
for routine in program.routines:
|
||||
potentially_calls = []
|
||||
for (called_routine, called_routine_type) in routine.called_routines:
|
||||
if isinstance(called_routine_type, RoutineType):
|
||||
potentially_calls.append(called_routine.name)
|
||||
elif isinstance(called_routine_type, VectorType):
|
||||
for potentially_called in find_routines_matching_type(program, called_routine_type):
|
||||
potentially_calls.append(potentially_called.name)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
graph[routine.name] = {
|
||||
'potentially-calls': potentially_calls,
|
||||
}
|
||||
|
||||
# Symmetric closure
|
||||
# (Note, this information isn't used anywhere yet)
|
||||
|
||||
for routine in program.routines:
|
||||
potentially_called_by = []
|
||||
for (name, node) in graph.items():
|
||||
potential_calls = node['potentially-calls']
|
||||
if routine.name in potential_calls:
|
||||
potentially_called_by.append(name)
|
||||
graph[routine.name]['potentially-called-by'] = potentially_called_by
|
||||
|
||||
# Root set
|
||||
|
||||
root_set = set()
|
||||
|
||||
for routine in program.routines:
|
||||
if getattr(routine, 'preserved', False) or routine.name == 'main':
|
||||
root_set.add(routine)
|
||||
|
||||
# Reachability
|
||||
|
||||
for routine in root_set:
|
||||
mark_as_reachable(graph, routine.name)
|
||||
|
||||
return graph
|
||||
|
||||
|
||||
def prune_unreachable_routines(program, callgraph):
|
||||
return Program(1, defns=program.defns, routines=[
|
||||
r for r in program.routines if callgraph[r.name].get('reachable', False)
|
||||
])
|
@ -122,10 +122,11 @@ class Compiler(object):
|
||||
compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']
|
||||
|
||||
for roster_row in compilation_roster:
|
||||
for routine_name in roster_row[0:-1]:
|
||||
self.compile_routine(self.routines[routine_name], skip_final_goto=True)
|
||||
routine_name = roster_row[-1]
|
||||
self.compile_routine(self.routines[routine_name])
|
||||
for i, routine_name in enumerate(roster_row):
|
||||
if i < len(roster_row) - 1:
|
||||
self.compile_routine(self.routines[routine_name], next_routine=self.routines[roster_row[i + 1]])
|
||||
else:
|
||||
self.compile_routine(self.routines[routine_name])
|
||||
|
||||
for location, label in self.trampolines.items():
|
||||
self.emitter.resolve_label(label)
|
||||
@ -155,23 +156,45 @@ class Compiler(object):
|
||||
if defn.initial is None and defn.addr is None:
|
||||
self.emitter.resolve_bss_label(label)
|
||||
|
||||
def compile_routine(self, routine, skip_final_goto=False):
|
||||
self.current_routine = routine
|
||||
self.skip_final_goto = skip_final_goto
|
||||
self.final_goto_seen = False
|
||||
def compile_routine(self, routine, next_routine=None):
|
||||
assert isinstance(routine, Routine)
|
||||
|
||||
self.current_routine = routine
|
||||
|
||||
if routine.block:
|
||||
self.emitter.resolve_label(self.get_label(routine.name))
|
||||
self.compile_block(routine.block)
|
||||
if not self.final_goto_seen:
|
||||
|
||||
needs_rts = True
|
||||
last_op = self.emitter.get_tail()
|
||||
|
||||
if isinstance(last_op, JSR):
|
||||
if isinstance(last_op.operand, Absolute):
|
||||
if isinstance(last_op.operand.value, Label):
|
||||
label = last_op.operand.value
|
||||
self.emitter.retract()
|
||||
self.emitter.emit(JMP(Absolute(label)))
|
||||
last_op = self.emitter.get_tail()
|
||||
|
||||
if isinstance(last_op, JMP):
|
||||
needs_rts = False
|
||||
if isinstance(last_op.operand, Absolute):
|
||||
if isinstance(last_op.operand.value, Label):
|
||||
if next_routine and last_op.operand.value.name == next_routine.name:
|
||||
self.emitter.retract()
|
||||
|
||||
if needs_rts:
|
||||
self.emitter.emit(RTS())
|
||||
|
||||
self.current_routine = None
|
||||
self.skip_final_goto = False
|
||||
|
||||
def compile_block(self, block):
|
||||
assert isinstance(block, Block)
|
||||
block.shallow_contains_goto = False
|
||||
for instr in block.instrs:
|
||||
self.compile_instr(instr)
|
||||
if isinstance(instr, GoTo):
|
||||
block.shallow_contains_goto = True
|
||||
|
||||
def compile_instr(self, instr):
|
||||
if isinstance(instr, SingleOp):
|
||||
@ -437,19 +460,15 @@ class Compiler(object):
|
||||
raise NotImplementedError(location_type)
|
||||
|
||||
def compile_goto(self, instr):
|
||||
self.final_goto_seen = True
|
||||
if self.skip_final_goto:
|
||||
pass
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
location_type = self.get_type(location)
|
||||
if isinstance(location_type, RoutineType):
|
||||
self.emitter.emit(JMP(Absolute(label)))
|
||||
elif isinstance(location_type, VectorType):
|
||||
self.emitter.emit(JMP(Indirect(label)))
|
||||
else:
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
location_type = self.get_type(location)
|
||||
if isinstance(location_type, RoutineType):
|
||||
self.emitter.emit(JMP(Absolute(label)))
|
||||
elif isinstance(location_type, VectorType):
|
||||
self.emitter.emit(JMP(Indirect(label)))
|
||||
else:
|
||||
raise NotImplementedError(location_type)
|
||||
raise NotImplementedError(location_type)
|
||||
|
||||
def compile_cmp(self, instr, src, dest):
|
||||
"""`instr` is only for reporting purposes"""
|
||||
@ -662,12 +681,17 @@ class Compiler(object):
|
||||
else_label = Label('else_label')
|
||||
self.emitter.emit(cls(Relative(else_label)))
|
||||
self.compile_block(instr.block1)
|
||||
|
||||
if instr.block2 is not None:
|
||||
end_label = Label('end_label')
|
||||
self.emitter.emit(JMP(Absolute(end_label)))
|
||||
self.emitter.resolve_label(else_label)
|
||||
self.compile_block(instr.block2)
|
||||
self.emitter.resolve_label(end_label)
|
||||
if instr.block1.shallow_contains_goto:
|
||||
self.emitter.resolve_label(else_label)
|
||||
self.compile_block(instr.block2)
|
||||
else:
|
||||
end_label = Label('end_label')
|
||||
self.emitter.emit(JMP(Absolute(end_label)))
|
||||
self.emitter.resolve_label(else_label)
|
||||
self.compile_block(instr.block2)
|
||||
self.emitter.resolve_label(end_label)
|
||||
else:
|
||||
self.emitter.resolve_label(else_label)
|
||||
|
||||
|
@ -171,6 +171,16 @@ class Emitter(object):
|
||||
self.accum.append(thing)
|
||||
self.addr += thing.size()
|
||||
|
||||
def get_tail(self):
|
||||
if self.accum:
|
||||
return self.accum[-1]
|
||||
else:
|
||||
return None
|
||||
|
||||
def retract(self):
|
||||
thing = self.accum.pop()
|
||||
self.addr -= thing.size()
|
||||
|
||||
def serialize_to(self, stream):
|
||||
"""`stream` should be a file opened in binary mode."""
|
||||
addr = self.start_addr
|
||||
|
@ -21,8 +21,9 @@ class ForwardReference(object):
|
||||
|
||||
|
||||
class Parser(object):
|
||||
def __init__(self, symtab, text, filename):
|
||||
def __init__(self, symtab, text, filename, include_path):
|
||||
self.symtab = symtab
|
||||
self.include_path = include_path
|
||||
self.scanner = Scanner(text, filename)
|
||||
self.current_routine_name = None
|
||||
|
||||
@ -96,6 +97,12 @@ class Parser(object):
|
||||
def program(self):
|
||||
defns = []
|
||||
routines = []
|
||||
includes = []
|
||||
while self.scanner.consume('include'):
|
||||
filename = self.scanner.token
|
||||
self.scanner.scan()
|
||||
program = load_program(filename, self.symtab, self.include_path, include_file=True)
|
||||
includes.append(program)
|
||||
while self.scanner.on('typedef', 'const'):
|
||||
if self.scanner.on('typedef'):
|
||||
self.typedef()
|
||||
@ -111,13 +118,20 @@ class Parser(object):
|
||||
name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
self.current_routine_name = name
|
||||
preserved = False
|
||||
if self.scanner.consume('preserved'):
|
||||
preserved = True
|
||||
type_, routine = self.routine(name)
|
||||
self.declare(name, routine, type_)
|
||||
routine.preserved = preserved
|
||||
routines.append(routine)
|
||||
self.current_routine_name = None
|
||||
self.scanner.check_type('EOF')
|
||||
|
||||
program = Program(self.scanner.line_number, defns=defns, routines=routines)
|
||||
programs = includes + [program]
|
||||
program = merge_programs(programs)
|
||||
|
||||
self.resolve_symbols(program)
|
||||
return program
|
||||
|
||||
@ -466,6 +480,19 @@ class Parser(object):
|
||||
# - - - -
|
||||
|
||||
|
||||
def load_program(filename, symtab, include_path, include_file=False):
|
||||
import os
|
||||
if include_file:
|
||||
for include_dir in include_path:
|
||||
if os.path.exists(os.path.join(include_dir, filename)):
|
||||
filename = os.path.join(include_dir, filename)
|
||||
break
|
||||
text = open(filename).read()
|
||||
parser = Parser(symtab, text, filename, include_path)
|
||||
program = parser.program()
|
||||
return program
|
||||
|
||||
|
||||
def merge_programs(programs):
|
||||
"""Assumes that the programs do not have any conflicts."""
|
||||
|
||||
|
1
test.sh
1
test.sh
@ -10,4 +10,5 @@ falderal --substring-error \
|
||||
"tests/SixtyPical Storage.md" \
|
||||
"tests/SixtyPical Control Flow.md" \
|
||||
"tests/SixtyPical Fallthru.md" \
|
||||
"tests/SixtyPical Callgraph.md" \
|
||||
"tests/SixtyPical Compilation.md"
|
||||
|
223
tests/SixtyPical Callgraph.md
Normal file
223
tests/SixtyPical Callgraph.md
Normal file
@ -0,0 +1,223 @@
|
||||
SixtyPical Callgraph
|
||||
====================
|
||||
|
||||
This is a test suite, written in [Falderal][] format, for the ability of
|
||||
a SixtyPical analyzer to construct a callgraph of which routines call which
|
||||
other routines, and its ability to discover which routines will never be
|
||||
called.
|
||||
|
||||
[Falderal]: http://catseye.tc/node/Falderal
|
||||
|
||||
-> Tests for functionality "Dump callgraph info for SixtyPical program"
|
||||
|
||||
The `main` routine is always called. The thing that it will
|
||||
be called by is the system, but the callgraph analyzer simply
|
||||
considers it to be "reachable".
|
||||
|
||||
| define main routine
|
||||
| {
|
||||
| }
|
||||
= {
|
||||
= "main": {
|
||||
= "potentially-called-by": [],
|
||||
= "potentially-calls": [],
|
||||
= "reachable": true
|
||||
= }
|
||||
= }
|
||||
|
||||
If a routine is called by another routine, this fact will be noted.
|
||||
If it is reachable (directly or indirectly) from `main`, this will
|
||||
be noted as well.
|
||||
|
||||
| define main routine
|
||||
| {
|
||||
| call other
|
||||
| }
|
||||
|
|
||||
| define other routine
|
||||
| {
|
||||
| }
|
||||
= {
|
||||
= "main": {
|
||||
= "potentially-called-by": [],
|
||||
= "potentially-calls": [
|
||||
= "other"
|
||||
= ],
|
||||
= "reachable": true
|
||||
= },
|
||||
= "other": {
|
||||
= "potentially-called-by": [
|
||||
= "main"
|
||||
= ],
|
||||
= "potentially-calls": [],
|
||||
= "reachable": true
|
||||
= }
|
||||
= }
|
||||
|
||||
If a routine is not potentially called by any other routine that is
|
||||
ultimately potentially called by `main`, this absence will be noted
|
||||
— the routine will not be considered reachable — and a compiler or
|
||||
linker will be permitted to omit it from the final executable.
|
||||
|
||||
| define main routine
|
||||
| {
|
||||
| }
|
||||
|
|
||||
| define other routine
|
||||
| {
|
||||
| }
|
||||
= {
|
||||
= "main": {
|
||||
= "potentially-called-by": [],
|
||||
= "potentially-calls": [],
|
||||
= "reachable": true
|
||||
= },
|
||||
= "other": {
|
||||
= "potentially-called-by": [],
|
||||
= "potentially-calls": []
|
||||
= }
|
||||
= }
|
||||
|
||||
If a routine is not called by another routine, but it is declared
|
||||
explicitly as `preserved`, then it will still be considered
|
||||
reachable, and a compiler or linker will not be permitted to omit it
|
||||
from the final executable. This is useful for interrupt routines
|
||||
and such that really are used by some part of the system, even if
|
||||
not directly by another SixtyPical routine.
|
||||
|
||||
| define main routine
|
||||
| {
|
||||
| }
|
||||
|
|
||||
| define other preserved routine
|
||||
| {
|
||||
| }
|
||||
= {
|
||||
= "main": {
|
||||
= "potentially-called-by": [],
|
||||
= "potentially-calls": [],
|
||||
= "reachable": true
|
||||
= },
|
||||
= "other": {
|
||||
= "potentially-called-by": [],
|
||||
= "potentially-calls": [],
|
||||
= "reachable": true
|
||||
= }
|
||||
= }
|
||||
|
||||
If a routine is called from a preserved routine, that routine is
|
||||
reachable.
|
||||
|
||||
| define main routine
|
||||
| {
|
||||
| }
|
||||
|
|
||||
| define other1 preserved routine
|
||||
| {
|
||||
| call other2
|
||||
| }
|
||||
|
|
||||
| define other2 preserved routine
|
||||
| {
|
||||
| }
|
||||
= {
|
||||
= "main": {
|
||||
= "potentially-called-by": [],
|
||||
= "potentially-calls": [],
|
||||
= "reachable": true
|
||||
= },
|
||||
= "other1": {
|
||||
= "potentially-called-by": [],
|
||||
= "potentially-calls": [
|
||||
= "other2"
|
||||
= ],
|
||||
= "reachable": true
|
||||
= },
|
||||
= "other2": {
|
||||
= "potentially-called-by": [
|
||||
= "other1"
|
||||
= ],
|
||||
= "potentially-calls": [],
|
||||
= "reachable": true
|
||||
= }
|
||||
= }
|
||||
|
||||
If a group of routines potentially call each other, but neither is
|
||||
found to be reachable (directly or indirectly) from `main` or a
|
||||
`preserved` routine, the routines in the group will not be considered
|
||||
reachable.
|
||||
|
||||
| define main routine
|
||||
| {
|
||||
| }
|
||||
|
|
||||
| define other1 routine
|
||||
| {
|
||||
| call other2
|
||||
| }
|
||||
|
|
||||
| define other2 routine
|
||||
| {
|
||||
| call other1
|
||||
| }
|
||||
= {
|
||||
= "main": {
|
||||
= "potentially-called-by": [],
|
||||
= "potentially-calls": [],
|
||||
= "reachable": true
|
||||
= },
|
||||
= "other1": {
|
||||
= "potentially-called-by": [
|
||||
= "other2"
|
||||
= ],
|
||||
= "potentially-calls": [
|
||||
= "other2"
|
||||
= ]
|
||||
= },
|
||||
= "other2": {
|
||||
= "potentially-called-by": [
|
||||
= "other1"
|
||||
= ],
|
||||
= "potentially-calls": [
|
||||
= "other1"
|
||||
= ]
|
||||
= }
|
||||
= }
|
||||
|
||||
-> Tests for functionality "Compile SixtyPical program with unreachable routine removal"
|
||||
|
||||
Basic test for actually removing unreachable routines from the resulting
|
||||
executable when compiling SixtyPical programs.
|
||||
|
||||
| define main routine outputs a trashes z, n
|
||||
| {
|
||||
| ld a, 100
|
||||
| }
|
||||
|
|
||||
| define other1 routine
|
||||
| {
|
||||
| call other2
|
||||
| }
|
||||
|
|
||||
| define other2 routine
|
||||
| {
|
||||
| call other1
|
||||
| }
|
||||
= $080D LDA #$64
|
||||
= $080F RTS
|
||||
|
||||
Test that marking routine as `preserved` preserves it in the output.
|
||||
|
||||
| define main routine outputs a trashes z, n
|
||||
| {
|
||||
| ld a, 100
|
||||
| }
|
||||
|
|
||||
| define other preserved routine outputs a trashes z, n
|
||||
| {
|
||||
| ld a, 5
|
||||
| }
|
||||
= $080D LDA #$64
|
||||
= $080F RTS
|
||||
= $0810 LDA #$05
|
||||
= $0812 RTS
|
@ -51,10 +51,12 @@ Call extern.
|
||||
| {
|
||||
| ld a, 65
|
||||
| call chrout
|
||||
| ld a, 0
|
||||
| }
|
||||
= $080D LDA #$41
|
||||
= $080F JSR $FFD2
|
||||
= $0812 RTS
|
||||
= $0812 LDA #$00
|
||||
= $0814 RTS
|
||||
|
||||
Call defined routine.
|
||||
|
||||
@ -71,13 +73,39 @@ Call defined routine.
|
||||
| trashes a, x, y, z, n
|
||||
| {
|
||||
| call foo
|
||||
| ld a, 1
|
||||
| }
|
||||
= $080D JSR $0811
|
||||
= $0810 RTS
|
||||
= $0811 LDA #$00
|
||||
= $0813 LDX #$00
|
||||
= $0815 LDY #$00
|
||||
= $0817 RTS
|
||||
= $080D JSR $0813
|
||||
= $0810 LDA #$01
|
||||
= $0812 RTS
|
||||
= $0813 LDA #$00
|
||||
= $0815 LDX #$00
|
||||
= $0817 LDY #$00
|
||||
= $0819 RTS
|
||||
|
||||
Tail call is optimized into a jump.
|
||||
|
||||
| define foo routine
|
||||
| outputs a, x, y
|
||||
| trashes z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| ld x, 0
|
||||
| ld y, 0
|
||||
| }
|
||||
|
|
||||
| define main routine
|
||||
| trashes a, x, y, z, n
|
||||
| {
|
||||
| ld a, 1
|
||||
| call foo
|
||||
| }
|
||||
= $080D LDA #$01
|
||||
= $080F JMP $0812
|
||||
= $0812 LDA #$00
|
||||
= $0814 LDX #$00
|
||||
= $0816 LDY #$00
|
||||
= $0818 RTS
|
||||
|
||||
Access a defined memory location.
|
||||
|
||||
@ -610,7 +638,6 @@ Compiling `repeat forever`.
|
||||
= $080D LDY #$41
|
||||
= $080F INY
|
||||
= $0810 JMP $080F
|
||||
= $0813 RTS
|
||||
|
||||
The body of `repeat forever` can be empty.
|
||||
|
||||
@ -620,7 +647,6 @@ The body of `repeat forever` can be empty.
|
||||
| } forever
|
||||
| }
|
||||
= $080D JMP $080D
|
||||
= $0810 RTS
|
||||
|
||||
Compiling `for ... up to`.
|
||||
|
||||
@ -1055,7 +1081,7 @@ Copy word to word table and back, with constant offsets.
|
||||
= $0848 STA $084D
|
||||
= $084B RTS
|
||||
|
||||
Indirect call.
|
||||
Indirect call. TODO: we don't need the final RTS here, omit it.
|
||||
|
||||
| vector routine
|
||||
| outputs x
|
||||
@ -1076,16 +1102,15 @@ Indirect call.
|
||||
| copy bar, foo
|
||||
| call foo
|
||||
| }
|
||||
= $080D LDA #$1B
|
||||
= $080F STA $0822
|
||||
= $080D LDA #$1A
|
||||
= $080F STA $0821
|
||||
= $0812 LDA #$08
|
||||
= $0814 STA $0823
|
||||
= $0817 JSR $081E
|
||||
= $081A RTS
|
||||
= $081B LDX #$C8
|
||||
= $081D RTS
|
||||
= $081E JMP ($0822)
|
||||
= $0821 RTS
|
||||
= $0814 STA $0822
|
||||
= $0817 JMP $081D
|
||||
= $081A LDX #$C8
|
||||
= $081C RTS
|
||||
= $081D JMP ($0821)
|
||||
= $0820 RTS
|
||||
|
||||
Compiling `goto`. Note that no `RTS` is emitted after the `JMP`.
|
||||
|
||||
@ -1139,28 +1164,27 @@ Copying to and from a vector table.
|
||||
| call one
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDA #$3F
|
||||
= $0811 STA $0846
|
||||
= $080F LDA #$3E
|
||||
= $0811 STA $0845
|
||||
= $0814 LDA #$08
|
||||
= $0816 STA $0847
|
||||
= $0819 LDA #$3F
|
||||
= $081B STA $0848,X
|
||||
= $0816 STA $0846
|
||||
= $0819 LDA #$3E
|
||||
= $081B STA $0847,X
|
||||
= $081E LDA #$08
|
||||
= $0820 STA $0948,X
|
||||
= $0823 LDA $0846
|
||||
= $0826 STA $0848,X
|
||||
= $0829 LDA $0847
|
||||
= $082C STA $0948,X
|
||||
= $082F LDA $0848,X
|
||||
= $0832 STA $0846
|
||||
= $0835 LDA $0948,X
|
||||
= $0838 STA $0847
|
||||
= $083B JSR $0842
|
||||
= $083E RTS
|
||||
= $083F LDX #$C8
|
||||
= $0841 RTS
|
||||
= $0842 JMP ($0846)
|
||||
= $0845 RTS
|
||||
= $0820 STA $0947,X
|
||||
= $0823 LDA $0845
|
||||
= $0826 STA $0847,X
|
||||
= $0829 LDA $0846
|
||||
= $082C STA $0947,X
|
||||
= $082F LDA $0847,X
|
||||
= $0832 STA $0845
|
||||
= $0835 LDA $0947,X
|
||||
= $0838 STA $0846
|
||||
= $083B JMP $0841
|
||||
= $083E LDX #$C8
|
||||
= $0840 RTS
|
||||
= $0841 JMP ($0845)
|
||||
= $0844 RTS
|
||||
|
||||
Copying to and from a vector table, with constant offsets.
|
||||
|
||||
@ -1190,28 +1214,27 @@ Copying to and from a vector table, with constant offsets.
|
||||
| call one
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDA #$3F
|
||||
= $0811 STA $0846
|
||||
= $080F LDA #$3E
|
||||
= $0811 STA $0845
|
||||
= $0814 LDA #$08
|
||||
= $0816 STA $0847
|
||||
= $0819 LDA #$3F
|
||||
= $081B STA $0849,X
|
||||
= $0816 STA $0846
|
||||
= $0819 LDA #$3E
|
||||
= $081B STA $0848,X
|
||||
= $081E LDA #$08
|
||||
= $0820 STA $0949,X
|
||||
= $0823 LDA $0846
|
||||
= $0826 STA $084A,X
|
||||
= $0829 LDA $0847
|
||||
= $082C STA $094A,X
|
||||
= $082F LDA $084B,X
|
||||
= $0832 STA $0846
|
||||
= $0835 LDA $094B,X
|
||||
= $0838 STA $0847
|
||||
= $083B JSR $0842
|
||||
= $083E RTS
|
||||
= $083F LDX #$C8
|
||||
= $0841 RTS
|
||||
= $0842 JMP ($0846)
|
||||
= $0845 RTS
|
||||
= $0820 STA $0948,X
|
||||
= $0823 LDA $0845
|
||||
= $0826 STA $0849,X
|
||||
= $0829 LDA $0846
|
||||
= $082C STA $0949,X
|
||||
= $082F LDA $084A,X
|
||||
= $0832 STA $0845
|
||||
= $0835 LDA $094A,X
|
||||
= $0838 STA $0846
|
||||
= $083B JMP $0841
|
||||
= $083E LDX #$C8
|
||||
= $0840 RTS
|
||||
= $0841 JMP ($0845)
|
||||
= $0844 RTS
|
||||
|
||||
### add, sub
|
||||
|
||||
@ -1697,15 +1720,14 @@ just the same as initialized global storage locations are.
|
||||
| ld x, t
|
||||
| call foo
|
||||
| }
|
||||
= $080D LDX $081F
|
||||
= $0810 JSR $0814
|
||||
= $0813 RTS
|
||||
= $0814 STX $081E
|
||||
= $0817 INC $081E
|
||||
= $081A LDX $081E
|
||||
= $081D RTS
|
||||
= $081E .byte $FF
|
||||
= $081F .byte $07
|
||||
= $080D LDX $081E
|
||||
= $0810 JMP $0813
|
||||
= $0813 STX $081D
|
||||
= $0816 INC $081D
|
||||
= $0819 LDX $081D
|
||||
= $081C RTS
|
||||
= $081D .byte $FF
|
||||
= $081E .byte $07
|
||||
|
||||
Memory locations defined local dynamic to a routine are allocated
|
||||
just the same as uninitialized global storage locations are.
|
||||
@ -1730,13 +1752,12 @@ just the same as uninitialized global storage locations are.
|
||||
| call foo
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F STX $0821
|
||||
= $0812 JSR $0816
|
||||
= $0815 RTS
|
||||
= $0816 STX $0820
|
||||
= $0819 INC $0820
|
||||
= $081C LDX $0820
|
||||
= $081F RTS
|
||||
= $080F STX $0820
|
||||
= $0812 JMP $0815
|
||||
= $0815 STX $081F
|
||||
= $0818 INC $081F
|
||||
= $081B LDX $081F
|
||||
= $081E RTS
|
||||
|
||||
Memory locations defined local dynamic to a routine are allocated
|
||||
just the same as uninitialized global storage locations are, even
|
||||
@ -1763,9 +1784,8 @@ when declared with a fixed address.
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F STX $0401
|
||||
= $0812 JSR $0816
|
||||
= $0815 RTS
|
||||
= $0816 STX $0400
|
||||
= $0819 INC $0400
|
||||
= $081C LDX $0400
|
||||
= $081F RTS
|
||||
= $0812 JMP $0815
|
||||
= $0815 STX $0400
|
||||
= $0818 INC $0400
|
||||
= $081B LDX $0400
|
||||
= $081E RTS
|
||||
|
@ -384,11 +384,6 @@ It can optimize out one of the `goto`s if they are the same.
|
||||
|
||||
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
|
||||
@ -411,11 +406,10 @@ in the "true" branch is a `goto`.
|
||||
| }
|
||||
= $080D RTS
|
||||
= $080E LDA #$00
|
||||
= $0810 BNE $081A
|
||||
= $0810 BNE $0817
|
||||
= $0812 LDA #$01
|
||||
= $0814 JMP $081F
|
||||
= $0817 JMP $081F
|
||||
= $081A LDA #$02
|
||||
= $081C JMP $080D
|
||||
= $081F LDA #$FF
|
||||
= $0821 RTS
|
||||
= $0814 JMP $081C
|
||||
= $0817 LDA #$02
|
||||
= $0819 JMP $080D
|
||||
= $081C LDA #$FF
|
||||
= $081E RTS
|
||||
|
@ -73,6 +73,18 @@ Extern routines
|
||||
| @ 65487
|
||||
= ok
|
||||
|
||||
Preserved routine.
|
||||
|
||||
| define main routine {
|
||||
| ld a, $ff
|
||||
| add a, $01
|
||||
| }
|
||||
| define foo preserved routine {
|
||||
| ld a, 0
|
||||
| add a, 1
|
||||
| }
|
||||
= ok
|
||||
|
||||
Trash.
|
||||
|
||||
| define main routine {
|
||||
|
@ -13,6 +13,12 @@ implementation, `sixtypical`, that is going to implement these functionalities.
|
||||
-> Functionality "Compile SixtyPical program" is implemented by
|
||||
-> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
||||
|
||||
-> Functionality "Dump callgraph info for SixtyPical program" is implemented by
|
||||
-> shell command "bin/sixtypical --dump-callgraph --analyze-only --traceback %(test-body-file)"
|
||||
|
||||
-> Functionality "Compile SixtyPical program with unreachable routine removal" is implemented by
|
||||
-> shell command "bin/sixtypical --output-format=c64-basic-prg --prune-unreachable-routines --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
||||
|
||||
-> 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)"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user