git @ Cat's Eye Technologies SixtyPical / bb6ad5d
Refactor Analyzer. Get analysis tests passing once again. Chris Pressey 6 years ago
3 changed file(s) with 182 addition(s) and 153 deletion(s). Raw diff Collapse all Expand all
1818
1919 from sixtypical.parser import Parser
2020 from sixtypical.evaluator import Evaluator
21 from sixtypical.analyzer import analyze_program
21 from sixtypical.analyzer import Analyzer
2222 from sixtypical.emitter import Emitter, Byte, Word
2323 from sixtypical.compiler import Compiler
2424
4949
5050 for filename in args:
5151 text = open(filename).read()
52 p = Parser(text)
53 program = p.program()
52 parser = Parser(text)
53 program = parser.program()
5454
5555 if options.analyze:
5656 try:
57 analyze_program(program)
57 analyzer = Analyzer()
58 analyzer.analyze_program(program)
5859 except Exception as e:
5960 if options.traceback:
6061 raise
3737
3838
3939 class IncompatibleConstraintsError(StaticAnalysisError):
40 pass
41
42
43 class IllegalJumpError(StaticAnalysisError):
4044 pass
4145
4246
125129 self.set_touched(*refs)
126130 self.set_meaningful(*refs)
127131
128 def analyze_program(program):
129 assert isinstance(program, Program)
130 routines = {r.name: r for r in program.routines}
131 for routine in program.routines:
132 analyze_routine(routine, routines)
133
134
135 def analyze_routine(routine, routines):
136 assert isinstance(routine, Routine)
137 if routine.block is None:
138 # it's an extern, that's fine
139 return
140 type = routine.location.type
141 context = Context(type.inputs, type.outputs, type.trashes)
142 analyze_block(routine.block, context, routines)
143 for ref in type.outputs:
144 context.assert_meaningful(ref, exception_class=UninitializedOutputError)
145 for ref in context.each_touched():
146 if ref not in type.outputs and ref not in type.trashes:
147 raise IllegalWriteError(ref.name)
148
149
150 def analyze_block(block, context, routines):
151 assert isinstance(block, Block)
152 for i in block.instrs:
153 analyze_instr(i, context, routines)
154
155
156 def analyze_instr(instr, context, routines):
157 assert isinstance(instr, Instr)
158 opcode = instr.opcode
159 dest = instr.dest
160 src = instr.src
161
162 if opcode == 'ld':
163 if instr.index:
164 if src.type == TYPE_BYTE_TABLE and dest.type == TYPE_BYTE:
132
133 class Analyzer(object):
134
135 def __init__(self):
136 self.current_routine = None
137 self.has_encountered_goto = False
138
139 def analyze_program(self, program):
140 assert isinstance(program, Program)
141 routines = {r.name: r for r in program.routines}
142 for routine in program.routines:
143 self.analyze_routine(routine, routines)
144
145 def analyze_routine(self, routine, routines):
146 assert isinstance(routine, Routine)
147 self.current_routine = routine
148 self.has_encountered_goto = False
149 if routine.block is None:
150 # it's an extern, that's fine
151 return
152 type = routine.location.type
153 context = Context(type.inputs, type.outputs, type.trashes)
154 self.analyze_block(routine.block, context, routines)
155 for ref in type.outputs:
156 context.assert_meaningful(ref, exception_class=UninitializedOutputError)
157 for ref in context.each_touched():
158 if ref not in type.outputs and ref not in type.trashes:
159 raise IllegalWriteError(ref.name)
160 self.current_routine = None
161
162 def analyze_block(self, block, context, routines):
163 assert isinstance(block, Block)
164 for i in block.instrs:
165 if self.has_encountered_goto:
166 raise IllegalJumpError(i)
167 self.analyze_instr(i, context, routines)
168
169 def analyze_instr(self, instr, context, routines):
170 assert isinstance(instr, Instr)
171 opcode = instr.opcode
172 dest = instr.dest
173 src = instr.src
174
175 if opcode == 'ld':
176 if instr.index:
177 if src.type == TYPE_BYTE_TABLE and dest.type == TYPE_BYTE:
178 pass
179 else:
180 raise TypeMismatchError((src, dest))
181 elif src.type != dest.type:
182 raise TypeMismatchError((src, dest))
183 context.assert_meaningful(src)
184 context.set_written(dest, FLAG_Z, FLAG_N)
185 elif opcode == 'st':
186 if instr.index:
187 if src.type == TYPE_BYTE and dest.type == TYPE_BYTE_TABLE:
188 pass
189 else:
190 raise TypeMismatchError((src, dest))
191 elif src.type != dest.type:
192 raise TypeMismatchError((src, dest))
193 context.assert_meaningful(src)
194 context.set_written(dest)
195 elif opcode in ('add', 'sub'):
196 context.assert_meaningful(src, dest, FLAG_C)
197 context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
198 elif opcode in ('inc', 'dec'):
199 context.assert_meaningful(dest)
200 context.set_written(dest, FLAG_Z, FLAG_N)
201 elif opcode == 'cmp':
202 context.assert_meaningful(src, dest)
203 context.set_written(FLAG_Z, FLAG_N, FLAG_C)
204 elif opcode in ('and', 'or', 'xor'):
205 context.assert_meaningful(src, dest)
206 context.set_written(dest, FLAG_Z, FLAG_N)
207 elif opcode in ('shl', 'shr'):
208 context.assert_meaningful(dest, FLAG_C)
209 context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
210 elif opcode == 'call':
211 type = instr.location.type
212 for ref in type.inputs:
213 context.assert_meaningful(ref)
214 for ref in type.outputs:
215 context.set_written(ref)
216 for ref in type.trashes:
217 context.assert_writeable(ref)
218 context.set_touched(ref)
219 context.set_unmeaningful(ref)
220 elif opcode == 'if':
221 context1 = context.clone()
222 context2 = context.clone()
223 self.analyze_block(instr.block1, context1, routines)
224 if instr.block2 is not None:
225 self.analyze_block(instr.block2, context2, routines)
226 # TODO may we need to deal with touched separately here too?
227 # probably not; if it wasn't meaningful in the first place, it
228 # doesn't really matter if you modified it or not, coming out.
229 for ref in context1.each_meaningful():
230 context2.assert_meaningful(ref, exception_class=InconsistentInitializationError)
231 for ref in context2.each_meaningful():
232 context1.assert_meaningful(ref, exception_class=InconsistentInitializationError)
233 context.set_from(context1)
234 elif opcode == 'repeat':
235 # it will always be executed at least once, so analyze it having
236 # been executed the first time.
237 self.analyze_block(instr.block, context, routines)
238
239 # now analyze it having been executed a second time, with the context
240 # of it having already been executed.
241 self.analyze_block(instr.block, context, routines)
242
243 # NB I *think* that's enough... but it might not be?
244 elif opcode == 'copy':
245 # check that their types are basically compatible
246 if src.type == dest.type:
247 pass
248 elif isinstance(src.type, ExecutableType) and \
249 isinstance(dest.type, VectorType):
165250 pass
166251 else:
167252 raise TypeMismatchError((src, dest))
168 elif src.type != dest.type:
169 raise TypeMismatchError((src, dest))
170 context.assert_meaningful(src)
171 context.set_written(dest, FLAG_Z, FLAG_N)
172 elif opcode == 'st':
173 if instr.index:
174 if src.type == TYPE_BYTE and dest.type == TYPE_BYTE_TABLE:
175 pass
176 else:
177 raise TypeMismatchError((src, dest))
178 elif src.type != dest.type:
179 raise TypeMismatchError((src, dest))
180 context.assert_meaningful(src)
181 context.set_written(dest)
182 elif opcode in ('add', 'sub'):
183 context.assert_meaningful(src, dest, FLAG_C)
184 context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
185 elif opcode in ('inc', 'dec'):
186 context.assert_meaningful(dest)
187 context.set_written(dest, FLAG_Z, FLAG_N)
188 elif opcode == 'cmp':
189 context.assert_meaningful(src, dest)
190 context.set_written(FLAG_Z, FLAG_N, FLAG_C)
191 elif opcode in ('and', 'or', 'xor'):
192 context.assert_meaningful(src, dest)
193 context.set_written(dest, FLAG_Z, FLAG_N)
194 elif opcode in ('shl', 'shr'):
195 context.assert_meaningful(dest, FLAG_C)
196 context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
197 elif opcode == 'call':
198 type = instr.location.type
199 for ref in type.inputs:
200 context.assert_meaningful(ref)
201 for ref in type.outputs:
202 context.set_written(ref)
203 for ref in type.trashes:
204 context.assert_writeable(ref)
205 context.set_touched(ref)
206 context.set_unmeaningful(ref)
207 elif opcode == 'if':
208 context1 = context.clone()
209 context2 = context.clone()
210 analyze_block(instr.block1, context1, routines)
211 if instr.block2 is not None:
212 analyze_block(instr.block2, context2, routines)
213 # TODO may we need to deal with touched separately here too?
214 # probably not; if it wasn't meaningful in the first place, it
215 # doesn't really matter if you modified it or not, coming out.
216 for ref in context1.each_meaningful():
217 context2.assert_meaningful(ref, exception_class=InconsistentInitializationError)
218 for ref in context2.each_meaningful():
219 context1.assert_meaningful(ref, exception_class=InconsistentInitializationError)
220 context.set_from(context1)
221 elif opcode == 'repeat':
222 # it will always be executed at least once, so analyze it having
223 # been executed the first time.
224 analyze_block(instr.block, context, routines)
225
226 # now analyze it having been executed a second time, with the context
227 # of it having already been executed.
228 analyze_block(instr.block, context, routines)
229
230 # NB I *think* that's enough... but it might not be?
231 elif opcode == 'copy':
232 # check that their types are basically compatible
233 if src.type == dest.type:
234 pass
235 elif isinstance(src.type, ExecutableType) and \
236 isinstance(dest.type, VectorType):
237 pass
253
254 # if dealing with routines and vectors,
255 # check that they're not incompatible
256 if isinstance(src.type, ExecutableType) and \
257 isinstance(dest.type, VectorType):
258 if not (src.type.inputs <= dest.type.inputs):
259 raise IncompatibleConstraintsError(src.type.inputs - dest.type.inputs)
260 if not (src.type.outputs <= dest.type.outputs):
261 raise IncompatibleConstraintsError(src.type.outputs - dest.type.outputs)
262 if not (src.type.trashes <= dest.type.trashes):
263 raise IncompatibleConstraintsError(src.type.trashes - dest.type.trashes)
264
265 context.assert_meaningful(src)
266 context.set_written(dest)
267 context.set_touched(REG_A, FLAG_Z, FLAG_N)
268 context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
269 elif opcode == 'with-sei':
270 self.analyze_block(instr.block, context, routines)
271 elif opcode == 'goto':
272 location = instr.location
273 type = location.type
274
275 if not isinstance(type, ExecutableType):
276 raise TypeMismatchError(location)
277
278 # assert that the dest routine's inputs are all initialized
279 for ref in type.inputs:
280 context.assert_meaningful(ref)
281
282 # and that this routine's trashes and output constraints are a
283 # superset of the called routine's
284 current_type = self.current_routine.location.type
285 if not (type.outputs <= current_type.outputs):
286 raise IncompatibleConstraintsError(type.outputs - current_type.outputs)
287 if not (type.trashes <= current_type.trashes):
288 raise IncompatibleConstraintsError(type.trashes - current_type.trashes)
289 self.has_encountered_goto = True
238290 else:
239 raise TypeMismatchError((src, dest))
240
241 # if dealing with routines and vectors,
242 # check that they're not incompatible
243 if isinstance(src.type, ExecutableType) and \
244 isinstance(dest.type, VectorType):
245 if not (src.type.inputs <= dest.type.inputs):
246 raise IncompatibleConstraintsError(src.type.inputs - dest.type.inputs)
247 if not (src.type.outputs <= dest.type.outputs):
248 raise IncompatibleConstraintsError(src.type.outputs - dest.type.outputs)
249 if not (src.type.trashes <= dest.type.trashes):
250 raise IncompatibleConstraintsError(src.type.trashes - dest.type.trashes)
251
252 context.assert_meaningful(src)
253 context.set_written(dest)
254 context.set_touched(REG_A, FLAG_Z, FLAG_N)
255 context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
256 elif opcode == 'with-sei':
257 analyze_block(instr.block, context, routines)
258 elif opcode == 'goto':
259 location = instr.location
260 type = location.type
261
262 if not isinstance(type, ExecutableType):
263 raise TypeMismatchError(location)
264
265 # assert that the dest routine's inputs are all initialized
266 for ref in type.inputs:
267 context.assert_meaningful(ref)
268
269 # and that the called routine's output constraints are a
270 # superset of this routine's
271 current_type = self.current_routine.type
272 if not (current_type.outputs <= type.outputs):
273 raise IncompatibleConstraintsError(current_type.outputs - type.outputs)
274 if not (current_type.trashes <= type.trashes):
275 raise IncompatibleConstraintsError(current_type.trashes - type.trashes)
276 else:
277 raise NotImplementedError(opcode)
291 raise NotImplementedError(opcode)
11481148 | }
11491149 ? UninitializedOutputError: x
11501150
1151 `goto`, if present, must be the final instruction in a routine.
1151 `goto`, if present, must be in tail position (the final instruction in a routine.)
11521152
11531153 | routine bar trashes x, z, n {
11541154 | ld x, 200
11811181 | goto bar
11821182 | }
11831183 | }
1184 = ok
1185
1186 | routine bar trashes x, z, n {
1187 | ld x, 200
1188 | }
1189 |
1190 | routine main trashes x, z, n {
1191 | ld x, 0
1192 | if z {
1193 | ld x, 1
1194 | goto bar
1195 | }
1196 | ld x, 0
1197 | }
11841198 ? IllegalJumpError
11851199
11861200 Can't `goto` a routine that outputs or trashes more than the current routine.
11941208 | ld x, 0
11951209 | goto bar
11961210 | }
1197 ? IllegalWriteError: y
1211 ? IncompatibleConstraintsError
11981212
11991213 | routine bar outputs y trashes z, n {
12001214 | ld y, 200
12041218 | ld x, 0
12051219 | goto bar
12061220 | }
1207 ? IllegalWriteError: y
1221 ? IncompatibleConstraintsError