mirror of
https://github.com/catseye/SixtyPical.git
synced 2024-11-25 23:49:17 +00:00
Collect exit contexts. Disturbs fallthru analysis, but otherwise?
This commit is contained in:
parent
0ca545c89a
commit
21e623f3ad
@ -101,6 +101,7 @@ class Context(object):
|
||||
self._touched = set()
|
||||
self._range = dict()
|
||||
self._writeable = set()
|
||||
self._terminated = False
|
||||
self._gotos_encountered = set()
|
||||
|
||||
for ref in inputs:
|
||||
@ -132,6 +133,13 @@ class Context(object):
|
||||
c._writeable = set(self._writeable)
|
||||
return c
|
||||
|
||||
def update_from(self, other):
|
||||
self.routines = other.routines
|
||||
self.routine = other.routine
|
||||
self._touched = set(other._touched)
|
||||
self._range = dict(other._range)
|
||||
self._writeable = set(other._writeable)
|
||||
|
||||
def each_meaningful(self):
|
||||
for ref in self._range.keys():
|
||||
yield ref
|
||||
@ -279,6 +287,13 @@ class Context(object):
|
||||
def encountered_gotos(self):
|
||||
return self._gotos_encountered
|
||||
|
||||
def set_terminated(self):
|
||||
# Having a terminated context and having encountered gotos is not the same thing.
|
||||
self._terminated = True
|
||||
|
||||
def has_terminated(self):
|
||||
return self._terminated
|
||||
|
||||
def assert_types_for_read_table(self, instr, src, dest, type_):
|
||||
if (not TableType.is_a_table_type(src.ref.type, type_)) or (not dest.type == type_):
|
||||
raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name))
|
||||
@ -364,19 +379,20 @@ class Analyzer(object):
|
||||
|
||||
def analyze_routine(self, routine):
|
||||
assert isinstance(routine, Routine)
|
||||
self.current_routine = routine
|
||||
if routine.block is None:
|
||||
# it's an extern, that's fine
|
||||
return
|
||||
|
||||
self.current_routine = routine
|
||||
type_ = routine.location.type
|
||||
context = Context(self.routines, routine, type_.inputs, type_.outputs, type_.trashes)
|
||||
self.exit_contexts = []
|
||||
|
||||
if self.debug:
|
||||
print("at start of routine `{}`:".format(routine.name))
|
||||
print(context)
|
||||
|
||||
self.analyze_block(routine.block, context)
|
||||
trashed = set(context.each_touched()) - set(context.each_meaningful())
|
||||
|
||||
if self.debug:
|
||||
print("at end of routine `{}`:".format(routine.name))
|
||||
@ -390,6 +406,20 @@ class Analyzer(object):
|
||||
print('-' * 79)
|
||||
print('')
|
||||
|
||||
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())
|
||||
for ex in self.exit_contexts[1:]:
|
||||
if set(ex.each_meaningful()) != exit_meaningful:
|
||||
raise InconsistentInitializationError('?')
|
||||
if set(ex.each_touched()) != exit_touched:
|
||||
raise InconsistentInitializationError('?')
|
||||
context.update_from(exit_context)
|
||||
|
||||
trashed = set(context.each_touched()) - set(context.each_meaningful())
|
||||
|
||||
# these all apply whether we encountered goto(s) in this routine, or not...:
|
||||
|
||||
# can't trash an output.
|
||||
@ -406,6 +436,7 @@ class Analyzer(object):
|
||||
if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref):
|
||||
raise ForbiddenWriteError(routine, ref.name)
|
||||
|
||||
self.exit_contexts = None
|
||||
self.current_routine = None
|
||||
return context
|
||||
|
||||
@ -438,6 +469,9 @@ class Analyzer(object):
|
||||
dest = instr.dest
|
||||
src = instr.src
|
||||
|
||||
if context.has_terminated():
|
||||
raise IllegalJumpError(instr, instr) # TODO: maybe a better name for this
|
||||
|
||||
if opcode == 'ld':
|
||||
if isinstance(src, IndexedRef):
|
||||
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||
@ -677,26 +711,38 @@ class Analyzer(object):
|
||||
|
||||
context.encounter_gotos(set([instr.location]))
|
||||
|
||||
# now that we have encountered a goto here, we set the
|
||||
# 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.
|
||||
# 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:
|
||||
context.set_touched(ref) # ?
|
||||
context.set_written(ref)
|
||||
exit_context.set_touched(ref) # ?
|
||||
exit_context.set_written(ref)
|
||||
|
||||
for ref in type_.trashes:
|
||||
context.assert_writeable(ref)
|
||||
context.set_touched(ref)
|
||||
context.set_unmeaningful(ref)
|
||||
exit_context.assert_writeable(ref)
|
||||
exit_context.set_touched(ref)
|
||||
exit_context.set_unmeaningful(ref)
|
||||
|
||||
# TODO is that... all we have to do? You'll note the above
|
||||
# is a lot like call. We do rely on, if we are in a branch,
|
||||
# the branch-merge to take care of... a lot? The fact that
|
||||
# we don't actually continue on from here, I mean.
|
||||
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()
|
||||
|
||||
elif opcode == 'trash':
|
||||
context.set_touched(instr.dest)
|
||||
@ -736,11 +782,21 @@ class Analyzer(object):
|
||||
message='initialized in block 2 but not in block 1 of `if {}`'.format(instr.src)
|
||||
)
|
||||
|
||||
# merge the contexts. this used to be a method called `set_from`
|
||||
context._touched = set(context1._touched) | set(context2._touched)
|
||||
context.set_meaningful(*list(outgoing_meaningful))
|
||||
context._writeable = set(context1._writeable) | set(context2._writeable)
|
||||
context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos())
|
||||
# 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)
|
||||
context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos())
|
||||
|
||||
for ref in outgoing_trashes:
|
||||
context.set_touched(ref)
|
||||
@ -753,15 +809,15 @@ class Analyzer(object):
|
||||
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)
|
||||
|
||||
if context.encountered_gotos():
|
||||
raise IllegalJumpError(instr, instr)
|
||||
|
||||
def analyze_for(self, instr, context):
|
||||
context.assert_meaningful(instr.dest)
|
||||
context.assert_writeable(instr.dest)
|
||||
|
@ -3174,7 +3174,33 @@ Here, we declare that main outputs a, but we goto a routine that does not output
|
||||
| }
|
||||
| ld a, 1
|
||||
| }
|
||||
? UnmeaningfulReadError
|
||||
? UnmeaningfulOutputError: a
|
||||
|
||||
Here, we declare that main outputs a, and we goto a routine that outputs a so that's OK.
|
||||
|
||||
| define bar routine
|
||||
| inputs x
|
||||
| outputs a
|
||||
| trashes x, z, n
|
||||
| {
|
||||
| ld x, 200
|
||||
| ld a, 1
|
||||
| }
|
||||
|
|
||||
| define main routine
|
||||
| outputs a
|
||||
| trashes x, z, n
|
||||
| {
|
||||
| ld x, 0
|
||||
| if z {
|
||||
| ld x, 1
|
||||
| goto bar
|
||||
| } else {
|
||||
| ld x, 2
|
||||
| }
|
||||
| ld a, 1
|
||||
| }
|
||||
= ok
|
||||
|
||||
TODO: we should have a lot more test cases for the above, here.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user