Merge pull request #13 from catseye/develop-0.16
Develop 0.16
Chris Pressey authored 4 years ago
GitHub committed 4 years ago
0 | 0 | History of SixtyPical |
1 | 1 | ===================== |
2 | ||
3 | 0.16 | |
4 | ---- | |
5 | ||
6 | * Added `save` block, which allows the named locations to be modified | |
7 | arbitrarily inside the block, and automatically restored at the end. | |
8 | * More thorough tests and justifications written for the case of | |
9 | assigning a routine to a vector with a "wider" type. | |
10 | * Support for `copy [ptra]+y, [ptrb]+y` to indirect LDA indirect STA. | |
11 | * Support for `shl foo` and `shr foo` where `foo` is a byte storage. | |
12 | * Support for `I a, btable + x` where `I` is `add`, `sub`, `cmp`, | |
13 | `and`, `or`, or `xor` | |
14 | * Support for `I btable + x` where `I` is `shl`, `shr`, `inc`, `dec` | |
15 | * `or a, z`, `and a, z`, and `eor a, z` compile to zero-page operations | |
16 | if the address of z < 256. | |
17 | * Removed `--prelude` in favour of specifying both format and prelude | |
18 | with a single option, `--output-format`. Documentation for same. | |
2 | 19 | |
3 | 20 | 0.15 |
4 | 21 | ---- |
0 | 0 | SixtyPical |
1 | 1 | ========== |
2 | 2 | |
3 | _Version 0.15. Work-in-progress, everything is subject to change._ | |
3 | _Version 0.16. Work-in-progress, everything is subject to change._ | |
4 | 4 | |
5 | 5 | **SixtyPical** is a 6502-like programming language with advanced |
6 | 6 | static analysis. |
53 | 53 | tree, which contains more extensive examples, including an entire |
54 | 54 | game(-like program); see [eg/README.md](eg/README.md) for a listing. |
55 | 55 | |
56 | [VICE]: http://vice-emu.sourceforge.net/ | |
57 | ||
56 | 58 | Documentation |
57 | 59 | ------------- |
58 | 60 | |
64 | 66 | * [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md) |
65 | 67 | * [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md) |
66 | 68 | * [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md) |
69 | * [Output formats supported by `sixtypical`](doc/Output%20Formats.md) | |
67 | 70 | |
68 | 71 | TODO |
69 | 72 | ---- |
70 | 73 | |
71 | ### Save registers on stack | |
74 | ### `low` and `high` address operators | |
72 | 75 | |
73 | This preserves them, so that, semantically, they can be used later even though they | |
74 | are trashed inside the block. | |
76 | To turn `word` type into `byte`. | |
75 | 77 | |
76 | ### And at some point... | |
78 | Trying to remember if we have a compelling case for this or now. The best I can think | |
79 | of is for implementing 16-bit `cmp` in an efficient way. Maybe we should see if we | |
80 | can get by with 16-bit `cmp` instead though. | |
77 | 81 | |
78 | * `low` and `high` address operators - to turn `word` type into `byte`. | |
79 | * Tests, and implementation, ensuring a routine can be assigned to a vector of "wider" type | |
80 | * Related: can we simply view a (small) part of a buffer as a byte table? If not, why not? | |
81 | * Related: add constant to buffer to get new buffer. (Or to table, but... well, maybe.) | |
82 | * Check that the buffer being read or written to through pointer, appears in appropriate inputs or outputs set. | |
83 | (Associate each pointer with the buffer it points into.) | |
84 | * `static` pointers -- currently not possible because pointers must be zero-page, thus `@`, thus uninitialized. | |
85 | * Question the value of the "consistent initialization" principle for `if` statement analysis. | |
86 | * `interrupt` routines -- to indicate that "the supervisor" has stored values on the stack, so we can trash them. | |
87 | * Add absolute addressing in shl/shr, absolute-indexed for add, sub, etc. | |
88 | * Automatic tail-call optimization (could be tricky, w/constraints?) | |
89 | * Possibly `ld x, [ptr] + y`, possibly `st x, [ptr] + y`. | |
90 | * Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA! | |
91 | * Optimize `or|and|eor a, z` to zero-page operations if address of z < 256. | |
82 | The problem is that once a byte is extracted, putting it back into a word is awkward. | |
83 | The address operators have to modify a destination in a special way. That is, when | |
84 | you say `st a, >word`, you are updating `word` to be `word & $ff | a << 8`, somelike. | |
85 | Is that consistent with `st`? Well, probably it is, but we have to explain it. | |
86 | It might make more sense, then, for it to be "part of the operation" instead of "part of | |
87 | the reference"; something like `st.hi x, word`; `st.lo y, word`. Dunno. | |
92 | 88 | |
93 | [VICE]: http://vice-emu.sourceforge.net/ | |
89 | ### Save multiple values in single block | |
90 | ||
91 | As a shortcut for the idiom | |
92 | ||
93 | save a { save var { | |
94 | ... | |
95 | } } | |
96 | ||
97 | allow | |
98 | ||
99 | save a, var { | |
100 | ... | |
101 | } | |
102 | ||
103 | ### Save values to other-than-the-stack | |
104 | ||
105 | Allow | |
106 | ||
107 | save a to temp_a { | |
108 | ... | |
109 | } | |
110 | ||
111 | Which uses some other storage location instead of the stack. A local static | |
112 | would be a good candidate for such. | |
113 | ||
114 | ### Make all symbols forward-referencable | |
115 | ||
116 | Basically, don't do symbol-table lookups when parsing, but do have a more formal | |
117 | "symbol resolution" (linking) phase right after parsing. | |
118 | ||
119 | ### Associate each pointer with the buffer it points into | |
120 | ||
121 | Check that the buffer being read or written to through pointer, appears in appropriate | |
122 | inputs or outputs set. | |
123 | ||
124 | In the analysis, when we obtain a pointer, we need to record, in contect, what buffer | |
125 | that pointer came from. | |
126 | ||
127 | When we write through that pointer, we need to set that buffer as written. | |
128 | ||
129 | When we read through the pointer, we need to check that the buffer is readable. | |
130 | ||
131 | ### Table overlays | |
132 | ||
133 | They are uninitialized, but the twist is, the address is a buffer that is | |
134 | an input to and/or output of the routine. So, they are defined (insofar | |
135 | as the buffer is defined.) | |
136 | ||
137 | They are therefore a "view" of a section of a buffer. | |
138 | ||
139 | This is slightly dangerous since it does permit aliases: the buffer and the | |
140 | table refer to the same memory. | |
141 | ||
142 | Although, if they are `static`, you could say, in the routine in which they | |
143 | are `static`, as soon as you've established one, you can no longer use the | |
144 | buffer; and the ones you establish must be disjoint. | |
145 | ||
146 | (That seems to be the most compelling case for restricting them to `static`.) | |
147 | ||
148 | An alternative would be `static` pointers, which are currently not possible because | |
149 | pointers must be zero-page, thus `@`, thus uninitialized. | |
150 | ||
151 | ### Question "consistent initialization" | |
152 | ||
153 | Question the value of the "consistent initialization" principle for `if` statement analysis. | |
154 | ||
155 | Part of this is the trashes at the end; I think what it should be is that the trashes | |
156 | after the `if` is the union of the trashes in each of the branches; this would obviate the | |
157 | need to `trash` values explicitly, but if you tried to access them afterwards, it would still | |
158 | error. | |
159 | ||
160 | ### Tail-call optimization | |
161 | ||
162 | More generally, define a block as having zero or one `goto`s at the end. (and `goto`s cannot | |
163 | appear elsewhere.) | |
164 | ||
165 | If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can. | |
166 | The constraints should iron out the same both ways. | |
167 | ||
168 | And - once we have this - why do we need `goto` to be in tail position, strictly? | |
169 | As long as the routine has consistent type context every place it exits, that should be fine. | |
170 | ||
171 | ### "Include" directives | |
172 | ||
173 | Search a searchlist of include paths. And use them to make libraries of routines. | |
174 | ||
175 | One such library routine might be an `interrupt routine` type for various architectures. | |
176 | Since "the supervisor" has stored values on the stack, we should be able to trash them | |
177 | with impunity, in such a routine. |
80 | 80 | |
81 | 81 | fh = sys.stdout |
82 | 82 | |
83 | if options.origin.startswith('0x'): | |
84 | start_addr = int(options.origin, 16) | |
85 | else: | |
86 | start_addr = int(options.origin, 10) | |
87 | ||
88 | output_format = options.output_format | |
89 | ||
90 | prelude = [] | |
91 | if options.prelude == 'c64': | |
92 | output_format = 'prg' | |
83 | if options.output_format == 'raw': | |
84 | start_addr = 0x0000 | |
85 | prelude = [] | |
86 | elif options.output_format == 'prg': | |
87 | start_addr = 0xc000 | |
88 | prelude = [] | |
89 | elif options.output_format == 'c64-basic-prg': | |
93 | 90 | start_addr = 0x0801 |
94 | 91 | prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32, |
95 | 92 | 0x30, 0x36, 0x31, 0x00, 0x00, 0x00] |
96 | elif options.prelude == 'vic20': | |
97 | output_format = 'prg' | |
93 | elif options.output_format == 'vic20-basic-prg': | |
98 | 94 | start_addr = 0x1001 |
99 | 95 | prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34, |
100 | 96 | 0x31, 0x30, 0x39, 0x00, 0x00, 0x00] |
101 | elif options.prelude == 'atari2600': | |
102 | output_format = 'crtbb' | |
97 | elif options.output_format == 'atari2600-cart': | |
103 | 98 | start_addr = 0xf000 |
104 | 99 | prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9, |
105 | 0x00,0x95, 0x00, 0xca, 0xd0, 0xfb] | |
100 | 0x00, 0x95, 0x00, 0xca, 0xd0, 0xfb] | |
101 | else: | |
102 | raise NotImplementedError("Unknown output format: {}".format(options.output_format)) | |
106 | 103 | |
107 | elif options.prelude: | |
108 | raise NotImplementedError("Unknown prelude: {}".format(options.prelude)) | |
104 | if options.origin is not None: | |
105 | if options.origin.startswith('0x'): | |
106 | start_addr = int(options.origin, 16) | |
107 | else: | |
108 | start_addr = int(options.origin, 10) | |
109 | 109 | |
110 | 110 | # If we are outputting a .PRG, we output the load address first. |
111 | 111 | # We don't use the Emitter for this b/c not part of addr space. |
112 | if output_format == 'prg': | |
112 | if options.output_format in ('prg', 'c64-basic-prg', 'vic20-basic-prg'): | |
113 | 113 | fh.write(Word(start_addr).serialize(0)) |
114 | 114 | |
115 | 115 | emitter = Emitter(start_addr) |
120 | 120 | |
121 | 121 | # If we are outputting a cartridge with boot and BRK address |
122 | 122 | # at the end, pad to ROM size minus 4 bytes, and emit addresses. |
123 | if output_format == 'crtbb': | |
123 | if options.output_format == 'atari2600-cart': | |
124 | 124 | emitter.pad_to_size(4096 - 4) |
125 | 125 | emitter.emit(Word(start_addr)) |
126 | 126 | emitter.emit(Word(start_addr)) |
140 | 140 | ) |
141 | 141 | |
142 | 142 | argparser.add_argument( |
143 | "--origin", type=str, default='0xc000', | |
143 | "--origin", type=str, default=None, | |
144 | 144 | help="Location in memory where the `main` routine will be " |
145 | "located. Default: 0xc000." | |
145 | "located. Default: depends on output format." | |
146 | 146 | ) |
147 | 147 | argparser.add_argument( |
148 | "--output-format", type=str, default='prg', | |
149 | help="Executable format to produce. Options are: prg, crtbb. " | |
150 | "Default: prg." | |
151 | ) | |
152 | argparser.add_argument( | |
153 | "--prelude", type=str, | |
154 | help="Insert a snippet of code before the compiled program so that " | |
155 | "it can be booted automatically on a particular platform. " | |
156 | "Also sets the origin and format. " | |
157 | "Options are: c64, vic20, atari2600." | |
148 | "--output-format", type=str, default='raw', | |
149 | help="Executable format to produce; also sets a default origin. " | |
150 | "Options are: raw, prg, c64-basic-prg, vic20-basic-prg, atari2600-cart." | |
151 | "Default: raw." | |
158 | 152 | ) |
159 | 153 | |
160 | 154 | argparser.add_argument( |
0 | Output Formats | |
1 | ============== | |
2 | ||
3 | `sixtypical` can generate an output file in a number of formats. | |
4 | ||
5 | ### `raw` | |
6 | ||
7 | The file contains only the emitted bytes of the compiled SixtyPical | |
8 | program. | |
9 | ||
10 | The default origin is $0000; it is not unlikely you will want to | |
11 | override this. | |
12 | ||
13 | Note that the origin is not stored in the output file in this format; | |
14 | that information must be recorded separately. | |
15 | ||
16 | ### `prg` | |
17 | ||
18 | The first two bytes of the file contain the origin address in | |
19 | little-endian format. The remainder of the file is the emitted bytes | |
20 | of the compiled SixtyPical program, starting at that origin. | |
21 | ||
22 | The default origin is $C000; it is likely you will want to | |
23 | override this. | |
24 | ||
25 | This format coincides with Commodore's PRG format for disk files, | |
26 | thus its name. | |
27 | ||
28 | ### `c64-basic-prg` | |
29 | ||
30 | The first few bytes of the file contain a short Commodore 2.0 BASIC | |
31 | program. Directly after this is the emitted bytes of the compiled | |
32 | SixtyPical program. The BASIC program contains a `SYS` to that code. | |
33 | ||
34 | The default origin is $0801; it is unlikely that you will want to | |
35 | override this. | |
36 | ||
37 | This format allows the PRG file to be loaded and run on a Commodore 64 | |
38 | with | |
39 | ||
40 | LOAD"FOO.PRG",8:RUN | |
41 | ||
42 | ### `vic20-basic-prg` | |
43 | ||
44 | Exactly like `--c64-basic-prg` except intended for the Commodore VIC-20. | |
45 | ||
46 | The default origin is $1001; it is unlikely that you will want to | |
47 | override this. | |
48 | ||
49 | This format allows the PRG file to be loaded and run on a VIC-20 with | |
50 | ||
51 | LOAD"FOO.PRG",8:RUN | |
52 | ||
53 | ### `atari2600-cart` | |
54 | ||
55 | The file starts with a short machine-language prelude which is intended | |
56 | to initialize an Atari 2600 system, followed by the emitted bytes of the | |
57 | compiled SixtyPical program. | |
58 | ||
59 | The file is padded to 4096 bytes in length. The padding is mostly | |
60 | zeroes, except for the final 4 bytes of the file, which consist of | |
61 | two addresses in little-endian format; both are the origin address. | |
62 | ||
63 | The default origin is $F000; it is unlikely you will want to | |
64 | override this. | |
65 | ||
66 | This is the format used by Atari 2600 cartridges. |
405 | 405 | } |
406 | 406 | |
407 | 407 | define game_state_play game_state_routine |
408 | static byte save_x : 0 | |
409 | 408 | { |
410 | 409 | ld x, 0 |
411 | repeat { | |
410 | for x up to 15 { | |
412 | 411 | copy actor_pos + x, pos |
413 | 412 | copy actor_delta + x, delta |
414 | 413 | |
415 | st x, save_x | |
416 | ||
417 | copy actor_logic + x, dispatch_logic | |
418 | call dispatch_logic | |
419 | ||
420 | if c { | |
421 | // Player died! Want no dead! Break out of the loop (this is a bit awkward.) | |
422 | call clear_screen | |
423 | copy game_state_game_over, dispatch_game_state | |
424 | ld x, 15 | |
425 | } else { | |
426 | ld x, save_x | |
414 | // | |
415 | // Save our loop counter on the stack temporarily. This means that routines | |
416 | // like `dispatch_logic` and `clear_screen` are allowed to do whatever they | |
417 | // want with the `x` register; we will restore it at the end of this block. | |
418 | // | |
419 | save x { | |
420 | copy actor_logic + x, dispatch_logic | |
421 | call dispatch_logic | |
422 | ||
423 | if c { | |
424 | // Player died! Want no dead! | |
425 | call clear_screen | |
426 | copy game_state_game_over, dispatch_game_state | |
427 | } | |
427 | 428 | } |
428 | 429 | |
429 | 430 | copy pos, actor_pos + x |
430 | 431 | copy delta, actor_delta + x |
431 | ||
432 | inc x | |
433 | cmp x, 16 | |
434 | } until z | |
432 | } | |
435 | 433 | |
436 | 434 | goto save_cinv |
437 | 435 | } |
4 | 4 | arch="$1" |
5 | 5 | shift 1 |
6 | 6 | if [ "X$arch" = "Xc64" ]; then |
7 | prelude='c64' | |
7 | output_format='c64-basic-prg' | |
8 | 8 | if [ -e vicerc ]; then |
9 | 9 | emu="x64 -config vicerc" |
10 | 10 | else |
11 | 11 | emu="x64" |
12 | 12 | fi |
13 | 13 | elif [ "X$arch" = "Xvic20" ]; then |
14 | prelude='vic20' | |
14 | output_format='vic20-basic-prg' | |
15 | 15 | if [ -e vicerc ]; then |
16 | 16 | emu="xvic -config vicerc" |
17 | 17 | else |
18 | 18 | emu="xvic" |
19 | 19 | fi |
20 | 20 | elif [ "X$arch" = "Xatari2600" ]; then |
21 | prelude='atari2600' | |
21 | output_format='atari2600-cart' | |
22 | 22 | emu='stella' |
23 | 23 | else |
24 | 24 | echo $usage && exit 1 |
37 | 37 | ### do it ### |
38 | 38 | |
39 | 39 | out=/tmp/a-out.prg |
40 | bin/sixtypical --traceback --prelude=$prelude $src > $out || exit 1 | |
40 | bin/sixtypical --traceback --output-format=$output_format $src > $out || exit 1 | |
41 | 41 | ls -la $out |
42 | 42 | $emu $out |
43 | 43 | rm -f $out |
0 | 0 | # encoding: UTF-8 |
1 | 1 | |
2 | from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff | |
2 | from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff, Save | |
3 | 3 | from sixtypical.model import ( |
4 | 4 | TYPE_BYTE, TYPE_WORD, |
5 | 5 | TableType, BufferType, PointerType, VectorType, RoutineType, |
268 | 268 | self._writeable.remove(ref) |
269 | 269 | |
270 | 270 | def set_writeable(self, *refs): |
271 | """Intended to be used for implementing analyzing `for`.""" | |
271 | """Intended to be used for implementing analyzing `for`, but also used in `save`.""" | |
272 | 272 | for ref in refs: |
273 | 273 | self._writeable.add(ref) |
274 | 274 | |
277 | 277 | |
278 | 278 | def encountered_gotos(self): |
279 | 279 | return self._gotos_encountered |
280 | ||
281 | def assert_types_for_read_table(self, instr, src, dest, type_): | |
282 | if (not TableType.is_a_table_type(src.ref.type, type_)) or (not dest.type == type_): | |
283 | raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name)) | |
284 | self.assert_meaningful(src, src.index) | |
285 | self.assert_in_range(src.index, src.ref) | |
286 | ||
287 | def assert_types_for_update_table(self, instr, dest, type_): | |
288 | if not TableType.is_a_table_type(dest.ref.type, type_): | |
289 | raise TypeMismatchError(instr, '{}'.format(dest.ref.name)) | |
290 | self.assert_meaningful(dest.index) | |
291 | self.assert_in_range(dest.index, dest.ref) | |
292 | self.set_written(dest.ref) | |
293 | ||
294 | def extract(self, location): | |
295 | """Sets the given location as writeable in the context, and returns a 'baton' representing | |
296 | the previous state of context for that location. This 'baton' can be used to later restore | |
297 | this state of context.""" | |
298 | # Used in `save`. | |
299 | baton = ( | |
300 | location, | |
301 | location in self._touched, | |
302 | self._range.get(location, None), | |
303 | location in self._writeable, | |
304 | ) | |
305 | self.set_writeable(location) | |
306 | return baton | |
307 | ||
308 | def re_introduce(self, baton): | |
309 | """Given a 'baton' produced by `extract()`, restores the context for that saved location | |
310 | to what it was before `extract()` was called.""" | |
311 | # Used in `save`. | |
312 | location, was_touched, was_range, was_writeable = baton | |
313 | ||
314 | if was_touched: | |
315 | self._touched.add(location) | |
316 | elif location in self._touched: | |
317 | self._touched.remove(location) | |
318 | ||
319 | if was_range is not None: | |
320 | self._range[location] = was_range | |
321 | elif location in self._range: | |
322 | del self._range[location] | |
323 | ||
324 | if was_writeable: | |
325 | self._writeable.add(location) | |
326 | elif location in self._writeable: | |
327 | self._writeable.remove(location) | |
280 | 328 | |
281 | 329 | |
282 | 330 | class Analyzer(object): |
286 | 334 | self.routines = {} |
287 | 335 | self.debug = debug |
288 | 336 | |
289 | def assert_type(self, type, *locations): | |
337 | def assert_type(self, type_, *locations): | |
290 | 338 | for location in locations: |
291 | if location.type != type: | |
339 | if location.type != type_: | |
292 | 340 | raise TypeMismatchError(self.current_routine, location.name) |
293 | 341 | |
294 | 342 | def assert_affected_within(self, name, affecting_type, limiting_type): |
371 | 419 | self.analyze_for(instr, context) |
372 | 420 | elif isinstance(instr, WithInterruptsOff): |
373 | 421 | self.analyze_block(instr.block, context) |
422 | if context.encountered_gotos(): | |
423 | raise IllegalJumpError(instr, instr) | |
424 | elif isinstance(instr, Save): | |
425 | self.analyze_save(instr, context) | |
374 | 426 | else: |
375 | 427 | raise NotImplementedError |
376 | 428 | |
385 | 437 | |
386 | 438 | if opcode == 'ld': |
387 | 439 | if isinstance(src, IndexedRef): |
388 | if TableType.is_a_table_type(src.ref.type, TYPE_BYTE) and dest.type == TYPE_BYTE: | |
389 | pass | |
390 | else: | |
391 | raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name)) | |
392 | context.assert_meaningful(src, src.index) | |
393 | context.assert_in_range(src.index, src.ref) | |
440 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) | |
394 | 441 | elif isinstance(src, IndirectRef): |
395 | 442 | # copying this analysis from the matching branch in `copy`, below |
396 | 443 | if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE: |
406 | 453 | context.set_written(dest, FLAG_Z, FLAG_N) |
407 | 454 | elif opcode == 'st': |
408 | 455 | if isinstance(dest, IndexedRef): |
409 | if src.type == TYPE_BYTE and TableType.is_a_table_type(dest.ref.type, TYPE_BYTE): | |
410 | pass | |
411 | else: | |
412 | raise TypeMismatchError(instr, (src, dest)) | |
413 | context.assert_meaningful(dest.index) | |
414 | context.assert_in_range(dest.index, dest.ref) | |
415 | context.set_written(dest.ref) | |
456 | if src.type != TYPE_BYTE: | |
457 | raise TypeMismatchError(instr, (src, dest)) | |
458 | context.assert_types_for_update_table(instr, dest, TYPE_BYTE) | |
416 | 459 | elif isinstance(dest, IndirectRef): |
417 | 460 | # copying this analysis from the matching branch in `copy`, below |
418 | 461 | if isinstance(dest.ref.type, PointerType) and src.type == TYPE_BYTE: |
422 | 465 | context.assert_meaningful(dest.ref, REG_Y) |
423 | 466 | context.set_written(dest.ref) |
424 | 467 | elif src.type != dest.type: |
425 | raise TypeMismatchError(instr, '{} and {}'.format(src, name)) | |
468 | raise TypeMismatchError(instr, '{} and {}'.format(src, dest)) | |
426 | 469 | else: |
427 | 470 | context.set_written(dest) |
428 | 471 | # FIXME: context.copy_range(src, dest) ? |
429 | 472 | context.assert_meaningful(src) |
430 | 473 | elif opcode == 'add': |
431 | 474 | context.assert_meaningful(src, dest, FLAG_C) |
432 | if src.type == TYPE_BYTE: | |
475 | if isinstance(src, IndexedRef): | |
476 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) | |
477 | elif src.type == TYPE_BYTE: | |
433 | 478 | self.assert_type(TYPE_BYTE, src, dest) |
434 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) | |
435 | 479 | else: |
436 | 480 | self.assert_type(TYPE_WORD, src) |
437 | 481 | if dest.type == TYPE_WORD: |
438 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) | |
439 | 482 | context.set_touched(REG_A) |
440 | 483 | context.set_unmeaningful(REG_A) |
441 | 484 | elif isinstance(dest.type, PointerType): |
442 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) | |
443 | 485 | context.set_touched(REG_A) |
444 | 486 | context.set_unmeaningful(REG_A) |
445 | 487 | else: |
446 | 488 | self.assert_type(TYPE_WORD, dest) |
489 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) | |
447 | 490 | context.invalidate_range(dest) |
448 | 491 | elif opcode == 'sub': |
449 | 492 | context.assert_meaningful(src, dest, FLAG_C) |
450 | if src.type == TYPE_BYTE: | |
493 | if isinstance(src, IndexedRef): | |
494 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) | |
495 | elif src.type == TYPE_BYTE: | |
451 | 496 | self.assert_type(TYPE_BYTE, src, dest) |
452 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) | |
453 | 497 | else: |
454 | 498 | self.assert_type(TYPE_WORD, src, dest) |
455 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) | |
456 | 499 | context.set_touched(REG_A) |
457 | 500 | context.set_unmeaningful(REG_A) |
458 | context.invalidate_range(dest) | |
459 | elif opcode in ('inc', 'dec'): | |
460 | self.assert_type(TYPE_BYTE, dest) | |
461 | context.assert_meaningful(dest) | |
462 | context.set_written(dest, FLAG_Z, FLAG_N) | |
501 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) | |
463 | 502 | context.invalidate_range(dest) |
464 | 503 | elif opcode == 'cmp': |
465 | self.assert_type(TYPE_BYTE, src, dest) | |
466 | 504 | context.assert_meaningful(src, dest) |
505 | if isinstance(src, IndexedRef): | |
506 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) | |
507 | else: | |
508 | self.assert_type(TYPE_BYTE, src, dest) | |
467 | 509 | context.set_written(FLAG_Z, FLAG_N, FLAG_C) |
468 | 510 | elif opcode == 'and': |
469 | self.assert_type(TYPE_BYTE, src, dest) | |
511 | if isinstance(src, IndexedRef): | |
512 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) | |
513 | else: | |
514 | self.assert_type(TYPE_BYTE, src, dest) | |
470 | 515 | context.assert_meaningful(src, dest) |
471 | 516 | context.set_written(dest, FLAG_Z, FLAG_N) |
472 | 517 | # If you AND the A register with a value V, the resulting value of A |
473 | 518 | # cannot exceed the value of V; i.e. the maximum value of A becomes |
474 | 519 | # the maximum value of V. |
475 | context.set_top_of_range(dest, context.get_top_of_range(src)) | |
520 | if not isinstance(src, IndexedRef): | |
521 | context.set_top_of_range(dest, context.get_top_of_range(src)) | |
476 | 522 | elif opcode in ('or', 'xor'): |
477 | self.assert_type(TYPE_BYTE, src, dest) | |
523 | if isinstance(src, IndexedRef): | |
524 | context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE) | |
525 | else: | |
526 | self.assert_type(TYPE_BYTE, src, dest) | |
478 | 527 | context.assert_meaningful(src, dest) |
479 | 528 | context.set_written(dest, FLAG_Z, FLAG_N) |
480 | 529 | context.invalidate_range(dest) |
530 | elif opcode in ('inc', 'dec'): | |
531 | context.assert_meaningful(dest) | |
532 | if isinstance(dest, IndexedRef): | |
533 | context.assert_types_for_update_table(instr, dest, TYPE_BYTE) | |
534 | context.set_written(dest.ref, FLAG_Z, FLAG_N) | |
535 | #context.invalidate_range(dest) | |
536 | else: | |
537 | self.assert_type(TYPE_BYTE, dest) | |
538 | context.set_written(dest, FLAG_Z, FLAG_N) | |
539 | context.invalidate_range(dest) | |
481 | 540 | elif opcode in ('shl', 'shr'): |
482 | self.assert_type(TYPE_BYTE, dest) | |
483 | 541 | context.assert_meaningful(dest, FLAG_C) |
484 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C) | |
485 | context.invalidate_range(dest) | |
542 | if isinstance(dest, IndexedRef): | |
543 | context.assert_types_for_update_table(instr, dest, TYPE_BYTE) | |
544 | context.set_written(dest.ref, FLAG_Z, FLAG_N, FLAG_C) | |
545 | #context.invalidate_range(dest) | |
546 | else: | |
547 | self.assert_type(TYPE_BYTE, dest) | |
548 | context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C) | |
549 | context.invalidate_range(dest) | |
486 | 550 | elif opcode == 'call': |
487 | 551 | type = instr.location.type |
488 | 552 | if isinstance(type, VectorType): |
516 | 580 | pass |
517 | 581 | else: |
518 | 582 | raise TypeMismatchError(instr, (src, dest)) |
583 | elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef): | |
584 | if isinstance(src.ref.type, PointerType) and isinstance(dest.ref.type, PointerType): | |
585 | pass | |
586 | else: | |
587 | raise TypeMismatchError(instr, (src, dest)) | |
519 | 588 | |
520 | 589 | elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef): |
521 | 590 | if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD): |
560 | 629 | context.set_written(dest.ref) |
561 | 630 | elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef): |
562 | 631 | context.assert_meaningful(src.ref, REG_Y) |
632 | # TODO more sophisticated? | |
563 | 633 | context.set_written(dest) |
634 | elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef): | |
635 | context.assert_meaningful(src.ref, REG_Y) | |
636 | # TODO more sophisticated? | |
637 | context.set_written(dest.ref) | |
564 | 638 | elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef): |
565 | 639 | context.assert_meaningful(src, dest.ref, dest.index) |
566 | 640 | context.set_written(dest.ref) |
694 | 768 | # after it is executed, we know the range of the loop variable. |
695 | 769 | context.set_range(instr.dest, instr.final, instr.final) |
696 | 770 | context.set_writeable(instr.dest) |
771 | ||
772 | def analyze_save(self, instr, context): | |
773 | if len(instr.locations) != 1: | |
774 | raise NotImplementedError("Only 1 location in save is supported right now") | |
775 | location = instr.locations[0] | |
776 | self.assert_type(TYPE_BYTE, location) | |
777 | ||
778 | baton = context.extract(location) | |
779 | self.analyze_block(instr.block, context) | |
780 | if context.encountered_gotos(): | |
781 | raise IllegalJumpError(instr, instr) | |
782 | context.re_introduce(baton) | |
783 | ||
784 | if location == REG_A: | |
785 | pass | |
786 | else: | |
787 | context.set_touched(REG_A) | |
788 | context.set_unmeaningful(REG_A) |
89 | 89 | |
90 | 90 | class WithInterruptsOff(Instr): |
91 | 91 | child_attrs = ('block',) |
92 | ||
93 | ||
94 | class Save(Instr): | |
95 | value_attrs = ('locations',) | |
96 | child_attrs = ('block',) |
0 | 0 | # encoding: UTF-8 |
1 | 1 | |
2 | from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff | |
2 | from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff, Save | |
3 | 3 | from sixtypical.model import ( |
4 | 4 | ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef, |
5 | 5 | TYPE_BIT, TYPE_BYTE, TYPE_WORD, |
11 | 11 | Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative, |
12 | 12 | LDA, LDX, LDY, STA, STX, STY, |
13 | 13 | TAX, TAY, TXA, TYA, |
14 | PHA, PLA, | |
14 | 15 | CLC, SEC, ADC, SBC, ROL, ROR, |
15 | 16 | INC, INX, INY, DEC, DEX, DEY, |
16 | 17 | CMP, CPX, CPY, AND, ORA, EOR, |
168 | 169 | return self.compile_for(instr) |
169 | 170 | elif isinstance(instr, WithInterruptsOff): |
170 | 171 | return self.compile_with_interrupts_off(instr) |
172 | elif isinstance(instr, Save): | |
173 | return self.compile_save(instr) | |
171 | 174 | else: |
172 | 175 | raise NotImplementedError |
173 | 176 | |
243 | 246 | if dest == REG_A: |
244 | 247 | if isinstance(src, ConstantRef): |
245 | 248 | self.emitter.emit(ADC(Immediate(Byte(src.value)))) |
249 | elif isinstance(src, IndexedRef): | |
250 | self.emitter.emit(ADC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name)))) | |
246 | 251 | else: |
247 | 252 | self.emitter.emit(ADC(Absolute(self.get_label(src.name)))) |
248 | 253 | elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD: |
291 | 296 | if dest == REG_A: |
292 | 297 | if isinstance(src, ConstantRef): |
293 | 298 | self.emitter.emit(SBC(Immediate(Byte(src.value)))) |
299 | elif isinstance(src, IndexedRef): | |
300 | self.emitter.emit(SBC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name)))) | |
294 | 301 | else: |
295 | 302 | self.emitter.emit(SBC(Absolute(self.get_label(src.name)))) |
296 | 303 | elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD: |
315 | 322 | raise UnsupportedOpcodeError(instr) |
316 | 323 | else: |
317 | 324 | raise UnsupportedOpcodeError(instr) |
318 | elif opcode == 'inc': | |
319 | self.compile_inc(instr, instr.dest) | |
320 | elif opcode == 'dec': | |
321 | self.compile_dec(instr, instr.dest) | |
322 | 325 | elif opcode == 'cmp': |
323 | 326 | self.compile_cmp(instr, instr.src, instr.dest) |
324 | 327 | elif opcode in ('and', 'or', 'xor',): |
330 | 333 | if dest == REG_A: |
331 | 334 | if isinstance(src, ConstantRef): |
332 | 335 | self.emitter.emit(cls(Immediate(Byte(src.value)))) |
333 | else: | |
334 | self.emitter.emit(cls(Absolute(self.get_label(src.name)))) | |
336 | elif isinstance(src, IndexedRef): | |
337 | self.emitter.emit(cls(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name)))) | |
338 | else: | |
339 | self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(src.name)))) | |
335 | 340 | else: |
336 | 341 | raise UnsupportedOpcodeError(instr) |
342 | elif opcode == 'inc': | |
343 | self.compile_inc(instr, instr.dest) | |
344 | elif opcode == 'dec': | |
345 | self.compile_dec(instr, instr.dest) | |
337 | 346 | elif opcode in ('shl', 'shr'): |
338 | 347 | cls = { |
339 | 348 | 'shl': ROL, |
341 | 350 | }[opcode] |
342 | 351 | if dest == REG_A: |
343 | 352 | self.emitter.emit(cls()) |
344 | else: | |
345 | raise UnsupportedOpcodeError(instr) | |
353 | elif isinstance(dest, IndexedRef): | |
354 | self.emitter.emit(cls(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name)))) | |
355 | else: | |
356 | self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(dest.name)))) | |
346 | 357 | elif opcode == 'call': |
347 | 358 | location = instr.location |
348 | 359 | label = self.get_label(instr.location.name) |
388 | 399 | raise UnsupportedOpcodeError(instr) |
389 | 400 | if isinstance(src, ConstantRef): |
390 | 401 | self.emitter.emit(cls(Immediate(Byte(src.value)))) |
402 | elif isinstance(src, IndexedRef): | |
403 | # FIXME might not work for some dest's (that is, cls's) | |
404 | self.emitter.emit(cls(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name)))) | |
391 | 405 | else: |
392 | 406 | self.emitter.emit(cls(Absolute(self.get_label(src.name)))) |
393 | 407 | |
397 | 411 | self.emitter.emit(INX()) |
398 | 412 | elif dest == REG_Y: |
399 | 413 | self.emitter.emit(INY()) |
414 | elif isinstance(dest, IndexedRef): | |
415 | self.emitter.emit(INC(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name)))) | |
400 | 416 | else: |
401 | 417 | self.emitter.emit(INC(Absolute(self.get_label(dest.name)))) |
402 | 418 | |
406 | 422 | self.emitter.emit(DEX()) |
407 | 423 | elif dest == REG_Y: |
408 | 424 | self.emitter.emit(DEY()) |
425 | elif isinstance(dest, IndexedRef): | |
426 | self.emitter.emit(DEC(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name)))) | |
409 | 427 | else: |
410 | 428 | self.emitter.emit(DEC(Absolute(self.get_label(dest.name)))) |
411 | 429 | |
427 | 445 | dest_label = self.get_label(dest.name) |
428 | 446 | self.emitter.emit(LDA(IndirectY(src_label))) |
429 | 447 | self.emitter.emit(STA(Absolute(dest_label))) |
448 | elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef) and isinstance(src.ref.type, PointerType) and isinstance(dest.ref.type, PointerType): | |
449 | ### copy [ptra] + y, [ptrb] + y | |
450 | src_label = self.get_label(src.ref.name) | |
451 | dest_label = self.get_label(dest.ref.name) | |
452 | self.emitter.emit(LDA(IndirectY(src_label))) | |
453 | self.emitter.emit(STA(IndirectY(dest_label))) | |
430 | 454 | elif isinstance(src, AddressRef) and isinstance(dest, LocationRef) and isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType): |
431 | 455 | ### copy ^buf, ptr |
432 | 456 | src_label = self.get_label(src.ref.name) |
591 | 615 | self.emitter.emit(SEI()) |
592 | 616 | self.compile_block(instr.block) |
593 | 617 | self.emitter.emit(CLI()) |
618 | ||
619 | def compile_save(self, instr): | |
620 | location = instr.locations[0] | |
621 | if location == REG_A: | |
622 | self.emitter.emit(PHA()) | |
623 | self.compile_block(instr.block) | |
624 | self.emitter.emit(PLA()) | |
625 | elif location == REG_X: | |
626 | self.emitter.emit(TXA()) | |
627 | self.emitter.emit(PHA()) | |
628 | self.compile_block(instr.block) | |
629 | self.emitter.emit(PLA()) | |
630 | self.emitter.emit(TAX()) | |
631 | elif location == REG_Y: | |
632 | self.emitter.emit(TYA()) | |
633 | self.emitter.emit(PHA()) | |
634 | self.compile_block(instr.block) | |
635 | self.emitter.emit(PLA()) | |
636 | self.emitter.emit(TAY()) | |
637 | else: | |
638 | src_label = self.get_label(location.name) | |
639 | self.emitter.emit(LDA(Absolute(src_label))) | |
640 | self.emitter.emit(PHA()) | |
641 | self.compile_block(instr.block) | |
642 | self.emitter.emit(PLA()) | |
643 | self.emitter.emit(STA(Absolute(src_label))) |
132 | 132 | Absolute: 0x2d, |
133 | 133 | AbsoluteX: 0x3d, |
134 | 134 | AbsoluteY: 0x39, |
135 | ZeroPage: 0x25, | |
135 | 136 | } |
136 | 137 | |
137 | 138 | |
209 | 210 | class DEC(Instruction): |
210 | 211 | opcodes = { |
211 | 212 | Absolute: 0xce, |
213 | AbsoluteX: 0xde, | |
212 | 214 | } |
213 | 215 | |
214 | 216 | |
230 | 232 | Absolute: 0x4d, |
231 | 233 | AbsoluteX: 0x5d, |
232 | 234 | AbsoluteY: 0x59, |
235 | ZeroPage: 0x45, | |
233 | 236 | } |
234 | 237 | |
235 | 238 | |
298 | 301 | Absolute: 0x0d, |
299 | 302 | AbsoluteX: 0x1d, |
300 | 303 | AbsoluteY: 0x19, |
304 | ZeroPage: 0x05, | |
305 | } | |
306 | ||
307 | ||
308 | class PHA(Instruction): | |
309 | opcodes = { | |
310 | Implied: 0x48, | |
311 | } | |
312 | ||
313 | ||
314 | class PLA(Instruction): | |
315 | opcodes = { | |
316 | Implied: 0x68, | |
301 | 317 | } |
302 | 318 | |
303 | 319 |
0 | 0 | # encoding: UTF-8 |
1 | 1 | |
2 | from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff | |
2 | from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save | |
3 | 3 | from sixtypical.model import ( |
4 | 4 | TYPE_BIT, TYPE_BYTE, TYPE_WORD, |
5 | 5 | RoutineType, VectorType, TableType, BufferType, PointerType, |
441 | 441 | elif self.scanner.token in ("shl", "shr", "inc", "dec"): |
442 | 442 | opcode = self.scanner.token |
443 | 443 | self.scanner.scan() |
444 | dest = self.locexpr() | |
444 | dest = self.indexed_locexpr() | |
445 | 445 | return SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=None) |
446 | 446 | elif self.scanner.token in ("nop",): |
447 | 447 | opcode = self.scanner.token |
469 | 469 | self.scanner.expect("off") |
470 | 470 | block = self.block() |
471 | 471 | return WithInterruptsOff(self.scanner.line_number, block=block) |
472 | elif self.scanner.consume("save"): | |
473 | locations = self.locexprs() | |
474 | block = self.block() | |
475 | return Save(self.scanner.line_number, locations=locations, block=block) | |
472 | 476 | elif self.scanner.consume("trash"): |
473 | 477 | dest = self.locexpr() |
474 | 478 | return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest) |
472 | 472 | | ld a, many + x |
473 | 473 | | } |
474 | 474 | ? UnmeaningfulReadError: x |
475 | ||
476 | There are other operations you can do on tables. (1/3) | |
477 | ||
478 | | byte table[256] many | |
479 | | | |
480 | | routine main | |
481 | | inputs many | |
482 | | outputs many | |
483 | | trashes a, x, c, n, z, v | |
484 | | { | |
485 | | ld x, 0 | |
486 | | ld a, 0 | |
487 | | st off, c | |
488 | | add a, many + x | |
489 | | sub a, many + x | |
490 | | cmp a, many + x | |
491 | | } | |
492 | = ok | |
493 | ||
494 | There are other operations you can do on tables. (2/3) | |
495 | ||
496 | | byte table[256] many | |
497 | | | |
498 | | routine main | |
499 | | inputs many | |
500 | | outputs many | |
501 | | trashes a, x, c, n, z | |
502 | | { | |
503 | | ld x, 0 | |
504 | | ld a, 0 | |
505 | | and a, many + x | |
506 | | or a, many + x | |
507 | | xor a, many + x | |
508 | | } | |
509 | = ok | |
510 | ||
511 | There are other operations you can do on tables. (3/3) | |
512 | ||
513 | | byte table[256] many | |
514 | | | |
515 | | routine main | |
516 | | inputs many | |
517 | | outputs many | |
518 | | trashes a, x, c, n, z | |
519 | | { | |
520 | | ld x, 0 | |
521 | | ld a, 0 | |
522 | | st off, c | |
523 | | shl many + x | |
524 | | shr many + x | |
525 | | inc many + x | |
526 | | dec many + x | |
527 | | } | |
528 | = ok | |
475 | 529 | |
476 | 530 | Copying to and from a word table. |
477 | 531 | |
1119 | 1173 | |
1120 | 1174 | Some rudimentary tests for `shl`. |
1121 | 1175 | |
1122 | | routine main | |
1123 | | inputs a, c | |
1124 | | outputs a, c, z, n | |
1176 | | byte foo | |
1177 | | routine main | |
1178 | | inputs foo, a, c | |
1179 | | outputs foo, a, c, z, n | |
1125 | 1180 | | { |
1126 | 1181 | | shl a |
1182 | | shl foo | |
1127 | 1183 | | } |
1128 | 1184 | = ok |
1129 | 1185 | |
1147 | 1203 | |
1148 | 1204 | Some rudimentary tests for `shr`. |
1149 | 1205 | |
1150 | | routine main | |
1151 | | inputs a, c | |
1152 | | outputs a, c, z, n | |
1206 | | byte foo | |
1207 | | routine main | |
1208 | | inputs foo, a, c | |
1209 | | outputs foo, a, c, z, n | |
1153 | 1210 | | { |
1154 | 1211 | | shr a |
1212 | | shr foo | |
1155 | 1213 | | } |
1156 | 1214 | = ok |
1157 | 1215 | |
1878 | 1936 | | } |
1879 | 1937 | ? UnmeaningfulReadError: y |
1880 | 1938 | |
1939 | ### save ### | |
1940 | ||
1941 | Basic neutral test, where the `save` makes no difference. | |
1942 | ||
1943 | | routine main | |
1944 | | inputs a, x | |
1945 | | outputs a, x | |
1946 | | trashes z, n | |
1947 | | { | |
1948 | | ld a, 1 | |
1949 | | save x { | |
1950 | | ld a, 2 | |
1951 | | } | |
1952 | | ld a, 3 | |
1953 | | } | |
1954 | = ok | |
1955 | ||
1956 | Saving any location (other than `a`) will trash `a`. | |
1957 | ||
1958 | | routine main | |
1959 | | inputs a, x | |
1960 | | outputs a, x | |
1961 | | trashes z, n | |
1962 | | { | |
1963 | | ld a, 1 | |
1964 | | save x { | |
1965 | | ld a, 2 | |
1966 | | } | |
1967 | | } | |
1968 | ? UnmeaningfulOutputError | |
1969 | ||
1970 | Saving `a` does not trash anything. | |
1971 | ||
1972 | | routine main | |
1973 | | inputs a, x | |
1974 | | outputs a, x | |
1975 | | trashes z, n | |
1976 | | { | |
1977 | | ld x, 1 | |
1978 | | save a { | |
1979 | | ld x, 2 | |
1980 | | } | |
1981 | | ld x, 3 | |
1982 | | } | |
1983 | = ok | |
1984 | ||
1985 | A defined value that has been saved can be trashed inside the block. | |
1986 | It will continue to be defined outside the block. | |
1987 | ||
1988 | | routine main | |
1989 | | outputs x, y | |
1990 | | trashes a, z, n | |
1991 | | { | |
1992 | | ld x, 0 | |
1993 | | save x { | |
1994 | | ld y, 0 | |
1995 | | trash x | |
1996 | | } | |
1997 | | } | |
1998 | = ok | |
1999 | ||
2000 | A trashed value that has been saved can be used inside the block. | |
2001 | It will continue to be trashed outside the block. | |
2002 | ||
2003 | | routine main | |
2004 | | inputs a | |
2005 | | outputs a, x | |
2006 | | trashes z, n | |
2007 | | { | |
2008 | | ld x, 0 | |
2009 | | trash x | |
2010 | | save x { | |
2011 | | ld a, 0 | |
2012 | | ld x, 1 | |
2013 | | } | |
2014 | | } | |
2015 | ? UnmeaningfulOutputError: x | |
2016 | ||
2017 | The known range of a value will be preserved outside the block as well. | |
2018 | ||
2019 | | word one: 77 | |
2020 | | word table[32] many | |
2021 | | | |
2022 | | routine main | |
2023 | | inputs a, many, one | |
2024 | | outputs many, one | |
2025 | | trashes a, x, n, z | |
2026 | | { | |
2027 | | and a, 31 | |
2028 | | ld x, a | |
2029 | | save x { | |
2030 | | ld x, 255 | |
2031 | | } | |
2032 | | copy one, many + x | |
2033 | | copy many + x, one | |
2034 | | } | |
2035 | = ok | |
2036 | ||
2037 | | word one: 77 | |
2038 | | word table[32] many | |
2039 | | | |
2040 | | routine main | |
2041 | | inputs a, many, one | |
2042 | | outputs many, one | |
2043 | | trashes a, x, n, z | |
2044 | | { | |
2045 | | and a, 63 | |
2046 | | ld x, a | |
2047 | | save x { | |
2048 | | ld x, 1 | |
2049 | | } | |
2050 | | copy one, many + x | |
2051 | | copy many + x, one | |
2052 | | } | |
2053 | ? RangeExceededError | |
2054 | ||
2055 | The known properties of a value are preserved inside the block, too. | |
2056 | ||
2057 | | word one: 77 | |
2058 | | word table[32] many | |
2059 | | | |
2060 | | routine main | |
2061 | | inputs a, many, one | |
2062 | | outputs many, one | |
2063 | | trashes a, x, n, z | |
2064 | | { | |
2065 | | and a, 31 | |
2066 | | ld x, a | |
2067 | | save x { | |
2068 | | copy one, many + x | |
2069 | | copy many + x, one | |
2070 | | } | |
2071 | | copy one, many + x | |
2072 | | copy many + x, one | |
2073 | | } | |
2074 | = ok | |
2075 | ||
2076 | A value which is not output from the routine, is preserved by the | |
2077 | routine; and can appear in a `save` exactly because a `save` preserves it. | |
2078 | ||
2079 | | routine main | |
2080 | | outputs y | |
2081 | | trashes a, z, n | |
2082 | | { | |
2083 | | save x { | |
2084 | | ld y, 0 | |
2085 | | ld x, 1 | |
2086 | | } | |
2087 | | } | |
2088 | = ok | |
2089 | ||
2090 | Because saving anything except `a` trashes `a`, a common idiom is to save `a` | |
2091 | first in a nested series of `save`s. | |
2092 | ||
2093 | | routine main | |
2094 | | inputs a | |
2095 | | outputs a | |
2096 | | trashes z, n | |
2097 | | { | |
2098 | | save a { | |
2099 | | save x { | |
2100 | | ld a, 0 | |
2101 | | ld x, 1 | |
2102 | | } | |
2103 | | } | |
2104 | | } | |
2105 | = ok | |
2106 | ||
2107 | Not just registers, but also user-defined locations can be saved. | |
2108 | ||
2109 | | byte foo | |
2110 | | | |
2111 | | routine main | |
2112 | | trashes a, z, n | |
2113 | | { | |
2114 | | save foo { | |
2115 | | st 5, foo | |
2116 | | } | |
2117 | | } | |
2118 | = ok | |
2119 | ||
2120 | But only if they are bytes. | |
2121 | ||
2122 | | word foo | |
2123 | | | |
2124 | | routine main | |
2125 | | trashes a, z, n | |
2126 | | { | |
2127 | | save foo { | |
2128 | | copy 555, foo | |
2129 | | } | |
2130 | | } | |
2131 | ? TypeMismatchError | |
2132 | ||
2133 | | byte table[16] tab | |
2134 | | | |
2135 | | routine main | |
2136 | | trashes a, y, z, n | |
2137 | | { | |
2138 | | save tab { | |
2139 | | ld y, 0 | |
2140 | | st 5, tab + y | |
2141 | | } | |
2142 | | } | |
2143 | ? TypeMismatchError | |
2144 | ||
2145 | A `goto` cannot appear within a `save` block, even if it is otherwise in tail position. | |
2146 | ||
2147 | | routine other | |
2148 | | trashes a, z, n | |
2149 | | { | |
2150 | | ld a, 0 | |
2151 | | } | |
2152 | | | |
2153 | | routine main | |
2154 | | trashes a, z, n | |
2155 | | { | |
2156 | | ld a, 1 | |
2157 | | save x { | |
2158 | | ld x, 2 | |
2159 | | goto other | |
2160 | | } | |
2161 | | } | |
2162 | ? IllegalJumpError | |
2163 | ||
2164 | ### with interrupts ### | |
2165 | ||
2166 | | vector routine | |
2167 | | inputs x | |
2168 | | outputs x | |
2169 | | trashes z, n | |
2170 | | bar | |
2171 | | | |
2172 | | routine foo | |
2173 | | inputs x | |
2174 | | outputs x | |
2175 | | trashes z, n | |
2176 | | { | |
2177 | | inc x | |
2178 | | } | |
2179 | | | |
2180 | | routine main | |
2181 | | outputs bar | |
2182 | | trashes a, n, z | |
2183 | | { | |
2184 | | with interrupts off { | |
2185 | | copy foo, bar | |
2186 | | } | |
2187 | | } | |
2188 | = ok | |
2189 | ||
2190 | A `goto` cannot appear within a `with interrupts` block, even if it is | |
2191 | otherwise in tail position. | |
2192 | ||
2193 | | vector routine | |
2194 | | inputs x | |
2195 | | outputs x | |
2196 | | trashes z, n | |
2197 | | bar | |
2198 | | | |
2199 | | routine foo | |
2200 | | inputs x | |
2201 | | outputs x | |
2202 | | trashes z, n | |
2203 | | { | |
2204 | | inc x | |
2205 | | } | |
2206 | | | |
2207 | | routine other | |
2208 | | trashes bar, a, n, z | |
2209 | | { | |
2210 | | ld a, 0 | |
2211 | | } | |
2212 | | | |
2213 | | routine main | |
2214 | | trashes bar, a, n, z | |
2215 | | { | |
2216 | | with interrupts off { | |
2217 | | copy foo, bar | |
2218 | | goto other | |
2219 | | } | |
2220 | | } | |
2221 | ? IllegalJumpError | |
2222 | ||
1881 | 2223 | ### copy ### |
1882 | 2224 | |
1883 | 2225 | Can't `copy` from a memory location that isn't initialized. |
2110 | 2452 | | ld y, 0 |
2111 | 2453 | | copy ^buf, ptr |
2112 | 2454 | | copy [ptr] + y, foo |
2455 | | } | |
2456 | = ok | |
2457 | ||
2458 | Read and write through two pointers. | |
2459 | ||
2460 | | buffer[2048] buf | |
2461 | | pointer ptra | |
2462 | | pointer ptrb | |
2463 | | | |
2464 | | routine main | |
2465 | | inputs buf | |
2466 | | outputs buf | |
2467 | | trashes a, y, z, n, ptra, ptrb | |
2468 | | { | |
2469 | | ld y, 0 | |
2470 | | copy ^buf, ptra | |
2471 | | copy ^buf, ptrb | |
2472 | | copy [ptra] + y, [ptrb] + y | |
2113 | 2473 | | } |
2114 | 2474 | = ok |
2115 | 2475 | |
2222 | 2582 | | } |
2223 | 2583 | ? ConstantConstraintError: foo |
2224 | 2584 | |
2225 | You can copy the address of a routine into a vector, if that vector is | |
2226 | declared appropriately. | |
2585 | #### routine-vector type compatibility | |
2586 | ||
2587 | You can copy the address of a routine into a vector, if that vector type | |
2588 | is at least as "wide" as the type of the routine. More specifically, | |
2589 | ||
2590 | - the vector must take _at least_ the inputs that the routine takes | |
2591 | - the vector must produce _at least_ the outputs that the routine produces | |
2592 | - the vector must trash _at least_ what the routine trashes | |
2593 | ||
2594 | If the vector and the routine have the very same signature, that's not an error. | |
2595 | ||
2596 | | vector routine | |
2597 | | inputs x, y | |
2598 | | outputs x, y | |
2599 | | trashes z, n | |
2600 | | vec | |
2601 | | | |
2602 | | routine foo | |
2603 | | inputs x, y | |
2604 | | outputs x, y | |
2605 | | trashes z, n | |
2606 | | { | |
2607 | | inc x | |
2608 | | inc y | |
2609 | | } | |
2610 | | | |
2611 | | routine main | |
2612 | | outputs vec | |
2613 | | trashes a, z, n | |
2614 | | { | |
2615 | | copy foo, vec | |
2616 | | } | |
2617 | = ok | |
2618 | ||
2619 | If the vector takes an input that the routine doesn't take, that's not an error. | |
2620 | (The interface requires that a parameter be specified before calling, but the | |
2621 | implementation doesn't actually read it.) | |
2622 | ||
2623 | | vector routine | |
2624 | | inputs x, y, a | |
2625 | | outputs x, y | |
2626 | | trashes z, n | |
2627 | | vec | |
2628 | | | |
2629 | | routine foo | |
2630 | | inputs x, y | |
2631 | | outputs x, y | |
2632 | | trashes z, n | |
2633 | | { | |
2634 | | inc x | |
2635 | | inc y | |
2636 | | } | |
2637 | | | |
2638 | | routine main | |
2639 | | outputs vec | |
2640 | | trashes a, z, n | |
2641 | | { | |
2642 | | copy foo, vec | |
2643 | | } | |
2644 | = ok | |
2645 | ||
2646 | If the vector fails to take an input that the routine takes, that's an error. | |
2227 | 2647 | |
2228 | 2648 | | vector routine |
2229 | 2649 | | inputs x |
2230 | | outputs x | |
2650 | | outputs x, y | |
2231 | 2651 | | trashes z, n |
2232 | 2652 | | vec |
2233 | 2653 | | |
2234 | 2654 | | routine foo |
2235 | | inputs x | |
2236 | | outputs x | |
2655 | | inputs x, y | |
2656 | | outputs x, y | |
2237 | 2657 | | trashes z, n |
2238 | 2658 | | { |
2239 | 2659 | | inc x |
2660 | | inc y | |
2240 | 2661 | | } |
2241 | 2662 | | |
2242 | 2663 | | routine main |
2245 | 2666 | | { |
2246 | 2667 | | copy foo, vec |
2247 | 2668 | | } |
2248 | = ok | |
2249 | ||
2250 | But not if the vector is declared inappropriately. | |
2669 | ? IncompatibleConstraintsError | |
2670 | ||
2671 | If the vector produces an output that the routine doesn't produce, that's not an error. | |
2672 | (The interface claims the result of calling the routine is defined, but the implementation | |
2673 | actually preserves it instead of changing it; the caller can still treat it as a defined | |
2674 | output.) | |
2251 | 2675 | |
2252 | 2676 | | vector routine |
2253 | | inputs y | |
2254 | | outputs y | |
2677 | | inputs x, y | |
2678 | | outputs x, y, a | |
2255 | 2679 | | trashes z, n |
2256 | 2680 | | vec |
2257 | 2681 | | |
2258 | 2682 | | routine foo |
2259 | | inputs x | |
2260 | | outputs x | |
2683 | | inputs x, y | |
2684 | | outputs x, y | |
2261 | 2685 | | trashes z, n |
2262 | 2686 | | { |
2263 | 2687 | | inc x |
2688 | | inc y | |
2264 | 2689 | | } |
2265 | 2690 | | |
2266 | 2691 | | routine main |
2269 | 2694 | | { |
2270 | 2695 | | copy foo, vec |
2271 | 2696 | | } |
2697 | = ok | |
2698 | ||
2699 | If the vector fails to produce an output that the routine produces, that's an error. | |
2700 | ||
2701 | | vector routine | |
2702 | | inputs x, y | |
2703 | | outputs x | |
2704 | | trashes z, n | |
2705 | | vec | |
2706 | | | |
2707 | | routine foo | |
2708 | | inputs x, y | |
2709 | | outputs x, y | |
2710 | | trashes z, n | |
2711 | | { | |
2712 | | inc x | |
2713 | | inc y | |
2714 | | } | |
2715 | | | |
2716 | | routine main | |
2717 | | outputs vec | |
2718 | | trashes a, z, n | |
2719 | | { | |
2720 | | copy foo, vec | |
2721 | | } | |
2272 | 2722 | ? IncompatibleConstraintsError |
2273 | 2723 | |
2274 | "Appropriately" means, if the routine affects no more than what is named | |
2275 | in the input/output sets of the vector. | |
2724 | If the vector fails to trash something the routine trashes, that's an error. | |
2276 | 2725 | |
2277 | 2726 | | vector routine |
2278 | | inputs a, x | |
2279 | | outputs x | |
2280 | | trashes a, z, n | |
2727 | | inputs x, y | |
2728 | | outputs x, y | |
2729 | | trashes z | |
2281 | 2730 | | vec |
2282 | 2731 | | |
2283 | 2732 | | routine foo |
2284 | | inputs x | |
2285 | | outputs x | |
2733 | | inputs x, y | |
2734 | | outputs x, y | |
2286 | 2735 | | trashes z, n |
2287 | 2736 | | { |
2288 | 2737 | | inc x |
2738 | | inc y | |
2289 | 2739 | | } |
2290 | 2740 | | |
2291 | 2741 | | routine main |
2294 | 2744 | | { |
2295 | 2745 | | copy foo, vec |
2296 | 2746 | | } |
2297 | = ok | |
2747 | ? IncompatibleConstraintsError | |
2748 | ||
2749 | If the vector trashes something the routine doesn't trash, that's not an error. | |
2750 | (The implementation preserves something the interface doesn't guarantee is | |
2751 | preserved. The caller gets no guarantee that it's preserved. It actually is, | |
2752 | but it doesn't know that.) | |
2753 | ||
2754 | | vector routine | |
2755 | | inputs x, y | |
2756 | | outputs x, y | |
2757 | | trashes a, z, n | |
2758 | | vec | |
2759 | | | |
2760 | | routine foo | |
2761 | | inputs x, y | |
2762 | | outputs x, y | |
2763 | | trashes z, n | |
2764 | | { | |
2765 | | inc x | |
2766 | | inc y | |
2767 | | } | |
2768 | | | |
2769 | | routine main | |
2770 | | outputs vec | |
2771 | | trashes a, z, n | |
2772 | | { | |
2773 | | copy foo, vec | |
2774 | | } | |
2775 | = ok | |
2776 | ||
2777 | #### other properties of routines | |
2298 | 2778 | |
2299 | 2779 | Routines are read-only. |
2300 | 2780 |
6 | 6 | [Falderal]: http://catseye.tc/node/Falderal |
7 | 7 | |
8 | 8 | -> Functionality "Compile SixtyPical program" is implemented by |
9 | -> shell command "bin/sixtypical --prelude=c64 --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo" | |
9 | -> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo" | |
10 | 10 | |
11 | 11 | -> Tests for functionality "Compile SixtyPical program" |
12 | 12 | |
111 | 111 | = $080F STA $0400 |
112 | 112 | = $0812 RTS |
113 | 113 | |
114 | Accesses to memory locations in zero-page with `ld` and `st` use zero-page addressing. | |
114 | Accesses to memory locations in zero-page with `ld` and `st` | |
115 | and `and`, `or`, and `xor` use zero-page addressing. | |
115 | 116 | |
116 | 117 | | byte zp @ $00 |
117 | 118 | | byte screen @ 100 |
125 | 126 | | st a, screen |
126 | 127 | | ld a, zp |
127 | 128 | | st a, zp |
129 | | and a, zp | |
130 | | or a, zp | |
131 | | xor a, zp | |
128 | 132 | | } |
129 | 133 | = $080D LDA $64 |
130 | 134 | = $080F STA $64 |
131 | 135 | = $0811 LDA $00 |
132 | 136 | = $0813 STA $00 |
133 | = $0815 RTS | |
137 | = $0815 AND $00 | |
138 | = $0817 ORA $00 | |
139 | = $0819 EOR $00 | |
140 | = $081B RTS | |
134 | 141 | |
135 | 142 | Memory location with initial value. |
136 | 143 | |
212 | 219 | |
213 | 220 | Initialized word table, initialized with list of word values. |
214 | 221 | |
215 | | word table[8] message : 65535, 0, 127 | |
222 | | word table[4] message : 65535, 0, 127, 127 | |
216 | 223 | | |
217 | 224 | | routine main |
218 | 225 | | { |
224 | 231 | = $0811 BRK |
225 | 232 | = $0812 .byte $7F |
226 | 233 | = $0813 BRK |
227 | = $0814 BRK | |
234 | = $0814 .byte $7F | |
228 | 235 | = $0815 BRK |
229 | 236 | |
230 | 237 | Some instructions. |
266 | 273 | | cmp y, foo |
267 | 274 | | shl a |
268 | 275 | | shr a |
276 | | shl foo | |
277 | | shr foo | |
269 | 278 | | } |
270 | 279 | = $080D LDA #$00 |
271 | 280 | = $080F LDX #$00 |
272 | 281 | = $0811 LDY #$00 |
273 | = $0813 STA $0853 | |
274 | = $0816 STX $0853 | |
275 | = $0819 STY $0853 | |
282 | = $0813 STA $0859 | |
283 | = $0816 STX $0859 | |
284 | = $0819 STY $0859 | |
276 | 285 | = $081C SEC |
277 | 286 | = $081D CLC |
278 | 287 | = $081E ADC #$01 |
279 | = $0820 ADC $0853 | |
288 | = $0820 ADC $0859 | |
280 | 289 | = $0823 SBC #$01 |
281 | = $0825 SBC $0853 | |
282 | = $0828 INC $0853 | |
290 | = $0825 SBC $0859 | |
291 | = $0828 INC $0859 | |
283 | 292 | = $082B INX |
284 | 293 | = $082C INY |
285 | = $082D DEC $0853 | |
294 | = $082D DEC $0859 | |
286 | 295 | = $0830 DEX |
287 | 296 | = $0831 DEY |
288 | 297 | = $0832 AND #$FF |
289 | = $0834 AND $0853 | |
298 | = $0834 AND $0859 | |
290 | 299 | = $0837 ORA #$FF |
291 | = $0839 ORA $0853 | |
300 | = $0839 ORA $0859 | |
292 | 301 | = $083C EOR #$FF |
293 | = $083E EOR $0853 | |
302 | = $083E EOR $0859 | |
294 | 303 | = $0841 CMP #$01 |
295 | = $0843 CMP $0853 | |
304 | = $0843 CMP $0859 | |
296 | 305 | = $0846 CPX #$01 |
297 | = $0848 CPX $0853 | |
306 | = $0848 CPX $0859 | |
298 | 307 | = $084B CPY #$01 |
299 | = $084D CPY $0853 | |
308 | = $084D CPY $0859 | |
300 | 309 | = $0850 ROL A |
301 | 310 | = $0851 ROR A |
302 | = $0852 RTS | |
311 | = $0852 ROL $0859 | |
312 | = $0855 ROR $0859 | |
313 | = $0858 RTS | |
314 | ||
315 | Some instructions on tables. (1/3) | |
316 | ||
317 | | byte table[256] many | |
318 | | | |
319 | | routine main | |
320 | | inputs many | |
321 | | outputs many | |
322 | | trashes a, x, c, n, z, v | |
323 | | { | |
324 | | ld x, 0 | |
325 | | ld a, 0 | |
326 | | st off, c | |
327 | | add a, many + x | |
328 | | sub a, many + x | |
329 | | cmp a, many + x | |
330 | | } | |
331 | = $080D LDX #$00 | |
332 | = $080F LDA #$00 | |
333 | = $0811 CLC | |
334 | = $0812 ADC $081C,X | |
335 | = $0815 SBC $081C,X | |
336 | = $0818 CMP $081C,X | |
337 | = $081B RTS | |
338 | ||
339 | Some instructions on tables. (2/3) | |
340 | ||
341 | | byte table[256] many | |
342 | | | |
343 | | routine main | |
344 | | inputs many | |
345 | | outputs many | |
346 | | trashes a, x, c, n, z | |
347 | | { | |
348 | | ld x, 0 | |
349 | | ld a, 0 | |
350 | | and a, many + x | |
351 | | or a, many + x | |
352 | | xor a, many + x | |
353 | | } | |
354 | = $080D LDX #$00 | |
355 | = $080F LDA #$00 | |
356 | = $0811 AND $081B,X | |
357 | = $0814 ORA $081B,X | |
358 | = $0817 EOR $081B,X | |
359 | = $081A RTS | |
360 | ||
361 | Some instructions on tables. (3/3) | |
362 | ||
363 | | byte table[256] many | |
364 | | | |
365 | | routine main | |
366 | | inputs many | |
367 | | outputs many | |
368 | | trashes a, x, c, n, z | |
369 | | { | |
370 | | ld x, 0 | |
371 | | ld a, 0 | |
372 | | st off, c | |
373 | | shl many + x | |
374 | | shr many + x | |
375 | | inc many + x | |
376 | | dec many + x | |
377 | | } | |
378 | = $080D LDX #$00 | |
379 | = $080F LDA #$00 | |
380 | = $0811 CLC | |
381 | = $0812 ROL $081F,X | |
382 | = $0815 ROR $081F,X | |
383 | = $0818 INC $081F,X | |
384 | = $081B DEC $081F,X | |
385 | = $081E RTS | |
303 | 386 | |
304 | 387 | Compiling `if`. |
305 | 388 | |
493 | 576 | = $0815 BNE $080F |
494 | 577 | = $0817 RTS |
495 | 578 | |
579 | Compiling `save`. | |
580 | ||
581 | | routine main | |
582 | | inputs a | |
583 | | outputs a | |
584 | | trashes z, n | |
585 | | { | |
586 | | save a { | |
587 | | save x { | |
588 | | ld a, 0 | |
589 | | ld x, 1 | |
590 | | } | |
591 | | } | |
592 | | } | |
593 | = $080D PHA | |
594 | = $080E TXA | |
595 | = $080F PHA | |
596 | = $0810 LDA #$00 | |
597 | = $0812 LDX #$01 | |
598 | = $0814 PLA | |
599 | = $0815 TAX | |
600 | = $0816 PLA | |
601 | = $0817 RTS | |
602 | ||
603 | Compiling `save` on a user-defined location. | |
604 | ||
605 | | byte foo | |
606 | | routine main | |
607 | | trashes a, z, n | |
608 | | { | |
609 | | save foo { | |
610 | | ld a, 0 | |
611 | | st a, foo | |
612 | | } | |
613 | | } | |
614 | = $080D LDA $081B | |
615 | = $0810 PHA | |
616 | = $0811 LDA #$00 | |
617 | = $0813 STA $081B | |
618 | = $0816 PLA | |
619 | = $0817 STA $081B | |
620 | = $081A RTS | |
621 | ||
496 | 622 | Indexed access. |
497 | 623 | |
498 | 624 | | byte one |
513 | 639 | = $0814 LDA $0819,X |
514 | 640 | = $0817 RTS |
515 | 641 | |
516 | Byte tables take up 256 bytes in memory. | |
642 | Byte tables take up, at most, 256 bytes in memory. | |
517 | 643 | |
518 | 644 | | byte table[256] tab1 |
519 | 645 | | byte table[256] tab2 |
1028 | 1154 | = $0819 STA $101F |
1029 | 1155 | = $081C LDA ($FE),Y |
1030 | 1156 | = $081E RTS |
1157 | ||
1158 | Read and write through two pointers. | |
1159 | ||
1160 | | buffer[2048] buf | |
1161 | | pointer ptra @ 252 | |
1162 | | pointer ptrb @ 254 | |
1163 | | | |
1164 | | routine main | |
1165 | | inputs buf | |
1166 | | outputs buf | |
1167 | | trashes a, y, z, n, ptra, ptrb | |
1168 | | { | |
1169 | | ld y, 0 | |
1170 | | copy ^buf, ptra | |
1171 | | copy ^buf, ptrb | |
1172 | | copy [ptra] + y, [ptrb] + y | |
1173 | | } | |
1174 | = $080D LDY #$00 | |
1175 | = $080F LDA #$24 | |
1176 | = $0811 STA $FC | |
1177 | = $0813 LDA #$08 | |
1178 | = $0815 STA $FD | |
1179 | = $0817 LDA #$24 | |
1180 | = $0819 STA $FE | |
1181 | = $081B LDA #$08 | |
1182 | = $081D STA $FF | |
1183 | = $081F LDA ($FC),Y | |
1184 | = $0821 STA ($FE),Y | |
1185 | = $0823 RTS | |
1031 | 1186 | |
1032 | 1187 | Write the `a` register through a pointer. |
1033 | 1188 |
64 | 64 | -> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)" |
65 | 65 | |
66 | 66 | -> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by |
67 | -> shell command "bin/sixtypical --prelude=c64 --optimize-fallthru --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo" | |
67 | -> shell command "bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo" | |
68 | 68 | |
69 | 69 | -> Tests for functionality "Dump fallthru info for SixtyPical program" |
70 | 70 |
28 | 28 | | routine main { |
29 | 29 | | ld a, 0 |
30 | 30 | | add a, 1 // We are adding the thing. |
31 | | sub a, 1 | |
32 | | shl a | |
33 | | shr a | |
34 | | and a, 1 | |
35 | | or a, 1 | |
36 | | xor a, 1 | |
31 | 37 | | } |
32 | 38 | = ok |
33 | 39 | |
154 | 160 | | } |
155 | 161 | = ok |
156 | 162 | |
163 | Other blocks. | |
164 | ||
165 | | routine main trashes a, x, c, z, v { | |
166 | | with interrupts off { | |
167 | | save a, x, c { | |
168 | | ld a, 0 | |
169 | | } | |
170 | | } | |
171 | | save a, x, c { | |
172 | | ld a, 0 | |
173 | | } | |
174 | | } | |
175 | = ok | |
176 | ||
157 | 177 | User-defined memory addresses of different types. |
158 | 178 | |
159 | 179 | | byte byt |
166 | 186 | | } |
167 | 187 | = ok |
168 | 188 | |
169 | Tables of different types. | |
170 | ||
171 | | byte table[256] tab | |
172 | | word table[256] wtab | |
173 | | vector (routine trashes a) table[256] vtab | |
174 | | | |
175 | | routine main { | |
189 | Tables of different types and some operations on them. | |
190 | ||
191 | | byte table[256] many | |
192 | | word table[256] wmany | |
193 | | vector (routine trashes a) table[256] vmany | |
194 | | | |
195 | | routine main { | |
196 | | ld x, 0 | |
197 | | ld a, 0 | |
198 | | st off, c | |
199 | | add a, many + x | |
200 | | sub a, many + x | |
201 | | cmp a, many + x | |
202 | | and a, many + x | |
203 | | or a, many + x | |
204 | | xor a, many + x | |
205 | | shl many + x | |
206 | | shr many + x | |
207 | | inc many + x | |
208 | | dec many + x | |
176 | 209 | | } |
177 | 210 | = ok |
178 | 211 | |
267 | 300 | | routine main { |
268 | 301 | | ld a, 100 |
269 | 302 | | st a, screen |
303 | | shl screen | |
304 | | shr screen | |
270 | 305 | | } |
271 | 306 | = ok |
272 | 307 | |
564 | 599 | |
565 | 600 | | buffer[2048] buf |
566 | 601 | | pointer ptr |
602 | | pointer ptrb | |
567 | 603 | | byte foo |
568 | 604 | | |
569 | 605 | | routine main { |
570 | 606 | | copy ^buf, ptr |
571 | 607 | | copy 123, [ptr] + y |
572 | 608 | | copy [ptr] + y, foo |
609 | | copy [ptr] + y, [ptrb] + y | |
573 | 610 | | } |
574 | 611 | = ok |
575 | 612 |