git @ Cat's Eye Technologies SixtyPical / 9536dd0
Merge pull request #12 from catseye/save-block Save block Chris Pressey authored 3 years ago GitHub committed 3 years ago
8 changed file(s) with 455 addition(s) and 7 deletion(s). Raw diff Collapse all Expand all
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
291291 self.assert_in_range(dest.index, dest.ref)
292292 self.set_written(dest.ref)
293293
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)
328
294329
295330 class Analyzer(object):
296331
299334 self.routines = {}
300335 self.debug = debug
301336
302 def assert_type(self, type, *locations):
337 def assert_type(self, type_, *locations):
303338 for location in locations:
304 if location.type != type:
339 if location.type != type_:
305340 raise TypeMismatchError(self.current_routine, location.name)
306341
307342 def assert_affected_within(self, name, affecting_type, limiting_type):
384419 self.analyze_for(instr, context)
385420 elif isinstance(instr, WithInterruptsOff):
386421 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)
387426 else:
388427 raise NotImplementedError
389428
426465 context.assert_meaningful(dest.ref, REG_Y)
427466 context.set_written(dest.ref)
428467 elif src.type != dest.type:
429 raise TypeMismatchError(instr, '{} and {}'.format(src, name))
468 raise TypeMismatchError(instr, '{} and {}'.format(src, dest))
430469 else:
431470 context.set_written(dest)
432471 # FIXME: context.copy_range(src, dest) ?
729768 # after it is executed, we know the range of the loop variable.
730769 context.set_range(instr.dest, instr.final, instr.final)
731770 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
612615 self.emitter.emit(SEI())
613616 self.compile_block(instr.block)
614617 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)))
305305 }
306306
307307
308 class PHA(Instruction):
309 opcodes = {
310 Implied: 0x48,
311 }
312
313
314 class PLA(Instruction):
315 opcodes = {
316 Implied: 0x68,
317 }
318
319
308320 class ROL(Instruction):
309321 opcodes = {
310322 Implied: 0x2a, # Accumulator
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,
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)
19371937 | }
19381938 ? UnmeaningfulReadError: y
19391939
1940 ### save ###
1941
1942 Basic neutral test, where the `save` makes no difference.
1943
1944 | routine main
1945 | inputs a, x
1946 | outputs a, x
1947 | trashes z, n
1948 | {
1949 | ld a, 1
1950 | save x {
1951 | ld a, 2
1952 | }
1953 | ld a, 3
1954 | }
1955 = ok
1956
1957 Saving any location (other than `a`) will trash `a`.
1958
1959 | routine main
1960 | inputs a, x
1961 | outputs a, x
1962 | trashes z, n
1963 | {
1964 | ld a, 1
1965 | save x {
1966 | ld a, 2
1967 | }
1968 | }
1969 ? UnmeaningfulOutputError
1970
1971 Saving `a` does not trash anything.
1972
1973 | routine main
1974 | inputs a, x
1975 | outputs a, x
1976 | trashes z, n
1977 | {
1978 | ld x, 1
1979 | save a {
1980 | ld x, 2
1981 | }
1982 | ld x, 3
1983 | }
1984 = ok
1985
1986 A defined value that has been saved can be trashed inside the block.
1987 It will continue to be defined outside the block.
1988
1989 | routine main
1990 | outputs x, y
1991 | trashes a, z, n
1992 | {
1993 | ld x, 0
1994 | save x {
1995 | ld y, 0
1996 | trash x
1997 | }
1998 | }
1999 = ok
2000
2001 A trashed value that has been saved can be used inside the block.
2002 It will continue to be trashed outside the block.
2003
2004 | routine main
2005 | inputs a
2006 | outputs a, x
2007 | trashes z, n
2008 | {
2009 | ld x, 0
2010 | trash x
2011 | save x {
2012 | ld a, 0
2013 | ld x, 1
2014 | }
2015 | }
2016 ? UnmeaningfulOutputError: x
2017
2018 The known range of a value will be preserved outside the block as well.
2019
2020 | word one: 77
2021 | word table[32] many
2022 |
2023 | routine main
2024 | inputs a, many, one
2025 | outputs many, one
2026 | trashes a, x, n, z
2027 | {
2028 | and a, 31
2029 | ld x, a
2030 | save x {
2031 | ld x, 255
2032 | }
2033 | copy one, many + x
2034 | copy many + x, one
2035 | }
2036 = ok
2037
2038 | word one: 77
2039 | word table[32] many
2040 |
2041 | routine main
2042 | inputs a, many, one
2043 | outputs many, one
2044 | trashes a, x, n, z
2045 | {
2046 | and a, 63
2047 | ld x, a
2048 | save x {
2049 | ld x, 1
2050 | }
2051 | copy one, many + x
2052 | copy many + x, one
2053 | }
2054 ? RangeExceededError
2055
2056 The known properties of a value are preserved inside the block, too.
2057
2058 | word one: 77
2059 | word table[32] many
2060 |
2061 | routine main
2062 | inputs a, many, one
2063 | outputs many, one
2064 | trashes a, x, n, z
2065 | {
2066 | and a, 31
2067 | ld x, a
2068 | save x {
2069 | copy one, many + x
2070 | copy many + x, one
2071 | }
2072 | copy one, many + x
2073 | copy many + x, one
2074 | }
2075 = ok
2076
2077 A value which is not output from the routine, is preserved by the
2078 routine; and can appear in a `save` exactly because a `save` preserves it.
2079
2080 | routine main
2081 | outputs y
2082 | trashes a, z, n
2083 | {
2084 | save x {
2085 | ld y, 0
2086 | ld x, 1
2087 | }
2088 | }
2089 = ok
2090
2091 Because saving anything except `a` trashes `a`, a common idiom is to save `a`
2092 first in a nested series of `save`s.
2093
2094 | routine main
2095 | inputs a
2096 | outputs a
2097 | trashes z, n
2098 | {
2099 | save a {
2100 | save x {
2101 | ld a, 0
2102 | ld x, 1
2103 | }
2104 | }
2105 | }
2106 = ok
2107
2108 Not just registers, but also user-defined locations can be saved.
2109
2110 | byte foo
2111 |
2112 | routine main
2113 | trashes a, z, n
2114 | {
2115 | save foo {
2116 | st 5, foo
2117 | }
2118 | }
2119 = ok
2120
2121 But only if they are bytes.
2122
2123 | word foo
2124 |
2125 | routine main
2126 | trashes a, z, n
2127 | {
2128 | save foo {
2129 | copy 555, foo
2130 | }
2131 | }
2132 ? TypeMismatchError
2133
2134 | byte table[16] tab
2135 |
2136 | routine main
2137 | trashes a, y, z, n
2138 | {
2139 | save tab {
2140 | ld y, 0
2141 | st 5, tab + y
2142 | }
2143 | }
2144 ? TypeMismatchError
2145
2146 A `goto` cannot appear within a `save` block, even if it is otherwise in tail position.
2147
2148 | routine other
2149 | trashes a, z, n
2150 | {
2151 | ld a, 0
2152 | }
2153 |
2154 | routine main
2155 | trashes a, z, n
2156 | {
2157 | ld a, 1
2158 | save x {
2159 | ld x, 2
2160 | goto other
2161 | }
2162 | }
2163 ? IllegalJumpError
2164
2165 ### with interrupts ###
2166
2167 | vector routine
2168 | inputs x
2169 | outputs x
2170 | trashes z, n
2171 | bar
2172 |
2173 | routine foo
2174 | inputs x
2175 | outputs x
2176 | trashes z, n
2177 | {
2178 | inc x
2179 | }
2180 |
2181 | routine main
2182 | outputs bar
2183 | trashes a, n, z
2184 | {
2185 | with interrupts off {
2186 | copy foo, bar
2187 | }
2188 | }
2189 = ok
2190
2191 A `goto` cannot appear within a `with interrupts` block, even if it is
2192 otherwise in tail position.
2193
2194 | vector routine
2195 | inputs x
2196 | outputs x
2197 | trashes z, n
2198 | bar
2199 |
2200 | routine foo
2201 | inputs x
2202 | outputs x
2203 | trashes z, n
2204 | {
2205 | inc x
2206 | }
2207 |
2208 | routine other
2209 | trashes bar, a, n, z
2210 | {
2211 | ld a, 0
2212 | }
2213 |
2214 | routine main
2215 | trashes bar, a, n, z
2216 | {
2217 | with interrupts off {
2218 | copy foo, bar
2219 | goto other
2220 | }
2221 | }
2222 ? IllegalJumpError
2223
19402224 ### copy ###
19412225
19422226 Can't `copy` from a memory location that isn't initialized.
578578 = $0815 BNE $080F
579579 = $0817 RTS
580580
581 Compiling `save`.
582
583 | routine main
584 | inputs a
585 | outputs a
586 | trashes z, n
587 | {
588 | save a {
589 | save x {
590 | ld a, 0
591 | ld x, 1
592 | }
593 | }
594 | }
595 = $080D PHA
596 = $080E TXA
597 = $080F PHA
598 = $0810 LDA #$00
599 = $0812 LDX #$01
600 = $0814 PLA
601 = $0815 TAX
602 = $0816 PLA
603 = $0817 RTS
604
605 Compiling `save` on a user-defined location.
606
607 | byte foo
608 | routine main
609 | trashes a, z, n
610 | {
611 | save foo {
612 | ld a, 0
613 | st a, foo
614 | }
615 | }
616 = $080D LDA $081B
617 = $0810 PHA
618 = $0811 LDA #$00
619 = $0813 STA $081B
620 = $0816 PLA
621 = $0817 STA $081B
622 = $081A RTS
623
581624 Indexed access.
582625
583626 | byte one
156156 | ld x, 15
157157 | for x down to 0 {
158158 | ld a, tab + x
159 | }
160 | }
161 = ok
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
159173 | }
160174 | }
161175 = ok