git @ Cat's Eye Technologies SixtyPical / 21e623f
Collect exit contexts. Disturbs fallthru analysis, but otherwise? Chris Pressey 3 years ago
2 changed file(s) with 105 addition(s) and 23 deletion(s). Raw diff Collapse all Expand all
100100 self._touched = set()
101101 self._range = dict()
102102 self._writeable = set()
103 self._terminated = False
103104 self._gotos_encountered = set()
104105
105106 for ref in inputs:
130131 c._range = dict(self._range)
131132 c._writeable = set(self._writeable)
132133 return c
134
135 def update_from(self, other):
136 self.routines = other.routines
137 self.routine = other.routine
138 self._touched = set(other._touched)
139 self._range = dict(other._range)
140 self._writeable = set(other._writeable)
133141
134142 def each_meaningful(self):
135143 for ref in self._range.keys():
278286 def encountered_gotos(self):
279287 return self._gotos_encountered
280288
289 def set_terminated(self):
290 # Having a terminated context and having encountered gotos is not the same thing.
291 self._terminated = True
292
293 def has_terminated(self):
294 return self._terminated
295
281296 def assert_types_for_read_table(self, instr, src, dest, type_):
282297 if (not TableType.is_a_table_type(src.ref.type, type_)) or (not dest.type == type_):
283298 raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name))
363378
364379 def analyze_routine(self, routine):
365380 assert isinstance(routine, Routine)
366 self.current_routine = routine
367381 if routine.block is None:
368382 # it's an extern, that's fine
369383 return
384
385 self.current_routine = routine
370386 type_ = routine.location.type
371387 context = Context(self.routines, routine, type_.inputs, type_.outputs, type_.trashes)
388 self.exit_contexts = []
372389
373390 if self.debug:
374391 print("at start of routine `{}`:".format(routine.name))
375392 print(context)
376393
377394 self.analyze_block(routine.block, context)
378 trashed = set(context.each_touched()) - set(context.each_meaningful())
379395
380396 if self.debug:
381397 print("at end of routine `{}`:".format(routine.name))
389405 print('-' * 79)
390406 print('')
391407
408 if self.exit_contexts:
409 # check that they are all consistent
410 exit_context = self.exit_contexts[0]
411 exit_meaningful = set(exit_context.each_meaningful())
412 exit_touched = set(exit_context.each_touched())
413 for ex in self.exit_contexts[1:]:
414 if set(ex.each_meaningful()) != exit_meaningful:
415 raise InconsistentInitializationError('?')
416 if set(ex.each_touched()) != exit_touched:
417 raise InconsistentInitializationError('?')
418 context.update_from(exit_context)
419
420 trashed = set(context.each_touched()) - set(context.each_meaningful())
421
392422 # these all apply whether we encountered goto(s) in this routine, or not...:
393423
394424 # can't trash an output.
405435 if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref):
406436 raise ForbiddenWriteError(routine, ref.name)
407437
438 self.exit_contexts = None
408439 self.current_routine = None
409440 return context
410441
436467 opcode = instr.opcode
437468 dest = instr.dest
438469 src = instr.src
470
471 if context.has_terminated():
472 raise IllegalJumpError(instr, instr) # TODO: maybe a better name for this
439473
440474 if opcode == 'ld':
441475 if isinstance(src, IndexedRef):
676710
677711 context.encounter_gotos(set([instr.location]))
678712
679 # now that we have encountered a goto here, we set the
713 # Now that we have encountered a goto, we update the
680714 # context here to match what someone calling the goto'ed
681715 # function directly, would expect. (which makes sense
682716 # when you think about it; if this goto's F, then calling
683717 # this is like calling F, from the perspective of what is
684 # returned.
718 # returned.)
719 #
720 # However, this isn't the current context anymore. This
721 # is an exit context of this routine.
722
723 exit_context = context.clone()
685724
686725 for ref in type_.outputs:
687 context.set_touched(ref) # ?
688 context.set_written(ref)
726 exit_context.set_touched(ref) # ?
727 exit_context.set_written(ref)
689728
690729 for ref in type_.trashes:
691 context.assert_writeable(ref)
692 context.set_touched(ref)
693 context.set_unmeaningful(ref)
694
695 # TODO is that... all we have to do? You'll note the above
696 # is a lot like call. We do rely on, if we are in a branch,
697 # the branch-merge to take care of... a lot? The fact that
698 # we don't actually continue on from here, I mean.
730 exit_context.assert_writeable(ref)
731 exit_context.set_touched(ref)
732 exit_context.set_unmeaningful(ref)
733
734 self.exit_contexts.append(exit_context)
735
736 # When we get to the end, we'll check that all the
737 # exit contexts are consistent with each other.
738
739 # We set the current context as having terminated.
740 # If we are in a branch, the merge will deal with
741 # having terminated. If we are at the end of the
742 # routine, the routine end will deal with that.
743
744 context.set_terminated()
699745
700746 elif opcode == 'trash':
701747 context.set_touched(instr.dest)
735781 message='initialized in block 2 but not in block 1 of `if {}`'.format(instr.src)
736782 )
737783
738 # merge the contexts. this used to be a method called `set_from`
739 context._touched = set(context1._touched) | set(context2._touched)
740 context.set_meaningful(*list(outgoing_meaningful))
741 context._writeable = set(context1._writeable) | set(context2._writeable)
742 context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos())
784 # merge the contexts.
785
786 # first, the easy case: if one of the contexts has terminated, just use the other one.
787 # if both have terminated, we return a terminated context, and that's OK.
788
789 if context1.has_terminated():
790 context.update_from(context2)
791 elif context2.has_terminated():
792 context.update_from(context1)
793 else:
794 # the more complicated case: merge the contents of the contexts.
795 context._touched = set(context1._touched) | set(context2._touched)
796 context.set_meaningful(*list(outgoing_meaningful))
797 context._writeable = set(context1._writeable) | set(context2._writeable)
798 context.encounter_gotos(context1.encountered_gotos() | context2.encountered_gotos())
743799
744800 for ref in outgoing_trashes:
745801 context.set_touched(ref)
752808 if instr.src is not None: # None indicates 'repeat forever'
753809 context.assert_meaningful(instr.src)
754810
811 if context.encountered_gotos():
812 raise IllegalJumpError(instr, instr)
813
755814 # now analyze it having been executed a second time, with the context
756815 # of it having already been executed.
757816 self.analyze_block(instr.block, context)
758817 if instr.src is not None:
759818 context.assert_meaningful(instr.src)
760
761 if context.encountered_gotos():
762 raise IllegalJumpError(instr, instr)
763819
764820 def analyze_for(self, instr, context):
765821 context.assert_meaningful(instr.dest)
31733173 | }
31743174 | ld a, 1
31753175 | }
3176 ? UnmeaningfulReadError
3176 ? UnmeaningfulOutputError: a
3177
3178 Here, we declare that main outputs a, and we goto a routine that outputs a so that's OK.
3179
3180 | define bar routine
3181 | inputs x
3182 | outputs a
3183 | trashes x, z, n
3184 | {
3185 | ld x, 200
3186 | ld a, 1
3187 | }
3188 |
3189 | define main routine
3190 | outputs a
3191 | trashes x, z, n
3192 | {
3193 | ld x, 0
3194 | if z {
3195 | ld x, 1
3196 | goto bar
3197 | } else {
3198 | ld x, 2
3199 | }
3200 | ld a, 1
3201 | }
3202 = ok
31773203
31783204 TODO: we should have a lot more test cases for the above, here.
31793205