git @ Cat's Eye Technologies SixtyPical / master
Merge pull request #22 from catseye/develop-0.21 Develop 0.21 Chris Pressey authored 9 months ago GitHub committed 9 months ago
50 changed file(s) with 886 addition(s) and 393 deletion(s). Raw diff Collapse all Expand all
0 # Python CircleCI 2.0 configuration file
1 #
2 # Check https://circleci.com/docs/2.0/language-python/ for more details
3 #
4 version: 2
5 jobs:
6 build:
7 docker:
8 - image: circleci/python:3.6.1
9
10 working_directory: ~/SixtyPical
11
12 steps:
13 - checkout
14
15 - run:
16 name: install dependencies
17 command: |
18 echo "hi"
19 git clone https://github.com/catseye/Falderal
20 git clone https://github.com/catseye/dcc6502
21 (cd dcc6502 && make)
22
23 - run:
24 name: run tests
25 command: |
26 PATH=dcc6502:Falderal/bin:$PATH ./test.sh
00 History of SixtyPical
11 =====================
2
3 0.21
4 ----
5
6 * A source file can be included in another source file
7 by means of the `include` directive.
8 * A routine can be declared `preserved`, which prevents a
9 compiler from omitting it from the final executable, even
10 if it determines it is not called by any other routine.
11 * The reference implementation constructs a callgraph and
12 determines the set of routines which are not reachable
13 (directly or indirectly) from `main`, with an eye to
14 omitting them from the final executable.
15 * Added `--prune-unreachable-routines` option, which causes
16 the compiler to in fact omit routines determined to be
17 unreachable as described above.
18 * Added `--include-path` option, which configures the list
19 of directories that are searched when a source file is
20 included with the `include` directive.
21 * Code generation now performs modest peephole optimization
22 at the end of each routine. This results in better code
23 generation for constructs in tail position, notably
24 tail optimization of `calls`, but also for `goto`s and
25 `if` blocks at the end of a routine.
26 * Began collecting architecture-specific and portable
27 include-files for a nascent "standard library".
228
329 0.20
430 ----
00 SixtyPical
11 ==========
22
3 _Version 0.20. Work-in-progress, everything is subject to change._
3 _Version 0.21. Work-in-progress, everything is subject to change._
44
5 **SixtyPical** is a [low-level](#low-level) programming language
6 supporting a sophisticated [static analysis](#static-analysis).
5 **SixtyPical** brings [extended static checking][] to the [6502][].
6
7 SixtyPical is a [low-level](#low-level) programming language
8 supporting some advanced [static analysis](#static-analysis) methods.
79 Its reference compiler can generate [efficient code](#efficient-code) for
810 several 6502-based [target platforms](#target-platforms) while catching many
911 common mistakes at compile-time, reducing the time spent in debugging.
113115 * [Literate test suite for SixtyPical analysis (control flow)](tests/SixtyPical%20Control%20Flow.md)
114116 * [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
115117 * [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
118 * [Literate test suite for SixtyPical callgraph construction](tests/SixtyPical%20Callgraph.md)
116119
117120 Documentation
118121 -------------
123126 * [Output formats supported by `sixtypical`](doc/Output%20Formats.md)
124127 * [TODO](TODO.md)
125128
129 [6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502
126130 [MOS Technology 6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502
131 [extended static checking]: https://en.wikipedia.org/wiki/Extended_static_checking
127132 [effect system]: https://en.wikipedia.org/wiki/Effect_system
128133 [abstractly interprets]: https://en.wikipedia.org/wiki/Abstract_interpretation
129134 [calling conventions]: https://en.wikipedia.org/wiki/Calling_convention
3030 Not all computers think `'A'` should be `65`. Allow the character set to be
3131 mapped. Probably copy what Ophis does.
3232
33 ### "Include" directives
34
35 Search a searchlist of include paths. And use them to make libraries of routines.
36
37 One such library routine might be an `interrupt routine` type for various architectures.
38 Since "the supervisor" has stored values on the stack, we should be able to trash them
39 with impunity, in such a routine.
40
4133 ### Pointers into non-byte tables
4234
4335 Right now you cannot get a pointer into a non-byte (for instance, word or vector) table.
6254 constraints might be violated in this case. We should probably disallow
6355 recursion by default. (Which means assembling the callgraph in all cases.)
6456
57 However note, it's okay for a routine to goto itself. It's a common
58 pattern for implementing a state machine, for a routine to tail-goto a
59 vector, which might contain the address of the same routine.
60
61 The problems only come up, I think, when a routine calls itself re-entrantly.
62
63 So the callgraph would need to distinguish between these two cases.
64
6565 ### Analyze memory usage
6666
6767 If you define two variables that occupy the same address, an analysis error ought
8282 use up a word in zero-page, which we consider a precious resource, it allow those
8383 zero-page locations to be re-used.
8484
85 ### Tail-call optimization
86
87 If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
88 if the block is in tail position. The constraints should iron out the same both ways.
89
90 As long as the routine has consistent type context every place it exits, that should be fine.
91
92 ### Branch optimization in `if`
93
94 Currently the `if` generator is not smart enough to avoid generating silly
95 jump instructions. (See the Fallthru tests.) Improve it.
96
97 ### Dead code removal
98
99 Once we have a call graph we can omit routines that we're sure aren't called.
100
101 This would let us use include-files and standard-libraries nicely: any
102 routines they define, but that you don't use, don't get included.
103
104 Analyzing the set of possible routines that a vector can take on would help
105 this immensely.
106
10785 Implementation
10886 --------------
10987
110 ### Line numbers in analysis error messages
88 ### Filename and line number in analysis error messages
11189
11290 For analysis errors, there is a line number, but it's the line of the routine
11391 after the routine in which the analysis error occurred. Fix this.
92
93 ### Better selection of options
94
95 `-O` should turn on the standard optimizations.
96
97 There should maybe be a flag to turn off tail-call optimization.
98
99 Some options should automatically add the appropriate architecture include
100 directory to the path.
101
102 Distribution
103 ------------
104
105 ### Demo game
106
107 Seems you're not be able to get killed unless you go off the top or bottom of
108 the screen? In particular, you cannot collide with a bar?
114109
115110 Blue-skying
116111 -----------
1616 import traceback
1717
1818 from sixtypical.symtab import SymbolTable
19 from sixtypical.parser import Parser, merge_programs
19 from sixtypical.parser import Parser, load_program, merge_programs
2020 from sixtypical.analyzer import Analyzer
21 from sixtypical.callgraph import construct_callgraph, prune_unreachable_routines
2122 from sixtypical.outputter import outputter_class_for
2223 from sixtypical.compiler import Compiler
2324
2425
2526 def process_input_files(filenames, options):
2627 symtab = SymbolTable()
28 include_path = options.include_path.split(':')
2729
2830 programs = []
2931
3032 for filename in options.filenames:
31 text = open(filename).read()
32 parser = Parser(symtab, text, filename)
33 program = load_program(filename, symtab, include_path, include_file=False)
3334 if options.debug:
3435 print(symtab)
35 program = parser.program()
3636 programs.append(program)
3737
3838 if options.parse_only:
4646 analyzer.analyze_program(program)
4747 finally:
4848 if options.dump_exit_contexts:
49 sys.stdout.write(json.dumps(analyzer.exit_contexts_map, indent=4, sort_keys=True, separators=(',', ':')))
49 sys.stdout.write(json.dumps(analyzer.exit_contexts_map, indent=4, sort_keys=True, separators=(',', ': ')))
5050 sys.stdout.write("\n")
51
52 callgraph = construct_callgraph(program)
53 if options.dump_callgraph:
54 sys.stdout.write(json.dumps(callgraph, indent=4, sort_keys=True, separators=(',', ': ')))
55 if options.prune_unreachable_routines:
56 program = prune_unreachable_routines(program, callgraph)
5157
5258 compilation_roster = None
5359 if options.optimize_fallthru:
5460 from sixtypical.fallthru import FallthruAnalyzer
5561
56 def dump(data, label=None):
57 if not options.dump_fallthru_info:
58 return
59 if label:
60 sys.stdout.write("*** {}:\n".format(label))
61 sys.stdout.write(json.dumps(data, indent=4, sort_keys=True, separators=(',', ':')))
62 sys.stdout.write("\n")
63
6462 fa = FallthruAnalyzer(symtab, debug=options.debug)
6563 fa.analyze_program(program)
6664 compilation_roster = fa.serialize()
67 dump(compilation_roster)
65 if options.dump_fallthru_info:
66 sys.stdout.write(json.dumps(compilation_roster, indent=4, sort_keys=True, separators=(',', ': ')))
6867
6968 if options.analyze_only or (options.output is None and not options.run_on):
7069 return
139138 )
140139
141140 argparser.add_argument(
141 "--include-path", "-I", type=str, metavar='PATH', default='.',
142 help="A colon-separated list of directories in which to look for "
143 "files which are included during `include` directives."
144 )
145
146 argparser.add_argument(
142147 "--analyze-only",
143148 action="store_true",
144149 help="Only parse and analyze the program; do not compile it."
159164 "--dump-fallthru-info",
160165 action="store_true",
161166 help="Dump the ordered fallthru map, in JSON, to stdout after analyzing the program."
167 )
168 argparser.add_argument(
169 "--prune-unreachable-routines",
170 action="store_true",
171 help="Omit code for unreachable routines (as determined by the callgraph) "
172 "from the final output."
173 )
174 argparser.add_argument(
175 "--dump-callgraph",
176 action="store_true",
177 help="Dump the call graph, in JSON, to stdout after analyzing the program."
162178 )
163179 argparser.add_argument(
164180 "--parse-only",
184200 argparser.add_argument(
185201 "--version",
186202 action="version",
187 version="%(prog)s 0.20"
203 version="%(prog)s 0.21"
188204 )
189205
190206 options, unknown = argparser.parse_known_args(sys.argv[1:])
22
33 ### rudiments
44
5 In the [rudiments](rudiments/) directory are programs which are not for
6 any particular machine, but meant to demonstrate the features of SixtyPical.
7 Some are meant to fail and produce an error message. Others can run on
8 any architecture where there is a routine at 65490 which outputs the value
9 of the accumulator as an ASCII character.
5 In the [rudiments](rudiments/) directory are programs which are
6 meant to demonstrate the elementary features of SixtyPical, and
7 to serve as manual integration test cases. See
8 [the README in that directory](rudiments/README.md) for details.
109
1110 ### c64
1211
1312 In the [c64](c64/) directory are programs that run on the Commodore 64.
1413 The directory itself contains some simple demos, for example
1514 [hearts.60p](c64/hearts.60p), while there are subdirectories for more
16 elaborate demos:
17
18 * [demo-game](c64/demo-game/): a little game-like program written as a
19 "can we write something you'd see in practice?" test case for SixtyPical.
20
21 * [ribos](c64/ribos/): a well-commented example of a C64 raster interrupt
22 routine. Originally written with the P65 assembler (which has since
23 been reborn as [Ophis][]).
24
25 The second version of Ribos has been translated to SixtyPical.
26
27 * [petulant](c64/petulant/): "The PETulant Cursor", a tiny (44 bytes)
28 "display hack". Originally written in the late 80's. Rewritten with
29 the P65 assembler (now Ophis) and re-released on April 1st, 2008 (a
30 hint as to its nature).
31
32 Translated to SixtyPical (in 2018), after adding some optimizations
33 to the SixtyPical compiler, the resulting executable is still 44 bytes!
15 elaborate demos, like the flagship demo game. See
16 [the README in that directory](c64/README.md) for details.
3417
3518 ### vic20
3619
00 This directory contains SixtyPical example programs
11 specifically for the Commodore 64.
22
3 See the [README in the parent directory](../README.md) for
4 more information on these example programs.
3 There are subdirectories for more elaborate demos:
4
5 * [demo-game](demo-game/): a little game-like program written as a
6 "can we write something you'd see in practice?" test case for SixtyPical.
7
8 * [ribos](ribos/): a well-commented example of a C64 raster interrupt
9 routine. Originally written with the P65 assembler (which has since
10 been reborn as [Ophis][]).
11
12 The second version of Ribos has been translated to SixtyPical.
13
14 * [petulant](petulant/): "The PETulant Cursor", a tiny (44 bytes)
15 "display hack". Originally written in the late 80's. Rewritten with
16 the P65 assembler (now Ophis) and re-released on April 1st, 2008 (a
17 hint as to its nature).
18
19 Translated to SixtyPical (in 2018), after adding some optimizations
20 to the SixtyPical compiler, the resulting executable is still 44 bytes!
21
22 [Ophis]: http://michaelcmartin.github.io/Ophis/
00 // ****************************
11 // * Demo Game for SixtyPical *
22 // ****************************
3
4 include "joystick.60p"
35
46 // ----------------------------------------------------------------
57 // Type Definitions
5557 byte vic_bg @ 53281
5658 byte table[2048] screen @ 1024
5759 byte table[2048] colormap @ 55296
58 byte joy2 @ $dc00
5960
6061 // ----------------------------------------------------------------
6162 // Global Variables
6869 word new_pos
6970
7071 word table[256] actor_delta
71 word delta
7272
7373 byte player_died
7474
101101 // ----------------------------------------------------------------
102102 // Utility Routines
103103 // ----------------------------------------------------------------
104
105 define read_stick routine
106 inputs joy2
107 outputs delta
108 trashes a, x, z, n
109 {
110 ld x, joy2
111 ld a, x
112 and a, 1 // up
113 if z {
114 copy $ffd8, delta // -40
115 } else {
116 ld a, x
117 and a, 2 // down
118 if z {
119 copy word 40, delta
120 } else {
121 ld a, x
122 and a, 4 // left
123 if z {
124 copy $ffff, delta // -1
125 } else {
126 ld a, x
127 and a, 8 // right
128 if z {
129 copy word 1, delta
130 } else {
131 copy word 0, delta
132 }
133 }
134 }
135 }
136 }
137
138 // You can repeatedly (i.e. as part of actor logic or an IRQ handler)
139 // call this routine.
140 // Upon return, if carry is set, the button was pressed then released.
141
142 define check_button routine
143 inputs joy2
144 outputs c
145 trashes a, z, n
146 static byte button_down : 0
147 {
148 ld a, button_down
149 if z {
150 ld a, joy2
151 and a, $10
152 if z {
153 ld a, 1
154 st a, button_down
155 }
156 st off, c
157 } else {
158 ld a, joy2
159 and a, $10
160 if not z {
161 ld a, 0
162 st a, button_down
163 st on, c
164 } else {
165 st off, c
166 }
167 }
168 }
169104
170105 define clear_screen routine
171106 outputs screen, colormap
424359 // * Main Game Loop Driver *
425360 // *************************
426361
427 define our_cinv game_state_routine
362 define our_cinv preserved game_state_routine
428363 {
429364 goto dispatch_game_state
430365 }
0 #!/bin/sh
1
2 # This script builds and runs the demo game. You need
3 # the VICE emulatore installed, in particular VICE's x64.
4
5 # You might want a `vicerc` file like the following:
6 # [C64]
7 # VICIIDoubleScan=0
8 # VICIIDoubleSize=0
9 # KeySet1NorthWest=0
10 # KeySet1North=273
11 # KeySet1NorthEast=0
12 # KeySet1East=275
13 # KeySet1SouthEast=0
14 # KeySet1South=274
15 # KeySet1SouthWest=0
16 # KeySet1West=276
17 # KeySet1Fire=306
18 # KeySetEnable=1
19 # JoyDevice1=0
20 # JoyDevice2=2
21
22 ../../../bin/sixtypical --run-on x64 -I ../../../include/c64/ demo-game.60p
0 include "joystick.60p"
1
2 word screen @ 1024
3
4 define main routine
5 inputs joy2
6 outputs delta
7 trashes a, x, z, n, screen
8 {
9 repeat {
10 call read_stick
11 copy delta, screen
12 ld a, 1
13 } until z
14 }
+0
-49
eg/c64/joystick.60p less more
0 word screen @ 1024
1 byte joy2 @ $dc00
2
3 word delta
4
5 define read_stick routine
6 inputs joy2
7 outputs delta
8 trashes a, x, z, n
9 {
10 ld x, joy2
11 ld a, x
12 and a, 1 // up
13 if z {
14 copy $ffd8, delta // -40
15 } else {
16 ld a, x
17 and a, 2 // down
18 if z {
19 copy word 40, delta
20 } else {
21 ld a, x
22 and a, 4 // left
23 if z {
24 copy $ffff, delta // -1
25 } else {
26 ld a, x
27 and a, 8 // right
28 if z {
29 copy word 1, delta
30 } else {
31 copy word 0, delta
32 }
33 }
34 }
35 }
36 }
37
38 define main routine
39 inputs joy2
40 outputs delta
41 trashes a, x, z, n, screen
42 {
43 repeat {
44 call read_stick
45 copy delta, screen
46 ld a, 1
47 } until z
48 }
11 the rudiments of SixtyPical.
22
33 Examples that are meant to fail and produce an error message
4 are in the `errorful/` subdirectory.
4 when being compiled are in the `errorful/` subdirectory.
55
6 These files are intended to be architecture-agnostic.
7 For the ones that do produce output, an appropriate source
8 under `support/` should be included first, so that system entry
9 points such as `chrout` are defined. In addition, some of these
10 programs use "standard" support modules, so those should be included
11 first too. For example:
6 The other sources are portable across architectures. They use
7 `include` directives to bring in architecture-dependent libraries
8 to produce output. Libraries for such are provided in the
9 architecture-specific subdirectories of the `include` directory
10 in the root directory of this repository; make sure it is on the
11 compiler's include search path. For example:
1212
13 sixtypical --run-on=x64 support/c64.60p support/stdlib.60p vector-table.60p
13 sixtypical --run-on=x64 -I../../include/c64/:../../include/stdlib/ vector-table.60p
1414
1515 `chrout` is a routine with outputs the value of the accumulator
1616 as an ASCII character, disturbing none of the other registers,
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print YY
1
2 include "chrout.60p"
23
34 word score
45
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print Y
1
2 include "chrout.60p"
23
34 byte table[2048] buf
45 pointer ptr @ 254
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print AA
1
2 include "chrout.60p"
23
34 define print routine
45 trashes a, z, n
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print ENGGL
1
2 include "chrout.60p"
23
34 byte b
45
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print ENGGL
1
2 include "chrout.60p"
23
34 word w1
45
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print ENGGL
1
2 include "chrout.60p"
23
34 word w1
45 word w2
0 // Demonstrates vector tables.
1 // Include `support/${PLATFORM}.60p` before this source
20 // Should print YN
1
2 include "chrout.60p"
33
44 define main routine
55 trashes a, x, y, z, n, c, v
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print YA
1
2 include "chrout.60p"
23
34 define main routine
45 trashes a, x, y, z, n, c, v
0 // should fail analysis with an UnmeaningfulReadError
1 // because adding 4 to the accumulator reads the carry
2 // but the carry has neither been provided as input
3 // nor set to anything in particular by this routine.
4
05 define add_four routine
16 inputs a
27 outputs a
0 // should fail analysis with a RangeExceededError
1 // because the index is detected to fall outside the
2 // allowable range of the table it is indexing.
3
04 byte table[8] message : "WHAT?"
15
26 define main routine
0 vector vec
0 // should fail analysis with a ConstantConstraintError
1 // because it cannot copy the address of `foo` into `vec`
2 // because it has incompatible constraints.
3
4 vector routine
15 inputs y
26 outputs y
37 trashes z, n
8 vec
49
5 routine foo
10 define foo routine
611 inputs x
712 outputs x
813 trashes z, n
1015 inc x
1116 }
1217
13 routine main
18 define main routine
1419 inputs foo
1520 outputs vec
1621 trashes a, z, n
0 // Include `support/${PLATFORM}.60p` and `support/stdlib.60p` before this source
10 // Should print 01
1
2 include "chrout.60p"
3 include "prbyte.60p"
24
35 byte lives
46
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print AB
1
2 include "chrout.60p"
23
34 define bar routine trashes a, z, n {
45 ld a, 66
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print ABCDEFGHIJKLMNOPQRSTUVWXYZ
1
2 include "chrout.60p"
23
34 define main routine
45 trashes a, y, z, n, c
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print AB
1
2 include "chrout.60p"
23
34 byte foo
45
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print H (being ASCII 72 = 8 * 9)
1
2 include "chrout.60p"
23
34 // Increase y by 7, circuitously
45 //
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print A
1
2 include "chrout.60p"
23
34 define main routine
45 inputs a
+0
-6
eg/rudiments/support/c64.60p less more
0 // Implementation of `chrout` for the Commodore 64 platform.
1
2 define chrout routine
3 inputs a
4 trashes a
5 @ 65490
+0
-26
eg/rudiments/support/stdlib.60p less more
0 byte table[16] hexchars : "0123456789ABCDEF"
1
2 define prbyte routine
3 inputs a, hexchars
4 trashes a, z, n, c, v
5 {
6 save x {
7 save a {
8 st off, c
9 shr a
10 shr a
11 shr a
12 shr a
13 and a, 15
14 ld x, a
15 ld a, hexchars + x
16 call chrout
17 }
18 save a {
19 and a, 15
20 ld x, a
21 ld a, hexchars + x
22 call chrout
23 }
24 }
25 }
+0
-6
eg/rudiments/support/vic20.60p less more
0 // Implementation of `chrout` for the Commodore VIC-20 platform.
1
2 define chrout routine
3 inputs a
4 trashes a
5 @ 65490
0 // Should print AABAB
1
02 // Demonstrates vector tables.
1 // Include `support/${PLATFORM}.60p` before this source
2 // Should print AABAB
3
4 include "chrout.60p"
35
46 vector routine
57 trashes a, z, n
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print AB
1
2 include "chrout.60p"
23
34 vector routine
45 trashes a, z, n
0 // Include `support/${PLATFORM}.60p` before this source
10 // Should print YY
1
2 include "chrout.60p"
23
34 word one
45 word table[256] many
0 // Implementation of `chrout` for the Commodore 64 platform.
1
2 define chrout routine
3 inputs a
4 trashes a
5 @ 65490
0 byte joy2 @ $dc00
1
2 word delta
3
4 // Read the joystick and compute the delta it represents
5 // in a row-based 40-column grid like the C64's screen.
6
7 define read_stick routine
8 inputs joy2
9 outputs delta
10 trashes a, x, z, n
11 {
12 ld x, joy2
13 ld a, x
14 and a, 1 // up
15 if z {
16 copy $ffd8, delta // -40
17 } else {
18 ld a, x
19 and a, 2 // down
20 if z {
21 copy word 40, delta
22 } else {
23 ld a, x
24 and a, 4 // left
25 if z {
26 copy $ffff, delta // -1
27 } else {
28 ld a, x
29 and a, 8 // right
30 if z {
31 copy word 1, delta
32 } else {
33 copy word 0, delta
34 }
35 }
36 }
37 }
38 }
39
40 // You can repeatedly (i.e. as part of actor logic or an IRQ handler)
41 // call this routine.
42 // Upon return, if carry is set, the button was pressed then released.
43
44 define check_button routine
45 inputs joy2
46 outputs c
47 trashes a, z, n
48 static byte button_down : 0
49 {
50 ld a, button_down
51 if z {
52 ld a, joy2
53 and a, $10
54 if z {
55 ld a, 1
56 st a, button_down
57 }
58 st off, c
59 } else {
60 ld a, joy2
61 and a, $10
62 if not z {
63 ld a, 0
64 st a, button_down
65 st on, c
66 } else {
67 st off, c
68 }
69 }
70 }
0 byte table[16] hexchars : "0123456789ABCDEF"
1
2 define prbyte routine
3 inputs a, hexchars
4 trashes a, z, n, c, v
5 {
6 save x {
7 save a {
8 st off, c
9 shr a
10 shr a
11 shr a
12 shr a
13 and a, 15
14 ld x, a
15 ld a, hexchars + x
16 call chrout
17 }
18 save a {
19 and a, 15
20 ld x, a
21 ld a, hexchars + x
22 call chrout
23 }
24 }
25 }
0 // Implementation of `chrout` for the Commodore VIC-20 platform.
1
2 define chrout routine
3 inputs a
4 trashes a
5 @ 65490
140140 def analyze_program(self, program):
141141 assert isinstance(program, Program)
142142 for routine in program.routines:
143 context = self.analyze_routine(routine)
143 routine.called_routines = set()
144 context, type_ = self.analyze_routine(routine)
145 if type_:
146 routine.routine_type = type_
144147 routine.encountered_gotos = list(context.encountered_gotos()) if context else []
148 routine.called_routines = list(routine.called_routines)
145149
146150 def analyze_routine(self, routine):
147151 assert isinstance(routine, Routine)
152 type_ = self.get_type_for_name(routine.name)
153
148154 if routine.block is None:
149155 # it's an extern, that's fine
150 return None
156 return None, type_
151157
152158 self.current_routine = routine
153 type_ = self.get_type_for_name(routine.name)
159
154160 context = AnalysisContext(self.symtab, routine, type_.inputs, type_.outputs, type_.trashes)
155161
156162 # register any local statics as already-initialized
208214
209215 self.exit_contexts = None
210216 self.current_routine = None
211 return context
217 return context, type_
212218
213219 def analyze_block(self, block, context):
214220 assert isinstance(block, Block)
511517 raise NotImplementedError(opcode)
512518
513519 def analyze_call(self, instr, context):
514 type = self.get_type(instr.location)
515 if not isinstance(type, (RoutineType, VectorType)):
520 type_ = self.get_type(instr.location)
521 if not isinstance(type_, (RoutineType, VectorType)):
516522 raise TypeMismatchError(instr, instr.location.name)
517 if isinstance(type, VectorType):
518 type = type.of_type
519 for ref in type.inputs:
523
524 self.current_routine.called_routines.add((instr.location, type_))
525
526 if isinstance(type_, VectorType):
527 type_ = type_.of_type
528 for ref in type_.inputs:
520529 context.assert_meaningful(ref)
521 for ref in type.outputs:
530 for ref in type_.outputs:
522531 context.set_written(ref)
523 for ref in type.trashes:
532 for ref in type_.trashes:
524533 context.assert_writeable(ref)
525534 context.set_touched(ref)
526535 context.set_unmeaningful(ref)
531540
532541 if not isinstance(type_, (RoutineType, VectorType)):
533542 raise TypeMismatchError(instr, location.name)
543
544 self.current_routine.called_routines.add((instr.location, type_))
534545
535546 # assert that the dest routine's inputs are all initialized
536547 if isinstance(type_, VectorType):
0 from sixtypical.ast import Program
1 from sixtypical.model import RoutineType, VectorType
2
3
4 def find_routines_matching_type(program, type_):
5 """Return the subset of routines of the program whose value
6 may be assigned to a location of the given type_ (typically
7 a vector)."""
8 return [r for r in program.routines if RoutineType.executable_types_compatible(r.routine_type, type_)]
9
10
11 def mark_as_reachable(graph, routine_name):
12 node = graph[routine_name]
13 if node.get('reachable', False):
14 return
15 node['reachable'] = True
16 for next_routine_name in node['potentially-calls']:
17 mark_as_reachable(graph, next_routine_name)
18
19
20 def construct_callgraph(program):
21 graph = {}
22
23 for routine in program.routines:
24 potentially_calls = []
25 for (called_routine, called_routine_type) in routine.called_routines:
26 if isinstance(called_routine_type, RoutineType):
27 potentially_calls.append(called_routine.name)
28 elif isinstance(called_routine_type, VectorType):
29 for potentially_called in find_routines_matching_type(program, called_routine_type):
30 potentially_calls.append(potentially_called.name)
31 else:
32 raise NotImplementedError
33 graph[routine.name] = {
34 'potentially-calls': potentially_calls,
35 }
36
37 # Symmetric closure
38 # (Note, this information isn't used anywhere yet)
39
40 for routine in program.routines:
41 potentially_called_by = []
42 for (name, node) in graph.items():
43 potential_calls = node['potentially-calls']
44 if routine.name in potential_calls:
45 potentially_called_by.append(name)
46 graph[routine.name]['potentially-called-by'] = potentially_called_by
47
48 # Root set
49
50 root_set = set()
51
52 for routine in program.routines:
53 if getattr(routine, 'preserved', False) or routine.name == 'main':
54 root_set.add(routine)
55
56 # Reachability
57
58 for routine in root_set:
59 mark_as_reachable(graph, routine.name)
60
61 return graph
62
63
64 def prune_unreachable_routines(program, callgraph):
65 return Program(1, defns=program.defns, routines=[
66 r for r in program.routines if callgraph[r.name].get('reachable', False)
67 ])
121121 compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']
122122
123123 for roster_row in compilation_roster:
124 for routine_name in roster_row[0:-1]:
125 self.compile_routine(self.routines[routine_name], skip_final_goto=True)
126 routine_name = roster_row[-1]
127 self.compile_routine(self.routines[routine_name])
124 for i, routine_name in enumerate(roster_row):
125 if i < len(roster_row) - 1:
126 self.compile_routine(self.routines[routine_name], next_routine=self.routines[roster_row[i + 1]])
127 else:
128 self.compile_routine(self.routines[routine_name])
128129
129130 for location, label in self.trampolines.items():
130131 self.emitter.resolve_label(label)
154155 if defn.initial is None and defn.addr is None:
155156 self.emitter.resolve_bss_label(label)
156157
157 def compile_routine(self, routine, skip_final_goto=False):
158 def compile_routine(self, routine, next_routine=None):
159 assert isinstance(routine, Routine)
160
158161 self.current_routine = routine
159 self.skip_final_goto = skip_final_goto
160 self.final_goto_seen = False
161 assert isinstance(routine, Routine)
162
162163 if routine.block:
163164 self.emitter.resolve_label(self.get_label(routine.name))
164165 self.compile_block(routine.block)
165 if not self.final_goto_seen:
166
167 needs_rts = True
168 last_op = self.emitter.get_tail()
169
170 if isinstance(last_op, JSR):
171 if isinstance(last_op.operand, Absolute):
172 if isinstance(last_op.operand.value, Label):
173 label = last_op.operand.value
174 self.emitter.retract()
175 self.emitter.emit(JMP(Absolute(label)))
176 last_op = self.emitter.get_tail()
177
178 if isinstance(last_op, JMP):
179 needs_rts = False
180 if isinstance(last_op.operand, Absolute):
181 if isinstance(last_op.operand.value, Label):
182 if next_routine and last_op.operand.value.name == next_routine.name:
183 self.emitter.retract()
184
185 if needs_rts:
166186 self.emitter.emit(RTS())
187
167188 self.current_routine = None
168 self.skip_final_goto = False
169189
170190 def compile_block(self, block):
171191 assert isinstance(block, Block)
192 block.shallow_contains_goto = False
172193 for instr in block.instrs:
173194 self.compile_instr(instr)
195 if isinstance(instr, GoTo):
196 block.shallow_contains_goto = True
174197
175198 def compile_instr(self, instr):
176199 if isinstance(instr, SingleOp):
436459 raise NotImplementedError(location_type)
437460
438461 def compile_goto(self, instr):
439 self.final_goto_seen = True
440 if self.skip_final_goto:
441 pass
442 else:
443 location = instr.location
444 label = self.get_label(instr.location.name)
445 location_type = self.get_type(location)
446 if isinstance(location_type, RoutineType):
447 self.emitter.emit(JMP(Absolute(label)))
448 elif isinstance(location_type, VectorType):
449 self.emitter.emit(JMP(Indirect(label)))
450 else:
451 raise NotImplementedError(location_type)
462 location = instr.location
463 label = self.get_label(instr.location.name)
464 location_type = self.get_type(location)
465 if isinstance(location_type, RoutineType):
466 self.emitter.emit(JMP(Absolute(label)))
467 elif isinstance(location_type, VectorType):
468 self.emitter.emit(JMP(Indirect(label)))
469 else:
470 raise NotImplementedError(location_type)
452471
453472 def compile_cmp(self, instr, src, dest):
454473 """`instr` is only for reporting purposes"""
661680 else_label = Label('else_label')
662681 self.emitter.emit(cls(Relative(else_label)))
663682 self.compile_block(instr.block1)
683
664684 if instr.block2 is not None:
665 end_label = Label('end_label')
666 self.emitter.emit(JMP(Absolute(end_label)))
667 self.emitter.resolve_label(else_label)
668 self.compile_block(instr.block2)
669 self.emitter.resolve_label(end_label)
685 if instr.block1.shallow_contains_goto:
686 self.emitter.resolve_label(else_label)
687 self.compile_block(instr.block2)
688 else:
689 end_label = Label('end_label')
690 self.emitter.emit(JMP(Absolute(end_label)))
691 self.emitter.resolve_label(else_label)
692 self.compile_block(instr.block2)
693 self.emitter.resolve_label(end_label)
670694 else:
671695 self.emitter.resolve_label(else_label)
672696
170170 self.accum.append(thing)
171171 self.addr += thing.size()
172172
173 def get_tail(self):
174 if self.accum:
175 return self.accum[-1]
176 else:
177 return None
178
179 def retract(self):
180 thing = self.accum.pop()
181 self.addr -= thing.size()
182
173183 def serialize_to(self, stream):
174184 """`stream` should be a file opened in binary mode."""
175185 addr = self.start_addr
2020
2121
2222 class Parser(object):
23 def __init__(self, symtab, text, filename):
23 def __init__(self, symtab, text, filename, include_path):
2424 self.symtab = symtab
25 self.include_path = include_path
2526 self.scanner = Scanner(text, filename)
2627 self.current_routine_name = None
2728
9596 def program(self):
9697 defns = []
9798 routines = []
99 includes = []
100 while self.scanner.consume('include'):
101 filename = self.scanner.token
102 self.scanner.scan()
103 program = load_program(filename, self.symtab, self.include_path, include_file=True)
104 includes.append(program)
98105 while self.scanner.on('typedef', 'const'):
99106 if self.scanner.on('typedef'):
100107 self.typedef()
110117 name = self.scanner.token
111118 self.scanner.scan()
112119 self.current_routine_name = name
120 preserved = False
121 if self.scanner.consume('preserved'):
122 preserved = True
113123 type_, routine = self.routine(name)
114124 self.declare(name, routine, type_)
125 routine.preserved = preserved
115126 routines.append(routine)
116127 self.current_routine_name = None
117128 self.scanner.check_type('EOF')
118129
119130 program = Program(self.scanner.line_number, defns=defns, routines=routines)
131 programs = includes + [program]
132 program = merge_programs(programs)
133
120134 self.resolve_symbols(program)
121135 return program
122136
465479 # - - - -
466480
467481
482 def load_program(filename, symtab, include_path, include_file=False):
483 import os
484 if include_file:
485 for include_dir in include_path:
486 if os.path.exists(os.path.join(include_dir, filename)):
487 filename = os.path.join(include_dir, filename)
488 break
489 text = open(filename).read()
490 parser = Parser(symtab, text, filename, include_path)
491 program = parser.program()
492 return program
493
494
468495 def merge_programs(programs):
469496 """Assumes that the programs do not have any conflicts."""
470497
99 "tests/SixtyPical Storage.md" \
1010 "tests/SixtyPical Control Flow.md" \
1111 "tests/SixtyPical Fallthru.md" \
12 "tests/SixtyPical Callgraph.md" \
1213 "tests/SixtyPical Compilation.md"
0 SixtyPical Callgraph
1 ====================
2
3 This is a test suite, written in [Falderal][] format, for the ability of
4 a SixtyPical analyzer to construct a callgraph of which routines call which
5 other routines, and its ability to discover which routines will never be
6 called.
7
8 [Falderal]: http://catseye.tc/node/Falderal
9
10 -> Tests for functionality "Dump callgraph info for SixtyPical program"
11
12 The `main` routine is always called. The thing that it will
13 be called by is the system, but the callgraph analyzer simply
14 considers it to be "reachable".
15
16 | define main routine
17 | {
18 | }
19 = {
20 = "main": {
21 = "potentially-called-by": [],
22 = "potentially-calls": [],
23 = "reachable": true
24 = }
25 = }
26
27 If a routine is called by another routine, this fact will be noted.
28 If it is reachable (directly or indirectly) from `main`, this will
29 be noted as well.
30
31 | define main routine
32 | {
33 | call other
34 | }
35 |
36 | define other routine
37 | {
38 | }
39 = {
40 = "main": {
41 = "potentially-called-by": [],
42 = "potentially-calls": [
43 = "other"
44 = ],
45 = "reachable": true
46 = },
47 = "other": {
48 = "potentially-called-by": [
49 = "main"
50 = ],
51 = "potentially-calls": [],
52 = "reachable": true
53 = }
54 = }
55
56 If a routine is not potentially called by any other routine that is
57 ultimately potentially called by `main`, this absence will be noted
58 — the routine will not be considered reachable — and a compiler or
59 linker will be permitted to omit it from the final executable.
60
61 | define main routine
62 | {
63 | }
64 |
65 | define other routine
66 | {
67 | }
68 = {
69 = "main": {
70 = "potentially-called-by": [],
71 = "potentially-calls": [],
72 = "reachable": true
73 = },
74 = "other": {
75 = "potentially-called-by": [],
76 = "potentially-calls": []
77 = }
78 = }
79
80 If a routine is not called by another routine, but it is declared
81 explicitly as `preserved`, then it will still be considered
82 reachable, and a compiler or linker will not be permitted to omit it
83 from the final executable. This is useful for interrupt routines
84 and such that really are used by some part of the system, even if
85 not directly by another SixtyPical routine.
86
87 | define main routine
88 | {
89 | }
90 |
91 | define other preserved routine
92 | {
93 | }
94 = {
95 = "main": {
96 = "potentially-called-by": [],
97 = "potentially-calls": [],
98 = "reachable": true
99 = },
100 = "other": {
101 = "potentially-called-by": [],
102 = "potentially-calls": [],
103 = "reachable": true
104 = }
105 = }
106
107 If a routine is called from a preserved routine, that routine is
108 reachable.
109
110 | define main routine
111 | {
112 | }
113 |
114 | define other1 preserved routine
115 | {
116 | call other2
117 | }
118 |
119 | define other2 preserved routine
120 | {
121 | }
122 = {
123 = "main": {
124 = "potentially-called-by": [],
125 = "potentially-calls": [],
126 = "reachable": true
127 = },
128 = "other1": {
129 = "potentially-called-by": [],
130 = "potentially-calls": [
131 = "other2"
132 = ],
133 = "reachable": true
134 = },
135 = "other2": {
136 = "potentially-called-by": [
137 = "other1"
138 = ],
139 = "potentially-calls": [],
140 = "reachable": true
141 = }
142 = }
143
144 If a group of routines potentially call each other, but neither is
145 found to be reachable (directly or indirectly) from `main` or a
146 `preserved` routine, the routines in the group will not be considered
147 reachable.
148
149 | define main routine
150 | {
151 | }
152 |
153 | define other1 routine
154 | {
155 | call other2
156 | }
157 |
158 | define other2 routine
159 | {
160 | call other1
161 | }
162 = {
163 = "main": {
164 = "potentially-called-by": [],
165 = "potentially-calls": [],
166 = "reachable": true
167 = },
168 = "other1": {
169 = "potentially-called-by": [
170 = "other2"
171 = ],
172 = "potentially-calls": [
173 = "other2"
174 = ]
175 = },
176 = "other2": {
177 = "potentially-called-by": [
178 = "other1"
179 = ],
180 = "potentially-calls": [
181 = "other1"
182 = ]
183 = }
184 = }
185
186 -> Tests for functionality "Compile SixtyPical program with unreachable routine removal"
187
188 Basic test for actually removing unreachable routines from the resulting
189 executable when compiling SixtyPical programs.
190
191 | define main routine outputs a trashes z, n
192 | {
193 | ld a, 100
194 | }
195 |
196 | define other1 routine
197 | {
198 | call other2
199 | }
200 |
201 | define other2 routine
202 | {
203 | call other1
204 | }
205 = $080D LDA #$64
206 = $080F RTS
207
208 Test that marking routine as `preserved` preserves it in the output.
209
210 | define main routine outputs a trashes z, n
211 | {
212 | ld a, 100
213 | }
214 |
215 | define other preserved routine outputs a trashes z, n
216 | {
217 | ld a, 5
218 | }
219 = $080D LDA #$64
220 = $080F RTS
221 = $0810 LDA #$05
222 = $0812 RTS
5050 | {
5151 | ld a, 65
5252 | call chrout
53 | ld a, 0
5354 | }
5455 = $080D LDA #$41
5556 = $080F JSR $FFD2
56 = $0812 RTS
57 = $0812 LDA #$00
58 = $0814 RTS
5759
5860 Call defined routine.
5961
7072 | trashes a, x, y, z, n
7173 | {
7274 | call foo
73 | }
74 = $080D JSR $0811
75 = $0810 RTS
76 = $0811 LDA #$00
77 = $0813 LDX #$00
78 = $0815 LDY #$00
79 = $0817 RTS
75 | ld a, 1
76 | }
77 = $080D JSR $0813
78 = $0810 LDA #$01
79 = $0812 RTS
80 = $0813 LDA #$00
81 = $0815 LDX #$00
82 = $0817 LDY #$00
83 = $0819 RTS
84
85 Tail call is optimized into a jump.
86
87 | define foo routine
88 | outputs a, x, y
89 | trashes z, n
90 | {
91 | ld a, 0
92 | ld x, 0
93 | ld y, 0
94 | }
95 |
96 | define main routine
97 | trashes a, x, y, z, n
98 | {
99 | ld a, 1
100 | call foo
101 | }
102 = $080D LDA #$01
103 = $080F JMP $0812
104 = $0812 LDA #$00
105 = $0814 LDX #$00
106 = $0816 LDY #$00
107 = $0818 RTS
80108
81109 Access a defined memory location.
82110
609637 = $080D LDY #$41
610638 = $080F INY
611639 = $0810 JMP $080F
612 = $0813 RTS
613640
614641 The body of `repeat forever` can be empty.
615642
619646 | } forever
620647 | }
621648 = $080D JMP $080D
622 = $0810 RTS
623649
624650 Compiling `for ... up to`.
625651
10541080 = $0848 STA $084D
10551081 = $084B RTS
10561082
1057 Indirect call.
1083 Indirect call. TODO: we don't need the final RTS here, omit it.
10581084
10591085 | vector routine
10601086 | outputs x
10751101 | copy bar, foo
10761102 | call foo
10771103 | }
1078 = $080D LDA #$1B
1079 = $080F STA $0822
1104 = $080D LDA #$1A
1105 = $080F STA $0821
10801106 = $0812 LDA #$08
1081 = $0814 STA $0823
1082 = $0817 JSR $081E
1083 = $081A RTS
1084 = $081B LDX #$C8
1085 = $081D RTS
1086 = $081E JMP ($0822)
1087 = $0821 RTS
1107 = $0814 STA $0822
1108 = $0817 JMP $081D
1109 = $081A LDX #$C8
1110 = $081C RTS
1111 = $081D JMP ($0821)
1112 = $0820 RTS
10881113
10891114 Compiling `goto`. Note that no `RTS` is emitted after the `JMP`.
10901115
11381163 | call one
11391164 | }
11401165 = $080D LDX #$00
1141 = $080F LDA #$3F
1142 = $0811 STA $0846
1166 = $080F LDA #$3E
1167 = $0811 STA $0845
11431168 = $0814 LDA #$08
1144 = $0816 STA $0847
1145 = $0819 LDA #$3F
1146 = $081B STA $0848,X
1169 = $0816 STA $0846
1170 = $0819 LDA #$3E
1171 = $081B STA $0847,X
11471172 = $081E LDA #$08
1148 = $0820 STA $0948,X
1149 = $0823 LDA $0846
1150 = $0826 STA $0848,X
1151 = $0829 LDA $0847
1152 = $082C STA $0948,X
1153 = $082F LDA $0848,X
1154 = $0832 STA $0846
1155 = $0835 LDA $0948,X
1156 = $0838 STA $0847
1157 = $083B JSR $0842
1158 = $083E RTS
1159 = $083F LDX #$C8
1160 = $0841 RTS
1161 = $0842 JMP ($0846)
1162 = $0845 RTS
1173 = $0820 STA $0947,X
1174 = $0823 LDA $0845
1175 = $0826 STA $0847,X
1176 = $0829 LDA $0846
1177 = $082C STA $0947,X
1178 = $082F LDA $0847,X
1179 = $0832 STA $0845
1180 = $0835 LDA $0947,X
1181 = $0838 STA $0846
1182 = $083B JMP $0841
1183 = $083E LDX #$C8
1184 = $0840 RTS
1185 = $0841 JMP ($0845)
1186 = $0844 RTS
11631187
11641188 Copying to and from a vector table, with constant offsets.
11651189
11891213 | call one
11901214 | }
11911215 = $080D LDX #$00
1192 = $080F LDA #$3F
1193 = $0811 STA $0846
1216 = $080F LDA #$3E
1217 = $0811 STA $0845
11941218 = $0814 LDA #$08
1195 = $0816 STA $0847
1196 = $0819 LDA #$3F
1197 = $081B STA $0849,X
1219 = $0816 STA $0846
1220 = $0819 LDA #$3E
1221 = $081B STA $0848,X
11981222 = $081E LDA #$08
1199 = $0820 STA $0949,X
1200 = $0823 LDA $0846
1201 = $0826 STA $084A,X
1202 = $0829 LDA $0847
1203 = $082C STA $094A,X
1204 = $082F LDA $084B,X
1205 = $0832 STA $0846
1206 = $0835 LDA $094B,X
1207 = $0838 STA $0847
1208 = $083B JSR $0842
1209 = $083E RTS
1210 = $083F LDX #$C8
1211 = $0841 RTS
1212 = $0842 JMP ($0846)
1213 = $0845 RTS
1223 = $0820 STA $0948,X
1224 = $0823 LDA $0845
1225 = $0826 STA $0849,X
1226 = $0829 LDA $0846
1227 = $082C STA $0949,X
1228 = $082F LDA $084A,X
1229 = $0832 STA $0845
1230 = $0835 LDA $094A,X
1231 = $0838 STA $0846
1232 = $083B JMP $0841
1233 = $083E LDX #$C8
1234 = $0840 RTS
1235 = $0841 JMP ($0845)
1236 = $0844 RTS
12141237
12151238 ### add, sub
12161239
16961719 | ld x, t
16971720 | call foo
16981721 | }
1699 = $080D LDX $081F
1700 = $0810 JSR $0814
1701 = $0813 RTS
1702 = $0814 STX $081E
1703 = $0817 INC $081E
1704 = $081A LDX $081E
1705 = $081D RTS
1706 = $081E .byte $FF
1707 = $081F .byte $07
1722 = $080D LDX $081E
1723 = $0810 JMP $0813
1724 = $0813 STX $081D
1725 = $0816 INC $081D
1726 = $0819 LDX $081D
1727 = $081C RTS
1728 = $081D .byte $FF
1729 = $081E .byte $07
17081730
17091731 Memory locations defined local dynamic to a routine are allocated
17101732 just the same as uninitialized global storage locations are.
17291751 | call foo
17301752 | }
17311753 = $080D LDX #$00
1732 = $080F STX $0821
1733 = $0812 JSR $0816
1734 = $0815 RTS
1735 = $0816 STX $0820
1736 = $0819 INC $0820
1737 = $081C LDX $0820
1738 = $081F RTS
1754 = $080F STX $0820
1755 = $0812 JMP $0815
1756 = $0815 STX $081F
1757 = $0818 INC $081F
1758 = $081B LDX $081F
1759 = $081E RTS
17391760
17401761 Memory locations defined local dynamic to a routine are allocated
17411762 just the same as uninitialized global storage locations are, even
17621783 | }
17631784 = $080D LDX #$00
17641785 = $080F STX $0401
1765 = $0812 JSR $0816
1766 = $0815 RTS
1767 = $0816 STX $0400
1768 = $0819 INC $0400
1769 = $081C LDX $0400
1770 = $081F RTS
1786 = $0812 JMP $0815
1787 = $0815 STX $0400
1788 = $0818 INC $0400
1789 = $081B LDX $0400
1790 = $081E RTS
383383
384384 It cannot optimize out the `goto`s if they are different.
385385
386 Note, this currently produces unfortunately unoptimized code,
387 because generating code for the "true" branch of an `if` always
388 generates a jump out of the `if`, even if the last instruction
389 in the "true" branch is a `goto`.
390
391386 | define foo routine trashes a, z, n
392387 | {
393388 | ld a, 0
410405 | }
411406 = $080D RTS
412407 = $080E LDA #$00
413 = $0810 BNE $081A
408 = $0810 BNE $0817
414409 = $0812 LDA #$01
415 = $0814 JMP $081F
416 = $0817 JMP $081F
417 = $081A LDA #$02
418 = $081C JMP $080D
419 = $081F LDA #$FF
420 = $0821 RTS
410 = $0814 JMP $081C
411 = $0817 LDA #$02
412 = $0819 JMP $080D
413 = $081C LDA #$FF
414 = $081E RTS
7070 | outputs a
7171 | trashes x
7272 | @ 65487
73 = ok
74
75 Preserved routine.
76
77 | define main routine {
78 | ld a, $ff
79 | add a, $01
80 | }
81 | define foo preserved routine {
82 | ld a, 0
83 | add a, 1
84 | }
7385 = ok
7486
7587 Trash.
1212 -> Functionality "Compile SixtyPical program" is implemented by
1313 -> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
1414
15 -> Functionality "Dump callgraph info for SixtyPical program" is implemented by
16 -> shell command "bin/sixtypical --dump-callgraph --analyze-only --traceback %(test-body-file)"
17
18 -> Functionality "Compile SixtyPical program with unreachable routine removal" is implemented by
19 -> 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"
20
1521 -> Functionality "Dump fallthru info for SixtyPical program" is implemented by
1622 -> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)"
1723