git @ Cat's Eye Technologies SixtyPical / 0.16
Merge pull request #13 from catseye/develop-0.16 Develop 0.16 Chris Pressey authored 3 years ago GitHub committed 3 years ago
16 changed file(s) with 1175 addition(s) and 176 deletion(s). Raw diff Collapse all Expand all
00 History of SixtyPical
11 =====================
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.
219
320 0.15
421 ----
00 SixtyPical
11 ==========
22
3 _Version 0.15. Work-in-progress, everything is subject to change._
3 _Version 0.16. Work-in-progress, everything is subject to change._
44
55 **SixtyPical** is a 6502-like programming language with advanced
66 static analysis.
5353 tree, which contains more extensive examples, including an entire
5454 game(-like program); see [eg/README.md](eg/README.md) for a listing.
5555
56 [VICE]: http://vice-emu.sourceforge.net/
57
5658 Documentation
5759 -------------
5860
6466 * [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
6567 * [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
6668 * [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md)
69 * [Output formats supported by `sixtypical`](doc/Output%20Formats.md)
6770
6871 TODO
6972 ----
7073
71 ### Save registers on stack
74 ### `low` and `high` address operators
7275
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`.
7577
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.
7781
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.
9288
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.
8080
8181 fh = sys.stdout
8282
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':
9390 start_addr = 0x0801
9491 prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
9592 0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
96 elif options.prelude == 'vic20':
97 output_format = 'prg'
93 elif options.output_format == 'vic20-basic-prg':
9894 start_addr = 0x1001
9995 prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
10096 0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
101 elif options.prelude == 'atari2600':
102 output_format = 'crtbb'
97 elif options.output_format == 'atari2600-cart':
10398 start_addr = 0xf000
10499 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))
106103
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)
109109
110110 # If we are outputting a .PRG, we output the load address first.
111111 # 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'):
113113 fh.write(Word(start_addr).serialize(0))
114114
115115 emitter = Emitter(start_addr)
120120
121121 # If we are outputting a cartridge with boot and BRK address
122122 # 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':
124124 emitter.pad_to_size(4096 - 4)
125125 emitter.emit(Word(start_addr))
126126 emitter.emit(Word(start_addr))
140140 )
141141
142142 argparser.add_argument(
143 "--origin", type=str, default='0xc000',
143 "--origin", type=str, default=None,
144144 help="Location in memory where the `main` routine will be "
145 "located. Default: 0xc000."
145 "located. Default: depends on output format."
146146 )
147147 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."
158152 )
159153
160154 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.
405405 }
406406
407407 define game_state_play game_state_routine
408 static byte save_x : 0
409408 {
410409 ld x, 0
411 repeat {
410 for x up to 15 {
412411 copy actor_pos + x, pos
413412 copy actor_delta + x, delta
414413
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 }
427428 }
428429
429430 copy pos, actor_pos + x
430431 copy delta, actor_delta + x
431
432 inc x
433 cmp x, 16
434 } until z
432 }
435433
436434 goto save_cinv
437435 }
44 arch="$1"
55 shift 1
66 if [ "X$arch" = "Xc64" ]; then
7 prelude='c64'
7 output_format='c64-basic-prg'
88 if [ -e vicerc ]; then
99 emu="x64 -config vicerc"
1010 else
1111 emu="x64"
1212 fi
1313 elif [ "X$arch" = "Xvic20" ]; then
14 prelude='vic20'
14 output_format='vic20-basic-prg'
1515 if [ -e vicerc ]; then
1616 emu="xvic -config vicerc"
1717 else
1818 emu="xvic"
1919 fi
2020 elif [ "X$arch" = "Xatari2600" ]; then
21 prelude='atari2600'
21 output_format='atari2600-cart'
2222 emu='stella'
2323 else
2424 echo $usage && exit 1
3737 ### do it ###
3838
3939 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
4141 ls -la $out
4242 $emu $out
4343 rm -f $out
00 # encoding: UTF-8
11
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
33 from sixtypical.model import (
44 TYPE_BYTE, TYPE_WORD,
55 TableType, BufferType, PointerType, VectorType, RoutineType,
268268 self._writeable.remove(ref)
269269
270270 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`."""
272272 for ref in refs:
273273 self._writeable.add(ref)
274274
277277
278278 def encountered_gotos(self):
279279 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)
280328
281329
282330 class Analyzer(object):
286334 self.routines = {}
287335 self.debug = debug
288336
289 def assert_type(self, type, *locations):
337 def assert_type(self, type_, *locations):
290338 for location in locations:
291 if location.type != type:
339 if location.type != type_:
292340 raise TypeMismatchError(self.current_routine, location.name)
293341
294342 def assert_affected_within(self, name, affecting_type, limiting_type):
371419 self.analyze_for(instr, context)
372420 elif isinstance(instr, WithInterruptsOff):
373421 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)
374426 else:
375427 raise NotImplementedError
376428
385437
386438 if opcode == 'ld':
387439 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)
394441 elif isinstance(src, IndirectRef):
395442 # copying this analysis from the matching branch in `copy`, below
396443 if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE:
406453 context.set_written(dest, FLAG_Z, FLAG_N)
407454 elif opcode == 'st':
408455 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)
416459 elif isinstance(dest, IndirectRef):
417460 # copying this analysis from the matching branch in `copy`, below
418461 if isinstance(dest.ref.type, PointerType) and src.type == TYPE_BYTE:
422465 context.assert_meaningful(dest.ref, REG_Y)
423466 context.set_written(dest.ref)
424467 elif src.type != dest.type:
425 raise TypeMismatchError(instr, '{} and {}'.format(src, name))
468 raise TypeMismatchError(instr, '{} and {}'.format(src, dest))
426469 else:
427470 context.set_written(dest)
428471 # FIXME: context.copy_range(src, dest) ?
429472 context.assert_meaningful(src)
430473 elif opcode == 'add':
431474 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:
433478 self.assert_type(TYPE_BYTE, src, dest)
434 context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
435479 else:
436480 self.assert_type(TYPE_WORD, src)
437481 if dest.type == TYPE_WORD:
438 context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
439482 context.set_touched(REG_A)
440483 context.set_unmeaningful(REG_A)
441484 elif isinstance(dest.type, PointerType):
442 context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
443485 context.set_touched(REG_A)
444486 context.set_unmeaningful(REG_A)
445487 else:
446488 self.assert_type(TYPE_WORD, dest)
489 context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
447490 context.invalidate_range(dest)
448491 elif opcode == 'sub':
449492 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:
451496 self.assert_type(TYPE_BYTE, src, dest)
452 context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
453497 else:
454498 self.assert_type(TYPE_WORD, src, dest)
455 context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
456499 context.set_touched(REG_A)
457500 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)
463502 context.invalidate_range(dest)
464503 elif opcode == 'cmp':
465 self.assert_type(TYPE_BYTE, src, dest)
466504 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)
467509 context.set_written(FLAG_Z, FLAG_N, FLAG_C)
468510 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)
470515 context.assert_meaningful(src, dest)
471516 context.set_written(dest, FLAG_Z, FLAG_N)
472517 # If you AND the A register with a value V, the resulting value of A
473518 # cannot exceed the value of V; i.e. the maximum value of A becomes
474519 # 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))
476522 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)
478527 context.assert_meaningful(src, dest)
479528 context.set_written(dest, FLAG_Z, FLAG_N)
480529 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)
481540 elif opcode in ('shl', 'shr'):
482 self.assert_type(TYPE_BYTE, dest)
483541 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)
486550 elif opcode == 'call':
487551 type = instr.location.type
488552 if isinstance(type, VectorType):
516580 pass
517581 else:
518582 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))
519588
520589 elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef):
521590 if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
560629 context.set_written(dest.ref)
561630 elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
562631 context.assert_meaningful(src.ref, REG_Y)
632 # TODO more sophisticated?
563633 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)
564638 elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
565639 context.assert_meaningful(src, dest.ref, dest.index)
566640 context.set_written(dest.ref)
694768 # after it is executed, we know the range of the loop variable.
695769 context.set_range(instr.dest, instr.final, instr.final)
696770 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)
8989
9090 class WithInterruptsOff(Instr):
9191 child_attrs = ('block',)
92
93
94 class Save(Instr):
95 value_attrs = ('locations',)
96 child_attrs = ('block',)
00 # encoding: UTF-8
11
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
33 from sixtypical.model import (
44 ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
55 TYPE_BIT, TYPE_BYTE, TYPE_WORD,
1111 Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative,
1212 LDA, LDX, LDY, STA, STX, STY,
1313 TAX, TAY, TXA, TYA,
14 PHA, PLA,
1415 CLC, SEC, ADC, SBC, ROL, ROR,
1516 INC, INX, INY, DEC, DEX, DEY,
1617 CMP, CPX, CPY, AND, ORA, EOR,
168169 return self.compile_for(instr)
169170 elif isinstance(instr, WithInterruptsOff):
170171 return self.compile_with_interrupts_off(instr)
172 elif isinstance(instr, Save):
173 return self.compile_save(instr)
171174 else:
172175 raise NotImplementedError
173176
243246 if dest == REG_A:
244247 if isinstance(src, ConstantRef):
245248 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))))
246251 else:
247252 self.emitter.emit(ADC(Absolute(self.get_label(src.name))))
248253 elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
291296 if dest == REG_A:
292297 if isinstance(src, ConstantRef):
293298 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))))
294301 else:
295302 self.emitter.emit(SBC(Absolute(self.get_label(src.name))))
296303 elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
315322 raise UnsupportedOpcodeError(instr)
316323 else:
317324 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)
322325 elif opcode == 'cmp':
323326 self.compile_cmp(instr, instr.src, instr.dest)
324327 elif opcode in ('and', 'or', 'xor',):
330333 if dest == REG_A:
331334 if isinstance(src, ConstantRef):
332335 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))))
335340 else:
336341 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)
337346 elif opcode in ('shl', 'shr'):
338347 cls = {
339348 'shl': ROL,
341350 }[opcode]
342351 if dest == REG_A:
343352 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))))
346357 elif opcode == 'call':
347358 location = instr.location
348359 label = self.get_label(instr.location.name)
388399 raise UnsupportedOpcodeError(instr)
389400 if isinstance(src, ConstantRef):
390401 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))))
391405 else:
392406 self.emitter.emit(cls(Absolute(self.get_label(src.name))))
393407
397411 self.emitter.emit(INX())
398412 elif dest == REG_Y:
399413 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))))
400416 else:
401417 self.emitter.emit(INC(Absolute(self.get_label(dest.name))))
402418
406422 self.emitter.emit(DEX())
407423 elif dest == REG_Y:
408424 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))))
409427 else:
410428 self.emitter.emit(DEC(Absolute(self.get_label(dest.name))))
411429
427445 dest_label = self.get_label(dest.name)
428446 self.emitter.emit(LDA(IndirectY(src_label)))
429447 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)))
430454 elif isinstance(src, AddressRef) and isinstance(dest, LocationRef) and isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType):
431455 ### copy ^buf, ptr
432456 src_label = self.get_label(src.ref.name)
591615 self.emitter.emit(SEI())
592616 self.compile_block(instr.block)
593617 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)))
132132 Absolute: 0x2d,
133133 AbsoluteX: 0x3d,
134134 AbsoluteY: 0x39,
135 ZeroPage: 0x25,
135136 }
136137
137138
209210 class DEC(Instruction):
210211 opcodes = {
211212 Absolute: 0xce,
213 AbsoluteX: 0xde,
212214 }
213215
214216
230232 Absolute: 0x4d,
231233 AbsoluteX: 0x5d,
232234 AbsoluteY: 0x59,
235 ZeroPage: 0x45,
233236 }
234237
235238
298301 Absolute: 0x0d,
299302 AbsoluteX: 0x1d,
300303 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,
301317 }
302318
303319
00 # encoding: UTF-8
11
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
33 from sixtypical.model import (
44 TYPE_BIT, TYPE_BYTE, TYPE_WORD,
55 RoutineType, VectorType, TableType, BufferType, PointerType,
441441 elif self.scanner.token in ("shl", "shr", "inc", "dec"):
442442 opcode = self.scanner.token
443443 self.scanner.scan()
444 dest = self.locexpr()
444 dest = self.indexed_locexpr()
445445 return SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=None)
446446 elif self.scanner.token in ("nop",):
447447 opcode = self.scanner.token
469469 self.scanner.expect("off")
470470 block = self.block()
471471 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)
472476 elif self.scanner.consume("trash"):
473477 dest = self.locexpr()
474478 return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest)
472472 | ld a, many + x
473473 | }
474474 ? 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
475529
476530 Copying to and from a word table.
477531
11191173
11201174 Some rudimentary tests for `shl`.
11211175
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
11251180 | {
11261181 | shl a
1182 | shl foo
11271183 | }
11281184 = ok
11291185
11471203
11481204 Some rudimentary tests for `shr`.
11491205
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
11531210 | {
11541211 | shr a
1212 | shr foo
11551213 | }
11561214 = ok
11571215
18781936 | }
18791937 ? UnmeaningfulReadError: y
18801938
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
18812223 ### copy ###
18822224
18832225 Can't `copy` from a memory location that isn't initialized.
21102452 | ld y, 0
21112453 | copy ^buf, ptr
21122454 | 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
21132473 | }
21142474 = ok
21152475
22222582 | }
22232583 ? ConstantConstraintError: foo
22242584
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.
22272647
22282648 | vector routine
22292649 | inputs x
2230 | outputs x
2650 | outputs x, y
22312651 | trashes z, n
22322652 | vec
22332653 |
22342654 | routine foo
2235 | inputs x
2236 | outputs x
2655 | inputs x, y
2656 | outputs x, y
22372657 | trashes z, n
22382658 | {
22392659 | inc x
2660 | inc y
22402661 | }
22412662 |
22422663 | routine main
22452666 | {
22462667 | copy foo, vec
22472668 | }
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.)
22512675
22522676 | vector routine
2253 | inputs y
2254 | outputs y
2677 | inputs x, y
2678 | outputs x, y, a
22552679 | trashes z, n
22562680 | vec
22572681 |
22582682 | routine foo
2259 | inputs x
2260 | outputs x
2683 | inputs x, y
2684 | outputs x, y
22612685 | trashes z, n
22622686 | {
22632687 | inc x
2688 | inc y
22642689 | }
22652690 |
22662691 | routine main
22692694 | {
22702695 | copy foo, vec
22712696 | }
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 | }
22722722 ? IncompatibleConstraintsError
22732723
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.
22762725
22772726 | 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
22812730 | vec
22822731 |
22832732 | routine foo
2284 | inputs x
2285 | outputs x
2733 | inputs x, y
2734 | outputs x, y
22862735 | trashes z, n
22872736 | {
22882737 | inc x
2738 | inc y
22892739 | }
22902740 |
22912741 | routine main
22942744 | {
22952745 | copy foo, vec
22962746 | }
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
22982778
22992779 Routines are read-only.
23002780
66 [Falderal]: http://catseye.tc/node/Falderal
77
88 -> 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"
1010
1111 -> Tests for functionality "Compile SixtyPical program"
1212
111111 = $080F STA $0400
112112 = $0812 RTS
113113
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.
115116
116117 | byte zp @ $00
117118 | byte screen @ 100
125126 | st a, screen
126127 | ld a, zp
127128 | st a, zp
129 | and a, zp
130 | or a, zp
131 | xor a, zp
128132 | }
129133 = $080D LDA $64
130134 = $080F STA $64
131135 = $0811 LDA $00
132136 = $0813 STA $00
133 = $0815 RTS
137 = $0815 AND $00
138 = $0817 ORA $00
139 = $0819 EOR $00
140 = $081B RTS
134141
135142 Memory location with initial value.
136143
212219
213220 Initialized word table, initialized with list of word values.
214221
215 | word table[8] message : 65535, 0, 127
222 | word table[4] message : 65535, 0, 127, 127
216223 |
217224 | routine main
218225 | {
224231 = $0811 BRK
225232 = $0812 .byte $7F
226233 = $0813 BRK
227 = $0814 BRK
234 = $0814 .byte $7F
228235 = $0815 BRK
229236
230237 Some instructions.
266273 | cmp y, foo
267274 | shl a
268275 | shr a
276 | shl foo
277 | shr foo
269278 | }
270279 = $080D LDA #$00
271280 = $080F LDX #$00
272281 = $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
276285 = $081C SEC
277286 = $081D CLC
278287 = $081E ADC #$01
279 = $0820 ADC $0853
288 = $0820 ADC $0859
280289 = $0823 SBC #$01
281 = $0825 SBC $0853
282 = $0828 INC $0853
290 = $0825 SBC $0859
291 = $0828 INC $0859
283292 = $082B INX
284293 = $082C INY
285 = $082D DEC $0853
294 = $082D DEC $0859
286295 = $0830 DEX
287296 = $0831 DEY
288297 = $0832 AND #$FF
289 = $0834 AND $0853
298 = $0834 AND $0859
290299 = $0837 ORA #$FF
291 = $0839 ORA $0853
300 = $0839 ORA $0859
292301 = $083C EOR #$FF
293 = $083E EOR $0853
302 = $083E EOR $0859
294303 = $0841 CMP #$01
295 = $0843 CMP $0853
304 = $0843 CMP $0859
296305 = $0846 CPX #$01
297 = $0848 CPX $0853
306 = $0848 CPX $0859
298307 = $084B CPY #$01
299 = $084D CPY $0853
308 = $084D CPY $0859
300309 = $0850 ROL A
301310 = $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
303386
304387 Compiling `if`.
305388
493576 = $0815 BNE $080F
494577 = $0817 RTS
495578
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
496622 Indexed access.
497623
498624 | byte one
513639 = $0814 LDA $0819,X
514640 = $0817 RTS
515641
516 Byte tables take up 256 bytes in memory.
642 Byte tables take up, at most, 256 bytes in memory.
517643
518644 | byte table[256] tab1
519645 | byte table[256] tab2
10281154 = $0819 STA $101F
10291155 = $081C LDA ($FE),Y
10301156 = $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
10311186
10321187 Write the `a` register through a pointer.
10331188
6464 -> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)"
6565
6666 -> 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"
6868
6969 -> Tests for functionality "Dump fallthru info for SixtyPical program"
7070
2828 | routine main {
2929 | ld a, 0
3030 | 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
3137 | }
3238 = ok
3339
154160 | }
155161 = ok
156162
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
157177 User-defined memory addresses of different types.
158178
159179 | byte byt
166186 | }
167187 = ok
168188
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
176209 | }
177210 = ok
178211
267300 | routine main {
268301 | ld a, 100
269302 | st a, screen
303 | shl screen
304 | shr screen
270305 | }
271306 = ok
272307
564599
565600 | buffer[2048] buf
566601 | pointer ptr
602 | pointer ptrb
567603 | byte foo
568604 |
569605 | routine main {
570606 | copy ^buf, ptr
571607 | copy 123, [ptr] + y
572608 | copy [ptr] + y, foo
609 | copy [ptr] + y, [ptrb] + y
573610 | }
574611 = ok
575612
00 #!/usr/bin/env python
11
2 # script that allows the binary output of sixtypical --prelude=c64 --compile to be
2 # script that allows the binary output of sixtypical --output-format=c64-basic-prg --compile to be
33 # disassembled by https://github.com/tcarmelveilleux/dcc6502
44
55 import sys