git @ Cat's Eye Technologies SixtyPical / 3f666f4
Add `reset` instruction; much refactoring. Chris Pressey 2 years ago
16 changed file(s) with 4286 addition(s) and 3890 deletion(s). Raw diff Collapse all Expand all
33 0.20
44 ----
55
6 * A `point ... into` block no longer initializes the pointer
7 by default. A subequent `reset` instruction must be used
8 to initialize the pointer. The pointer may be reset to any
9 valid offset within the table (not only 0) and it may be
10 reset multiple times inside the block.
11 * Local locations need no longer be static. If they are not
12 static, they are considered uninitialized until assigned,
13 and they can be declared with an explicit fixed address.
614 * Fixed a bug where two local statics could be declared with
715 the same name.
8 * Local locations need no longer be static. If they are not
9 static, they are considered uninitialized until assigned.
16 * Split context off from analyzer and put it in its own module.
17 * Split the SixtyPical Analysis tests across three files.
1018
1119 0.19
1220 ----
2828
2929 These holes need to be plugged.
3030
31 ### Reset pointer in `point into` blocks
32
33 We have `point into` blocks, but maybe the action when entering such a
34 block shouldn't always be to set the given pointer to the start of the given table.
35
36 That is, sometimes we would like to start at some fixed offset. And
37 sometimes we want to (re)set the pointer, without closing and starting a new block.
38
39 ### Pointers associated globally with a table
31 ### Pointers associated globally with a table(?)
4032
4133 We have `point into` blocks, but we would also like to sometimes pass a pointer
4234 around to different routines, and have them all "know" what table it operates on.
6052
6153 This is more an impressive trick than a really useful feature, but still.
6254 Impressive tricks are impressive.
63
64 ### Locals with explicit addresses
65
66 A local could also be given an explicit address. In this case, two locals in
67 different routines could be given the same address, and as long as the condition
68 in the above paragraph holds, that's okay. (If it doesn't, the analyzer should
69 detect it.)
70
71 This would permit local pointers, which would be one way of addressing the
72 "same pointer to different tables" problem.
7355
7456 ### Copy byte to/from table
7557
264264
265265 if c {
266266 point ptr into screen {
267 reset ptr 0
267268 st off, c
268269 add ptr, new_pos
269270 ld y, 0
279280 cmp a, 32
280281 if z {
281282 point ptr into screen {
283 reset ptr 0
282284 st off, c
283285 add ptr, pos
284286 copy 32, [ptr] + y
287289 copy new_pos, pos
288290
289291 point ptr into screen {
292 reset ptr 0
290293 st off, c
291294 add ptr, pos
292295 copy 81, [ptr] + y
306309
307310 if c {
308311 point ptr into screen {
312 reset ptr 0
309313 st off, c
310314 add ptr, new_pos
311315 ld y, 0
320324 cmp a, 32
321325 if z {
322326 point ptr into screen {
327 reset ptr 0
323328 st off, c
324329 add ptr, pos
325330 copy 32, [ptr] + y
328333 copy new_pos, pos
329334
330335 point ptr into screen {
336 reset ptr 0
331337 st off, c
332338 add ptr, pos
333339 copy 82, [ptr] + y
1010 trashes a, z, n, c, ptr
1111 {
1212 ld y, 0
13 copy ^buf, ptr
14 copy 123, [ptr] + y
15 copy [ptr] + y, foo
16 copy foo, [ptr] + y
13 point ptr into buf {
14 reset ptr 0
15 copy 123, [ptr] + y
16 copy [ptr] + y, foo
17 copy foo, [ptr] + y
18 }
1719
1820 // TODO: support saying `cmp foo, 123`, maybe
1921 ld a, foo
00 # encoding: UTF-8
11
22 from sixtypical.ast import (
3 Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
3 Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
44 )
5 from sixtypical.context import AnalysisContext
56 from sixtypical.model import (
67 TYPE_BYTE, TYPE_WORD,
78 TableType, PointerType, VectorType, RoutineType,
7980 pass
8081
8182
82 class AnalysisContext(object):
83 """
84 A location is touched if it was changed (or even potentially
85 changed) during this routine, or some routine called by this routine.
86
87 A location is meaningful if it was an input to this routine,
88 or if it was set to a meaningful value by some operation in this
89 routine (or some routine called by this routine).
90
91 If a location is meaningful, it has a range. This range represents
92 the lowest and highest values that it might possibly be (i.e. we know
93 it cannot possibly be below the lowest or above the highest.) In the
94 absence of any usage information, the range of a byte, is 0..255 and
95 the range of a word is 0..65535.
96
97 A location is writeable if it was listed in the outputs and trashes
98 lists of this routine. A location can also be temporarily marked
99 unwriteable in certain contexts, such as `for` loops.
100 """
101 def __init__(self, symtab, routine, inputs, outputs, trashes):
102 self.symtab = symtab
103 self.routine = routine # Routine (AST node)
104 self._touched = set() # {LocationRef}
105 self._range = dict() # LocationRef -> (Int, Int)
106 self._writeable = set() # {LocationRef}
107 self._terminated = False
108 self._gotos_encountered = set()
109 self._pointer_assoc = dict()
110
111 for ref in inputs:
112 if self.is_constant(ref):
113 raise ConstantConstraintError(self.routine, ref.name)
114 self._range[ref] = self.max_range(ref)
115 output_names = set()
116 for ref in outputs:
117 if self.is_constant(ref):
118 raise ConstantConstraintError(self.routine, ref.name)
119 output_names.add(ref.name)
120 self._writeable.add(ref)
121 for ref in trashes:
122 if self.is_constant(ref):
123 raise ConstantConstraintError(self.routine, ref.name)
124 if ref.name in output_names:
125 raise InconsistentConstraintsError(self.routine, ref.name)
126 self._writeable.add(ref)
127
128 def __str__(self):
129 return "{}(\n _touched={},\n _range={},\n _writeable={}\n)".format(
130 self.__class__.__name__,
131 LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable)
132 )
133
134 def to_json_data(self):
135 type_ = self.symtab.fetch_global_type(self.routine.name)
136 return {
137 'routine_inputs': ','.join(sorted(loc.name for loc in type_.inputs)),
138 'routine_outputs': ','.join(sorted(loc.name for loc in type_.outputs)),
139 'routine_trashes': ','.join(sorted(loc.name for loc in type_.trashes)),
140 'touched': ','.join(sorted(loc.name for loc in self._touched)),
141 'range': dict((loc.name, '{}-{}'.format(rng[0], rng[1])) for (loc, rng) in self._range.items()),
142 'writeable': ','.join(sorted(loc.name for loc in self._writeable)),
143 'terminated': self._terminated,
144 'gotos_encountered': ','.join(sorted(loc.name for loc in self._gotos_encountered)),
145 }
146
147 def clone(self):
148 c = AnalysisContext(self.symtab, self.routine, [], [], [])
149 c._touched = set(self._touched)
150 c._range = dict(self._range)
151 c._writeable = set(self._writeable)
152 c._pointer_assoc = dict(self._pointer_assoc)
153 c._gotos_encountered = set(self._gotos_encountered)
154 return c
155
156 def update_from(self, other):
157 """Replaces the information in this context, with the information from the other context.
158 This is an overwriting action - it does not attempt to merge the contexts.
159
160 We do not replace the gotos_encountered for technical reasons. (In `analyze_if`,
161 we merge those sets afterwards; at the end of `analyze_routine`, they are not distinct in the
162 set of contexts we are updating from, and we want to retain our own.)"""
163 self.routine = other.routine
164 self._touched = set(other._touched)
165 self._range = dict(other._range)
166 self._writeable = set(other._writeable)
167 self._terminated = other._terminated
168 self._pointer_assoc = dict(other._pointer_assoc)
169
170 def each_meaningful(self):
171 for ref in self._range.keys():
172 yield ref
173
174 def each_touched(self):
175 for ref in self._touched:
176 yield ref
177
178 def each_writeable(self):
179 for ref in self._writeable:
180 yield ref
181
182 def assert_meaningful(self, *refs, **kwargs):
183 exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
184 for ref in refs:
185 if self.symtab.has_local(self.routine.name, ref.name):
186 if ref not in self._range:
187 message = ref.name
188 if kwargs.get('message'):
189 message += ' (%s)' % kwargs['message']
190 raise exception_class(self.routine, message)
191 else:
192 continue
193 if self.is_constant(ref):
194 pass
195 elif isinstance(ref, LocationRef):
196 if ref not in self._range:
197 message = ref.name
198 if kwargs.get('message'):
199 message += ' (%s)' % kwargs['message']
200 raise exception_class(self.routine, message)
201 elif isinstance(ref, IndexedRef):
202 self.assert_meaningful(ref.ref, **kwargs)
203 self.assert_meaningful(ref.index, **kwargs)
204 else:
205 raise NotImplementedError(ref)
206
207 def assert_writeable(self, *refs, **kwargs):
208 exception_class = kwargs.get('exception_class', ForbiddenWriteError)
209 for ref in refs:
210 # locals are always writeable
211 if self.symtab.has_local(self.routine.name, ref.name):
212 continue
213 if ref not in self._writeable:
214 message = ref.name
215 if kwargs.get('message'):
216 message += ' (%s)' % kwargs['message']
217 raise exception_class(self.routine, message)
218
219 def assert_in_range(self, inside, outside, offset):
220 """Given two locations, assert that the first location, offset by the given offset,
221 is contained 'inside' the second location."""
222 assert isinstance(inside, LocationRef)
223 assert isinstance(outside, LocationRef)
224
225 # inside should always be meaningful
226 inside_range = self._range[inside]
227
228 # outside might not be meaningful, so default to max range if necessary
229 if outside in self._range:
230 outside_range = self._range[outside]
231 else:
232 outside_range = self.max_range(outside)
233
234 if (inside_range[0] + offset.value) < outside_range[0] or (inside_range[1] + offset.value) > outside_range[1]:
235 raise RangeExceededError(self.routine,
236 "Possible range of {} {} (+{}) exceeds acceptable range of {} {}".format(
237 inside, inside_range, offset, outside, outside_range
238 )
239 )
240
241 def set_touched(self, *refs):
242 for ref in refs:
243 self._touched.add(ref)
244 # TODO: it might be possible to invalidate the range here
245
246 def set_meaningful(self, *refs):
247 for ref in refs:
248 if ref not in self._range:
249 self._range[ref] = self.max_range(ref)
250
251 def set_top_of_range(self, ref, top):
252 self.assert_meaningful(ref)
253 (bottom, _) = self._range[ref]
254 self._range[ref] = (bottom, top)
255
256 def set_bottom_of_range(self, ref, bottom):
257 self.assert_meaningful(ref)
258 (top, _) = self._range[ref]
259 self._range[ref] = (bottom, top)
260
261 def set_range(self, ref, bottom, top):
262 self.assert_meaningful(ref)
263 self._range[ref] = (bottom, top)
264
265 def get_top_of_range(self, ref):
266 if isinstance(ref, ConstantRef):
267 return ref.value
268 self.assert_meaningful(ref)
269 (_, top) = self._range[ref]
270 return top
271
272 def get_bottom_of_range(self, ref):
273 if isinstance(ref, ConstantRef):
274 return ref.value
275 self.assert_meaningful(ref)
276 (bottom, _) = self._range[ref]
277 return bottom
278
279 def get_range(self, ref):
280 if isinstance(ref, ConstantRef):
281 return (ref.value, ref.value)
282 self.assert_meaningful(ref)
283 (bottom, top) = self._range[ref]
284 return bottom, top
285
286 def copy_range(self, src, dest):
287 self.assert_meaningful(src)
288 if src in self._range:
289 src_range = self._range[src]
290 else:
291 src_range = self.max_range(src)
292 self._range[dest] = src_range
293
294 def invalidate_range(self, ref):
295 self.assert_meaningful(ref)
296 self._range[ref] = self.max_range(ref)
297
298 def set_unmeaningful(self, *refs):
299 for ref in refs:
300 if ref in self._range:
301 del self._range[ref]
302
303 def set_written(self, *refs):
304 """A "helper" method which does the following common sequence for
305 the given refs: asserts they're all writable, and sets them all
306 as touched and meaningful."""
307 self.assert_writeable(*refs)
308 self.set_touched(*refs)
309 self.set_meaningful(*refs)
310
311 def set_unwriteable(self, *refs):
312 """Intended to be used for implementing analyzing `for`."""
313 for ref in refs:
314 self._writeable.remove(ref)
315
316 def set_writeable(self, *refs):
317 """Intended to be used for implementing analyzing `for`, but also used in `save`."""
318 for ref in refs:
319 self._writeable.add(ref)
320
321 def encounter_gotos(self, gotos):
322 self._gotos_encountered |= gotos
323
324 def encountered_gotos(self):
325 return self._gotos_encountered
326
327 def set_terminated(self):
328 # Having a terminated context and having encountered gotos is not the same thing.
329 self._terminated = True
330
331 def has_terminated(self):
332 return self._terminated
333
334 def extract(self, location):
335 """Sets the given location as writeable in the context, and returns a 'baton' representing
336 the previous state of context for that location. This 'baton' can be used to later restore
337 this state of context."""
338 # Used in `save`.
339 baton = (
340 location,
341 location in self._touched,
342 self._range.get(location, None),
343 location in self._writeable,
344 )
345 self.set_writeable(location)
346 return baton
347
348 def re_introduce(self, baton):
349 """Given a 'baton' produced by `extract()`, restores the context for that saved location
350 to what it was before `extract()` was called."""
351 # Used in `save`.
352 location, was_touched, was_range, was_writeable = baton
353
354 if was_touched:
355 self._touched.add(location)
356 elif location in self._touched:
357 self._touched.remove(location)
358
359 if was_range is not None:
360 self._range[location] = was_range
361 elif location in self._range:
362 del self._range[location]
363
364 if was_writeable:
365 self._writeable.add(location)
366 elif location in self._writeable:
367 self._writeable.remove(location)
368
369 def get_assoc(self, pointer):
370 return self._pointer_assoc.get(pointer)
371
372 def set_assoc(self, pointer, table):
373 self._pointer_assoc[pointer] = table
374
375 def is_constant(self, ref):
376 """read-only means that the program cannot change the value
377 of a location. constant means that the value of the location
378 will not change during the lifetime of the program."""
379 if isinstance(ref, ConstantRef):
380 return True
381 if isinstance(ref, (IndirectRef, IndexedRef)):
382 return False
383 if isinstance(ref, LocationRef):
384 type_ = self.symtab.fetch_global_type(ref.name)
385 return isinstance(type_, RoutineType)
386 raise NotImplementedError
387
388 def max_range(self, ref):
389 if isinstance(ref, ConstantRef):
390 return (ref.value, ref.value)
391 elif self.symtab.has_local(self.routine.name, ref.name):
392 return self.symtab.fetch_local_type(self.routine.name, ref.name).max_range
393 else:
394 return self.symtab.fetch_global_type(ref.name).max_range
395
396
39783 class Analyzer(object):
39884
39985 def __init__(self, symtab, debug=False):
41399 if isinstance(ref, ConstantRef):
414100 return ref.type
415101 if not isinstance(ref, LocationRef):
416 raise NotImplementedError
102 raise NotImplementedError(str(ref))
417103 return self.get_type_for_name(ref.name)
418104
419105 def assert_type(self, type_, *locations):
550236 self.analyze_save(instr, context)
551237 elif isinstance(instr, PointInto):
552238 self.analyze_point_into(instr, context)
239 elif isinstance(instr, Reset):
240 self.analyze_reset(instr, context)
553241 else:
554 raise NotImplementedError
242 raise NotImplementedError(str(instr))
555243
556244 def analyze_single_op(self, instr, context):
557245
771459 # 2. check that the context is meaningful
772460
773461 if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
774 context.assert_meaningful(src, REG_Y)
462 context.assert_meaningful(src, dest.ref, REG_Y)
775463
776464 target = context.get_assoc(dest.ref)
777465 if not target:
778466 raise ForbiddenWriteError(instr, dest.ref)
467 context.assert_writeable(target)
779468 context.set_touched(target)
780469 context.set_written(target)
781470
787476 raise UnmeaningfulReadError(instr, src.ref)
788477 context.assert_meaningful(origin)
789478
479 context.assert_writeable(dest)
790480 context.set_touched(dest)
791481 context.set_written(dest)
792482 elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
793 context.assert_meaningful(src.ref, REG_Y)
483 context.assert_meaningful(src.ref, dest.ref, REG_Y)
794484
795485 origin = context.get_assoc(src.ref)
796486 if not origin:
800490 target = context.get_assoc(dest.ref)
801491 if not target:
802492 raise ForbiddenWriteError(instr, dest.ref)
493 context.assert_writeable(target)
803494 context.set_touched(target)
804495 context.set_written(target)
805496
1019710 if context.get_assoc(instr.pointer):
1020711 raise ForbiddenWriteError(instr, instr.pointer)
1021712
1022 # associate pointer with table, mark it as meaningful.
713 # associate pointer with table
714 # (do not mark it as meaningful yet - that's reset's job.)
1023715
1024716 context.set_assoc(instr.pointer, instr.table)
1025 context.set_meaningful(instr.pointer)
1026 context.set_touched(instr.pointer)
717 context.set_unmeaningful(instr.pointer)
1027718
1028719 self.analyze_block(instr.block, context)
1029720 if context.encountered_gotos():
1033724
1034725 context.set_assoc(instr.pointer, None)
1035726 context.set_unmeaningful(instr.pointer)
727
728 def analyze_reset(self, instr, context):
729 type = self.get_type(instr.pointer)
730 if not isinstance(type, (PointerType)):
731 raise TypeMismatchError(instr, instr.pointer.name)
732
733 table = context.get_assoc(instr.pointer)
734 if not table:
735 raise ForbiddenWriteError(instr, '{} is not associated with any table'.format(instr.pointer.name))
736 context.assert_meaningful(table)
737 low_limit, high_limit = context.get_range(table)
738
739 assert isinstance(instr.offset, ConstantRef)
740 if instr.offset.value < low_limit or instr.offset.value > high_limit:
741 raise RangeExceededError(instr, instr.pointer.name)
742
743 context.set_meaningful(instr.pointer)
744 context.set_touched(instr.pointer)
7474 value_attrs = ('opcode', 'dest', 'src',)
7575
7676
77 class Reset(Instr):
78 value_attrs = ('pointer', 'offset',)
79
80
7781 class Call(Instr):
7882 value_attrs = ('location',)
7983
00 # encoding: UTF-8
11
22 from sixtypical.ast import (
3 Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
3 Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
44 )
55 from sixtypical.model import (
66 ConstantRef, LocationRef, IndexedRef, IndirectRef,
3636 self.routine_locals = {} # routine.name -> { local.name -> Label }
3737 self.labels = {} # global.name -> Label ("global" includes routines)
3838 self.trampolines = {} # Location -> Label
39 self.pointer_assoc = {} # pointer name -> table name (I'm not entirely happy about this)
3940 self.current_routine = None
4041
4142 # - - - - helper methods - - - -
190191 return self.compile_save(instr)
191192 elif isinstance(instr, PointInto):
192193 return self.compile_point_into(instr)
194 elif isinstance(instr, Reset):
195 return self.compile_reset(instr)
193196 else:
194197 raise NotImplementedError
195198
740743 self.emitter.emit(STA(Absolute(src_label)))
741744
742745 def compile_point_into(self, instr):
743 src_label = self.get_label(instr.table.name)
746 self.pointer_assoc[instr.pointer.name] = instr.table.name
747 self.compile_block(instr.block)
748 del self.pointer_assoc[instr.pointer.name]
749
750 def compile_reset(self, instr):
751 table_name = self.pointer_assoc[instr.pointer.name]
752 src_label = Offset(self.get_label(table_name), instr.offset.value)
744753 dest_label = self.get_label(instr.pointer.name)
745754
746755 self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
747756 self.emitter.emit(STA(ZeroPage(dest_label)))
748757 self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
749758 self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1))))
750
751 self.compile_block(instr.block)
0 # encoding: UTF-8
1
2 from sixtypical.model import (
3 RoutineType, ConstantRef, LocationRef, IndirectRef, IndexedRef,
4 )
5
6
7 class AnalysisContext(object):
8 """
9 A location is touched if it was changed (or even potentially
10 changed) during this routine, or some routine called by this routine.
11
12 A location is meaningful if it was an input to this routine,
13 or if it was set to a meaningful value by some operation in this
14 routine (or some routine called by this routine).
15
16 If a location is meaningful, it has a range. This range represents
17 the lowest and highest values that it might possibly be (i.e. we know
18 it cannot possibly be below the lowest or above the highest.) In the
19 absence of any usage information, the range of a byte, is 0..255 and
20 the range of a word is 0..65535.
21
22 A location is writeable if it was listed in the outputs and trashes
23 lists of this routine. A location can also be temporarily marked
24 unwriteable in certain contexts, such as `for` loops.
25 """
26 def __init__(self, symtab, routine, inputs, outputs, trashes):
27 from sixtypical.analyzer import ConstantConstraintError, InconsistentConstraintsError
28
29 self.symtab = symtab
30 self.routine = routine # Routine (AST node)
31 self._touched = set() # {LocationRef}
32 self._range = dict() # LocationRef -> (Int, Int)
33 self._writeable = set() # {LocationRef}
34 self._terminated = False
35 self._gotos_encountered = set()
36 self._pointer_assoc = dict()
37
38 for ref in inputs:
39 if self.is_constant(ref):
40 raise ConstantConstraintError(self.routine, ref.name)
41 self._range[ref] = self.max_range(ref)
42 output_names = set()
43 for ref in outputs:
44 if self.is_constant(ref):
45 raise ConstantConstraintError(self.routine, ref.name)
46 output_names.add(ref.name)
47 self._writeable.add(ref)
48 for ref in trashes:
49 if self.is_constant(ref):
50 raise ConstantConstraintError(self.routine, ref.name)
51 if ref.name in output_names:
52 raise InconsistentConstraintsError(self.routine, ref.name)
53 self._writeable.add(ref)
54
55 def __str__(self):
56 return "{}(\n _touched={},\n _range={},\n _writeable={}\n)".format(
57 self.__class__.__name__,
58 LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable)
59 )
60
61 def to_json_data(self):
62 type_ = self.symtab.fetch_global_type(self.routine.name)
63 return {
64 'routine_inputs': ','.join(sorted(loc.name for loc in type_.inputs)),
65 'routine_outputs': ','.join(sorted(loc.name for loc in type_.outputs)),
66 'routine_trashes': ','.join(sorted(loc.name for loc in type_.trashes)),
67 'touched': ','.join(sorted(loc.name for loc in self._touched)),
68 'range': dict((loc.name, '{}-{}'.format(rng[0], rng[1])) for (loc, rng) in self._range.items()),
69 'writeable': ','.join(sorted(loc.name for loc in self._writeable)),
70 'terminated': self._terminated,
71 'gotos_encountered': ','.join(sorted(loc.name for loc in self._gotos_encountered)),
72 }
73
74 def clone(self):
75 c = AnalysisContext(self.symtab, self.routine, [], [], [])
76 c._touched = set(self._touched)
77 c._range = dict(self._range)
78 c._writeable = set(self._writeable)
79 c._pointer_assoc = dict(self._pointer_assoc)
80 c._gotos_encountered = set(self._gotos_encountered)
81 return c
82
83 def update_from(self, other):
84 """Replaces the information in this context, with the information from the other context.
85 This is an overwriting action - it does not attempt to merge the contexts.
86
87 We do not replace the gotos_encountered for technical reasons. (In `analyze_if`,
88 we merge those sets afterwards; at the end of `analyze_routine`, they are not distinct in the
89 set of contexts we are updating from, and we want to retain our own.)"""
90 self.routine = other.routine
91 self._touched = set(other._touched)
92 self._range = dict(other._range)
93 self._writeable = set(other._writeable)
94 self._terminated = other._terminated
95 self._pointer_assoc = dict(other._pointer_assoc)
96
97 def each_meaningful(self):
98 for ref in self._range.keys():
99 yield ref
100
101 def each_touched(self):
102 for ref in self._touched:
103 yield ref
104
105 def each_writeable(self):
106 for ref in self._writeable:
107 yield ref
108
109 def assert_meaningful(self, *refs, **kwargs):
110 from sixtypical.analyzer import UnmeaningfulReadError
111
112 exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
113 for ref in refs:
114 if self.symtab.has_local(self.routine.name, ref.name):
115 if ref not in self._range:
116 message = ref.name
117 if kwargs.get('message'):
118 message += ' (%s)' % kwargs['message']
119 raise exception_class(self.routine, message)
120 else:
121 continue
122 if self.is_constant(ref):
123 pass
124 elif isinstance(ref, LocationRef):
125 if ref not in self._range:
126 message = ref.name
127 if kwargs.get('message'):
128 message += ' (%s)' % kwargs['message']
129 raise exception_class(self.routine, message)
130 elif isinstance(ref, IndexedRef):
131 self.assert_meaningful(ref.ref, **kwargs)
132 self.assert_meaningful(ref.index, **kwargs)
133 else:
134 raise NotImplementedError(ref)
135
136 def assert_writeable(self, *refs, **kwargs):
137 from sixtypical.analyzer import ForbiddenWriteError
138
139 exception_class = kwargs.get('exception_class', ForbiddenWriteError)
140 for ref in refs:
141 # locals are always writeable
142 if self.symtab.has_local(self.routine.name, ref.name):
143 continue
144 if ref not in self._writeable:
145 message = ref.name
146 if kwargs.get('message'):
147 message += ' (%s)' % kwargs['message']
148 raise exception_class(self.routine, message)
149
150 def assert_in_range(self, inside, outside, offset):
151 """Given two locations, assert that the first location, offset by the given offset,
152 is contained 'inside' the second location."""
153 from sixtypical.analyzer import RangeExceededError
154
155 assert isinstance(inside, LocationRef)
156 assert isinstance(outside, LocationRef)
157
158 # inside should always be meaningful
159 inside_range = self._range[inside]
160
161 # outside might not be meaningful, so default to max range if necessary
162 if outside in self._range:
163 outside_range = self._range[outside]
164 else:
165 outside_range = self.max_range(outside)
166
167 if (inside_range[0] + offset.value) < outside_range[0] or (inside_range[1] + offset.value) > outside_range[1]:
168 raise RangeExceededError(self.routine,
169 "Possible range of {} {} (+{}) exceeds acceptable range of {} {}".format(
170 inside, inside_range, offset, outside, outside_range
171 )
172 )
173
174 def set_touched(self, *refs):
175 for ref in refs:
176 self._touched.add(ref)
177 # TODO: it might be possible to invalidate the range here
178
179 def set_meaningful(self, *refs):
180 for ref in refs:
181 if ref not in self._range:
182 self._range[ref] = self.max_range(ref)
183
184 def set_top_of_range(self, ref, top):
185 self.assert_meaningful(ref)
186 (bottom, _) = self._range[ref]
187 self._range[ref] = (bottom, top)
188
189 def set_bottom_of_range(self, ref, bottom):
190 self.assert_meaningful(ref)
191 (top, _) = self._range[ref]
192 self._range[ref] = (bottom, top)
193
194 def set_range(self, ref, bottom, top):
195 self.assert_meaningful(ref)
196 self._range[ref] = (bottom, top)
197
198 def get_top_of_range(self, ref):
199 if isinstance(ref, ConstantRef):
200 return ref.value
201 self.assert_meaningful(ref)
202 (_, top) = self._range[ref]
203 return top
204
205 def get_bottom_of_range(self, ref):
206 if isinstance(ref, ConstantRef):
207 return ref.value
208 self.assert_meaningful(ref)
209 (bottom, _) = self._range[ref]
210 return bottom
211
212 def get_range(self, ref):
213 if isinstance(ref, ConstantRef):
214 return (ref.value, ref.value)
215 self.assert_meaningful(ref)
216 (bottom, top) = self._range[ref]
217 return bottom, top
218
219 def copy_range(self, src, dest):
220 self.assert_meaningful(src)
221 if src in self._range:
222 src_range = self._range[src]
223 else:
224 src_range = self.max_range(src)
225 self._range[dest] = src_range
226
227 def invalidate_range(self, ref):
228 self.assert_meaningful(ref)
229 self._range[ref] = self.max_range(ref)
230
231 def set_unmeaningful(self, *refs):
232 for ref in refs:
233 if ref in self._range:
234 del self._range[ref]
235
236 def set_written(self, *refs):
237 """A "helper" method which does the following common sequence for
238 the given refs: asserts they're all writable, and sets them all
239 as touched and meaningful."""
240 self.assert_writeable(*refs)
241 self.set_touched(*refs)
242 self.set_meaningful(*refs)
243
244 def set_unwriteable(self, *refs):
245 """Intended to be used for implementing analyzing `for`."""
246 for ref in refs:
247 self._writeable.remove(ref)
248
249 def set_writeable(self, *refs):
250 """Intended to be used for implementing analyzing `for`, but also used in `save`."""
251 for ref in refs:
252 self._writeable.add(ref)
253
254 def encounter_gotos(self, gotos):
255 self._gotos_encountered |= gotos
256
257 def encountered_gotos(self):
258 return self._gotos_encountered
259
260 def set_terminated(self):
261 # Having a terminated context and having encountered gotos is not the same thing.
262 self._terminated = True
263
264 def has_terminated(self):
265 return self._terminated
266
267 def extract(self, location):
268 """Sets the given location as writeable in the context, and returns a 'baton' representing
269 the previous state of context for that location. This 'baton' can be used to later restore
270 this state of context."""
271 # Used in `save`.
272 baton = (
273 location,
274 location in self._touched,
275 self._range.get(location, None),
276 location in self._writeable,
277 )
278 self.set_writeable(location)
279 return baton
280
281 def re_introduce(self, baton):
282 """Given a 'baton' produced by `extract()`, restores the context for that saved location
283 to what it was before `extract()` was called."""
284 # Used in `save`.
285 location, was_touched, was_range, was_writeable = baton
286
287 if was_touched:
288 self._touched.add(location)
289 elif location in self._touched:
290 self._touched.remove(location)
291
292 if was_range is not None:
293 self._range[location] = was_range
294 elif location in self._range:
295 del self._range[location]
296
297 if was_writeable:
298 self._writeable.add(location)
299 elif location in self._writeable:
300 self._writeable.remove(location)
301
302 def get_assoc(self, pointer):
303 return self._pointer_assoc.get(pointer)
304
305 def set_assoc(self, pointer, table):
306 self._pointer_assoc[pointer] = table
307
308 def is_constant(self, ref):
309 """read-only means that the program cannot change the value
310 of a location. constant means that the value of the location
311 will not change during the lifetime of the program."""
312 if isinstance(ref, ConstantRef):
313 return True
314 if isinstance(ref, (IndirectRef, IndexedRef)):
315 return False
316 if isinstance(ref, LocationRef):
317 type_ = self.symtab.fetch_global_type(ref.name)
318 return isinstance(type_, RoutineType)
319 raise NotImplementedError
320
321 def max_range(self, ref):
322 if isinstance(ref, ConstantRef):
323 return (ref.value, ref.value)
324 elif self.symtab.has_local(self.routine.name, ref.name):
325 return self.symtab.fetch_local_type(self.routine.name, ref.name).max_range
326 else:
327 return self.symtab.fetch_global_type(ref.name).max_range
127127
128128 class HighAddressByte(Emittable):
129129 def __init__(self, label):
130 assert isinstance(label, Label)
130 assert isinstance(label, (Label, Offset))
131131 self.label = label
132132
133133 def size(self):
142142
143143 class LowAddressByte(Emittable):
144144 def __init__(self, label):
145 assert isinstance(label, Label)
145 assert isinstance(label, (Label, Offset))
146146 self.label = label
147147
148148 def size(self):
00 # encoding: UTF-8
11
22 from sixtypical.ast import (
3 Program, Defn, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
3 Program, Defn, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
44 )
55 from sixtypical.model import (
66 TYPE_BIT, TYPE_BYTE, TYPE_WORD,
431431 final = self.const()
432432 block = self.block()
433433 return For(self.scanner.line_number, dest=dest, direction=direction, final=final, block=block)
434 elif self.scanner.consume('reset'):
435 pointer = self.locexpr()
436 offset = self.const()
437 return Reset(self.scanner.line_number, pointer=pointer, offset=offset)
434438 elif self.scanner.token in ("ld",):
435439 # the same as add, sub, cmp etc below, except supports an indlocexpr for the src
436440 opcode = self.scanner.token
00 #!/bin/sh
11
22 falderal --substring-error \
3 tests/SixtyPical\ Syntax.md \
4 tests/SixtyPical\ Analysis.md \
5 tests/SixtyPical\ Fallthru.md \
6 tests/SixtyPical\ Compilation.md
3 "tests/SixtyPical Syntax.md" \
4 "tests/SixtyPical Analysis.md" \
5 "tests/SixtyPical Storage.md" \
6 "tests/SixtyPical Control Flow.md" \
7 "tests/SixtyPical Fallthru.md" \
8 "tests/SixtyPical Compilation.md"
33 This is a test suite, written in [Falderal][] format, for the SixtyPical
44 static analysis rules.
55
6 This file mostly contains tests for operations.
7 For rudiments and storage, see [SixtyPical Storage](SixtyPical%20Storage.md).
8 For control flow, see [SixtyPical Control Flow](SixtyPical%20Control%20Flow.md).
9
610 [Falderal]: http://catseye.tc/node/Falderal
711
812 -> Functionality "Analyze SixtyPical program" is implemented by
913 -> shell command "bin/sixtypical --analyze-only --traceback %(test-body-file) && echo ok"
1014
1115 -> Tests for functionality "Analyze SixtyPical program"
12
13 ### Rudiments ###
14
15 Routines must declare their inputs, outputs, and memory locations they trash.
16
17 | define up routine
18 | inputs a
19 | outputs a
20 | trashes c, z, v, n
21 | {
22 | st off, c
23 | add a, 1
24 | }
25 = ok
26
27 Routines may not declare a memory location to be both an output and trashed.
28
29 | define main routine
30 | outputs a
31 | trashes a
32 | {
33 | ld a, 0
34 | }
35 ? InconsistentConstraintsError: a
36
37 If a routine declares it outputs a location, that location should be initialized.
38
39 | define main routine
40 | outputs a, x, z, n
41 | {
42 | ld x, 0
43 | }
44 ? UnmeaningfulOutputError: a
45
46 | define main routine
47 | inputs a
48 | outputs a
49 | {
50 | }
51 = ok
52
53 If a routine declares it outputs a location, that location may or may not have
54 been initialized. Trashing is mainly a signal to the caller.
55
56 | define main routine
57 | trashes x, z, n
58 | {
59 | ld x, 0
60 | }
61 = ok
62
63 | define main routine
64 | trashes x, z, n
65 | {
66 | }
67 = ok
68
69 If a routine modifies a location, it needs to either output it or trash it.
70
71 | define main routine
72 | {
73 | ld x, 0
74 | }
75 ? ForbiddenWriteError: x
76
77 | define main routine
78 | outputs x, z, n
79 | {
80 | ld x, 0
81 | }
82 = ok
83
84 | define main routine
85 | trashes x, z, n
86 | {
87 | ld x, 0
88 | }
89 = ok
90
91 This is true regardless of whether it's an input or not.
92
93 | define main routine
94 | inputs x
95 | {
96 | ld x, 0
97 | }
98 ? ForbiddenWriteError: x
99
100 | define main routine
101 | inputs x
102 | outputs x, z, n
103 | {
104 | ld x, 0
105 | }
106 = ok
107
108 | define main routine
109 | inputs x
110 | trashes x, z, n
111 | {
112 | ld x, 0
113 | }
114 = ok
115
116 If a routine trashes a location, this must be declared.
117
118 | define foo routine
119 | trashes x
120 | {
121 | trash x
122 | }
123 = ok
124
125 | define foo routine
126 | {
127 | trash x
128 | }
129 ? ForbiddenWriteError: x
130
131 | define foo routine
132 | outputs x
133 | {
134 | trash x
135 | }
136 ? UnmeaningfulOutputError: x
137
138 If a routine causes a location to be trashed, this must be declared in the caller.
139
140 | define trash_x routine
141 | trashes x, z, n
142 | {
143 | ld x, 0
144 | }
145 |
146 | define foo routine
147 | trashes x, z, n
148 | {
149 | call trash_x
150 | }
151 = ok
152
153 | define trash_x routine
154 | trashes x, z, n
155 | {
156 | ld x, 0
157 | }
158 |
159 | define foo routine
160 | trashes z, n
161 | {
162 | call trash_x
163 | }
164 ? ForbiddenWriteError: x
165
166 | define trash_x routine
167 | trashes x, z, n
168 | {
169 | ld x, 0
170 | }
171 |
172 | define foo routine
173 | outputs x
174 | trashes z, n
175 | {
176 | call trash_x
177 | }
178 ? UnmeaningfulOutputError: x (in foo, line 12)
179
180 If a routine reads or writes a user-define memory location, it needs to declare that too.
181
182 | byte b1 @ 60000
183 | byte b2 : 3
184 | word w1 @ 60001
185 | word w2 : 2000
186 |
187 | define main routine
188 | inputs b1, w1
189 | outputs b2, w2
190 | trashes a, z, n
191 | {
192 | ld a, b1
193 | st a, b2
194 | copy w1, w2
195 | }
196 = ok
197
198 ### call ###
199
200 You can't call a non-routine.
201
202 | byte up
203 |
204 | define main routine outputs x, y trashes z, n {
205 | ld x, 0
206 | ld y, 1
207 | call up
208 | }
209 ? TypeMismatchError: up
210
211 | define main routine outputs x, y trashes z, n {
212 | ld x, 0
213 | ld y, 1
214 | call x
215 | }
216 ? TypeMismatchError: x
217
218 Nor can you goto a non-routine.
219
220 | byte foo
221 |
222 | define main routine {
223 | goto foo
224 | }
225 ? TypeMismatchError: foo
226
227 ### ld ###
228
229 Can't `ld` from a memory location that isn't initialized.
230
231 | define main routine
232 | inputs a, x
233 | trashes a, z, n
234 | {
235 | ld a, x
236 | }
237 = ok
238
239 | define main routine
240 | inputs a
241 | trashes a
242 | {
243 | ld a, x
244 | }
245 ? UnmeaningfulReadError: x
246
247 Can't `ld` to a memory location that doesn't appear in (outputs ∪ trashes).
248
249 | define main routine
250 | trashes a, z, n
251 | {
252 | ld a, 0
253 | }
254 = ok
255
256 | define main routine
257 | outputs a
258 | trashes z, n
259 | {
260 | ld a, 0
261 | }
262 = ok
263
264 | define main routine
265 | outputs z, n
266 | trashes a
267 | {
268 | ld a, 0
269 | }
270 = ok
271
272 | define main routine
273 | trashes z, n
274 | {
275 | ld a, 0
276 | }
277 ? ForbiddenWriteError: a
278
279 | define main routine
280 | trashes a, n
281 | {
282 | ld a, 0
283 | }
284 ? ForbiddenWriteError: z
285
286 Can't `ld` a `word` type.
287
288 | word foo
289 |
290 | define main routine
291 | inputs foo
292 | trashes a, n, z
293 | {
294 | ld a, foo
295 | }
296 ? TypeMismatchError: foo and a
297
298 ### st ###
299
300 Can't `st` from a memory location that isn't initialized.
301
302 | byte lives
303 | define main routine
304 | inputs x
305 | trashes lives
306 | {
307 | st x, lives
308 | }
309 = ok
310
311 | byte lives
312 | define main routine
313 | trashes x, lives
314 | {
315 | st x, lives
316 | }
317 ? UnmeaningfulReadError: x
318
319 Can't `st` to a memory location that doesn't appear in (outputs ∪ trashes).
320
321 | byte lives
322 | define main routine
323 | trashes lives
324 | {
325 | st 0, lives
326 | }
327 = ok
328
329 | byte lives
330 | define main routine
331 | outputs lives
332 | {
333 | st 0, lives
334 | }
335 = ok
336
337 | byte lives
338 | define main routine
339 | inputs lives
340 | {
341 | st 0, lives
342 | }
343 ? ForbiddenWriteError: lives
344
345 Can't `st` a `word` type.
346
347 | word foo
348 |
349 | define main routine
350 | outputs foo
351 | trashes a, n, z
352 | {
353 | ld a, 0
354 | st a, foo
355 | }
356 ? TypeMismatchError
357
358 ### tables ###
359
360 Storing to a table, you must use an index.
361
362 | byte one
363 | byte table[256] many
364 |
365 | define main routine
366 | outputs one
367 | trashes a, x, n, z
368 | {
369 | ld x, 0
370 | ld a, 0
371 | st a, one
372 | }
373 = ok
374
375 | byte one
376 | byte table[256] many
377 |
378 | define main routine
379 | outputs many
380 | trashes a, x, n, z
381 | {
382 | ld x, 0
383 | ld a, 0
384 | st a, many
385 | }
386 ? TypeMismatchError
387
388 | byte one
389 | byte table[256] many
390 |
391 | define main routine
392 | outputs one
393 | trashes a, x, n, z
394 | {
395 | ld x, 0
396 | ld a, 0
397 | st a, one + x
398 | }
399 ? TypeMismatchError
400
401 | byte one
402 | byte table[256] many
403 |
404 | define main routine
405 | outputs many
406 | trashes a, x, n, z
407 | {
408 | ld x, 0
409 | ld a, 0
410 | st a, many + x
411 | }
412 = ok
413
414 The index must be initialized.
415
416 | byte one
417 | byte table[256] many
418 |
419 | define main routine
420 | outputs many
421 | trashes a, x, n, z
422 | {
423 | ld a, 0
424 | st a, many + x
425 | }
426 ? UnmeaningfulReadError: x
427
428 Reading from a table, you must use an index.
429
430 | byte one
431 |
432 | define main routine
433 | outputs one
434 | trashes a, x, n, z
435 | {
436 | ld x, 0
437 | st x, one
438 | ld a, one
439 | }
440 = ok
441
442 | byte one
443 |
444 | define main routine
445 | outputs one
446 | trashes a, x, n, z
447 | {
448 | ld x, 0
449 | st x, one
450 | ld a, one + x
451 | }
452 ? TypeMismatchError
453
454 | byte table[256] many
455 |
456 | define main routine
457 | outputs many
458 | trashes a, x, n, z
459 | {
460 | ld x, 0
461 | ld a, 0
462 | st a, many + x
463 | ld a, many
464 | }
465 ? TypeMismatchError
466
467 | byte table[256] many
468 |
469 | define main routine
470 | outputs many
471 | trashes a, x, n, z
472 | {
473 | ld x, 0
474 | ld a, 0
475 | st a, many + x
476 | ld a, many + x
477 | }
478 = ok
479
480 | byte table[256] many
481 |
482 | define main routine
483 | inputs many
484 | outputs many
485 | trashes a, x, n, z
486 | {
487 | ld x, 0
488 | ld a, many + x
489 | }
490 = ok
491
492 The index must be initialized.
493
494 | byte table[256] many
495 |
496 | define main routine
497 | inputs many
498 | outputs many
499 | trashes a, x, n, z
500 | {
501 | ld a, many + x
502 | }
503 ? UnmeaningfulReadError: x
504
505 Storing to a table, you may also include a constant offset.
506
507 | byte one
508 | byte table[256] many
509 |
510 | define main routine
511 | outputs many
512 | trashes a, x, n, z
513 | {
514 | ld x, 0
515 | ld a, 0
516 | st a, many + 100 + x
517 | }
518 = ok
519
520 Reading from a table, you may also include a constant offset.
521
522 | byte table[256] many
523 |
524 | define main routine
525 | inputs many
526 | outputs many
527 | trashes a, x, n, z
528 | {
529 | ld x, 0
530 | ld a, many + 100 + x
531 | }
532 = ok
533
534 Using a constant offset, you can read and write entries in
535 the table beyond the 256th.
536
537 | byte one
538 | byte table[1024] many
539 |
540 | define main routine
541 | inputs many
542 | outputs many
543 | trashes a, x, n, z
544 | {
545 | ld x, 0
546 | ld a, many + 999 + x
547 | st a, many + 1000 + x
548 | }
549 = ok
550
551 There are other operations you can do on tables. (1/3)
552
553 | byte table[256] many
554 |
555 | define main routine
556 | inputs many
557 | outputs many
558 | trashes a, x, c, n, z, v
559 | {
560 | ld x, 0
561 | ld a, 0
562 | st off, c
563 | add a, many + x
564 | sub a, many + x
565 | cmp a, many + x
566 | }
567 = ok
568
569 There are other operations you can do on tables. (2/3)
570
571 | byte table[256] many
572 |
573 | define main routine
574 | inputs many
575 | outputs many
576 | trashes a, x, c, n, z
577 | {
578 | ld x, 0
579 | ld a, 0
580 | and a, many + x
581 | or a, many + x
582 | xor a, many + x
583 | }
584 = ok
585
586 There are other operations you can do on tables. (3/3)
587
588 | byte table[256] many
589 |
590 | define main routine
591 | inputs many
592 | outputs many
593 | trashes a, x, c, n, z
594 | {
595 | ld x, 0
596 | ld a, 0
597 | st off, c
598 | shl many + x
599 | shr many + x
600 | inc many + x
601 | dec many + x
602 | }
603 = ok
604
605 Copying to and from a word table.
606
607 | word one
608 | word table[256] many
609 |
610 | define main routine
611 | inputs one, many
612 | outputs one, many
613 | trashes a, x, n, z
614 | {
615 | ld x, 0
616 | copy one, many + x
617 | copy many + x, one
618 | }
619 = ok
620
621 | word one
622 | word table[256] many
623 |
624 | define main routine
625 | inputs one, many
626 | outputs one, many
627 | trashes a, x, n, z
628 | {
629 | ld x, 0
630 | copy one, many
631 | }
632 ? TypeMismatchError
633
634 | word one
635 | word table[256] many
636 |
637 | define main routine
638 | inputs one, many
639 | outputs one, many
640 | trashes a, x, n, z
641 | {
642 | ld x, 0
643 | copy one + x, many
644 | }
645 ? TypeMismatchError
646
647 You can also copy a literal word to a word table.
648 (Even if the table has fewer than 256 entries.)
649
650 | word table[32] many
651 |
652 | define main routine
653 | inputs many
654 | outputs many
655 | trashes a, x, n, z
656 | {
657 | ld x, 0
658 | copy 9999, many + x
659 | }
660 = ok
661
662 Copying to and from a word table with a constant offset.
663
664 | word one
665 | word table[256] many
666 |
667 | define main routine
668 | inputs one, many
669 | outputs one, many
670 | trashes a, x, n, z
671 | {
672 | ld x, 0
673 | copy one, many + 100 + x
674 | copy many + 100 + x, one
675 | copy 9999, many + 1 + x
676 | }
677 = ok
678
679 #### tables: range checking ####
680
681 It is a static analysis error if it cannot be proven that a read or write
682 to a table falls within the defined size of that table.
683
684 If a table has 256 entries, then there is never a problem (so long as
685 no constant offset is supplied), because a byte cannot index any entry
686 outside of 0..255.
687
688 But if the table has fewer than 256 entries, or if a constant offset is
689 supplied, there is the possibility that the index will refer to an
690 entry in the table which does not exist.
691
692 A SixtyPical implementation must be able to prove that the index is inside
693 the range of the table in various ways. The simplest is to show that a
694 constant value falls inside or outside the range of the table.
695
696 | byte table[32] many
697 |
698 | define main routine
699 | inputs many
700 | outputs many
701 | trashes a, x, n, z
702 | {
703 | ld x, 31
704 | ld a, many + x
705 | st a, many + x
706 | }
707 = ok
708
709 | byte table[32] many
710 |
711 | define main routine
712 | inputs many
713 | outputs many
714 | trashes a, x, n, z
715 | {
716 | ld x, 32
717 | ld a, many + x
718 | }
719 ? RangeExceededError
720
721 | byte table[32] many
722 |
723 | define main routine
724 | inputs many
725 | outputs many
726 | trashes a, x, n, z
727 | {
728 | ld x, 32
729 | ld a, 0
730 | st a, many + x
731 | }
732 ? RangeExceededError
733
734 Any constant offset is taken into account in this check.
735
736 | byte table[32] many
737 |
738 | define main routine
739 | inputs many
740 | outputs many
741 | trashes a, x, n, z
742 | {
743 | ld x, 31
744 | ld a, many + 1 + x
745 | }
746 ? RangeExceededError
747
748 | byte table[32] many
749 |
750 | define main routine
751 | inputs many
752 | outputs many
753 | trashes a, x, n, z
754 | {
755 | ld x, 31
756 | ld a, 0
757 | st a, many + 1 + x
758 | }
759 ? RangeExceededError
760
761 This applies to `copy` as well.
762
763 | word one: 77
764 | word table[32] many
765 |
766 | define main routine
767 | inputs many, one
768 | outputs many, one
769 | trashes a, x, n, z
770 | {
771 | ld x, 31
772 | copy one, many + x
773 | copy many + x, one
774 | }
775 = ok
776
777 | word one: 77
778 | word table[32] many
779 |
780 | define main routine
781 | inputs many, one
782 | outputs many, one
783 | trashes a, x, n, z
784 | {
785 | ld x, 32
786 | copy many + x, one
787 | }
788 ? RangeExceededError
789
790 | word one: 77
791 | word table[32] many
792 |
793 | define main routine
794 | inputs many, one
795 | outputs many, one
796 | trashes a, x, n, z
797 | {
798 | ld x, 32
799 | copy one, many + x
800 | }
801 ? RangeExceededError
802
803 Any constant offset is taken into account in this check.
804
805 | word one: 77
806 | word table[32] many
807 |
808 | define main routine
809 | inputs many, one
810 | outputs many, one
811 | trashes a, x, n, z
812 | {
813 | ld x, 31
814 | copy many + 1 + x, one
815 | }
816 ? RangeExceededError
817
818 | word one: 77
819 | word table[32] many
820 |
821 | define main routine
822 | inputs many, one
823 | outputs many, one
824 | trashes a, x, n, z
825 | {
826 | ld x, 31
827 | copy one, many + 1 + x
828 | }
829 ? RangeExceededError
830
831 `AND`'ing a register with a value ensures the range of the
832 register will not exceed the range of the value. This can
833 be used to "clip" the range of an index so that it fits in
834 a table.
835
836 | word one: 77
837 | word table[32] many
838 |
839 | define main routine
840 | inputs a, many, one
841 | outputs many, one
842 | trashes a, x, n, z
843 | {
844 | and a, 31
845 | ld x, a
846 | copy one, many + x
847 | copy many + x, one
848 | }
849 = ok
850
851 Tests for "clipping", but not enough.
852
853 | word one: 77
854 | word table[32] many
855 |
856 | define main routine
857 | inputs a, many, one
858 | outputs many, one
859 | trashes a, x, n, z
860 | {
861 | and a, 63
862 | ld x, a
863 | copy one, many + x
864 | copy many + x, one
865 | }
866 ? RangeExceededError
867
868 | word one: 77
869 | word table[32] many
870 |
871 | define main routine
872 | inputs a, many, one
873 | outputs many, one
874 | trashes a, x, n, z
875 | {
876 | and a, 31
877 | ld x, a
878 | copy one, many + 1 + x
879 | copy many + 1 + x, one
880 | }
881 ? RangeExceededError
882
883 If you alter the value after "clipping" it, the range can
884 no longer be guaranteed.
885
886 | word one: 77
887 | word table[32] many
888 |
889 | define main routine
890 | inputs a, many, one
891 | outputs many, one
892 | trashes a, x, n, z
893 | {
894 | and a, 31
895 | ld x, a
896 | inc x
897 | copy one, many + x
898 | copy many + x, one
899 | }
900 ? RangeExceededError
901
902 When the range of a location is known, incrementing or
903 decrementing that location's value will shift the known
904 range. It will not invalidate it unless the known range
905 is at the limits of the possible ranges for the type.
906
907 | vector routine
908 | trashes a, z, n
909 | print
910 |
911 | vector (routine
912 | trashes a, z, n)
913 | table[32] vectors
914 |
915 | define main routine
916 | inputs vectors, print
917 | outputs vectors
918 | trashes print, a, x, z, n, c
919 | {
920 | ld x, 0
921 | inc x
922 | copy print, vectors + x
923 | }
924 = ok
925
926 | vector routine
927 | trashes a, z, n
928 | print
929 |
930 | vector (routine
931 | trashes a, z, n)
932 | table[32] vectors
933 |
934 | define main routine
935 | inputs vectors, print
936 | outputs vectors
937 | trashes print, a, x, z, n, c
938 | {
939 | ld x, 32
940 | dec x
941 | copy print, vectors + x
942 | }
943 = ok
94416
94517 ### add ###
94618
1622694 | nop
1623695 | }
1624696 = ok
1625
1626 ### call ###
1627
1628 When calling a routine, all of the locations it lists as inputs must be
1629 initialized.
1630
1631 | byte lives
1632 |
1633 | define foo routine
1634 | inputs x
1635 | trashes lives
1636 | {
1637 | st x, lives
1638 | }
1639 |
1640 | define main routine
1641 | {
1642 | call foo
1643 | }
1644 ? UnmeaningfulReadError: x
1645
1646 Note that if you call a routine that trashes a location, you also trash it.
1647
1648 | byte lives
1649 |
1650 | define foo routine
1651 | inputs x
1652 | trashes lives
1653 | {
1654 | st x, lives
1655 | }
1656 |
1657 | define main routine
1658 | outputs x, z, n
1659 | {
1660 | ld x, 0
1661 | call foo
1662 | }
1663 ? ForbiddenWriteError: lives
1664
1665 | byte lives
1666 |
1667 | define foo routine
1668 | inputs x
1669 | trashes lives
1670 | {
1671 | st x, lives
1672 | }
1673 |
1674 | define main routine
1675 | outputs x, z, n
1676 | trashes lives
1677 | {
1678 | ld x, 0
1679 | call foo
1680 | }
1681 = ok
1682
1683 You can't output a value that the thing you called trashed.
1684
1685 | byte lives
1686 |
1687 | define foo routine
1688 | inputs x
1689 | trashes lives
1690 | {
1691 | st x, lives
1692 | }
1693 |
1694 | define main routine
1695 | outputs x, z, n, lives
1696 | {
1697 | ld x, 0
1698 | call foo
1699 | }
1700 ? UnmeaningfulOutputError: lives
1701
1702 ...unless you write to it yourself afterwards.
1703
1704 | byte lives
1705 |
1706 | define foo routine
1707 | inputs x
1708 | trashes lives
1709 | {
1710 | st x, lives
1711 | }
1712 |
1713 | define main routine
1714 | outputs x, z, n, lives
1715 | {
1716 | ld x, 0
1717 | call foo
1718 | st x, lives
1719 | }
1720 = ok
1721
1722 If a routine declares outputs, they are initialized in the caller after
1723 calling it.
1724
1725 | define foo routine
1726 | outputs x, z, n
1727 | {
1728 | ld x, 0
1729 | }
1730 |
1731 | define main routine
1732 | outputs a
1733 | trashes x, z, n
1734 | {
1735 | call foo
1736 | ld a, x
1737 | }
1738 = ok
1739
1740 | define foo routine
1741 | {
1742 | }
1743 |
1744 | define main routine
1745 | outputs a
1746 | trashes x
1747 | {
1748 | call foo
1749 | ld a, x
1750 | }
1751 ? UnmeaningfulReadError: x
1752
1753 If a routine trashes locations, they are uninitialized in the caller after
1754 calling it.
1755
1756 | define foo routine
1757 | trashes x, z, n
1758 | {
1759 | ld x, 0
1760 | }
1761 = ok
1762
1763 | define foo routine
1764 | trashes x, z, n
1765 | {
1766 | ld x, 0
1767 | }
1768 |
1769 | define main routine
1770 | outputs a
1771 | trashes x, z, n
1772 | {
1773 | call foo
1774 | ld a, x
1775 | }
1776 ? UnmeaningfulReadError: x
1777
1778 Calling an extern is just the same as calling a defined routine with the
1779 same constraints.
1780
1781 | define chrout routine
1782 | inputs a
1783 | trashes a
1784 | @ 65490
1785 |
1786 | define main routine
1787 | trashes a, z, n
1788 | {
1789 | ld a, 65
1790 | call chrout
1791 | }
1792 = ok
1793
1794 | define chrout routine
1795 | inputs a
1796 | trashes a
1797 | @ 65490
1798 |
1799 | define main routine
1800 | trashes a, z, n
1801 | {
1802 | call chrout
1803 | }
1804 ? UnmeaningfulReadError: a
1805
1806 | define chrout routine
1807 | inputs a
1808 | trashes a
1809 | @ 65490
1810 |
1811 | define main routine
1812 | trashes a, x, z, n
1813 | {
1814 | ld a, 65
1815 | call chrout
1816 | ld x, a
1817 | }
1818 ? UnmeaningfulReadError: a
1819
1820 ### trash ###
1821
1822 Trash does nothing except indicate that we do not care about the value anymore.
1823
1824 | define foo routine
1825 | inputs a
1826 | outputs x
1827 | trashes a, z, n
1828 | {
1829 | st a, x
1830 | ld a, 0
1831 | trash a
1832 | }
1833 = ok
1834
1835 | define foo routine
1836 | inputs a
1837 | outputs a, x
1838 | trashes z, n
1839 | {
1840 | st a, x
1841 | ld a, 0
1842 | trash a
1843 | }
1844 ? UnmeaningfulOutputError: a
1845
1846 | define foo routine
1847 | inputs a
1848 | outputs x
1849 | trashes a, z, n
1850 | {
1851 | st a, x
1852 | trash a
1853 | st a, x
1854 | }
1855 ? UnmeaningfulReadError: a
1856
1857 ### if ###
1858
1859 Both blocks of an `if` are analyzed.
1860
1861 | define foo routine
1862 | inputs a
1863 | outputs x
1864 | trashes a, z, n, c
1865 | {
1866 | cmp a, 42
1867 | if z {
1868 | ld x, 7
1869 | } else {
1870 | ld x, 23
1871 | }
1872 | }
1873 = ok
1874
1875 If a location is initialized in one block, it must be initialized in the other as well
1876 in order to be considered to be initialized after the block. If it is not consistent,
1877 it will be considered uninitialized.
1878
1879 | define foo routine
1880 | inputs a
1881 | outputs x
1882 | trashes a, z, n, c
1883 | {
1884 | cmp a, 42
1885 | if z {
1886 | ld x, 7
1887 | } else {
1888 | ld a, 23
1889 | }
1890 | }
1891 ? UnmeaningfulOutputError: x
1892
1893 | define foo routine
1894 | inputs a
1895 | outputs x
1896 | trashes a, z, n, c
1897 | {
1898 | cmp a, 42
1899 | if z {
1900 | ld a, 6
1901 | } else {
1902 | ld x, 7
1903 | }
1904 | }
1905 ? UnmeaningfulOutputError: x
1906
1907 | define foo routine
1908 | inputs a
1909 | outputs x
1910 | trashes a, z, n, c
1911 | {
1912 | cmp a, 42
1913 | if not z {
1914 | ld a, 6
1915 | } else {
1916 | ld x, 7
1917 | }
1918 | }
1919 ? UnmeaningfulOutputError: x
1920
1921 | define foo routine
1922 | inputs a
1923 | trashes a, x, z, n, c
1924 | {
1925 | cmp a, 42
1926 | if not z {
1927 | ld a, 6
1928 | } else {
1929 | ld x, 7
1930 | }
1931 | ld a, x
1932 | }
1933 ? UnmeaningfulReadError: x
1934
1935 If we don't care if it's uninitialized after the `if`, that's okay then.
1936
1937 | define foo routine
1938 | inputs a
1939 | trashes a, x, z, n, c
1940 | {
1941 | cmp a, 42
1942 | if not z {
1943 | ld a, 6
1944 | } else {
1945 | ld x, 7
1946 | }
1947 | }
1948 = ok
1949
1950 Or, if it does get initialized on both branches, that's okay then.
1951
1952 | define foo routine
1953 | inputs a
1954 | outputs x
1955 | trashes a, z, n, c
1956 | {
1957 | cmp a, 42
1958 | if not z {
1959 | ld x, 0
1960 | ld a, 6
1961 | } else {
1962 | ld x, 7
1963 | }
1964 | }
1965 = ok
1966
1967 However, this only pertains to initialization. If a value is already
1968 initialized, either because it was set previous to the `if`, or is an
1969 input to the routine, and it is initialized in one branch, it need not
1970 be initialized in the other.
1971
1972 | define foo routine
1973 | outputs x
1974 | trashes a, z, n, c
1975 | {
1976 | ld x, 0
1977 | ld a, 0
1978 | cmp a, 42
1979 | if z {
1980 | ld x, 7
1981 | } else {
1982 | ld a, 23
1983 | }
1984 | }
1985 = ok
1986
1987 | define foo routine
1988 | inputs x
1989 | outputs x
1990 | trashes a, z, n, c
1991 | {
1992 | ld a, 0
1993 | cmp a, 42
1994 | if z {
1995 | ld x, 7
1996 | } else {
1997 | ld a, 23
1998 | }
1999 | }
2000 = ok
2001
2002 An `if` with a single block is analyzed as if it had an empty `else` block.
2003
2004 | define foo routine
2005 | inputs a
2006 | outputs x
2007 | trashes a, z, n, c
2008 | {
2009 | cmp a, 42
2010 | if z {
2011 | ld x, 7
2012 | }
2013 | }
2014 ? UnmeaningfulOutputError: x
2015
2016 | define foo routine
2017 | inputs a
2018 | outputs x
2019 | trashes a, z, n, c
2020 | {
2021 | ld x, 0
2022 | cmp a, 42
2023 | if z {
2024 | ld x, 7
2025 | }
2026 | }
2027 = ok
2028
2029 | define foo routine
2030 | inputs a
2031 | outputs x
2032 | trashes a, z, n, c
2033 | {
2034 | ld x, 0
2035 | cmp a, 42
2036 | if not z {
2037 | ld x, 7
2038 | }
2039 | }
2040 = ok
2041
2042 The cardinal rule for trashes in an `if` is the "union rule": if one branch
2043 trashes {`a`} and the other branch trashes {`b`} then the whole `if` statement
2044 trashes {`a`, `b`}.
2045
2046 | define foo routine
2047 | inputs a, x, z
2048 | trashes a, x
2049 | {
2050 | if z {
2051 | trash a
2052 | } else {
2053 | trash x
2054 | }
2055 | }
2056 = ok
2057
2058 | define foo routine
2059 | inputs a, x, z
2060 | trashes a
2061 | {
2062 | if z {
2063 | trash a
2064 | } else {
2065 | trash x
2066 | }
2067 | }
2068 ? ForbiddenWriteError: x (in foo, line 10)
2069
2070 | define foo routine
2071 | inputs a, x, z
2072 | trashes x
2073 | {
2074 | if z {
2075 | trash a
2076 | } else {
2077 | trash x
2078 | }
2079 | }
2080 ? ForbiddenWriteError: a (in foo, line 10)
2081
2082 ### repeat ###
2083
2084 Repeat loop.
2085
2086 | define main routine
2087 | outputs x, y, n, z, c
2088 | {
2089 | ld x, 0
2090 | ld y, 15
2091 | repeat {
2092 | inc x
2093 | inc y
2094 | cmp x, 10
2095 | } until z
2096 | }
2097 = ok
2098
2099 You can initialize something inside the loop that was uninitialized outside.
2100
2101 | define main routine
2102 | outputs x, y, n, z, c
2103 | {
2104 | ld x, 0
2105 | repeat {
2106 | ld y, 15
2107 | inc x
2108 | cmp x, 10
2109 | } until z
2110 | }
2111 = ok
2112
2113 But you can't UNinitialize something at the end of the loop that you need
2114 initialized at the start.
2115
2116 | define foo routine
2117 | trashes y
2118 | {
2119 | }
2120 |
2121 | define main routine
2122 | outputs x, y, n, z, c
2123 | {
2124 | ld x, 0
2125 | ld y, 15
2126 | repeat {
2127 | inc x
2128 | inc y
2129 | call foo
2130 | cmp x, 10
2131 | } until z
2132 | }
2133 ? UnmeaningfulReadError: y
2134
2135 And if you trash the test expression (i.e. `z` in the below) inside the loop,
2136 this is an error too.
2137
2138 | word one : 0
2139 | word two : 0
2140 |
2141 | define main routine
2142 | inputs one, two
2143 | outputs two
2144 | trashes a, z, n
2145 | {
2146 | repeat {
2147 | copy one, two
2148 | } until z
2149 | }
2150 ? UnmeaningfulReadError: z
2151
2152 The body of `repeat forever` can be empty.
2153
2154 | define main routine
2155 | {
2156 | repeat {
2157 | } forever
2158 | }
2159 = ok
2160
2161 While `repeat` is most often used with `z`, it can also be used with `n`.
2162
2163 | define main routine
2164 | outputs y, n, z
2165 | {
2166 | ld y, 15
2167 | repeat {
2168 | dec y
2169 | } until n
2170 | }
2171 = ok
2172
2173 ### for ###
2174
2175 Basic "open-faced for" loop. We'll start with the "upto" variant.
2176
2177 #### upward-counting variant
2178
2179 Even though we do not give the starting value in the "for" construct,
2180 we know the exact range the loop variable takes on.
2181
2182 | byte table[16] tab
2183 |
2184 | define foo routine inputs tab trashes a, x, c, z, v, n {
2185 | ld x, 0
2186 | for x up to 15 {
2187 | ld a, tab + x
2188 | }
2189 | }
2190 = ok
2191
2192 | byte table[15] tab
2193 |
2194 | define foo routine inputs tab trashes a, x, c, z, v, n {
2195 | ld x, 0
2196 | for x up to 15 {
2197 | ld a, tab + x
2198 | }
2199 | }
2200 ? RangeExceededError
2201
2202 You need to initialize the loop variable before the loop.
2203
2204 | byte table[16] tab
2205 |
2206 | define foo routine inputs tab trashes a, x, c, z, v, n {
2207 | for x up to 15 {
2208 | ld a, 0
2209 | }
2210 | }
2211 ? UnmeaningfulReadError
2212
2213 Because routines currently do not include range constraints,
2214 the loop variable may not be useful as an input (the location
2215 is assumed to have the maximum range.)
2216
2217 | byte table[16] tab
2218 |
2219 | define foo routine
2220 | inputs tab, x
2221 | trashes a, x, c, z, v, n {
2222 | for x up to 15 {
2223 | ld a, 0
2224 | }
2225 | }
2226 ? RangeExceededError
2227
2228 You cannot modify the loop variable in a "for" loop.
2229
2230 | byte table[16] tab
2231 |
2232 | define foo routine inputs tab trashes a, x, c, z, v, n {
2233 | ld x, 0
2234 | for x up to 15 {
2235 | ld x, 0
2236 | }
2237 | }
2238 ? ForbiddenWriteError
2239
2240 This includes nesting a loop on the same variable.
2241
2242 | byte table[16] tab
2243 |
2244 | define foo routine inputs tab trashes a, x, c, z, v, n {
2245 | ld x, 0
2246 | for x up to 8 {
2247 | for x up to 15 {
2248 | ld a, 0
2249 | }
2250 | }
2251 | }
2252 ? ForbiddenWriteError
2253
2254 But nesting with two different variables is okay.
2255
2256 | byte table[16] tab
2257 |
2258 | define foo routine inputs tab trashes a, x, y, c, z, v, n {
2259 | ld x, 0
2260 | for x up to 8 {
2261 | ld a, x
2262 | ld y, a
2263 | for y up to 15 {
2264 | ld a, 0
2265 | }
2266 | }
2267 | }
2268 = ok
2269
2270 Inside the inner loop, the outer variable is still not writeable.
2271
2272 | byte table[16] tab
2273 |
2274 | define foo routine inputs tab trashes a, x, y, c, z, v, n {
2275 | ld x, 0
2276 | for x up to 8 {
2277 | ld a, x
2278 | ld y, a
2279 | for y up to 15 {
2280 | ld x, 0
2281 | }
2282 | }
2283 | }
2284 ? ForbiddenWriteError
2285
2286 If the range isn't known to be smaller than the final value, you can't go up to it.
2287
2288 | byte table[32] tab
2289 |
2290 | define foo routine inputs tab trashes a, x, c, z, v, n {
2291 | ld x, 16
2292 | for x up to 15 {
2293 | ld a, tab + x
2294 | }
2295 | }
2296 ? RangeExceededError
2297
2298 You can initialize something inside the loop that was uninitialized outside.
2299
2300 | define main routine
2301 | outputs x, y, n, z
2302 | trashes c
2303 | {
2304 | ld x, 0
2305 | for x up to 15 {
2306 | ld y, 15
2307 | }
2308 | }
2309 = ok
2310
2311 But you can't UNinitialize something at the end of the loop that you need
2312 initialized at the start of that loop.
2313
2314 | define foo routine
2315 | trashes y
2316 | {
2317 | }
2318 |
2319 | define main routine
2320 | outputs x, y, n, z
2321 | trashes c
2322 | {
2323 | ld x, 0
2324 | ld y, 15
2325 | for x up to 15 {
2326 | inc y
2327 | call foo
2328 | }
2329 | }
2330 ? UnmeaningfulReadError: y
2331
2332 The "for" loop does not preserve the `z` or `n` registers.
2333
2334 | define foo routine trashes x {
2335 | ld x, 0
2336 | for x up to 15 {
2337 | }
2338 | }
2339 ? ForbiddenWriteError
2340
2341 But it does preserve the other registers, such as `c`.
2342
2343 | define foo routine trashes x, z, n {
2344 | ld x, 0
2345 | for x up to 15 {
2346 | }
2347 | }
2348 = ok
2349
2350 In fact it does not strictly trash `z` and `n`, as they are
2351 always set to known values after the loop. TODO: document
2352 what these known values are!
2353
2354 | define foo routine outputs z, n trashes x {
2355 | ld x, 0
2356 | for x up to 15 {
2357 | }
2358 | }
2359 = ok
2360
2361 #### downward-counting variant
2362
2363 In a "for" loop (downward-counting variant), we know the exact range the loop variable takes on.
2364
2365 | byte table[16] tab
2366 |
2367 | define foo routine inputs tab trashes a, x, c, z, v, n {
2368 | ld x, 15
2369 | for x down to 0 {
2370 | ld a, tab + x
2371 | }
2372 | }
2373 = ok
2374
2375 | byte table[15] tab
2376 |
2377 | define foo routine inputs tab trashes a, x, c, z, v, n {
2378 | ld x, 15
2379 | for x down to 0 {
2380 | ld a, tab + x
2381 | }
2382 | }
2383 ? RangeExceededError
2384
2385 You need to initialize the loop variable before a "for" loop (downward variant).
2386
2387 | byte table[16] tab
2388 |
2389 | define foo routine inputs tab trashes a, x, c, z, v, n {
2390 | for x down to 15 {
2391 | ld a, 0
2392 | }
2393 | }
2394 ? UnmeaningfulReadError
2395
2396 You cannot modify the loop variable in a "for" loop (downward variant).
2397
2398 | byte table[16] tab
2399 |
2400 | define foo routine inputs tab trashes a, x, c, z, v, n {
2401 | ld x, 15
2402 | for x down to 0 {
2403 | ld x, 0
2404 | }
2405 | }
2406 ? ForbiddenWriteError
2407
2408 If the range isn't known to be larger than the final value, you can't go down to it.
2409
2410 | byte table[32] tab
2411 |
2412 | define foo routine inputs tab trashes a, x, c, z, v, n {
2413 | ld x, 0
2414 | for x down to 0 {
2415 | ld a, tab + x
2416 | }
2417 | }
2418 ? RangeExceededError
2419
2420 The "for" loop does not preserve the `z` or `n` registers.
2421
2422 | define foo routine trashes x {
2423 | ld x, 15
2424 | for x down to 0 {
2425 | }
2426 | }
2427 ? ForbiddenWriteError
2428
2429 But it does preserve the other registers, such as `c`.
2430
2431 | define foo routine trashes x, z, n {
2432 | ld x, 15
2433 | for x down to 0 {
2434 | }
2435 | }
2436 = ok
2437
2438 In fact it does not strictly trash `z` and `n`, as they are
2439 always set to known values after the loop. TODO: document
2440 what these known values are!
2441
2442 | define foo routine outputs z, n trashes x {
2443 | ld x, 15
2444 | for x down to 0 {
2445 | }
2446 | }
2447 = ok
2448
2449 ### save ###
2450
2451 Basic neutral test, where the `save` makes no difference.
2452
2453 | define main routine
2454 | inputs a, x
2455 | outputs a, x
2456 | trashes z, n
2457 | {
2458 | ld a, 1
2459 | save x {
2460 | ld a, 2
2461 | }
2462 | ld a, 3
2463 | }
2464 = ok
2465
2466 Saving any location (other than `a`) will trash `a`.
2467
2468 | define main routine
2469 | inputs a, x
2470 | outputs a, x
2471 | trashes z, n
2472 | {
2473 | ld a, 1
2474 | save x {
2475 | ld a, 2
2476 | }
2477 | }
2478 ? UnmeaningfulOutputError
2479
2480 Saving `a` does not trash anything.
2481
2482 | define main routine
2483 | inputs a, x
2484 | outputs a, x
2485 | trashes z, n
2486 | {
2487 | ld x, 1
2488 | save a {
2489 | ld x, 2
2490 | }
2491 | ld x, 3
2492 | }
2493 = ok
2494
2495 A defined value that has been saved can be trashed inside the block.
2496 It will continue to be defined outside the block.
2497
2498 | define main routine
2499 | outputs x, y
2500 | trashes a, z, n
2501 | {
2502 | ld x, 0
2503 | save x {
2504 | ld y, 0
2505 | trash x
2506 | }
2507 | }
2508 = ok
2509
2510 A trashed value that has been saved can be used inside the block.
2511 It will continue to be trashed outside the block.
2512
2513 (Note, both x and a are unmeaningful in this test.)
2514
2515 | define main routine
2516 | inputs a
2517 | outputs a, x
2518 | trashes z, n
2519 | {
2520 | ld x, 0
2521 | trash x
2522 | save x {
2523 | ld a, 0
2524 | ld x, 1
2525 | }
2526 | }
2527 ? UnmeaningfulOutputError
2528
2529 The known range of a value will be preserved outside the block as well.
2530
2531 | word one: 77
2532 | word table[32] many
2533 |
2534 | define main routine
2535 | inputs a, many, one
2536 | outputs many, one
2537 | trashes a, x, n, z
2538 | {
2539 | and a, 31
2540 | ld x, a
2541 | save x {
2542 | ld x, 255
2543 | }
2544 | copy one, many + x
2545 | copy many + x, one
2546 | }
2547 = ok
2548
2549 | word one: 77
2550 | word table[32] many
2551 |
2552 | define main routine
2553 | inputs a, many, one
2554 | outputs many, one
2555 | trashes a, x, n, z
2556 | {
2557 | and a, 63
2558 | ld x, a
2559 | save x {
2560 | ld x, 1
2561 | }
2562 | copy one, many + x
2563 | copy many + x, one
2564 | }
2565 ? RangeExceededError
2566
2567 The known properties of a value are preserved inside the block, too.
2568
2569 | word one: 77
2570 | word table[32] many
2571 |
2572 | define main routine
2573 | inputs a, many, one
2574 | outputs many, one
2575 | trashes a, x, n, z
2576 | {
2577 | and a, 31
2578 | ld x, a
2579 | save x {
2580 | copy one, many + x
2581 | copy many + x, one
2582 | }
2583 | copy one, many + x
2584 | copy many + x, one
2585 | }
2586 = ok
2587
2588 A value which is not output from the routine, is preserved by the
2589 routine; and can appear in a `save` exactly because a `save` preserves it.
2590
2591 | define main routine
2592 | outputs y
2593 | trashes a, z, n
2594 | {
2595 | save x {
2596 | ld y, 0
2597 | ld x, 1
2598 | }
2599 | }
2600 = ok
2601
2602 Because saving anything except `a` trashes `a`, a common idiom is to save `a`
2603 first in a nested series of `save`s.
2604
2605 | define main routine
2606 | inputs a
2607 | outputs a
2608 | trashes z, n
2609 | {
2610 | save a {
2611 | save x {
2612 | ld a, 0
2613 | ld x, 1
2614 | }
2615 | }
2616 | }
2617 = ok
2618
2619 There is a shortcut syntax for a nested series of `save`s.
2620
2621 | define main routine
2622 | inputs a
2623 | outputs a
2624 | trashes z, n
2625 | {
2626 | save a, x {
2627 | ld a, 0
2628 | ld x, 1
2629 | }
2630 | }
2631 = ok
2632
2633 `a` is only preserved if it is the outermost thing `save`d.
2634
2635 | define main routine
2636 | inputs a
2637 | outputs a
2638 | trashes z, n
2639 | {
2640 | save x, a {
2641 | ld a, 0
2642 | ld x, 1
2643 | }
2644 | }
2645 ? UnmeaningfulOutputError: a
2646
2647 Not just registers, but also user-defined locations can be saved.
2648
2649 | byte foo
2650 |
2651 | define main routine
2652 | trashes a, z, n
2653 | {
2654 | save foo {
2655 | st 5, foo
2656 | }
2657 | }
2658 = ok
2659
2660 But only if they are bytes.
2661
2662 | word foo
2663 |
2664 | define main routine
2665 | trashes a, z, n
2666 | {
2667 | save foo {
2668 | copy 555, foo
2669 | }
2670 | }
2671 ? TypeMismatchError
2672
2673 | byte table[16] tab
2674 |
2675 | define main routine
2676 | trashes a, y, z, n
2677 | {
2678 | save tab {
2679 | ld y, 0
2680 | st 5, tab + y
2681 | }
2682 | }
2683 ? TypeMismatchError
2684
2685 A `goto` cannot appear within a `save` block.
2686
2687 | define other routine
2688 | trashes a, z, n
2689 | {
2690 | ld a, 0
2691 | }
2692 |
2693 | define main routine
2694 | trashes a, z, n
2695 | {
2696 | ld a, 1
2697 | save x {
2698 | ld x, 2
2699 | goto other
2700 | }
2701 | }
2702 ? IllegalJumpError
2703697
2704698 ### with interrupts ###
2705699
2759753 | }
2760754 ? IllegalJumpError
2761755
2762 ### copy ###
2763
2764 Can't `copy` from a memory location that isn't initialized.
2765
2766 | byte lives
2767 | define main routine
2768 | inputs x
2769 | outputs lives
2770 | trashes a, z, n
2771 | {
2772 | copy x, lives
2773 | }
2774 = ok
2775
2776 | byte lives
2777 | define main routine
2778 | outputs lives
2779 | trashes x, a, z, n
2780 | {
2781 | copy x, lives
2782 | }
2783 ? UnmeaningfulReadError: x
2784
2785 Can't `copy` to a memory location that doesn't appear in (outputs ∪ trashes).
2786
2787 | byte lives
2788 | define main routine
2789 | trashes lives, a, z, n
2790 | {
2791 | copy 0, lives
2792 | }
2793 = ok
2794
2795 | byte lives
2796 | define main routine
2797 | outputs lives
2798 | trashes a, z, n
2799 | {
2800 | copy 0, lives
2801 | }
2802 = ok
2803
2804 | byte lives
2805 | define main routine
2806 | inputs lives
2807 | trashes a, z, n
2808 | {
2809 | copy 0, lives
2810 | }
2811 ? ForbiddenWriteError: lives
2812
2813 a, z, and n are trashed, and must be declared as such.
2814
2815 (Note, both n and z are forbidden writes in this test.)
2816
2817 | byte lives
2818 | define main routine
2819 | outputs lives
2820 | {
2821 | copy 0, lives
2822 | }
2823 ? ForbiddenWriteError
2824
2825 a, z, and n are trashed, and must not be declared as outputs.
2826
2827 (Note, both n and a are unmeaningful outputs in this test.)
2828
2829 | byte lives
2830 | define main routine
2831 | outputs lives, a, z, n
2832 | {
2833 | copy 0, lives
2834 | }
2835 ? UnmeaningfulOutputError
2836
2837 Unless of course you subsequently initialize them.
2838
2839 | byte lives
2840 | define main routine
2841 | outputs lives, a, z, n
2842 | {
2843 | copy 0, lives
2844 | ld a, 0
2845 | }
2846 = ok
2847
2848 Can `copy` from a `byte` to a `byte`.
2849
2850 | byte source : 0
2851 | byte dest
2852 |
2853 | define main routine
2854 | inputs source
2855 | outputs dest
2856 | trashes a, z, n
2857 | {
2858 | copy source, dest
2859 | }
2860 = ok
2861
2862 The understanding is that, because `copy` trashes `a`, `a` cannot be used
2863 as the destination of a `copy`.
2864
2865 | byte source : 0
2866 | byte dest
2867 |
2868 | define main routine
2869 | inputs source
2870 | outputs dest
2871 | trashes a, z, n
2872 | {
2873 | copy source, a
2874 | }
2875 ? ForbiddenWriteError
2876
2877 Can `copy` from a `word` to a `word`.
2878
2879 | word source : 0
2880 | word dest
2881 |
2882 | define main routine
2883 | inputs source
2884 | outputs dest
2885 | trashes a, z, n
2886 | {
2887 | copy source, dest
2888 | }
2889 = ok
2890
2891 Can't `copy` from a `byte` to a `word`.
2892
2893 | byte source : 0
2894 | word dest
2895 |
2896 | define main routine
2897 | inputs source
2898 | outputs dest
2899 | trashes a, z, n
2900 | {
2901 | copy source, dest
2902 | }
2903 ? TypeMismatchError
2904
2905 Can't `copy` from a `word` to a `byte`.
2906
2907 | word source : 0
2908 | byte dest
2909 |
2910 | define main routine
2911 | inputs source
2912 | outputs dest
2913 | trashes a, z, n
2914 | {
2915 | copy source, dest
2916 | }
2917 ? TypeMismatchError
2918
2919 ### point ... into blocks ###
2920
2921 Pointer must be a pointer type.
2922
2923 | byte table[256] tab
2924 | word ptr
2925 |
2926 | define main routine
2927 | inputs tab
2928 | outputs y, tab
2929 | trashes a, z, n, ptr
2930 | {
2931 | ld y, 0
2932 | point ptr into tab {
2933 | copy 123, [ptr] + y
2934 | }
2935 | }
2936 ? TypeMismatchError
2937
2938 Cannot write through pointer outside a `point ... into` block.
2939
2940 | byte table[256] tab
2941 | pointer ptr
2942 |
2943 | define main routine
2944 | inputs tab, ptr
2945 | outputs y, tab
2946 | trashes a, z, n, ptr
2947 | {
2948 | ld y, 0
2949 | copy 123, [ptr] + y
2950 | }
2951 ? ForbiddenWriteError
2952
2953 | byte table[256] tab
2954 | pointer ptr
2955 |
2956 | define main routine
2957 | inputs tab
2958 | outputs y, tab
2959 | trashes a, z, n, ptr
2960 | {
2961 | ld y, 0
2962 | point ptr into tab {
2963 | copy 123, [ptr] + y
2964 | }
2965 | copy 123, [ptr] + y
2966 | }
2967 ? ForbiddenWriteError
2968
2969 Write literal through a pointer into a table.
2970
2971 | byte table[256] tab
2972 | pointer ptr
2973 |
2974 | define main routine
2975 | inputs tab
2976 | outputs y, tab
2977 | trashes a, z, n, ptr
2978 | {
2979 | ld y, 0
2980 | point ptr into tab {
2981 | copy 123, [ptr] + y
2982 | }
2983 | }
2984 = ok
2985
2986 Writing into a table via a pointer does use `y`.
2987
2988 | byte table[256] tab
2989 | pointer ptr
2990 |
2991 | define main routine
2992 | inputs tab
2993 | outputs tab
2994 | trashes a, z, n, ptr
2995 | {
2996 | point ptr into tab {
2997 | copy 123, [ptr] + y
2998 | }
2999 | }
3000 ? UnmeaningfulReadError
3001
3002 Write stored value through a pointer into a table.
3003
3004 | byte table[256] tab
3005 | pointer ptr
3006 | byte foo
3007 |
3008 | define main routine
3009 | inputs foo, tab
3010 | outputs y, tab
3011 | trashes a, z, n, ptr
3012 | {
3013 | ld y, 0
3014 | point ptr into tab {
3015 | copy foo, [ptr] + y
3016 | }
3017 | }
3018 = ok
3019
3020 Read a table entry via a pointer.
3021
3022 | byte table[256] tab
3023 | pointer ptr
3024 | byte foo
3025 |
3026 | define main routine
3027 | inputs tab
3028 | outputs foo
3029 | trashes a, y, z, n, ptr
3030 | {
3031 | ld y, 0
3032 | point ptr into tab {
3033 | copy [ptr] + y, foo
3034 | }
3035 | }
3036 = ok
3037
3038 Read and write through two pointers into a table.
3039
3040 | byte table[256] tab
3041 | pointer ptra
3042 | pointer ptrb
3043 |
3044 | define main routine
3045 | inputs tab
3046 | outputs tab
3047 | trashes a, y, z, n, ptra, ptrb
3048 | {
3049 | ld y, 0
3050 | point ptra into tab {
3051 | point ptrb into tab {
3052 | copy [ptra] + y, [ptrb] + y
3053 | }
3054 | }
3055 | }
3056 = ok
3057
3058 Read through a pointer into a table, to the `a` register. Note that this is done with `ld`,
3059 not `copy`.
3060
3061 | byte table[256] tab
3062 | pointer ptr
3063 | byte foo
3064 |
3065 | define main routine
3066 | inputs tab
3067 | outputs a
3068 | trashes y, z, n, ptr
3069 | {
3070 | ld y, 0
3071 | point ptr into tab {
3072 | ld a, [ptr] + y
3073 | }
3074 | }
3075 = ok
3076
3077 Write the `a` register through a pointer into a table. Note that this is done with `st`,
3078 not `copy`.
3079
3080 | byte table[256] tab
3081 | pointer ptr
3082 | byte foo
3083 |
3084 | define main routine
3085 | inputs tab
3086 | outputs tab
3087 | trashes a, y, z, n, ptr
3088 | {
3089 | ld y, 0
3090 | point ptr into tab {
3091 | ld a, 255
3092 | st a, [ptr] + y
3093 | }
3094 | }
3095 = ok
3096
3097 Cannot get a pointer into a non-byte (for instance, word) table.
3098
3099 | word table[256] tab
3100 | pointer ptr
3101 | byte foo
3102 |
3103 | define main routine
3104 | inputs tab
3105 | outputs foo
3106 | trashes a, y, z, n, ptr
3107 | {
3108 | ld y, 0
3109 | point ptr into tab {
3110 | copy [ptr] + y, foo
3111 | }
3112 | }
3113 ? TypeMismatchError
3114
3115 Cannot get a pointer into a non-byte (for instance, vector) table.
3116
3117 | vector (routine trashes a, z, n) table[256] tab
3118 | pointer ptr
3119 | vector (routine trashes a, z, n) foo
3120 |
3121 | define main routine
3122 | inputs tab
3123 | outputs foo
3124 | trashes a, y, z, n, ptr
3125 | {
3126 | ld y, 0
3127 | point ptr into tab {
3128 | copy [ptr] + y, foo
3129 | }
3130 | }
3131 ? TypeMismatchError
3132
3133 `point into` by itself only requires `ptr` to be writeable. By itself,
3134 it does not require `tab` to be readable or writeable.
3135
3136 | byte table[256] tab
3137 | pointer ptr
3138 |
3139 | define main routine
3140 | trashes a, z, n, ptr
3141 | {
3142 | point ptr into tab {
3143 | ld a, 0
3144 | }
3145 | }
3146 = ok
3147
3148 | byte table[256] tab
3149 | pointer ptr
3150 |
3151 | define main routine
3152 | trashes a, z, n
3153 | {
3154 | point ptr into tab {
3155 | ld a, 0
3156 | }
3157 | }
3158 ? ForbiddenWriteError
3159
3160 After a `point into` block, the pointer is no longer meaningful and cannot
3161 be considered an output of the routine.
3162
3163 | byte table[256] tab
3164 | pointer ptr
3165 |
3166 | define main routine
3167 | inputs tab
3168 | outputs y, tab, ptr
3169 | trashes a, z, n
3170 | {
3171 | ld y, 0
3172 | point ptr into tab {
3173 | copy 123, [ptr] + y
3174 | }
3175 | }
3176 ? UnmeaningfulOutputError
3177
3178 If code in a routine reads from a table through a pointer, the table must be in
3179 the `inputs` of that routine.
3180
3181 | byte table[256] tab
3182 | pointer ptr
3183 | byte foo
3184 |
3185 | define main routine
3186 | outputs foo
3187 | trashes a, y, z, n, ptr
3188 | {
3189 | ld y, 0
3190 | point ptr into tab {
3191 | copy [ptr] + y, foo
3192 | }
3193 | }
3194 ? UnmeaningfulReadError
3195
3196 Likewise, if code in a routine writes into a table via a pointer, the table must
3197 be in the `outputs` of that routine.
3198
3199 | byte table[256] tab
3200 | pointer ptr
3201 |
3202 | define main routine
3203 | inputs tab
3204 | trashes a, y, z, n, ptr
3205 | {
3206 | ld y, 0
3207 | point ptr into tab {
3208 | copy 123, [ptr] + y
3209 | }
3210 | }
3211 ? ForbiddenWriteError
3212
3213 If code in a routine reads from a table through a pointer, the pointer *should*
3214 remain inside the range of the table. This is currently not checked.
3215
3216 | byte table[32] tab
3217 | pointer ptr
3218 | byte foo
3219 |
3220 | define main routine
3221 | inputs tab
3222 | outputs foo
3223 | trashes a, y, c, z, n, v, ptr
3224 | {
3225 | ld y, 0
3226 | point ptr into tab {
3227 | st off, c
3228 | add ptr, word 100
3229 | copy [ptr] + y, foo
3230 | }
3231 | }
3232 = ok
3233
3234 Likewise, if code in a routine writes into a table through a pointer, the pointer
3235 *should* remain inside the range of the table. This is currently not checked.
3236
3237 | byte table[32] tab
3238 | pointer ptr
3239 |
3240 | define main routine
3241 | inputs tab
3242 | outputs tab
3243 | trashes a, y, c, z, n, v, ptr
3244 | {
3245 | ld y, 0
3246 | point ptr into tab {
3247 | st off, c
3248 | add ptr, word 100
3249 | copy 123, [ptr] + y
3250 | }
3251 | }
3252 = ok
3253
3254 ### routines ###
3255
3256 Routines are constants. You need not, and in fact cannot, specify a constant
3257 as an input to, an output of, or as a trashed value of a routine.
3258
3259 | vector routine
3260 | inputs x
3261 | outputs x
3262 | trashes z, n
3263 | vec
756 ### typedef ###
757
758 A typedef is a more-readable alias for a type. "Alias" means
759 that types have structural equivalence, not name equivalence.
760
761 | typedef routine
762 | inputs x
763 | outputs x
764 | trashes z, n
765 | routine_type
766 |
767 | vector routine_type vec
3264768 |
3265769 | define foo routine
3266770 | inputs x
3271775 | }
3272776 |
3273777 | define main routine
3274 | inputs foo
3275778 | outputs vec
3276779 | trashes a, z, n
3277780 | {
3278781 | copy foo, vec
3279782 | }
3280 ? ConstantConstraintError: foo
3281
3282 | vector routine
3283 | inputs x
3284 | outputs x
3285 | trashes z, n
3286 | vec
3287 |
3288 | define foo routine
3289 | inputs x
3290 | outputs x
3291 | trashes z, n
783 = ok
784
785 The new style routine definitions support typedefs.
786
787 | typedef routine
788 | inputs x
789 | outputs x
790 | trashes z, n
791 | routine_type
792 |
793 | vector routine_type vec
794 |
795 | define foo routine_type
3292796 | {
3293797 | inc x
3294 | }
3295 |
3296 | define main routine
3297 | outputs vec, foo
3298 | trashes a, z, n
3299 | {
3300 | copy foo, vec
3301 | }
3302 ? ConstantConstraintError: foo
3303
3304 | vector routine
3305 | inputs x
3306 | outputs x
3307 | trashes z, n
3308 | vec
3309 |
3310 | define foo routine
3311 | inputs x
3312 | outputs x
3313 | trashes z, n
3314 | {
3315 | inc x
3316 | }
3317 |
3318 | define main routine
3319 | outputs vec
3320 | trashes a, z, n, foo
3321 | {
3322 | copy foo, vec
3323 | }
3324 ? ConstantConstraintError: foo
3325
3326 #### routine-vector type compatibility
3327
3328 You can copy the address of a routine into a vector, if that vector type
3329 is at least as "wide" as the type of the routine. More specifically,
3330
3331 - the vector must take _at least_ the inputs that the routine takes
3332 - the vector must produce _at least_ the outputs that the routine produces
3333 - the vector must trash _at least_ what the routine trashes
3334
3335 If the vector and the routine have the very same signature, that's not an error.
3336
3337 | vector routine
3338 | inputs x, y
3339 | outputs x, y
3340 | trashes z, n
3341 | vec
3342 |
3343 | define foo routine
3344 | inputs x, y
3345 | outputs x, y
3346 | trashes z, n
3347 | {
3348 | inc x
3349 | inc y
3350798 | }
3351799 |
3352800 | define main routine
3356804 | copy foo, vec
3357805 | }
3358806 = ok
3359
3360 If the vector takes an input that the routine doesn't take, that's not an error.
3361 (The interface requires that a parameter be specified before calling, but the
3362 implementation doesn't actually read it.)
3363
3364 | vector routine
3365 | inputs x, y, a
3366 | outputs x, y
3367 | trashes z, n
3368 | vec
3369 |
3370 | define foo routine
3371 | inputs x, y
3372 | outputs x, y
3373 | trashes z, n
3374 | {
3375 | inc x
3376 | inc y
3377 | }
3378 |
3379 | define main routine
3380 | outputs vec
3381 | trashes a, z, n
3382 | {
3383 | copy foo, vec
3384 | }
3385 = ok
3386
3387 If the vector fails to take an input that the routine takes, that's an error.
3388
3389 | vector routine
3390 | inputs x
3391 | outputs x, y
3392 | trashes z, n
3393 | vec
3394 |
3395 | define foo routine
3396 | inputs x, y
3397 | outputs x, y
3398 | trashes z, n
3399 | {
3400 | inc x
3401 | inc y
3402 | }
3403 |
3404 | define main routine
3405 | outputs vec
3406 | trashes a, z, n
3407 | {
3408 | copy foo, vec
3409 | }
3410 ? IncompatibleConstraintsError
3411
3412 If the vector produces an output that the routine doesn't produce, that's not an error.
3413 (The interface claims the result of calling the routine is defined, but the implementation
3414 actually preserves it instead of changing it; the caller can still treat it as a defined
3415 output.)
3416
3417 | vector routine
3418 | inputs x, y
3419 | outputs x, y, a
3420 | trashes z, n
3421 | vec
3422 |
3423 | define foo routine
3424 | inputs x, y
3425 | outputs x, y