git @ Cat's Eye Technologies SixtyPical / 4615d8d
Distinct AST nodes for call and goto instructions. Chris Pressey 3 years ago
4 changed file(s) with 126 addition(s) and 101 deletion(s). Raw diff Collapse all Expand all
00 # encoding: UTF-8
11
22 from sixtypical.ast import (
3 Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save, PointInto
3 Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
44 )
55 from sixtypical.model import (
66 TYPE_BYTE, TYPE_WORD,
492492 def analyze_instr(self, instr, context):
493493 if isinstance(instr, SingleOp):
494494 self.analyze_single_op(instr, context)
495 elif isinstance(instr, Call):
496 self.analyze_call(instr, context)
497 elif isinstance(instr, GoTo):
498 self.analyze_goto(instr, context)
495499 elif isinstance(instr, If):
496500 self.analyze_if(instr, context)
497501 elif isinstance(instr, Repeat):
666670 self.assert_type(TYPE_BYTE, dest)
667671 context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
668672 context.invalidate_range(dest)
669 elif opcode == 'call':
670 type = instr.location.type
671 if not isinstance(type, (RoutineType, VectorType)):
672 raise TypeMismatchError(instr, instr.location)
673 if isinstance(type, VectorType):
674 type = type.of_type
675 for ref in type.inputs:
676 context.assert_meaningful(ref)
677 for ref in type.outputs:
678 context.set_written(ref)
679 for ref in type.trashes:
680 context.assert_writeable(ref)
681 context.set_touched(ref)
682 context.set_unmeaningful(ref)
683673 elif opcode == 'copy':
684674 if dest == REG_A:
685675 raise ForbiddenWriteError(instr, "{} cannot be used as destination for copy".format(dest))
788778
789779 context.set_touched(REG_A, FLAG_Z, FLAG_N)
790780 context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
791 elif opcode == 'goto':
792 location = instr.location
793 type_ = location.type
794
795 if not isinstance(type_, (RoutineType, VectorType)):
796 raise TypeMismatchError(instr, location)
797
798 # assert that the dest routine's inputs are all initialized
799 if isinstance(type_, VectorType):
800 type_ = type_.of_type
801 for ref in type_.inputs:
802 context.assert_meaningful(ref)
803
804 # and that this routine's trashes and output constraints are a
805 # superset of the called routine's
806 current_type = self.current_routine.location.type
807 self.assert_affected_within('outputs', type_, current_type)
808 self.assert_affected_within('trashes', type_, current_type)
809
810 context.encounter_gotos(set([instr.location]))
811
812 # Now that we have encountered a goto, we update the
813 # context here to match what someone calling the goto'ed
814 # function directly, would expect. (which makes sense
815 # when you think about it; if this goto's F, then calling
816 # this is like calling F, from the perspective of what is
817 # returned.)
818 #
819 # However, this isn't the current context anymore. This
820 # is an exit context of this routine.
821
822 exit_context = context.clone()
823
824 for ref in type_.outputs:
825 exit_context.set_touched(ref) # ?
826 exit_context.set_written(ref)
827
828 for ref in type_.trashes:
829 exit_context.assert_writeable(ref)
830 exit_context.set_touched(ref)
831 exit_context.set_unmeaningful(ref)
832
833 self.exit_contexts.append(exit_context)
834
835 # When we get to the end, we'll check that all the
836 # exit contexts are consistent with each other.
837
838 # We set the current context as having terminated.
839 # If we are in a branch, the merge will deal with
840 # having terminated. If we are at the end of the
841 # routine, the routine end will deal with that.
842
843 context.set_terminated()
844781
845782 elif opcode == 'trash':
846783 context.set_touched(instr.dest)
849786 pass
850787 else:
851788 raise NotImplementedError(opcode)
789
790 def analyze_call(self, instr, context):
791 type = instr.location.type
792 if not isinstance(type, (RoutineType, VectorType)):
793 raise TypeMismatchError(instr, instr.location)
794 if isinstance(type, VectorType):
795 type = type.of_type
796 for ref in type.inputs:
797 context.assert_meaningful(ref)
798 for ref in type.outputs:
799 context.set_written(ref)
800 for ref in type.trashes:
801 context.assert_writeable(ref)
802 context.set_touched(ref)
803 context.set_unmeaningful(ref)
804
805 def analyze_goto(self, instr, context):
806 location = instr.location
807 type_ = location.type
808
809 if not isinstance(type_, (RoutineType, VectorType)):
810 raise TypeMismatchError(instr, location)
811
812 # assert that the dest routine's inputs are all initialized
813 if isinstance(type_, VectorType):
814 type_ = type_.of_type
815 for ref in type_.inputs:
816 context.assert_meaningful(ref)
817
818 # and that this routine's trashes and output constraints are a
819 # superset of the called routine's
820 current_type = self.current_routine.location.type
821 self.assert_affected_within('outputs', type_, current_type)
822 self.assert_affected_within('trashes', type_, current_type)
823
824 context.encounter_gotos(set([instr.location]))
825
826 # Now that we have encountered a goto, we update the
827 # context here to match what someone calling the goto'ed
828 # function directly, would expect. (which makes sense
829 # when you think about it; if this goto's F, then calling
830 # this is like calling F, from the perspective of what is
831 # returned.)
832 #
833 # However, this isn't the current context anymore. This
834 # is an exit context of this routine.
835
836 exit_context = context.clone()
837
838 for ref in type_.outputs:
839 exit_context.set_touched(ref) # ?
840 exit_context.set_written(ref)
841
842 for ref in type_.trashes:
843 exit_context.assert_writeable(ref)
844 exit_context.set_touched(ref)
845 exit_context.set_unmeaningful(ref)
846
847 self.exit_contexts.append(exit_context)
848
849 # When we get to the end, we'll check that all the
850 # exit contexts are consistent with each other.
851
852 # We set the current context as having terminated.
853 # If we are in a branch, the merge will deal with
854 # having terminated. If we are at the end of the
855 # routine, the routine end will deal with that.
856
857 context.set_terminated()
852858
853859 def analyze_if(self, instr, context):
854860 incoming_meaningful = set(context.each_meaningful())
7171
7272
7373 class SingleOp(Instr):
74 value_attrs = ('opcode', 'dest', 'src', 'location',)
74 value_attrs = ('opcode', 'dest', 'src',)
75
76
77 class Call(Instr):
78 value_attrs = ('location',)
79
80
81 class GoTo(Instr):
82 value_attrs = ('location',)
7583
7684
7785 class If(Instr):
00 # encoding: UTF-8
11
22 from sixtypical.ast import (
3 Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save, PointInto
3 Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
44 )
55 from sixtypical.model import (
66 ConstantRef, LocationRef, IndexedRef, IndirectRef,
161161 def compile_instr(self, instr):
162162 if isinstance(instr, SingleOp):
163163 return self.compile_single_op(instr)
164 elif isinstance(instr, Call):
165 return self.compile_call(instr)
166 elif isinstance(instr, GoTo):
167 return self.compile_goto(instr)
164168 elif isinstance(instr, If):
165169 return self.compile_if(instr)
166170 elif isinstance(instr, Repeat):
392396 self.emitter.emit(cls(mode(Offset(self.get_label(dest.ref.name), dest.offset.value))))
393397 else:
394398 self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(dest.name))))
395 elif opcode == 'call':
396 location = instr.location
397 label = self.get_label(instr.location.name)
398 if isinstance(location.type, RoutineType):
399 self.emitter.emit(JSR(Absolute(label)))
400 elif isinstance(location.type, VectorType):
401 trampoline = self.trampolines.setdefault(
402 location, Label(location.name + '_trampoline')
403 )
404 self.emitter.emit(JSR(Absolute(trampoline)))
405 else:
406 raise NotImplementedError
407 elif opcode == 'goto':
408 self.final_goto_seen = True
409 if self.skip_final_goto:
410 pass
411 else:
412 location = instr.location
413 label = self.get_label(instr.location.name)
414 if isinstance(location.type, RoutineType):
415 self.emitter.emit(JMP(Absolute(label)))
416 elif isinstance(location.type, VectorType):
417 self.emitter.emit(JMP(Indirect(label)))
418 else:
419 raise NotImplementedError
420399 elif opcode == 'copy':
421400 self.compile_copy(instr, instr.src, instr.dest)
422401 elif opcode == 'trash':
425404 self.emitter.emit(NOP())
426405 else:
427406 raise NotImplementedError(opcode)
407
408 def compile_call(self, instr):
409 location = instr.location
410 label = self.get_label(instr.location.name)
411 if isinstance(location.type, RoutineType):
412 self.emitter.emit(JSR(Absolute(label)))
413 elif isinstance(location.type, VectorType):
414 trampoline = self.trampolines.setdefault(
415 location, Label(location.name + '_trampoline')
416 )
417 self.emitter.emit(JSR(Absolute(trampoline)))
418 else:
419 raise NotImplementedError
420
421 def compile_goto(self, instr):
422 self.final_goto_seen = True
423 if self.skip_final_goto:
424 pass
425 else:
426 location = instr.location
427 label = self.get_label(instr.location.name)
428 if isinstance(location.type, RoutineType):
429 self.emitter.emit(JMP(Absolute(label)))
430 elif isinstance(location.type, VectorType):
431 self.emitter.emit(JMP(Indirect(label)))
432 else:
433 raise NotImplementedError
428434
429435 def compile_cmp(self, instr, src, dest):
430436 """`instr` is only for reporting purposes"""
00 # encoding: UTF-8
11
22 from sixtypical.ast import (
3 Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save, PointInto
3 Program, Defn, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
44 )
55 from sixtypical.model import (
66 TYPE_BIT, TYPE_BYTE, TYPE_WORD,
112112 resolve_fwd_reference(node, 'location')
113113 resolve_fwd_reference(node, 'src')
114114 resolve_fwd_reference(node, 'dest')
115 if isinstance(node, (Call, GoTo)):
116 resolve_fwd_reference(node, 'location')
115117
116118 # --- grammar productions
117119
379381 self.scanner.expect('{')
380382 while not self.scanner.on('}'):
381383 instrs.append(self.instr())
382 if isinstance(instrs[-1], SingleOp) and instrs[-1].opcode == 'goto':
384 if isinstance(instrs[-1], GoTo):
383385 break
384386 self.scanner.expect('}')
385387 return Block(self.scanner.line_number, instrs=instrs)
449451 opcode = self.scanner.token
450452 self.scanner.scan()
451453 return SingleOp(self.scanner.line_number, opcode=opcode, dest=None, src=None)
452 elif self.scanner.token in ("call", "goto"):
453 opcode = self.scanner.token
454 self.scanner.scan()
454 elif self.scanner.consume("call"):
455455 name = self.scanner.token
456456 self.scanner.scan()
457 instr = SingleOp(self.scanner.line_number, opcode=opcode, location=ForwardReference(name), dest=None, src=None)
457 instr = Call(self.scanner.line_number, location=ForwardReference(name))
458 return instr
459 elif self.scanner.consume("goto"):
460 name = self.scanner.token
461 self.scanner.scan()
462 instr = GoTo(self.scanner.line_number, location=ForwardReference(name))
458463 return instr
459464 elif self.scanner.token in ("copy",):
460465 opcode = self.scanner.token