git @ Cat's Eye Technologies SixtyPical / b5763e8
Constants (such as routines) cannot be given in call-constraints. Chris Pressey 6 years ago
4 changed file(s) with 129 addition(s) and 39 deletion(s). Raw diff Collapse all Expand all
4343
4444 For 0.6:
4545
46 * routines shouldn't need to be listed as inputs.
4746 * A more involved demo for the C64 — one that sets up an interrupt.
4847
4948 For 0.7:
5049
50 * always analyze before executing or compiling, unless told not to
5151 * `word` type.
5252 * `word table` type.
5353 * `trash` instruction.
2828 pass
2929
3030
31 class InconsistentConstraintsError(StaticAnalysisError):
32 pass
33
34
3531 class TypeMismatchError(StaticAnalysisError):
3632 pass
3733
3834
39 class IncompatibleConstraintsError(StaticAnalysisError):
40 pass
41
42
4335 class IllegalJumpError(StaticAnalysisError):
36 pass
37
38
39 class ConstraintsError(StaticAnalysisError):
40 pass
41
42
43 class ConstantConstraintError(ConstraintsError):
44 pass
45
46
47 class InconsistentConstraintsError(ConstraintsError):
48 pass
49
50
51 class IncompatibleConstraintsError(ConstraintsError):
4452 pass
4553
4654
5664 A location is writeable if it was listed in the outputs and trashes
5765 lists of this routine.
5866 """
59 def __init__(self, routine, inputs, outputs, trashes):
67 def __init__(self, routines, routine, inputs, outputs, trashes):
68 self.routines = routines # Location -> AST node
6069 self.routine = routine
6170 self._touched = set()
6271 self._meaningful = set()
6372 self._writeable = set()
6473
6574 for ref in inputs:
75 if ref.is_constant():
76 raise ConstantConstraintError('%s in %s' % (ref.name, routine.name))
6677 self._meaningful.add(ref)
6778 output_names = set()
6879 for ref in outputs:
80 if ref.is_constant():
81 raise ConstantConstraintError('%s in %s' % (ref.name, routine.name))
6982 output_names.add(ref.name)
7083 self._writeable.add(ref)
7184 for ref in trashes:
85 if ref.is_constant():
86 raise ConstantConstraintError('%s in %s' % (ref.name, routine.name))
7287 if ref.name in output_names:
73 raise InconsistentConstraintsError(ref.name)
88 raise InconsistentConstraintsError('%s in %s' % (ref.name, routine.name))
7489 self._writeable.add(ref)
7590
7691 def clone(self):
77 c = Context(self.routine, [], [], [])
92 c = Context(self.routines, self.routine, [], [], [])
7893 c._touched = set(self._touched)
7994 c._meaningful = set(self._meaningful)
8095 c._writeable = set(self._writeable)
8196 return c
8297
8398 def set_from(self, c):
99 assert c.routines == self.routines
84100 assert c.routine == self.routine
85101 self._touched = set(c._touched)
86102 self._meaningful = set(c._meaningful)
97113 def assert_meaningful(self, *refs, **kwargs):
98114 exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
99115 for ref in refs:
100 if isinstance(ref, ConstantRef):
116 if isinstance(ref, ConstantRef) or ref in self.routines:
101117 pass
102118 elif isinstance(ref, LocationRef):
103119 if ref not in self._meaningful:
140156 def __init__(self):
141157 self.current_routine = None
142158 self.has_encountered_goto = False
159 self.routines = {}
143160
144161 def analyze_program(self, program):
145162 assert isinstance(program, Program)
146 routines = {r.name: r for r in program.routines}
163 self.routines = {r.location: r for r in program.routines}
147164 for routine in program.routines:
148 self.analyze_routine(routine, routines)
149
150 def analyze_routine(self, routine, routines):
165 self.analyze_routine(routine)
166
167 def analyze_routine(self, routine):
151168 assert isinstance(routine, Routine)
152169 self.current_routine = routine
153170 self.has_encountered_goto = False
155172 # it's an extern, that's fine
156173 return
157174 type = routine.location.type
158 context = Context(routine, type.inputs, type.outputs, type.trashes)
159 self.analyze_block(routine.block, context, routines)
175 context = Context(self.routines, routine, type.inputs, type.outputs, type.trashes)
176 self.analyze_block(routine.block, context)
160177 if not self.has_encountered_goto:
161178 for ref in type.outputs:
162179 context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError)
166183 raise ForbiddenWriteError(message)
167184 self.current_routine = None
168185
169 def analyze_block(self, block, context, routines):
186 def analyze_block(self, block, context):
170187 assert isinstance(block, Block)
171188 for i in block.instrs:
172189 if self.has_encountered_goto:
173190 raise IllegalJumpError(i)
174 self.analyze_instr(i, context, routines)
175
176 def analyze_instr(self, instr, context, routines):
191 self.analyze_instr(i, context)
192
193 def analyze_instr(self, instr, context):
177194 assert isinstance(instr, Instr)
178195 opcode = instr.opcode
179196 dest = instr.dest
227244 elif opcode == 'if':
228245 context1 = context.clone()
229246 context2 = context.clone()
230 self.analyze_block(instr.block1, context1, routines)
247 self.analyze_block(instr.block1, context1)
231248 if instr.block2 is not None:
232 self.analyze_block(instr.block2, context2, routines)
249 self.analyze_block(instr.block2, context2)
233250 # TODO may we need to deal with touched separately here too?
234251 # probably not; if it wasn't meaningful in the first place, it
235252 # doesn't really matter if you modified it or not, coming out.
241258 elif opcode == 'repeat':
242259 # it will always be executed at least once, so analyze it having
243260 # been executed the first time.
244 self.analyze_block(instr.block, context, routines)
261 self.analyze_block(instr.block, context)
245262
246263 # now analyze it having been executed a second time, with the context
247264 # of it having already been executed.
248 self.analyze_block(instr.block, context, routines)
265 self.analyze_block(instr.block, context)
249266
250267 # NB I *think* that's enough... but it might not be?
251268 elif opcode == 'copy':
274291 context.set_touched(REG_A, FLAG_Z, FLAG_N)
275292 context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
276293 elif opcode == 'with-sei':
277 self.analyze_block(instr.block, context, routines)
294 self.analyze_block(instr.block, context)
278295 elif opcode == 'goto':
279296 location = instr.location
280297 type = location.type
5656
5757
5858 class Ref(object):
59 pass
59 def is_constant(self):
60 """read-only means that the program cannot change the value
61 of a location. constant means that the value of the location
62 will not change during the lifetime of the program."""
63 raise NotImplementedError
6064
6165
6266 class LocationRef(Ref):
7983 def __repr__(self):
8084 return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.name)
8185
86 def is_constant(self):
87 return isinstance(self.type, RoutineType)
88
8289
8390 class ConstantRef(Ref):
8491 def __init__(self, type, value):
96103 def __repr__(self):
97104 return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.value)
98105
106 def is_constant(self):
107 return True
108
99109
100110 REG_A = LocationRef(TYPE_BYTE, 'a')
101111 REG_X = LocationRef(TYPE_BYTE, 'x')
10471047 | }
10481048 = ok
10491049
1050 You can copy the address of a routine into a vector, if that vector is declared appropriately.
1050 Routines are constants. You need not, and in fact cannot, specify a constant
1051 as an input to, an output of, or as a trashed value of a routine.
10511052
10521053 | vector vec
10531054 | inputs x
10691070 | {
10701071 | copy foo, vec
10711072 | }
1073 ? ConstantConstraintError: foo in main
1074
1075 | vector vec
1076 | inputs x
1077 | outputs x
1078 | trashes z, n
1079 |
1080 | routine foo
1081 | inputs x
1082 | outputs x
1083 | trashes z, n
1084 | {
1085 | inc x
1086 | }
1087 |
1088 | routine main
1089 | outputs vec, foo
1090 | trashes a, z, n
1091 | {
1092 | copy foo, vec
1093 | }
1094 ? ConstantConstraintError: foo in main
1095
1096 | vector vec
1097 | inputs x
1098 | outputs x
1099 | trashes z, n
1100 |
1101 | routine foo
1102 | inputs x
1103 | outputs x
1104 | trashes z, n
1105 | {
1106 | inc x
1107 | }
1108 |
1109 | routine main
1110 | outputs vec
1111 | trashes a, z, n, foo
1112 | {
1113 | copy foo, vec
1114 | }
1115 ? ConstantConstraintError: foo in main
1116
1117 You can copy the address of a routine into a vector, if that vector is
1118 declared appropriately.
1119
1120 | vector vec
1121 | inputs x
1122 | outputs x
1123 | trashes z, n
1124 |
1125 | routine foo
1126 | inputs x
1127 | outputs x
1128 | trashes z, n
1129 | {
1130 | inc x
1131 | }
1132 |
1133 | routine main
1134 | outputs vec
1135 | trashes a, z, n
1136 | {
1137 | copy foo, vec
1138 | }
10721139 = ok
10731140
10741141 But not if the vector is declared inappropriately.
10871154 | }
10881155 |
10891156 | routine main
1090 | inputs foo
10911157 | outputs vec
10921158 | trashes a, z, n
10931159 | {
11111177 | }
11121178 |
11131179 | routine main
1114 | inputs foo
11151180 | outputs vec
11161181 | trashes a, z, n
11171182 | {
11271192 | ld x, 200
11281193 | }
11291194 |
1130 | routine main inputs bar outputs x, foo trashes a, z, n {
1195 | routine main outputs x, foo trashes a, z, n {
11311196 | copy bar, foo
11321197 | call foo
11331198 | }
11411206 | ld x, 200
11421207 | }
11431208 |
1144 | routine main inputs bar outputs x, foo trashes z, n {
1209 | routine main outputs x, foo trashes z, n {
11451210 | ld x, 0
11461211 | copy bar, foo
11471212 | call foo
12411306 | ld x, 200
12421307 | }
12431308 |
1244 | routine main inputs bar outputs x trashes foo, a, z, n {
1309 | routine main outputs x trashes foo, a, z, n {
12451310 | copy bar, foo
12461311 | goto foo
12471312 | }
12591324 | }
12601325 |
12611326 | routine sub
1262 | inputs bar
12631327 | trashes foo, a, x, z, n {
12641328 | ld x, 0
12651329 | copy bar, foo
12661330 | goto foo
12671331 | }
12681332 |
1269 | routine main inputs bar
1333 | routine main
12701334 | outputs a
12711335 | trashes foo, x, z, n {
12721336 | call sub
12851349 | }
12861350 |
12871351 | routine sub
1288 | inputs bar
12891352 | outputs x
12901353 | trashes foo, a, z, n {
12911354 | ld x, 0
12931356 | goto foo
12941357 | }
12951358 |
1296 | routine main inputs bar
1359 | routine main
12971360 | outputs a
12981361 | trashes foo, x, z, n {
12991362 | call sub