2015-10-16 08:30:24 +00:00
|
|
|
# encoding: UTF-8
|
|
|
|
|
2015-10-16 12:06:18 +00:00
|
|
|
from sixtypical.ast import Program, Routine, Block, Instr
|
|
|
|
from sixtypical.model import (
|
2015-10-18 19:16:14 +00:00
|
|
|
TYPE_BYTE, TYPE_BYTE_TABLE, TYPE_ROUTINE, TYPE_VECTOR,
|
|
|
|
ConstantRef, LocationRef,
|
|
|
|
REG_A, FLAG_Z, FLAG_N, FLAG_V, FLAG_C
|
2015-10-16 12:06:18 +00:00
|
|
|
)
|
2015-10-16 08:30:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
class StaticAnalysisError(ValueError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class UninitializedAccessError(StaticAnalysisError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-10-16 08:38:38 +00:00
|
|
|
class UninitializedOutputError(StaticAnalysisError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-10-16 13:01:45 +00:00
|
|
|
class InconsistentInitializationError(StaticAnalysisError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-10-16 08:30:24 +00:00
|
|
|
class IllegalWriteError(StaticAnalysisError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class UsageClashError(StaticAnalysisError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-10-18 17:12:47 +00:00
|
|
|
class TypeMismatchError(StaticAnalysisError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-10-16 08:30:24 +00:00
|
|
|
class Context():
|
2015-10-18 21:27:51 +00:00
|
|
|
"""
|
|
|
|
A location is touched if it was changed (or even potentially
|
|
|
|
changed) during this routine, or some routine called by this routine.
|
|
|
|
|
|
|
|
A location is meaningful if it was an input to this routine,
|
|
|
|
or if it was set to a meaningful value by some operation in this
|
|
|
|
routine (or some routine called by this routine.
|
|
|
|
|
|
|
|
A location is writeable if it was listed in the outputs and trashes
|
|
|
|
lists of this routine.
|
|
|
|
"""
|
2015-10-16 08:30:24 +00:00
|
|
|
def __init__(self, inputs, outputs, trashes):
|
2015-10-18 21:27:51 +00:00
|
|
|
self._touched = set()
|
|
|
|
self._meaningful = set()
|
|
|
|
self._writeable = set()
|
2015-10-16 08:30:24 +00:00
|
|
|
|
|
|
|
for ref in inputs:
|
2015-10-18 21:27:51 +00:00
|
|
|
self._meaningful.add(ref)
|
2015-10-16 08:30:24 +00:00
|
|
|
output_names = set()
|
|
|
|
for ref in outputs:
|
|
|
|
output_names.add(ref.name)
|
2015-10-18 21:27:51 +00:00
|
|
|
self._writeable.add(ref)
|
2015-10-16 08:30:24 +00:00
|
|
|
for ref in trashes:
|
|
|
|
if ref.name in output_names:
|
|
|
|
raise UsageClashError(ref.name)
|
2015-10-18 21:27:51 +00:00
|
|
|
self._writeable.add(ref)
|
2015-10-16 08:30:24 +00:00
|
|
|
|
2015-10-16 09:54:12 +00:00
|
|
|
def clone(self):
|
|
|
|
c = Context([], [], [])
|
2015-10-18 21:27:51 +00:00
|
|
|
c._touched = set(self._touched)
|
|
|
|
c._meaningful = set(self._meaningful)
|
|
|
|
c._writeable = set(self._writeable)
|
2015-10-16 09:54:12 +00:00
|
|
|
return c
|
|
|
|
|
2015-10-16 12:06:18 +00:00
|
|
|
def set_from(self, c):
|
2015-10-18 21:27:51 +00:00
|
|
|
self._touched = set(c._touched)
|
|
|
|
self._meaningful = set(c._meaningful)
|
|
|
|
self._writeable = set(c._writeable)
|
|
|
|
|
|
|
|
def each_meaningful(self):
|
|
|
|
for ref in self._meaningful:
|
|
|
|
yield ref
|
2015-10-16 12:06:18 +00:00
|
|
|
|
2015-10-18 21:27:51 +00:00
|
|
|
def each_touched(self):
|
|
|
|
for ref in self._touched:
|
|
|
|
yield ref
|
2015-10-16 12:06:18 +00:00
|
|
|
|
2015-10-18 21:27:51 +00:00
|
|
|
def assert_meaningful(self, *refs, **kwargs):
|
2015-10-16 08:38:38 +00:00
|
|
|
exception_class = kwargs.get('exception_class', UninitializedAccessError)
|
2015-10-16 08:30:24 +00:00
|
|
|
for ref in refs:
|
|
|
|
if isinstance(ref, ConstantRef):
|
|
|
|
pass
|
|
|
|
elif isinstance(ref, LocationRef):
|
2015-10-18 21:27:51 +00:00
|
|
|
if ref not in self._meaningful:
|
2015-10-16 08:38:38 +00:00
|
|
|
raise exception_class(ref.name)
|
2015-10-16 08:30:24 +00:00
|
|
|
else:
|
|
|
|
raise ValueError(ref)
|
|
|
|
|
2015-10-16 12:06:18 +00:00
|
|
|
def assert_writeable(self, *refs):
|
2015-10-16 08:30:24 +00:00
|
|
|
for ref in refs:
|
2015-10-18 21:27:51 +00:00
|
|
|
if ref not in self._writeable:
|
2015-10-16 08:30:24 +00:00
|
|
|
raise IllegalWriteError(ref.name)
|
|
|
|
|
2015-10-18 21:27:51 +00:00
|
|
|
def set_touched(self, *refs):
|
2015-10-16 08:30:24 +00:00
|
|
|
for ref in refs:
|
2015-10-18 21:27:51 +00:00
|
|
|
self._touched.add(ref)
|
2015-10-16 08:30:24 +00:00
|
|
|
|
2015-10-18 21:27:51 +00:00
|
|
|
def set_meaningful(self, *refs):
|
2015-10-16 08:30:24 +00:00
|
|
|
for ref in refs:
|
2015-10-18 21:27:51 +00:00
|
|
|
self._meaningful.add(ref)
|
2015-10-16 08:30:24 +00:00
|
|
|
|
2015-10-18 21:27:51 +00:00
|
|
|
def set_unmeaningful(self, *refs):
|
|
|
|
for ref in refs:
|
|
|
|
if ref in self._meaningful:
|
|
|
|
self._meaningful.remove(ref)
|
2015-10-16 08:30:24 +00:00
|
|
|
|
2015-10-18 21:27:51 +00:00
|
|
|
def set_written(self, *refs):
|
|
|
|
"""A "helper" method which does the following common sequence for
|
|
|
|
the given refs: asserts they're all writable, and sets them all
|
|
|
|
as touched and meaningful."""
|
|
|
|
self.assert_writeable(*refs)
|
|
|
|
self.set_touched(*refs)
|
|
|
|
self.set_meaningful(*refs)
|
2015-10-16 08:30:24 +00:00
|
|
|
|
|
|
|
def analyze_program(program):
|
|
|
|
assert isinstance(program, Program)
|
|
|
|
routines = {r.name: r for r in program.routines}
|
|
|
|
for routine in program.routines:
|
|
|
|
analyze_routine(routine, routines)
|
|
|
|
|
|
|
|
|
|
|
|
def analyze_routine(routine, routines):
|
|
|
|
assert isinstance(routine, Routine)
|
2015-10-17 13:54:28 +00:00
|
|
|
if routine.block is None:
|
|
|
|
# it's an extern, that's fine
|
|
|
|
return
|
2015-10-16 08:30:24 +00:00
|
|
|
context = Context(routine.inputs, routine.outputs, routine.trashes)
|
|
|
|
analyze_block(routine.block, context, routines)
|
|
|
|
for ref in routine.outputs:
|
2015-10-18 21:27:51 +00:00
|
|
|
context.assert_meaningful(ref, exception_class=UninitializedOutputError)
|
|
|
|
for ref in context.each_touched():
|
|
|
|
if ref not in routine.outputs and ref not in routine.trashes:
|
|
|
|
raise IllegalWriteError(ref.name)
|
2015-10-16 08:30:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
def analyze_block(block, context, routines):
|
|
|
|
assert isinstance(block, Block)
|
|
|
|
for i in block.instrs:
|
|
|
|
analyze_instr(i, context, routines)
|
|
|
|
|
|
|
|
|
|
|
|
def analyze_instr(instr, context, routines):
|
|
|
|
assert isinstance(instr, Instr)
|
|
|
|
opcode = instr.opcode
|
|
|
|
dest = instr.dest
|
|
|
|
src = instr.src
|
|
|
|
|
|
|
|
if opcode == 'ld':
|
2015-10-18 17:12:47 +00:00
|
|
|
if instr.index:
|
|
|
|
if src.type == TYPE_BYTE_TABLE and dest.type == TYPE_BYTE:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
raise TypeMismatchError((src, dest))
|
|
|
|
elif src.type != dest.type:
|
|
|
|
raise TypeMismatchError((src, dest))
|
2015-10-18 21:27:51 +00:00
|
|
|
context.assert_meaningful(src)
|
|
|
|
context.set_written(dest, FLAG_Z, FLAG_N)
|
2015-10-16 08:30:24 +00:00
|
|
|
elif opcode == 'st':
|
2015-10-18 17:12:47 +00:00
|
|
|
if instr.index:
|
|
|
|
if src.type == TYPE_BYTE and dest.type == TYPE_BYTE_TABLE:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
raise TypeMismatchError((src, dest))
|
|
|
|
elif src.type != dest.type:
|
|
|
|
raise TypeMismatchError((src, dest))
|
2015-10-18 21:27:51 +00:00
|
|
|
context.assert_meaningful(src)
|
|
|
|
context.set_written(dest)
|
2015-10-16 08:30:24 +00:00
|
|
|
elif opcode in ('add', 'sub'):
|
2015-10-18 21:27:51 +00:00
|
|
|
context.assert_meaningful(src, dest, FLAG_C)
|
|
|
|
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
2015-10-16 08:30:24 +00:00
|
|
|
elif opcode in ('inc', 'dec'):
|
2015-10-18 21:27:51 +00:00
|
|
|
context.assert_meaningful(dest)
|
|
|
|
context.set_written(dest, FLAG_Z, FLAG_N)
|
2015-10-16 08:30:24 +00:00
|
|
|
elif opcode == 'cmp':
|
2015-10-18 21:27:51 +00:00
|
|
|
context.assert_meaningful(src, dest)
|
|
|
|
context.set_written(FLAG_Z, FLAG_N, FLAG_C)
|
2015-10-16 08:30:24 +00:00
|
|
|
elif opcode in ('and', 'or', 'xor'):
|
2015-10-18 21:27:51 +00:00
|
|
|
context.assert_meaningful(src, dest)
|
|
|
|
context.set_written(dest, FLAG_Z, FLAG_N)
|
2015-10-16 08:30:24 +00:00
|
|
|
elif opcode in ('shl', 'shr'):
|
2015-10-18 21:27:51 +00:00
|
|
|
context.assert_meaningful(dest, FLAG_C)
|
|
|
|
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
|
2015-10-16 08:30:24 +00:00
|
|
|
elif opcode == 'call':
|
|
|
|
routine = routines[instr.name]
|
|
|
|
for ref in routine.inputs:
|
2015-10-18 21:27:51 +00:00
|
|
|
context.assert_meaningful(ref)
|
2015-10-16 08:30:24 +00:00
|
|
|
for ref in routine.outputs:
|
2015-10-18 21:27:51 +00:00
|
|
|
context.set_written(ref)
|
2015-10-16 08:30:24 +00:00
|
|
|
for ref in routine.trashes:
|
2015-10-16 12:06:18 +00:00
|
|
|
context.assert_writeable(ref)
|
2015-10-18 21:27:51 +00:00
|
|
|
context.set_touched(ref)
|
|
|
|
context.set_unmeaningful(ref)
|
2015-10-16 08:30:24 +00:00
|
|
|
elif opcode == 'if':
|
2015-10-16 08:38:38 +00:00
|
|
|
context1 = context.clone()
|
|
|
|
context2 = context.clone()
|
|
|
|
analyze_block(instr.block1, context1, routines)
|
2015-10-17 17:25:54 +00:00
|
|
|
if instr.block2 is not None:
|
|
|
|
analyze_block(instr.block2, context2, routines)
|
2015-10-18 21:27:51 +00:00
|
|
|
# TODO may we need to deal with touched separately here too?
|
|
|
|
for ref in context1.each_meaningful():
|
|
|
|
context2.assert_meaningful(ref, exception_class=InconsistentInitializationError)
|
|
|
|
for ref in context2.each_meaningful():
|
|
|
|
context1.assert_meaningful(ref, exception_class=InconsistentInitializationError)
|
2015-10-16 12:06:18 +00:00
|
|
|
context.set_from(context1)
|
2015-10-18 12:37:35 +00:00
|
|
|
elif opcode == 'repeat':
|
|
|
|
# it will always be executed at least once, so analyze it having
|
|
|
|
# been executed the first time.
|
|
|
|
analyze_block(instr.block, context, routines)
|
|
|
|
|
|
|
|
# now analyze it having been executed a second time, with the context
|
|
|
|
# of it having already been executed.
|
|
|
|
analyze_block(instr.block, context, routines)
|
|
|
|
|
|
|
|
# NB I *think* that's enough... but it might not be?
|
2015-10-18 19:16:14 +00:00
|
|
|
elif opcode == 'copy':
|
|
|
|
if src.type == dest.type:
|
|
|
|
pass
|
|
|
|
elif src.type == TYPE_ROUTINE and dest.type == TYPE_VECTOR:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
raise TypeMismatchError((src, dest))
|
2015-10-18 21:27:51 +00:00
|
|
|
context.assert_meaningful(src)
|
|
|
|
context.set_written(dest)
|
|
|
|
context.set_touched(REG_A, FLAG_Z, FLAG_N)
|
|
|
|
context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
|
2015-10-18 22:15:40 +00:00
|
|
|
elif opcode == 'with-sei':
|
|
|
|
analyze_block(instr.block, context, routines)
|
2015-10-16 08:30:24 +00:00
|
|
|
else:
|
2015-10-16 12:06:18 +00:00
|
|
|
raise NotImplementedError(opcode)
|