37 | 37 |
|
38 | 38 |
|
39 | 39 |
class IncompatibleConstraintsError(StaticAnalysisError):
|
|
40 |
pass
|
|
41 |
|
|
42 |
|
|
43 |
class IllegalJumpError(StaticAnalysisError):
|
40 | 44 |
pass
|
41 | 45 |
|
42 | 46 |
|
|
125 | 129 |
self.set_touched(*refs)
|
126 | 130 |
self.set_meaningful(*refs)
|
127 | 131 |
|
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):
|
165 | 250 |
pass
|
166 | 251 |
else:
|
167 | 252 |
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
|
238 | 290 |
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)
|