SixtyPical/src/sixtypical/analyzer.py

758 lines
31 KiB
Python

# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
# encoding: UTF-8
from sixtypical.ast import (
Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
)
from sixtypical.context import AnalysisContext
from sixtypical.model import (
TYPE_BYTE, TYPE_WORD,
TableType, PointerType, VectorType, RoutineType,
ConstantRef, LocationRef, IndirectRef, IndexedRef,
REG_A, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C
)
class StaticAnalysisError(ValueError):
def __init__(self, ast, message):
super(StaticAnalysisError, self).__init__(ast, message)
def __str__(self):
ast = self.args[0]
message = self.args[1]
if isinstance(ast, Routine):
return "{} (in {}, line {})".format(message, ast.name, ast.line_number)
else:
return "{} (line {})".format(message, ast.line_number)
class UnmeaningfulReadError(StaticAnalysisError):
pass
class UnmeaningfulOutputError(StaticAnalysisError):
pass
class InconsistentInitializationError(StaticAnalysisError):
pass
class InconsistentExitError(StaticAnalysisError):
"""The type context differs at two different exit points of the routine."""
pass
class ForbiddenWriteError(StaticAnalysisError):
pass
class TypeMismatchError(StaticAnalysisError):
pass
class IllegalJumpError(StaticAnalysisError):
pass
class TerminatedContextError(StaticAnalysisError):
"""What the program is doing here is not valid, due to preceding `goto`s,
which make this dead code."""
pass
class RangeExceededError(StaticAnalysisError):
pass
class ConstraintsError(StaticAnalysisError):
"""The constraints of a routine (inputs, outputs, trashes) have been violated."""
pass
class ConstantConstraintError(ConstraintsError):
pass
class InconsistentConstraintsError(ConstraintsError):
pass
class IncompatibleConstraintsError(ConstraintsError):
pass
class Analyzer(object):
def __init__(self, symtab, debug=False):
self.symtab = symtab
self.current_routine = None
self.debug = debug
self.exit_contexts_map = {}
# - - - - helper methods - - - -
def get_type_for_name(self, name):
if self.current_routine and self.symtab.has_local(self.current_routine.name, name):
return self.symtab.fetch_local_type(self.current_routine.name, name)
return self.symtab.fetch_global_type(name)
def get_type(self, ref):
if isinstance(ref, ConstantRef):
return ref.type
if not isinstance(ref, LocationRef):
raise NotImplementedError(str(ref))
return self.get_type_for_name(ref.name)
def assert_type(self, type_, *locations):
for location in locations:
if self.get_type(location) != type_:
raise TypeMismatchError(self.current_routine, location.name)
def assert_affected_within(self, name, affecting_type, limiting_type):
assert name in ('inputs', 'outputs', 'trashes')
affected = getattr(affecting_type, name)
limited_to = getattr(limiting_type, name)
overage = affected - limited_to
if not overage:
return
message = '%s for %s are %s\n\nbut %s affects %s\n\nwhich exceeds it by: %s ' % (
name,
limiting_type, LocationRef.format_set(limited_to),
affecting_type, LocationRef.format_set(affected),
LocationRef.format_set(overage)
)
raise IncompatibleConstraintsError(self.current_routine, message)
def assert_types_for_read_table(self, context, instr, src, dest, type_, offset):
if (not TableType.is_a_table_type(self.get_type(src.ref), type_)) or (not self.get_type(dest) == type_):
raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name))
context.assert_meaningful(src, src.index)
context.assert_in_range(src.index, src.ref, offset)
def assert_types_for_update_table(self, context, instr, dest, type_, offset):
if not TableType.is_a_table_type(self.get_type(dest.ref), type_):
raise TypeMismatchError(instr, '{}'.format(dest.ref.name))
context.assert_meaningful(dest.index)
context.assert_in_range(dest.index, dest.ref, offset)
context.set_written(dest.ref)
# - - - - visitor methods - - - -
def analyze_program(self, program):
assert isinstance(program, Program)
for routine in program.routines:
routine.called_routines = set()
context, type_ = self.analyze_routine(routine)
if type_:
routine.routine_type = type_
routine.encountered_gotos = list(context.encountered_gotos()) if context else []
routine.called_routines = list(routine.called_routines)
def analyze_routine(self, routine):
assert isinstance(routine, Routine)
type_ = self.get_type_for_name(routine.name)
if routine.block is None:
# it's an extern, that's fine
return None, type_
self.current_routine = routine
context = AnalysisContext(self.symtab, routine, type_.inputs, type_.outputs, type_.trashes)
# register any local statics as already-initialized
for local_name, local_symentry in self.symtab.locals.get(routine.name, {}).items():
ref = self.symtab.fetch_local_ref(routine.name, local_name)
if local_symentry.ast_node.initial is not None:
context.set_meaningful(ref)
context.set_range(ref, local_symentry.ast_node.initial, local_symentry.ast_node.initial)
self.exit_contexts = []
self.analyze_block(routine.block, context)
trashed = set(context.each_touched()) - set(context.each_meaningful())
self.exit_contexts_map[routine.name] = {
'end_context': context.to_json_data(),
'exit_contexts': [e.to_json_data() for e in self.exit_contexts]
}
if self.exit_contexts:
# check that they are all consistent
exit_context = self.exit_contexts[0]
exit_meaningful = set(exit_context.each_meaningful())
exit_touched = set(exit_context.each_touched())
exit_writeable = set(exit_context.each_writeable())
for ex in self.exit_contexts[1:]:
if set(ex.each_meaningful()) != exit_meaningful:
raise InconsistentExitError(routine, "Exit contexts are not consistent")
if set(ex.each_touched()) != exit_touched:
raise InconsistentExitError(routine, "Exit contexts are not consistent")
if set(ex.each_writeable()) != exit_writeable:
raise InconsistentExitError(routine, "Exit contexts are not consistent")
# We now set the main context to the (consistent) exit context
# so that this routine is perceived as having the same effect
# that any of the goto'ed routines have.
context.update_from(exit_context)
# these all apply whether we encountered goto(s) in this routine, or not...:
# can't trash an output.
for ref in trashed:
if ref in type_.outputs:
raise UnmeaningfulOutputError(routine, ref.name)
# all outputs are meaningful.
for ref in type_.outputs:
context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError)
# if something was touched, then it should have been declared to be writable.
for ref in context.each_touched():
if ref not in type_.outputs and ref not in type_.trashes and not self.symtab.has_local(routine.name, ref.name):
raise ForbiddenWriteError(routine, ref.name)
self.exit_contexts = None
self.current_routine = None
return context, type_
def analyze_block(self, block, context):
assert isinstance(block, Block)
for i in block.instrs:
self.analyze_instr(i, context)
def analyze_instr(self, instr, context):
if isinstance(instr, SingleOp):
self.analyze_single_op(instr, context)
elif isinstance(instr, Call):
self.analyze_call(instr, context)
elif isinstance(instr, GoTo):
self.analyze_goto(instr, context)
elif isinstance(instr, If):
self.analyze_if(instr, context)
elif isinstance(instr, Repeat):
self.analyze_repeat(instr, context)
elif isinstance(instr, For):
self.analyze_for(instr, context)
elif isinstance(instr, WithInterruptsOff):
self.analyze_with_interrupts_off(instr, context)
elif isinstance(instr, Save):
self.analyze_save(instr, context)
elif isinstance(instr, PointInto):
self.analyze_point_into(instr, context)
elif isinstance(instr, Reset):
self.analyze_reset(instr, context)
else:
raise NotImplementedError(str(instr))
def analyze_single_op(self, instr, context):
opcode = instr.opcode
dest = instr.dest
src = instr.src
if context.has_terminated():
raise TerminatedContextError(instr, instr)
if opcode == 'ld':
if isinstance(src, IndexedRef):
self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset)
elif isinstance(src, IndirectRef):
# copying this analysis from the matching branch in `copy`, below
if isinstance(self.get_type(src.ref), PointerType) and self.get_type(dest) == TYPE_BYTE:
pass
else:
raise TypeMismatchError(instr, (src, dest))
origin = context.get_assoc(src.ref)
if not origin:
raise UnmeaningfulReadError(instr, src.ref)
context.assert_meaningful(origin)
context.assert_meaningful(src.ref, REG_Y)
elif self.get_type(src) != self.get_type(dest):
raise TypeMismatchError(instr, '{} and {}'.format(src.name, dest.name))
else:
context.assert_meaningful(src)
context.copy_range(src, dest)
context.set_written(dest, FLAG_Z, FLAG_N)
elif opcode == 'st':
if isinstance(dest, IndexedRef):
if self.get_type(src) != TYPE_BYTE:
raise TypeMismatchError(instr, (src, dest))
self.assert_types_for_update_table(context, instr, dest, TYPE_BYTE, dest.offset)
elif isinstance(dest, IndirectRef):
# copying this analysis from the matching branch in `copy`, below
if isinstance(self.get_type(dest.ref), PointerType) and self.get_type(src) == TYPE_BYTE:
pass
else:
raise TypeMismatchError(instr, (src, dest))
context.assert_meaningful(dest.ref, REG_Y)
target = context.get_assoc(dest.ref)
if not target:
raise ForbiddenWriteError(instr, dest.ref)
context.set_written(target)
elif self.get_type(src) != self.get_type(dest):
raise TypeMismatchError(instr, '{} and {}'.format(src, dest))
else:
context.set_written(dest)
# FIXME: context.copy_range(src, dest) ?
context.assert_meaningful(src)
elif opcode == 'add':
context.assert_meaningful(src, dest, FLAG_C)
if isinstance(src, IndexedRef):
self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset)
elif self.get_type(src) == TYPE_BYTE:
self.assert_type(TYPE_BYTE, src, dest)
if dest != REG_A:
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)
else:
self.assert_type(TYPE_WORD, src)
dest_type = self.get_type(dest)
if dest_type == TYPE_WORD:
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)
elif isinstance(dest_type, PointerType):
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)
else:
self.assert_type(TYPE_WORD, dest)
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
context.invalidate_range(dest)
elif opcode == 'sub':
context.assert_meaningful(src, dest, FLAG_C)
if isinstance(src, IndexedRef):
self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset)
elif self.get_type(src) == TYPE_BYTE:
self.assert_type(TYPE_BYTE, src, dest)
if dest != REG_A:
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)
else:
self.assert_type(TYPE_WORD, src, dest)
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
context.invalidate_range(dest)
elif opcode == 'cmp':
context.assert_meaningful(src, dest)
if isinstance(src, IndexedRef):
self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset)
elif self.get_type(src) == TYPE_BYTE:
self.assert_type(TYPE_BYTE, src, dest)
else:
self.assert_type(TYPE_WORD, src, dest)
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)
context.set_written(FLAG_Z, FLAG_N, FLAG_C)
elif opcode == 'and':
if isinstance(src, IndexedRef):
self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset)
else:
self.assert_type(TYPE_BYTE, src, dest)
context.assert_meaningful(src, dest)
context.set_written(dest, FLAG_Z, FLAG_N)
# If you AND the A register with a value V, the resulting value of A
# cannot exceed the value of V; i.e. the maximum value of A becomes
# the maximum value of V.
if not isinstance(src, IndexedRef):
context.set_top_of_range(dest, context.get_top_of_range(src))
elif opcode in ('or', 'xor'):
if isinstance(src, IndexedRef):
self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset)
else:
self.assert_type(TYPE_BYTE, src, dest)
context.assert_meaningful(src, dest)
context.set_written(dest, FLAG_Z, FLAG_N)
context.invalidate_range(dest)
elif opcode in ('inc', 'dec'):
context.assert_meaningful(dest)
if isinstance(dest, IndexedRef):
self.assert_types_for_update_table(context, instr, dest, TYPE_BYTE, dest.offset)
context.set_written(dest.ref, FLAG_Z, FLAG_N)
#context.invalidate_range(dest)
else:
self.assert_type(TYPE_BYTE, dest)
context.set_written(dest, FLAG_Z, FLAG_N)
bottom = context.get_bottom_of_range(dest)
top = context.get_top_of_range(dest)
if opcode == 'inc':
if bottom == top and top < 255:
context.set_range(dest, bottom + 1, top + 1)
else:
context.invalidate_range(dest)
elif opcode == 'dec':
if bottom == top and bottom > 0:
context.set_range(dest, bottom - 1, top - 1)
else:
context.invalidate_range(dest)
else:
raise NotImplementedError
elif opcode in ('shl', 'shr'):
context.assert_meaningful(dest, FLAG_C)
if isinstance(dest, IndexedRef):
self.assert_types_for_update_table(context, instr, dest, TYPE_BYTE, dest.offset)
context.set_written(dest.ref, FLAG_Z, FLAG_N, FLAG_C)
#context.invalidate_range(dest)
else:
self.assert_type(TYPE_BYTE, dest)
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
context.invalidate_range(dest)
elif opcode == 'copy':
if dest == REG_A:
raise ForbiddenWriteError(instr, "{} cannot be used as destination for copy".format(dest))
# 1. check that their types are compatible
if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
if self.get_type(src) == TYPE_BYTE and isinstance(self.get_type(dest.ref), PointerType):
pass
else:
raise TypeMismatchError(instr, (src, dest))
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
if isinstance(self.get_type(src.ref), PointerType) and self.get_type(dest) == TYPE_BYTE:
pass
else:
raise TypeMismatchError(instr, (src, dest))
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
if isinstance(self.get_type(src.ref), PointerType) and isinstance(self.get_type(dest.ref), PointerType):
pass
else:
raise TypeMismatchError(instr, (src, dest))
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef):
if self.get_type(src) == TYPE_WORD and TableType.is_a_table_type(self.get_type(dest.ref), TYPE_WORD):
pass
elif (isinstance(self.get_type(src), VectorType) and isinstance(self.get_type(dest.ref), TableType) and
RoutineType.executable_types_compatible(self.get_type(src).of_type, self.get_type(dest.ref).of_type)):
pass
elif (isinstance(self.get_type(src), RoutineType) and isinstance(self.get_type(dest.ref), TableType) and
RoutineType.executable_types_compatible(self.get_type(src), self.get_type(dest.ref).of_type)):
pass
else:
raise TypeMismatchError(instr, (src, dest))
context.assert_in_range(dest.index, dest.ref, dest.offset)
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
if TableType.is_a_table_type(self.get_type(src.ref), TYPE_WORD) and self.get_type(dest) == TYPE_WORD:
pass
elif (isinstance(self.get_type(src.ref), TableType) and isinstance(self.get_type(dest), VectorType) and
RoutineType.executable_types_compatible(self.get_type(src.ref).of_type, self.get_type(dest).of_type)):
pass
else:
raise TypeMismatchError(instr, (src, dest))
context.assert_in_range(src.index, src.ref, src.offset)
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, LocationRef):
if self.get_type(src) == self.get_type(dest):
pass
elif isinstance(self.get_type(src), RoutineType) and isinstance(self.get_type(dest), VectorType):
self.assert_affected_within('inputs', self.get_type(src), self.get_type(dest).of_type)
self.assert_affected_within('outputs', self.get_type(src), self.get_type(dest).of_type)
self.assert_affected_within('trashes', self.get_type(src), self.get_type(dest).of_type)
else:
raise TypeMismatchError(instr, (src, dest))
else:
raise TypeMismatchError(instr, (src, dest))
# 2. check that the context is meaningful
if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
context.assert_meaningful(src, dest.ref, REG_Y)
target = context.get_assoc(dest.ref)
if not target:
raise ForbiddenWriteError(instr, dest.ref)
context.set_written(target)
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
context.assert_meaningful(src.ref, REG_Y)
origin = context.get_assoc(src.ref)
if not origin:
raise UnmeaningfulReadError(instr, src.ref)
context.assert_meaningful(origin)
context.set_written(dest)
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
context.assert_meaningful(src.ref, dest.ref, REG_Y)
origin = context.get_assoc(src.ref)
if not origin:
raise UnmeaningfulReadError(instr, src.ref)
context.assert_meaningful(origin)
target = context.get_assoc(dest.ref)
if not target:
raise ForbiddenWriteError(instr, dest.ref)
context.set_written(target)
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
context.assert_meaningful(src, dest.ref, dest.index)
context.set_written(dest.ref)
elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef):
context.assert_meaningful(src, dest.ref, dest.index)
context.set_written(dest.ref)
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
context.assert_meaningful(src.ref, src.index)
context.set_written(dest)
else:
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)
elif opcode == 'trash':
context.set_touched(instr.dest)
context.set_unmeaningful(instr.dest)
elif opcode == 'nop':
pass
else:
raise NotImplementedError(opcode)
def analyze_call(self, instr, context):
type_ = self.get_type(instr.location)
if not isinstance(type_, (RoutineType, VectorType)):
raise TypeMismatchError(instr, instr.location.name)
self.current_routine.called_routines.add((instr.location, type_))
if isinstance(type_, VectorType):
type_ = type_.of_type
for ref in type_.inputs:
context.assert_meaningful(ref)
for ref in type_.outputs:
context.set_written(ref)
for ref in type_.trashes:
context.assert_writeable(ref)
context.set_touched(ref)
context.set_unmeaningful(ref)
def analyze_goto(self, instr, context):
location = instr.location
type_ = self.get_type(instr.location)
if not isinstance(type_, (RoutineType, VectorType)):
raise TypeMismatchError(instr, location.name)
self.current_routine.called_routines.add((instr.location, type_))
# assert that the dest routine's inputs are all initialized
if isinstance(type_, VectorType):
type_ = type_.of_type
for ref in type_.inputs:
context.assert_meaningful(ref)
# and that this routine's trashes and output constraints are a
# superset of the called routine's
current_type = self.get_type_for_name(self.current_routine.name)
self.assert_affected_within('outputs', type_, current_type)
self.assert_affected_within('trashes', type_, current_type)
context.encounter_gotos(set([instr.location]))
# Now that we have encountered a goto, we update the
# context here to match what someone calling the goto'ed
# function directly, would expect. (which makes sense
# when you think about it; if this goto's F, then calling
# this is like calling F, from the perspective of what is
# returned.)
#
# However, this isn't the current context anymore. This
# is an exit context of this routine.
exit_context = context.clone()
for ref in type_.outputs:
exit_context.set_written(ref)
for ref in type_.trashes:
exit_context.assert_writeable(ref)
exit_context.set_touched(ref)
exit_context.set_unmeaningful(ref)
self.exit_contexts.append(exit_context)
# When we get to the end, we'll check that all the
# exit contexts are consistent with each other.
# We set the current context as having terminated.
# If we are in a branch, the merge will deal with
# having terminated. If we are at the end of the
# routine, the routine end will deal with that.
context.set_terminated()
def analyze_if(self, instr, context):
incoming_meaningful = set(context.each_meaningful())
context1 = context.clone()
context2 = context.clone()
self.analyze_block(instr.block1, context1)
if instr.block2 is not None:
self.analyze_block(instr.block2, context2)
outgoing_meaningful = set(context1.each_meaningful()) & set(context2.each_meaningful())
outgoing_trashes = incoming_meaningful - outgoing_meaningful
# merge the contexts.
# first, the easy case: if one of the contexts has terminated, just use the other one.
# if both have terminated, we return a terminated context, and that's OK.
if context1.has_terminated():
context.update_from(context2)
elif context2.has_terminated():
context.update_from(context1)
else:
# the more complicated case: merge the contents of the contexts.
context._touched = set(context1._touched) | set(context2._touched)
context.set_meaningful(*list(outgoing_meaningful))
context._writeable = set(context1._writeable) | set(context2._writeable)
# in both cases, we need to merge the encountered gotos, in order that
# fallthru optimization continues to work correctly.
context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos())
for ref in outgoing_trashes:
context.set_touched(ref)
context.set_unmeaningful(ref)
def analyze_repeat(self, instr, context):
# it will always be executed at least once, so analyze it having
# been executed the first time.
self.analyze_block(instr.block, context)
if instr.src is not None: # None indicates 'repeat forever'
context.assert_meaningful(instr.src)
if context.encountered_gotos():
raise IllegalJumpError(instr, instr)
# now analyze it having been executed a second time, with the context
# of it having already been executed.
self.analyze_block(instr.block, context)
if instr.src is not None:
context.assert_meaningful(instr.src)
def analyze_for(self, instr, context):
context.assert_meaningful(instr.dest)
context.assert_writeable(instr.dest)
bottom, top = context.get_range(instr.dest)
final = instr.final.value
if instr.direction > 0:
if top >= final:
raise RangeExceededError(instr, "Top of range of {} is {} but must be lower than {}".format(
instr.dest, top, final
))
top = final
if instr.direction < 0:
if bottom <= final:
raise RangeExceededError(instr, "Bottom of range of {} is {} but must be higher than {}".format(
instr.dest, bottom, final
))
bottom = final
# inside the block, the loop variable cannot be modified, and we know its range.
context.set_range(instr.dest, bottom, top)
context.set_unwriteable(instr.dest)
# it will always be executed at least once, so analyze it having
# been executed the first time.
self.analyze_block(instr.block, context)
# now analyze it having been executed a second time, with the context
# of it having already been executed.
self.analyze_block(instr.block, context)
# after it is executed, we know the range of the loop variable.
context.set_range(instr.dest, instr.final, instr.final)
context.set_writeable(instr.dest)
def analyze_with_interrupts_off(self, instr, context):
block = instr.block
for instr in block.instrs:
if isinstance(instr, (Call, GoTo, WithInterruptsOff)):
raise IllegalJumpError(instr, instr)
self.analyze_instr(instr, context)
def analyze_save(self, instr, context):
batons = []
for location in instr.locations:
self.assert_type(TYPE_BYTE, location)
baton = context.extract(location)
batons.append(baton)
self.analyze_block(instr.block, context)
if context.encountered_gotos():
raise IllegalJumpError(instr, instr)
for location in reversed(instr.locations):
baton = batons.pop()
context.re_introduce(baton)
# We do this check outside the loop, because A is only preserved
# if it is the outermost thing being `save`d.
if location == REG_A:
pass
else:
context.set_touched(REG_A)
context.set_unmeaningful(REG_A)
def analyze_point_into(self, instr, context):
if not isinstance(self.get_type(instr.pointer), PointerType):
raise TypeMismatchError(instr, instr.pointer)
if not TableType.is_a_table_type(self.get_type(instr.table), TYPE_BYTE):
raise TypeMismatchError(instr, instr.table)
# check that pointer is not yet associated with any table.
if context.get_assoc(instr.pointer):
raise ForbiddenWriteError(instr, instr.pointer)
# associate pointer with table
# (do not mark it as meaningful yet - that's reset's job.)
context.set_assoc(instr.pointer, instr.table)
context.set_unmeaningful(instr.pointer)
self.analyze_block(instr.block, context)
if context.encountered_gotos():
raise IllegalJumpError(instr, instr)
# unassociate pointer with table, mark as unmeaningful.
context.set_assoc(instr.pointer, None)
context.set_unmeaningful(instr.pointer)
def analyze_reset(self, instr, context):
type = self.get_type(instr.pointer)
if not isinstance(type, (PointerType)):
raise TypeMismatchError(instr, instr.pointer.name)
table = context.get_assoc(instr.pointer)
if not table:
raise ForbiddenWriteError(instr, '{} is not associated with any table'.format(instr.pointer.name))
context.assert_meaningful(table)
low_limit, high_limit = context.get_range(table)
assert isinstance(instr.offset, ConstantRef)
if instr.offset.value < low_limit or instr.offset.value > high_limit:
raise RangeExceededError(instr, instr.pointer.name)
context.set_meaningful(instr.pointer)
context.set_touched(instr.pointer)