git @ Cat's Eye Technologies SixtyPical / 8467cd9
Merge pull request #21 from catseye/develop-0.20 Develop 0.20 Chris Pressey authored 2 years ago GitHub committed 2 years ago
24 changed file(s) with 4896 addition(s) and 4063 deletion(s). Raw diff Collapse all Expand all
00 History of SixtyPical
11 =====================
2
3 0.20
4 ----
5
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.
14 * Along with `goto`, `call` and `with interrupts off` are
15 now forbidden inside a `with interrupts off` block.
16 * More tests to assure that using `call` inside a `point into`
17 block or inside a `for` block does not cause trouble,
18 particularly when the routine being called also uses the
19 variable named in that block.
20 * Fixed a bug where two local statics could be declared with
21 the same name.
22 * Split analysis context support off from analyzer, and
23 symbol table support from parse, and it their own modules.
24 * Split the SixtyPical Analysis tests across three files,
25 and placed test appliances for `sixtypical` in own file.
226
327 0.19
428 ----
66
77 -----------------------------------------------------------------------------
88
9 Copyright (c)2014-2018 Chris Pressey, Cat's Eye Technologies.
9 Copyright (c)2014-2019 Chris Pressey, Cat's Eye Technologies.
1010
1111 The authors intend this Report to belong to the entire SixtyPical
1212 community, and so we grant permission to copy and distribute it for
2323
2424 -----------------------------------------------------------------------------
2525
26 Copyright (c)2014-2018, Chris Pressey, Cat's Eye Technologies.
26 Copyright (c)2014-2019, Chris Pressey, Cat's Eye Technologies.
2727 All rights reserved.
2828
2929 Redistribution and use in source and binary forms, with or without
00 SixtyPical
11 ==========
22
3 _Version 0.19. Work-in-progress, everything is subject to change._
3 _Version 0.20. Work-in-progress, everything is subject to change._
44
55 **SixtyPical** is a [low-level](#low-level) programming language
66 supporting a sophisticated [static analysis](#static-analysis).
108108
109109 * [SixtyPical specification](doc/SixtyPical.md)
110110 * [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md)
111 * [Literate test suite for SixtyPical analysis](tests/SixtyPical%20Analysis.md)
111 * [Literate test suite for SixtyPical analysis (operations)](tests/SixtyPical%20Analysis.md)
112 * [Literate test suite for SixtyPical analysis (storage)](tests/SixtyPical%20Storage.md)
113 * [Literate test suite for SixtyPical analysis (control flow)](tests/SixtyPical%20Control%20Flow.md)
112114 * [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
113115 * [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
114116
00 TODO for SixtyPical
11 ===================
2
3 Language
4 --------
25
36 ### Save values to other-than-the-stack
47
811 ...
912 }
1013
11 Which uses some other storage location instead of the stack. A local static
12 would be a good candidate for such.
14 Which uses some other storage location instead of the stack. A local non-static
15 would be a good candidate for such. At any rate, the location must not
16 be writeable by anything that is called from within the block. So, probably
17 just restrict this to local non-statics.
1318
14 ### Analyze `call` within blocks?
19 ### Copy byte to/from table
1520
16 What happens if you call another routine from inside a `with interrupts off` block?
21 Do we want a `copy bytevar, table + x` instruction? We don't currently have one.
22 You have to `ld a`, `st a`. I think maybe we should have one.
1723
18 What happens if you call another routine from inside a `save` block?
24 ### Character literals
1925
20 What happens if you call another routine from inside a `point into` block?
26 For goodness sake, let the programmer say `'A'` instead of `65`.
2127
22 What happens if you call another routine from inside a `for` block?
28 ### Character set mapping
2329
24 Remember that any of these may have a `goto` ... and they may have a second
25 instance of the same block (e.g. `with interrupts off` nested within
26 `with interrupts off` shouldn't be allowed to turn them back on after the
27 inner block has finished -- even if there is no `call`.)
30 Not all computers think `'A'` should be `65`. Allow the character set to be
31 mapped. Probably copy what Ophis does.
2832
29 These holes need to be plugged.
33 ### "Include" directives
3034
31 ### Reset pointer in `point into` blocks
35 Search a searchlist of include paths. And use them to make libraries of routines.
3236
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.
37 One such library routine might be an `interrupt routine` type for various architectures.
38 Since "the supervisor" has stored values on the stack, we should be able to trash them
39 with impunity, in such a routine.
3540
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.
41 ### Pointers into non-byte tables
3842
39 ### Pointers associated globally with a table
43 Right now you cannot get a pointer into a non-byte (for instance, word or vector) table.
44
45 Word and vector tables are stored as two byte tables in memory. This is useful for
46 indexed access, but makes pointer access more difficult.
47
48 Laying them out for pointer access would make indexed access more difficult.
49
50 ### Saving non-byte values
51
52 Right now you cannot save a word value.
53
54 There doesn't seem to be a hugely pressing reason why not.
55
56 Analysis
57 --------
58
59 ### Forbid recursion
60
61 What happens if a routine calls itself, directly or indirectly? Many
62 constraints might be violated in this case. We should probably disallow
63 recursion by default. (Which means assembling the callgraph in all cases.)
64
65 ### Analyze memory usage
66
67 If you define two variables that occupy the same address, an analysis error ought
68 to be raised. (But there should also be a way to annotate this as intentional.
69 Intentionally making two tables overlap could be valuable. However, the analysis
70 will probably completely miss this fact.)
71
72 Optimization
73 ------------
74
75 ### Space optimization of local non-statics
76
77 If there are two routines A and B, and A never calls B (even indirectly), and
78 B never calls A (even indirectly), then their non-static locals can
79 be allocated at the same space.
80
81 This is not just an impressive trick -- in the presence of local pointers, which
82 use up a word in zero-page, which we consider a precious resource, it allow those
83 zero-page locations to be re-used.
84
85 ### Tail-call optimization
86
87 If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
88 if the block is in tail position. The constraints should iron out the same both ways.
89
90 As long as the routine has consistent type context every place it exits, that should be fine.
91
92 ### Branch optimization in `if`
93
94 Currently the `if` generator is not smart enough to avoid generating silly
95 jump instructions. (See the Fallthru tests.) Improve it.
96
97 ### Dead code removal
98
99 Once we have a call graph we can omit routines that we're sure aren't called.
100
101 This would let us use include-files and standard-libraries nicely: any
102 routines they define, but that you don't use, don't get included.
103
104 Analyzing the set of possible routines that a vector can take on would help
105 this immensely.
106
107 Implementation
108 --------------
109
110 ### Line numbers in analysis error messages
111
112 For analysis errors, there is a line number, but it's the line of the routine
113 after the routine in which the analysis error occurred. Fix this.
114
115 Blue-skying
116 -----------
117
118 ### Pointers associated globally with a table(?)
40119
41120 We have `point into` blocks, but we would also like to sometimes pass a pointer
42121 around to different routines, and have them all "know" what table it operates on.
52131
53132 These can co-exist with general, non-specific-table-linked `pointer` variables.
54133
55 ### Local non-statics
56
57 Somewhat related to the above, it should be possible to declare a local storage
58 location which is not static.
59
60 In this case, it would be considered uninitialized each time the routine was
61 entered.
62
63 So, you do not have a guarantee that it has a valid value. But you are guaranteed
64 that no other routine can read or modify it.
65
66 It also enables a trick: if there are two routines A and B, and A never calls B
67 (even indirectly), and B never calls A (even indirectly), then their locals can
68 be allocated at the same space.
69
70 A local could also be given an explicit address. In this case, two locals in
71 different routines could be given the same address, and as long as the condition
72 in the above paragraph holds, that's okay. (If it doesn't, the analyzer should
73 detect it.)
74
75 This would permit local pointers, which would be one way of addressing the
76 "same pointer to different tables" problem.
77
78 ### Copy byte to/from table
79
80 Do we want a `copy bytevar, table + x` instruction? We don't currently have one.
81 You have to `ld a`, `st a`. I think maybe we should have one.
82
83 ### Analyze memory usage
84
85 If you define two variables that occupy the same address, an analysis error ought
86 to be raised. (But there should also be a way to annotate this as intentional.
87 Intentionally making two tables overlap could be valuable. However, the analysis
88 will probably completely miss this fact.)
89
90 ### Character literals
91
92 For goodness sake, let the programmer say `'A'` instead of `65`.
93
94 ### Character set mapping
95
96 Not all computers think `'A'` should be `65`. Allow the character set to be
97 mapped. Probably copy what Ophis does.
98
99 ### Tail-call optimization
100
101 If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
102 if the block is in tail position. The constraints should iron out the same both ways.
103
104 As long as the routine has consistent type context every place it exits, that should be fine.
105
106 ### "Include" directives
107
108 Search a searchlist of include paths. And use them to make libraries of routines.
109
110 One such library routine might be an `interrupt routine` type for various architectures.
111 Since "the supervisor" has stored values on the stack, we should be able to trash them
112 with impunity, in such a routine.
113
114 ### Line numbers in analysis error messages
115
116 For analysis errors, there is a line number, but it's the line of the routine
117 after the routine in which the analysis error occurred. Fix this.
134 If we have local pointers and space optimization for local non-statics, though,
135 these don't add as much.
1515 from tempfile import NamedTemporaryFile
1616 import traceback
1717
18 from sixtypical.parser import Parser, SymbolTable, merge_programs
18 from sixtypical.symtab import SymbolTable
19 from sixtypical.parser import Parser, merge_programs
1920 from sixtypical.analyzer import Analyzer
2021 from sixtypical.outputter import outputter_class_for
2122 from sixtypical.compiler import Compiler
183184 argparser.add_argument(
184185 "--version",
185186 action="version",
186 version="%(prog)s 0.19"
187 version="%(prog)s 0.20"
187188 )
188189
189190 options, unknown = argparser.parse_known_args(sys.argv[1:])
00 SixtyPical
11 ==========
22
3 This document describes the SixtyPical programming language version 0.19,
3 This document describes the SixtyPical programming language version 0.20,
44 both its static semantics (the capabilities and limits of the static
55 analyses it defines) and its runtime semantics (with reference to the
66 semantics of 6502 machine code.)
195195 There are extended instruction modes for using these types of memory location.
196196 See `copy` below, but here is some illustrative example code:
197197
198 point ptr into buf { // this is the only way to initialize a pointer
198 point ptr into buf { // this associates this pointer with this table
199 reset ptr 0 // this is the only way to initialize a pointer
199200 add ptr, 4 // note, this is unchecked against table's size!
200201 ld y, 0 // you must set this to something yourself
201202 copy [ptr] + y, byt // read memory through pointer, into byte
657658 | "repeat" Block ("until" ["not"] LocExpr | "forever")
658659 | "for" LocExpr ("up"|"down") "to" Const Block
659660 | "with" "interrupts" LitBit Block
661 | "point" LocExpr "into" LocExpr Block
662 | "reset" LocExpr Const
660663 .
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
271
270272 // check collision.
271273 ld a, [ptr] + y
272 }
273
274 // if "collision" is with your own self, treat it as if it's blank space!
275 cmp a, 81
276 if z {
277 ld a, 32
278 }
279 cmp a, 32
280 if z {
281 point ptr into screen {
274
275 // if "collision" is with your own self, treat it as if it's blank space!
276 cmp a, 81
277 if z {
278 ld a, 32
279 }
280 cmp a, 32
281 if z {
282 reset ptr 0
282283 st off, c
283284 add ptr, pos
284285 copy 32, [ptr] + y
285 }
286
287 copy new_pos, pos
288
289 point ptr into screen {
286
287 copy new_pos, pos
288
289 reset ptr 0
290290 st off, c
291291 add ptr, pos
292292 copy 81, [ptr] + y
293293 }
294 } else {
295 ld a, 1
296 st a, player_died
297 }
294 }
295 } else {
296 ld a, 1
297 st a, player_died
298298 }
299299 }
300300
306306
307307 if c {
308308 point ptr into screen {
309 reset ptr 0
309310 st off, c
310311 add ptr, new_pos
311312 ld y, 0
313
312314 // check collision.
313315 ld a, [ptr] + y
314 }
315 // if "collision" is with your own self, treat it as if it's blank space!
316 cmp a, 82
317 if z {
318 ld a, 32
319 }
320 cmp a, 32
321 if z {
322 point ptr into screen {
316
317 // if "collision" is with your own self, treat it as if it's blank space!
318 cmp a, 82
319 if z {
320 ld a, 32
321 }
322 cmp a, 32
323 if z {
324 reset ptr 0
323325 st off, c
324326 add ptr, pos
325327 copy 32, [ptr] + y
326 }
327
328 copy new_pos, pos
329
330 point ptr into screen {
328
329 copy new_pos, pos
330
331 reset ptr 0
331332 st off, c
332333 add ptr, pos
333334 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
0 // Include `support/${PLATFORM}.60p` before this source
1 // Should print H (being ASCII 72 = 8 * 9)
2
3 // Increase y by 7, circuitously
4 //
5 define foo routine
6 inputs y
7 outputs y, n, z
8 trashes a, c
9 {
10 save x {
11 ld x, 0
12 for x up to 6 {
13 inc y
14 }
15 }
16 }
17
18 // Each iteration increases y by 8; there are 9 iterations
19 //
20 define main routine
21 outputs x, y, n, z
22 trashes a, c
23 {
24 ld x, 0
25 ld y, 0
26 for x up to 8 {
27 inc y
28 call foo
29 }
30 ld a, y
31 call chrout
32 }
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 # statics are always meaningful
186 if self.symtab.has_static(self.routine.name, ref.name):
187 continue
188 if self.is_constant(ref):
189 pass
190 elif isinstance(ref, LocationRef):
191 if ref not in self._range:
192 message = ref.name
193 if kwargs.get('message'):
194 message += ' (%s)' % kwargs['message']
195 raise exception_class(self.routine, message)
196 elif isinstance(ref, IndexedRef):
197 self.assert_meaningful(ref.ref, **kwargs)
198 self.assert_meaningful(ref.index, **kwargs)
199 else:
200 raise NotImplementedError(ref)
201
202 def assert_writeable(self, *refs, **kwargs):
203 exception_class = kwargs.get('exception_class', ForbiddenWriteError)
204 for ref in refs:
205 # statics are always writeable
206 if self.symtab.has_static(self.routine.name, ref.name):
207 continue
208 if ref not in self._writeable:
209 message = ref.name
210 if kwargs.get('message'):
211 message += ' (%s)' % kwargs['message']
212 raise exception_class(self.routine, message)
213
214 def assert_in_range(self, inside, outside, offset):
215 """Given two locations, assert that the first location, offset by the given offset,
216 is contained 'inside' the second location."""
217 assert isinstance(inside, LocationRef)
218 assert isinstance(outside, LocationRef)
219
220 # inside should always be meaningful
221 inside_range = self._range[inside]
222
223 # outside might not be meaningful, so default to max range if necessary
224 if outside in self._range:
225 outside_range = self._range[outside]
226 else:
227 outside_range = self.max_range(outside)
228
229 if (inside_range[0] + offset.value) < outside_range[0] or (inside_range[1] + offset.value) > outside_range[1]:
230 raise RangeExceededError(self.routine,
231 "Possible range of {} {} (+{}) exceeds acceptable range of {} {}".format(
232 inside, inside_range, offset, outside, outside_range
233 )
234 )
235
236 def set_touched(self, *refs):
237 for ref in refs:
238 self._touched.add(ref)
239 # TODO: it might be possible to invalidate the range here
240
241 def set_meaningful(self, *refs):
242 for ref in refs:
243 if ref not in self._range:
244 self._range[ref] = self.max_range(ref)
245
246 def set_top_of_range(self, ref, top):
247 self.assert_meaningful(ref)
248 (bottom, _) = self._range[ref]
249 self._range[ref] = (bottom, top)
250
251 def set_bottom_of_range(self, ref, bottom):
252 self.assert_meaningful(ref)
253 (top, _) = self._range[ref]
254 self._range[ref] = (bottom, top)
255
256 def set_range(self, ref, bottom, top):
257 self.assert_meaningful(ref)
258 self._range[ref] = (bottom, top)
259
260 def get_top_of_range(self, ref):
261 if isinstance(ref, ConstantRef):
262 return ref.value
263 self.assert_meaningful(ref)
264 (_, top) = self._range[ref]
265 return top
266
267 def get_bottom_of_range(self, ref):
268 if isinstance(ref, ConstantRef):
269 return ref.value
270 self.assert_meaningful(ref)
271 (bottom, _) = self._range[ref]
272 return bottom
273
274 def get_range(self, ref):
275 if isinstance(ref, ConstantRef):
276 return (ref.value, ref.value)
277 self.assert_meaningful(ref)
278 (bottom, top) = self._range[ref]
279 return bottom, top
280
281 def copy_range(self, src, dest):
282 self.assert_meaningful(src)
283 if src in self._range:
284 src_range = self._range[src]
285 else:
286 src_range = self.max_range(src)
287 self._range[dest] = src_range
288
289 def invalidate_range(self, ref):
290 self.assert_meaningful(ref)
291 self._range[ref] = self.max_range(ref)
292
293 def set_unmeaningful(self, *refs):
294 for ref in refs:
295 if ref in self._range:
296 del self._range[ref]
297
298 def set_written(self, *refs):
299 """A "helper" method which does the following common sequence for
300 the given refs: asserts they're all writable, and sets them all
301 as touched and meaningful."""
302 self.assert_writeable(*refs)
303 self.set_touched(*refs)
304 self.set_meaningful(*refs)
305
306 def set_unwriteable(self, *refs):
307 """Intended to be used for implementing analyzing `for`."""
308 for ref in refs:
309 self._writeable.remove(ref)
310
311 def set_writeable(self, *refs):
312 """Intended to be used for implementing analyzing `for`, but also used in `save`."""
313 for ref in refs:
314 self._writeable.add(ref)
315
316 def encounter_gotos(self, gotos):
317 self._gotos_encountered |= gotos
318
319 def encountered_gotos(self):
320 return self._gotos_encountered
321
322 def set_terminated(self):
323 # Having a terminated context and having encountered gotos is not the same thing.
324 self._terminated = True
325
326 def has_terminated(self):
327 return self._terminated
328
329 def extract(self, location):
330 """Sets the given location as writeable in the context, and returns a 'baton' representing
331 the previous state of context for that location. This 'baton' can be used to later restore
332 this state of context."""
333 # Used in `save`.
334 baton = (
335 location,
336 location in self._touched,
337 self._range.get(location, None),
338 location in self._writeable,
339 )
340 self.set_writeable(location)
341 return baton
342
343 def re_introduce(self, baton):
344 """Given a 'baton' produced by `extract()`, restores the context for that saved location
345 to what it was before `extract()` was called."""
346 # Used in `save`.
347 location, was_touched, was_range, was_writeable = baton
348
349 if was_touched:
350 self._touched.add(location)
351 elif location in self._touched:
352 self._touched.remove(location)
353
354 if was_range is not None:
355 self._range[location] = was_range
356 elif location in self._range:
357 del self._range[location]
358
359 if was_writeable:
360 self._writeable.add(location)
361 elif location in self._writeable:
362 self._writeable.remove(location)
363
364 def get_assoc(self, pointer):
365 return self._pointer_assoc.get(pointer)
366
367 def set_assoc(self, pointer, table):
368 self._pointer_assoc[pointer] = table
369
370 def is_constant(self, ref):
371 """read-only means that the program cannot change the value
372 of a location. constant means that the value of the location
373 will not change during the lifetime of the program."""
374 if isinstance(ref, ConstantRef):
375 return True
376 if isinstance(ref, (IndirectRef, IndexedRef)):
377 return False
378 if isinstance(ref, LocationRef):
379 type_ = self.symtab.fetch_global_type(ref.name)
380 return isinstance(type_, RoutineType)
381 raise NotImplementedError
382
383 def max_range(self, ref):
384 if isinstance(ref, ConstantRef):
385 return (ref.value, ref.value)
386 elif self.symtab.has_static(self.routine.name, ref.name):
387 return self.symtab.fetch_static_type(self.routine.name, ref.name).max_range
388 else:
389 return self.symtab.fetch_global_type(ref.name).max_range
390
391
39283 class Analyzer(object):
39384
39485 def __init__(self, symtab, debug=False):
40091 # - - - - helper methods - - - -
40192
40293 def get_type_for_name(self, name):
403 if self.current_routine and self.symtab.has_static(self.current_routine.name, name):
404 return self.symtab.fetch_static_type(self.current_routine.name, name)
94 if self.current_routine and self.symtab.has_local(self.current_routine.name, name):
95 return self.symtab.fetch_local_type(self.current_routine.name, name)
40596 return self.symtab.fetch_global_type(name)
40697
40798 def get_type(self, ref):
40899 if isinstance(ref, ConstantRef):
409100 return ref.type
410101 if not isinstance(ref, LocationRef):
411 raise NotImplementedError
102 raise NotImplementedError(str(ref))
412103 return self.get_type_for_name(ref.name)
413104
414105 def assert_type(self, type_, *locations):
461152 self.current_routine = routine
462153 type_ = self.get_type_for_name(routine.name)
463154 context = AnalysisContext(self.symtab, routine, type_.inputs, type_.outputs, type_.trashes)
155
156 # register any local statics as already-initialized
157 for local_name, local_symentry in self.symtab.locals.get(routine.name, {}).items():
158 ref = self.symtab.fetch_local_ref(routine.name, local_name)
159 if local_symentry.ast_node.initial is not None:
160 context.set_meaningful(ref)
161 context.set_range(ref, local_symentry.ast_node.initial, local_symentry.ast_node.initial)
162
464163 self.exit_contexts = []
465164
466165 self.analyze_block(routine.block, context)
504203
505204 # if something was touched, then it should have been declared to be writable.
506205 for ref in context.each_touched():
507 if ref not in type_.outputs and ref not in type_.trashes and not self.symtab.has_static(routine.name, ref.name):
206 if ref not in type_.outputs and ref not in type_.trashes and not self.symtab.has_local(routine.name, ref.name):
508207 raise ForbiddenWriteError(routine, ref.name)
509208
510209 self.exit_contexts = None
530229 elif isinstance(instr, For):
531230 self.analyze_for(instr, context)
532231 elif isinstance(instr, WithInterruptsOff):
533 self.analyze_block(instr.block, context)
534 if context.encountered_gotos():
535 raise IllegalJumpError(instr, instr)
232 self.analyze_with_interrupts_off(instr, context)
536233 elif isinstance(instr, Save):
537234 self.analyze_save(instr, context)
538235 elif isinstance(instr, PointInto):
539236 self.analyze_point_into(instr, context)
237 elif isinstance(instr, Reset):
238 self.analyze_reset(instr, context)
540239 else:
541 raise NotImplementedError
240 raise NotImplementedError(str(instr))
542241
543242 def analyze_single_op(self, instr, context):
544243
588287 target = context.get_assoc(dest.ref)
589288 if not target:
590289 raise ForbiddenWriteError(instr, dest.ref)
591 context.set_touched(target)
592290 context.set_written(target)
593291
594292 elif self.get_type(src) != self.get_type(dest):
758456 # 2. check that the context is meaningful
759457
760458 if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
761 context.assert_meaningful(src, REG_Y)
459 context.assert_meaningful(src, dest.ref, REG_Y)
762460
763461 target = context.get_assoc(dest.ref)
764462 if not target:
765463 raise ForbiddenWriteError(instr, dest.ref)
766 context.set_touched(target)
767464 context.set_written(target)
768465
769466 elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
774471 raise UnmeaningfulReadError(instr, src.ref)
775472 context.assert_meaningful(origin)
776473
777 context.set_touched(dest)
778474 context.set_written(dest)
475
779476 elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
780 context.assert_meaningful(src.ref, REG_Y)
477 context.assert_meaningful(src.ref, dest.ref, REG_Y)
781478
782479 origin = context.get_assoc(src.ref)
783480 if not origin:
787484 target = context.get_assoc(dest.ref)
788485 if not target:
789486 raise ForbiddenWriteError(instr, dest.ref)
790 context.set_touched(target)
791487 context.set_written(target)
792488
793489 elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
798494 context.set_written(dest.ref)
799495 elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
800496 context.assert_meaningful(src.ref, src.index)
801 context.set_touched(dest)
802497 context.set_written(dest)
803498 else:
804499 context.assert_meaningful(src)
864559 exit_context = context.clone()
865560
866561 for ref in type_.outputs:
867 exit_context.set_touched(ref) # ?
868562 exit_context.set_written(ref)
869563
870564 for ref in type_.trashes:
972666 context.set_range(instr.dest, instr.final, instr.final)
973667 context.set_writeable(instr.dest)
974668
669 def analyze_with_interrupts_off(self, instr, context):
670 block = instr.block
671 for instr in block.instrs:
672 if isinstance(instr, (Call, GoTo, WithInterruptsOff)):
673 raise IllegalJumpError(instr, instr)
674 self.analyze_instr(instr, context)
675
975676 def analyze_save(self, instr, context):
976677 batons = []
977678 for location in instr.locations:
1006707 if context.get_assoc(instr.pointer):
1007708 raise ForbiddenWriteError(instr, instr.pointer)
1008709
1009 # associate pointer with table, mark it as meaningful.
710 # associate pointer with table
711 # (do not mark it as meaningful yet - that's reset's job.)
1010712
1011713 context.set_assoc(instr.pointer, instr.table)
1012 context.set_meaningful(instr.pointer)
1013 context.set_touched(instr.pointer)
714 context.set_unmeaningful(instr.pointer)
1014715
1015716 self.analyze_block(instr.block, context)
1016717 if context.encountered_gotos():
1020721
1021722 context.set_assoc(instr.pointer, None)
1022723 context.set_unmeaningful(instr.pointer)
724
725 def analyze_reset(self, instr, context):
726 type = self.get_type(instr.pointer)
727 if not isinstance(type, (PointerType)):
728 raise TypeMismatchError(instr, instr.pointer.name)
729
730 table = context.get_assoc(instr.pointer)
731 if not table:
732 raise ForbiddenWriteError(instr, '{} is not associated with any table'.format(instr.pointer.name))
733 context.assert_meaningful(table)
734 low_limit, high_limit = context.get_range(table)
735
736 assert isinstance(instr.offset, ConstantRef)
737 if instr.offset.value < low_limit or instr.offset.value > high_limit:
738 raise RangeExceededError(instr, instr.pointer.name)
739
740 context.set_meaningful(instr.pointer)
741 context.set_touched(instr.pointer)
5858
5959 class Routine(AST):
6060 value_attrs = ('name', 'addr', 'initial',)
61 children_attrs = ('statics',)
61 children_attrs = ('locals',)
6262 child_attrs = ('block',)
6363
6464
7272
7373 class SingleOp(Instr):
7474 value_attrs = ('opcode', 'dest', 'src',)
75
76
77 class Reset(Instr):
78 value_attrs = ('pointer', 'offset',)
7579
7680
7781 class Call(Instr):
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,
3333 self.symtab = symtab
3434 self.emitter = emitter
3535 self.routines = {} # routine.name -> Routine
36 self.routine_statics = {} # routine.name -> { static.name -> Label }
36 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 - - - -
4243
4344 def get_type_for_name(self, name):
44 if self.current_routine and self.symtab.has_static(self.current_routine.name, name):
45 return self.symtab.fetch_static_type(self.current_routine.name, name)
45 if self.current_routine and self.symtab.has_local(self.current_routine.name, name):
46 return self.symtab.fetch_local_type(self.current_routine.name, name)
4647 return self.symtab.fetch_global_type(name)
4748
4849 def get_type(self, ref):
7576
7677 def get_label(self, name):
7778 if self.current_routine:
78 static_label = self.routine_statics.get(self.current_routine.name, {}).get(name)
79 if static_label:
80 return static_label
79 local_label = self.routine_locals.get(self.current_routine.name, {}).get(name)
80 if local_label:
81 return local_label
8182 return self.labels[name]
8283
8384 def absolute_or_zero_page(self, label):
106107 label.set_addr(routine.addr)
107108 self.labels[routine.name] = label
108109
109 if hasattr(routine, 'statics'):
110 self.current_routine = routine
111 static_labels = {}
112 for defn in routine.statics:
113 length = self.compute_length_of_defn(defn)
114 label = Label(defn.name, addr=defn.addr, length=length)
115 static_labels[defn.name] = label
116 declarations.append((defn, self.symtab.fetch_static_type(routine.name, defn.name), label))
117 self.routine_statics[routine.name] = static_labels
118 self.current_routine = None
110 self.current_routine = routine
111 local_labels = {}
112 for defn in routine.locals:
113 length = self.compute_length_of_defn(defn)
114 label = Label(defn.name, addr=defn.addr, length=length)
115 local_labels[defn.name] = label
116 declarations.append((defn, self.symtab.fetch_local_type(routine.name, defn.name), label))
117 self.routine_locals[routine.name] = local_labels
118 self.current_routine = None
119119
120120 if compilation_roster is None:
121121 compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']
191191 return self.compile_save(instr)
192192 elif isinstance(instr, PointInto):
193193 return self.compile_point_into(instr)
194 elif isinstance(instr, Reset):
195 return self.compile_reset(instr)
194196 else:
195197 raise NotImplementedError
196198
741743 self.emitter.emit(STA(Absolute(src_label)))
742744
743745 def compile_point_into(self, instr):
744 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)
745753 dest_label = self.get_label(instr.pointer.name)
746754
747755 self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
748756 self.emitter.emit(STA(ZeroPage(dest_label)))
749757 self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
750758 self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1))))
751
752 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,
77 RoutineType, VectorType, TableType, PointerType,
8 LocationRef, ConstantRef, IndirectRef, IndexedRef,
8 ConstantRef, IndirectRef, IndexedRef,
99 )
1010 from sixtypical.scanner import Scanner
11
12
13 class SymEntry(object):
14 def __init__(self, ast_node, type_):
15 self.ast_node = ast_node
16 self.type_ = type_
17
18 def __repr__(self):
19 return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.type_)
11 from sixtypical.symtab import SymEntry
2012
2113
2214 class ForwardReference(object):
2517
2618 def __repr__(self):
2719 return "%s(%r)" % (self.__class__.__name__, self.name)
28
29
30 class SymbolTable(object):
31 def __init__(self):
32 self.symbols = {} # symbol name -> SymEntry
33 self.statics = {} # routine name -> (symbol name -> SymEntry)
34 self.typedefs = {} # type name -> Type AST
35 self.consts = {} # const name -> ConstantRef
36
37 for name in ('a', 'x', 'y'):
38 self.symbols[name] = SymEntry(None, TYPE_BYTE)
39 for name in ('c', 'z', 'n', 'v'):
40 self.symbols[name] = SymEntry(None, TYPE_BIT)
41
42 def __str__(self):
43 return "Symbols: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts)
44
45 def has_static(self, routine_name, name):
46 return name in self.statics.get(routine_name, {})
47
48 def fetch_global_type(self, name):
49 return self.symbols[name].type_
50
51 def fetch_static_type(self, routine_name, name):
52 return self.statics[routine_name][name].type_
53
54 def fetch_global_ref(self, name):
55 if name in self.symbols:
56 return LocationRef(name)
57 return None
58
59 def fetch_static_ref(self, routine_name, name):
60 routine_statics = self.statics.get(routine_name, {})
61 if name in routine_statics:
62 return LocationRef(name)
63 return None
6420
6521
6622 class Parser(object):
7531 def lookup(self, name, allow_forward=False, routine_name=None):
7632 model = self.symtab.fetch_global_ref(name)
7733 if model is None and routine_name:
78 model = self.symtab.fetch_static_ref(routine_name, name)
34 model = self.symtab.fetch_local_ref(routine_name, name)
7935 if model is None and allow_forward:
8036 return ForwardReference(name)
8137 if model is None:
8743 self.syntax_error('Symbol "%s" already declared' % name)
8844 self.symtab.symbols[name] = SymEntry(ast_node, type_)
8945
90 def declare_static(self, routine_name, name, ast_node, type_):
46 def declare_local(self, routine_name, name, ast_node, type_):
47 if self.symtab.fetch_local_ref(routine_name, name):
48 self.syntax_error('Symbol "%s" already declared locally' % name)
9149 if self.symtab.fetch_global_ref(name):
92 self.syntax_error('Symbol "%s" already declared' % name)
93 self.symtab.statics.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_)
50 self.syntax_error('Symbol "%s" already declared globally' % name)
51 self.symtab.locals.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_)
9452
9553 # ---- symbol resolution
9654
305263 type_ = self.defn_type()
306264 if not isinstance(type_, RoutineType):
307265 self.syntax_error("Can only define a routine, not {}".format(repr(type_)))
308 statics = []
266 locals_ = []
309267 if self.scanner.consume('@'):
310268 self.scanner.check_type('integer literal')
311269 block = None
312270 addr = int(self.scanner.token)
313271 self.scanner.scan()
314272 else:
315 statics = self.statics()
273 locals_ = self.locals()
316274 block = self.block()
317275 addr = None
318 return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, statics=statics)
276 return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, locals=locals_)
319277
320278 def labels(self):
321279 accum = []
369327 loc = IndexedRef(loc, offset, index)
370328 return loc
371329
372 def statics(self):
330 def locals(self):
373331 defns = []
374332 while self.scanner.consume('static'):
375333 type_, defn = self.defn()
376334 if defn.initial is None:
377335 self.syntax_error("Static definition {} must have initial value".format(defn))
378 self.declare_static(self.current_routine_name, defn.name, defn, type_)
336 self.declare_local(self.current_routine_name, defn.name, defn, type_)
337 defns.append(defn)
338 while self.scanner.consume('local'):
339 type_, defn = self.defn()
340 if defn.initial is not None:
341 self.syntax_error("Local definition {} may not have initial value".format(defn))
342 self.declare_local(self.current_routine_name, defn.name, defn, type_)
379343 defns.append(defn)
380344 return defns
381345
423387 final = self.const()
424388 block = self.block()
425389 return For(self.scanner.line_number, dest=dest, direction=direction, final=final, block=block)
390 elif self.scanner.consume('reset'):
391 pointer = self.locexpr()
392 offset = self.const()
393 return Reset(self.scanner.line_number, pointer=pointer, offset=offset)
426394 elif self.scanner.token in ("ld",):
427395 # the same as add, sub, cmp etc below, except supports an indlocexpr for the src
428396 opcode = self.scanner.token
0 # encoding: UTF-8
1
2 from sixtypical.model import (
3 TYPE_BIT, TYPE_BYTE, LocationRef,
4 )
5
6
7 class SymEntry(object):
8 def __init__(self, ast_node, type_):
9 self.ast_node = ast_node
10 self.type_ = type_
11
12 def __repr__(self):
13 return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.type_)
14
15
16 class SymbolTable(object):
17 def __init__(self):
18 self.symbols = {} # symbol name -> SymEntry
19 self.locals = {} # routine name -> (symbol name -> SymEntry)
20 self.typedefs = {} # type name -> Type AST
21 self.consts = {} # const name -> ConstantRef
22
23 for name in ('a', 'x', 'y'):
24 self.symbols[name] = SymEntry(None, TYPE_BYTE)
25 for name in ('c', 'z', 'n', 'v'):
26 self.symbols[name] = SymEntry(None, TYPE_BIT)
27
28 def __str__(self):
29 return "Symbols: {}\nLocals: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.locals, self.typedefs, self.consts)
30
31 def has_local(self, routine_name, name):
32 return name in self.locals.get(routine_name, {})
33
34 def fetch_global_type(self, name):
35 return self.symbols[name].type_
36
37 def fetch_local_type(self, routine_name, name):
38 return self.locals[routine_name][name].type_
39
40 def fetch_global_ref(self, name):
41 if name in self.symbols:
42 return LocationRef(name)
43 return None
44
45 def fetch_local_ref(self, routine_name, name):
46 routine_locals = self.locals.get(routine_name, {})
47 if name in routine_locals:
48 return LocationRef(name)
49 return None
00 #!/bin/sh
11
2 # This currently represents a lot of tests! If you only want to run a subset,
3 # it's probably best to run `falderal` manually on the file(s) you want to test.
4
25 falderal --substring-error \
3 tests/SixtyPical\ Syntax.md \
4 tests/SixtyPical\ Analysis.md \
5 tests/SixtyPical\ Fallthru.md \
6 tests/SixtyPical\ Compilation.md
6 "tests/appliances/sixtypical.md" \
7 "tests/SixtyPical Syntax.md" \
8 "tests/SixtyPical Analysis.md" \
9 "tests/SixtyPical Storage.md" \
10 "tests/SixtyPical Control Flow.md" \
11 "tests/SixtyPical Fallthru.md" \
12 "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
8 -> Functionality "Analyze SixtyPical program" is implemented by
9 -> shell command "bin/sixtypical --analyze-only --traceback %(test-body-file) && echo ok"
10
1112 -> 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
94413
94514 ### add ###
94615
1622691 | nop
1623692 | }
1624693 = 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