Implement local locations that aren't statically initialized.
Chris Pressey
3 years ago
0 | 0 | History of SixtyPical |
1 | 1 | ===================== |
2 | ||
3 | 0.20 | |
4 | ---- | |
5 | ||
6 | * Fixed a bug where two local statics could be declared with | |
7 | the same name. | |
8 | * Local locations need no longer be static. If they are not | |
9 | static, they are considered uninitialized until assigned. | |
2 | 10 | |
3 | 11 | 0.19 |
4 | 12 | ---- |
52 | 52 | |
53 | 53 | These can co-exist with general, non-specific-table-linked `pointer` variables. |
54 | 54 | |
55 | ### Local non-statics | |
55 | ### Space optimization of local non-statics | |
56 | 56 | |
57 | Somewhat related to the above, it should be possible to declare a local storage | |
58 | location which is not static. | |
57 | If there are two routines A and B, and A never calls B (even indirectly), and | |
58 | B never calls A (even indirectly), then their non-static locals can | |
59 | be allocated at the same space. | |
59 | 60 | |
60 | In this case, it would be considered uninitialized each time the routine was | |
61 | entered. | |
61 | This is more an impressive trick than a really useful feature, but still. | |
62 | Impressive tricks are impressive. | |
62 | 63 | |
63 | So, you do not have a guarantee that it has a valid value. But you are guaranteed | |
64 | that no other routine can read or modify it. | |
65 | ||
66 | It also enables a trick: if there are two routines A and B, and A never calls B | |
67 | (even indirectly), and B never calls A (even indirectly), then their locals can | |
68 | be allocated at the same space. | |
64 | ### Locals with explicit addresses | |
69 | 65 | |
70 | 66 | A local could also be given an explicit address. In this case, two locals in |
71 | 67 | different routines could be given the same address, and as long as the condition |
182 | 182 | def assert_meaningful(self, *refs, **kwargs): |
183 | 183 | exception_class = kwargs.get('exception_class', UnmeaningfulReadError) |
184 | 184 | for ref in refs: |
185 | # statics are always meaningful | |
186 | if self.symtab.has_static(self.routine.name, ref.name): | |
187 | continue | |
185 | if self.symtab.has_local(self.routine.name, ref.name): | |
186 | if ref not in self._range: | |
187 | message = ref.name | |
188 | if kwargs.get('message'): | |
189 | message += ' (%s)' % kwargs['message'] | |
190 | raise exception_class(self.routine, message) | |
191 | else: | |
192 | continue | |
188 | 193 | if self.is_constant(ref): |
189 | 194 | pass |
190 | 195 | elif isinstance(ref, LocationRef): |
202 | 207 | def assert_writeable(self, *refs, **kwargs): |
203 | 208 | exception_class = kwargs.get('exception_class', ForbiddenWriteError) |
204 | 209 | for ref in refs: |
205 | # statics are always writeable | |
206 | if self.symtab.has_static(self.routine.name, ref.name): | |
210 | # locals are always writeable | |
211 | if self.symtab.has_local(self.routine.name, ref.name): | |
207 | 212 | continue |
208 | 213 | if ref not in self._writeable: |
209 | 214 | message = ref.name |
383 | 388 | def max_range(self, ref): |
384 | 389 | if isinstance(ref, ConstantRef): |
385 | 390 | return (ref.value, ref.value) |
386 | elif self.symtab.has_static(self.routine.name, ref.name): | |
387 | return self.symtab.fetch_static_type(self.routine.name, ref.name).max_range | |
391 | elif self.symtab.has_local(self.routine.name, ref.name): | |
392 | return self.symtab.fetch_local_type(self.routine.name, ref.name).max_range | |
388 | 393 | else: |
389 | 394 | return self.symtab.fetch_global_type(ref.name).max_range |
390 | 395 | |
400 | 405 | # - - - - helper methods - - - - |
401 | 406 | |
402 | 407 | def get_type_for_name(self, name): |
403 | if self.current_routine and self.symtab.has_static(self.current_routine.name, name): | |
404 | return self.symtab.fetch_static_type(self.current_routine.name, name) | |
408 | if self.current_routine and self.symtab.has_local(self.current_routine.name, name): | |
409 | return self.symtab.fetch_local_type(self.current_routine.name, name) | |
405 | 410 | return self.symtab.fetch_global_type(name) |
406 | 411 | |
407 | 412 | def get_type(self, ref): |
461 | 466 | self.current_routine = routine |
462 | 467 | type_ = self.get_type_for_name(routine.name) |
463 | 468 | context = AnalysisContext(self.symtab, routine, type_.inputs, type_.outputs, type_.trashes) |
469 | ||
470 | # register any local statics as already-initialized | |
471 | for local_name, local_symentry in self.symtab.locals.get(routine.name, {}).items(): | |
472 | ref = self.symtab.fetch_local_ref(routine.name, local_name) | |
473 | if local_symentry.ast_node.initial is not None: | |
474 | context.set_meaningful(ref) | |
475 | context.set_range(ref, local_symentry.ast_node.initial, local_symentry.ast_node.initial) | |
476 | ||
464 | 477 | self.exit_contexts = [] |
465 | 478 | |
466 | 479 | self.analyze_block(routine.block, context) |
504 | 517 | |
505 | 518 | # if something was touched, then it should have been declared to be writable. |
506 | 519 | for ref in context.each_touched(): |
507 | if ref not in type_.outputs and ref not in type_.trashes and not self.symtab.has_static(routine.name, ref.name): | |
520 | if ref not in type_.outputs and ref not in type_.trashes and not self.symtab.has_local(routine.name, ref.name): | |
508 | 521 | raise ForbiddenWriteError(routine, ref.name) |
509 | 522 | |
510 | 523 | self.exit_contexts = None |
58 | 58 | |
59 | 59 | class Routine(AST): |
60 | 60 | value_attrs = ('name', 'addr', 'initial',) |
61 | children_attrs = ('statics',) | |
61 | children_attrs = ('locals',) | |
62 | 62 | child_attrs = ('block',) |
63 | 63 | |
64 | 64 |
33 | 33 | self.symtab = symtab |
34 | 34 | self.emitter = emitter |
35 | 35 | self.routines = {} # routine.name -> Routine |
36 | self.routine_statics = {} # routine.name -> { static.name -> Label } | |
36 | self.routine_locals = {} # routine.name -> { local.name -> Label } | |
37 | 37 | self.labels = {} # global.name -> Label ("global" includes routines) |
38 | 38 | self.trampolines = {} # Location -> Label |
39 | 39 | self.current_routine = None |
41 | 41 | # - - - - helper methods - - - - |
42 | 42 | |
43 | 43 | def get_type_for_name(self, name): |
44 | if self.current_routine and self.symtab.has_static(self.current_routine.name, name): | |
45 | return self.symtab.fetch_static_type(self.current_routine.name, name) | |
44 | if self.current_routine and self.symtab.has_local(self.current_routine.name, name): | |
45 | return self.symtab.fetch_local_type(self.current_routine.name, name) | |
46 | 46 | return self.symtab.fetch_global_type(name) |
47 | 47 | |
48 | 48 | def get_type(self, ref): |
75 | 75 | |
76 | 76 | def get_label(self, name): |
77 | 77 | if self.current_routine: |
78 | static_label = self.routine_statics.get(self.current_routine.name, {}).get(name) | |
79 | if static_label: | |
80 | return static_label | |
78 | local_label = self.routine_locals.get(self.current_routine.name, {}).get(name) | |
79 | if local_label: | |
80 | return local_label | |
81 | 81 | return self.labels[name] |
82 | 82 | |
83 | 83 | def absolute_or_zero_page(self, label): |
106 | 106 | label.set_addr(routine.addr) |
107 | 107 | self.labels[routine.name] = label |
108 | 108 | |
109 | if hasattr(routine, 'statics'): | |
110 | self.current_routine = routine | |
111 | static_labels = {} | |
112 | for defn in routine.statics: | |
113 | length = self.compute_length_of_defn(defn) | |
114 | label = Label(defn.name, addr=defn.addr, length=length) | |
115 | static_labels[defn.name] = label | |
116 | declarations.append((defn, self.symtab.fetch_static_type(routine.name, defn.name), label)) | |
117 | self.routine_statics[routine.name] = static_labels | |
118 | self.current_routine = None | |
109 | self.current_routine = routine | |
110 | local_labels = {} | |
111 | for defn in routine.locals: | |
112 | length = self.compute_length_of_defn(defn) | |
113 | label = Label(defn.name, addr=defn.addr, length=length) | |
114 | local_labels[defn.name] = label | |
115 | declarations.append((defn, self.symtab.fetch_local_type(routine.name, defn.name), label)) | |
116 | self.routine_locals[routine.name] = local_labels | |
117 | self.current_routine = None | |
119 | 118 | |
120 | 119 | if compilation_roster is None: |
121 | 120 | compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main'] |
30 | 30 | class SymbolTable(object): |
31 | 31 | def __init__(self): |
32 | 32 | self.symbols = {} # symbol name -> SymEntry |
33 | self.statics = {} # routine name -> (symbol name -> SymEntry) | |
33 | self.locals = {} # routine name -> (symbol name -> SymEntry) | |
34 | 34 | self.typedefs = {} # type name -> Type AST |
35 | 35 | self.consts = {} # const name -> ConstantRef |
36 | 36 | |
40 | 40 | self.symbols[name] = SymEntry(None, TYPE_BIT) |
41 | 41 | |
42 | 42 | def __str__(self): |
43 | return "Symbols: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts) | |
44 | ||
45 | def has_static(self, routine_name, name): | |
46 | return name in self.statics.get(routine_name, {}) | |
43 | return "Symbols: {}\nLocals: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.locals, self.typedefs, self.consts) | |
44 | ||
45 | def has_local(self, routine_name, name): | |
46 | return name in self.locals.get(routine_name, {}) | |
47 | 47 | |
48 | 48 | def fetch_global_type(self, name): |
49 | 49 | return self.symbols[name].type_ |
50 | 50 | |
51 | def fetch_static_type(self, routine_name, name): | |
52 | return self.statics[routine_name][name].type_ | |
51 | def fetch_local_type(self, routine_name, name): | |
52 | return self.locals[routine_name][name].type_ | |
53 | 53 | |
54 | 54 | def fetch_global_ref(self, name): |
55 | 55 | if name in self.symbols: |
56 | 56 | return LocationRef(name) |
57 | 57 | return None |
58 | 58 | |
59 | def fetch_static_ref(self, routine_name, name): | |
60 | routine_statics = self.statics.get(routine_name, {}) | |
61 | if name in routine_statics: | |
59 | def fetch_local_ref(self, routine_name, name): | |
60 | routine_locals = self.locals.get(routine_name, {}) | |
61 | if name in routine_locals: | |
62 | 62 | return LocationRef(name) |
63 | 63 | return None |
64 | 64 | |
75 | 75 | def lookup(self, name, allow_forward=False, routine_name=None): |
76 | 76 | model = self.symtab.fetch_global_ref(name) |
77 | 77 | if model is None and routine_name: |
78 | model = self.symtab.fetch_static_ref(routine_name, name) | |
78 | model = self.symtab.fetch_local_ref(routine_name, name) | |
79 | 79 | if model is None and allow_forward: |
80 | 80 | return ForwardReference(name) |
81 | 81 | if model is None: |
87 | 87 | self.syntax_error('Symbol "%s" already declared' % name) |
88 | 88 | self.symtab.symbols[name] = SymEntry(ast_node, type_) |
89 | 89 | |
90 | def declare_static(self, routine_name, name, ast_node, type_): | |
90 | def declare_local(self, routine_name, name, ast_node, type_): | |
91 | if self.symtab.fetch_local_ref(routine_name, name): | |
92 | self.syntax_error('Symbol "%s" already declared locally' % name) | |
91 | 93 | if self.symtab.fetch_global_ref(name): |
92 | self.syntax_error('Symbol "%s" already declared' % name) | |
93 | self.symtab.statics.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_) | |
94 | self.syntax_error('Symbol "%s" already declared globally' % name) | |
95 | self.symtab.locals.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_) | |
94 | 96 | |
95 | 97 | # ---- symbol resolution |
96 | 98 | |
305 | 307 | type_ = self.defn_type() |
306 | 308 | if not isinstance(type_, RoutineType): |
307 | 309 | self.syntax_error("Can only define a routine, not {}".format(repr(type_))) |
308 | statics = [] | |
310 | locals_ = [] | |
309 | 311 | if self.scanner.consume('@'): |
310 | 312 | self.scanner.check_type('integer literal') |
311 | 313 | block = None |
312 | 314 | addr = int(self.scanner.token) |
313 | 315 | self.scanner.scan() |
314 | 316 | else: |
315 | statics = self.statics() | |
317 | locals_ = self.locals() | |
316 | 318 | block = self.block() |
317 | 319 | addr = None |
318 | return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, statics=statics) | |
320 | return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, locals=locals_) | |
319 | 321 | |
320 | 322 | def labels(self): |
321 | 323 | accum = [] |
369 | 371 | loc = IndexedRef(loc, offset, index) |
370 | 372 | return loc |
371 | 373 | |
372 | def statics(self): | |
374 | def locals(self): | |
373 | 375 | defns = [] |
374 | 376 | while self.scanner.consume('static'): |
375 | 377 | type_, defn = self.defn() |
376 | 378 | if defn.initial is None: |
377 | 379 | self.syntax_error("Static definition {} must have initial value".format(defn)) |
378 | self.declare_static(self.current_routine_name, defn.name, defn, type_) | |
380 | self.declare_local(self.current_routine_name, defn.name, defn, type_) | |
381 | defns.append(defn) | |
382 | while self.scanner.consume('local'): | |
383 | type_, defn = self.defn() | |
384 | if defn.initial is not None: | |
385 | self.syntax_error("Local definition {} may not have initial value".format(defn)) | |
386 | self.declare_local(self.current_routine_name, defn.name, defn, type_) | |
379 | 387 | defns.append(defn) |
380 | 388 | return defns |
381 | 389 |
4250 | 4250 | | } |
4251 | 4251 | = ok |
4252 | 4252 | |
4253 | ### static ### | |
4253 | ### locals ### | |
4254 | 4254 | |
4255 | 4255 | When memory locations are defined static to a routine, they cannot be |
4256 | 4256 | directly input, nor directly output; and since they are always initialized, |
4275 | 4275 | | call foo |
4276 | 4276 | | } |
4277 | 4277 | = ok |
4278 | ||
4279 | When memory locations are defined local to a routine, but not static, | |
4280 | they cannot be directly input, nor directly output; but they are considered | |
4281 | uninitialized from the time the routine is called to until a value is stored | |
4282 | in them. | |
4283 | ||
4284 | | define main routine | |
4285 | | inputs x | |
4286 | | outputs x | |
4287 | | trashes z, n | |
4288 | | local byte t | |
4289 | | { | |
4290 | | st x, t | |
4291 | | inc t | |
4292 | | ld x, t | |
4293 | | } | |
4294 | = ok | |
4295 | ||
4296 | | define main routine | |
4297 | | outputs x | |
4298 | | trashes z, n | |
4299 | | local byte t | |
4300 | | { | |
4301 | | inc t | |
4302 | | ld x, t | |
4303 | | } | |
4304 | ? UnmeaningfulReadError: t |
745 | 745 | | } |
746 | 746 | ? SyntaxError |
747 | 747 | |
748 | Memory locations can be defined static to a routine. | |
748 | Memory locations can be defined local to a routine. | |
749 | 749 | |
750 | 750 | | define foo routine |
751 | 751 | | inputs x |
752 | 752 | | outputs x |
753 | 753 | | trashes z, n |
754 | 754 | | static byte t : 0 |
755 | | local word w | |
755 | 756 | | { |
756 | 757 | | st x, t |
757 | 758 | | inc t |
761 | 762 | | define main routine |
762 | 763 | | trashes a, x, z, n |
763 | 764 | | static byte t : 0 |
765 | | local word w | |
764 | 766 | | { |
765 | 767 | | ld x, t |
766 | 768 | | call foo |
767 | 769 | | } |
768 | 770 | = ok |
769 | 771 | |
770 | Static memory locations must always be given an initial value. | |
772 | Local static memory locations must always be given an initial value. | |
771 | 773 | |
772 | 774 | | define main routine |
773 | 775 | | inputs x |
781 | 783 | | } |
782 | 784 | ? SyntaxError |
783 | 785 | |
784 | Name of a static cannot shadow an existing global or static. | |
786 | Local dynamic memory locations may not be given an initial value. | |
787 | ||
788 | | define main routine | |
789 | | inputs x | |
790 | | outputs x | |
791 | | trashes z, n | |
792 | | local byte t : 10 | |
793 | | { | |
794 | | st x, t | |
795 | | inc t | |
796 | | ld x, t | |
797 | | } | |
798 | ? SyntaxError | |
799 | ||
800 | Name of a local cannot shadow an existing global or local. | |
785 | 801 | |
786 | 802 | | byte t |
787 | 803 | | |
789 | 805 | | inputs x |
790 | 806 | | outputs x |
791 | 807 | | trashes z, n |
792 | | static byte t | |
808 | | static byte t : 10 | |
793 | 809 | | { |
794 | 810 | | st x, t |
795 | 811 | | inc t |
801 | 817 | | inputs x |
802 | 818 | | outputs x |
803 | 819 | | trashes z, n |
804 | | static byte t | |
805 | | static byte t | |
820 | | static byte t : 10 | |
821 | | static byte t : 20 | |
806 | 822 | | { |
807 | 823 | | st x, t |
808 | 824 | | inc t |
809 | 825 | | ld x, t |
810 | 826 | | } |
811 | 827 | ? SyntaxError |
828 | ||
829 | | byte t | |
830 | | | |
831 | | define main routine | |
832 | | inputs x | |
833 | | outputs x | |
834 | | trashes z, n | |
835 | | local byte t | |
836 | | { | |
837 | | st x, t | |
838 | | inc t | |
839 | | ld x, t | |
840 | | } | |
841 | ? SyntaxError | |
842 | ||
843 | | define main routine | |
844 | | inputs x | |
845 | | outputs x | |
846 | | trashes z, n | |
847 | | local word w | |
848 | | local word w | |
849 | | { | |
850 | | st x, t | |
851 | | inc t | |
852 | | ld x, t | |
853 | | } | |
854 | ? SyntaxError | |
855 | ||
856 | Since the names of locals are lexically local to a routine, they cannot | |
857 | appear in the inputs, outputs, trashes list of the routine. | |
858 | ||
859 | | define main routine | |
860 | | inputs t | |
861 | | static byte t : 0 | |
862 | | { | |
863 | | inc t | |
864 | | } | |
865 | ? SyntaxError | |
866 | ||
867 | | define main routine | |
868 | | outputs t | |
869 | | static byte t : 0 | |
870 | | { | |
871 | | inc t | |
872 | | } | |
873 | ? SyntaxError | |
874 | ||
875 | | define main routine | |
876 | | trashes t | |
877 | | static byte t : 0 | |
878 | | { | |
879 | | inc t | |
880 | | } | |
881 | ? SyntaxError | |
882 | ||
883 | | define main routine | |
884 | | inputs t | |
885 | | local byte t | |
886 | | { | |
887 | | inc t | |
888 | | } | |
889 | ? SyntaxError | |
890 | ||
891 | | define main routine | |
892 | | outputs t | |
893 | | local byte t | |
894 | | { | |
895 | | inc t | |
896 | | } | |
897 | ? SyntaxError | |
898 | ||
899 | | define main routine | |
900 | | trashes t | |
901 | | local byte t | |
902 | | { | |
903 | | inc t | |
904 | | } | |
905 | ? SyntaxError |