From dd29b6fd4a9723be43b8a256a2c56c14e96e89e2 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Mon, 13 May 2019 12:32:18 +0100 Subject: [PATCH] Implement local locations that aren't statically initialized. --- HISTORY.md | 8 +++ TODO.md | 20 +++---- src/sixtypical/analyzer.py | 33 +++++++---- src/sixtypical/ast.py | 2 +- src/sixtypical/compiler.py | 31 +++++----- src/sixtypical/parser.py | 44 +++++++++------ tests/SixtyPical Analysis.md | 29 +++++++++- tests/SixtyPical Syntax.md | 106 +++++++++++++++++++++++++++++++++-- 8 files changed, 209 insertions(+), 64 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index ea5938b..4708f09 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,14 @@ History of SixtyPical ===================== +0.20 +---- + +* Fixed a bug where two local statics could be declared with + the same name. +* Local locations need no longer be static. If they are not + static, they are considered uninitialized until assigned. + 0.19 ---- diff --git a/TODO.md b/TODO.md index 34ed5e5..86abeb5 100644 --- a/TODO.md +++ b/TODO.md @@ -53,21 +53,17 @@ at different times. These can co-exist with general, non-specific-table-linked `pointer` variables. -### Local non-statics +### Space optimization of local non-statics -Somewhat related to the above, it should be possible to declare a local storage -location which is not static. - -In this case, it would be considered uninitialized each time the routine was -entered. - -So, you do not have a guarantee that it has a valid value. But you are guaranteed -that no other routine can read or modify it. - -It also enables a trick: if there are two routines A and B, and A never calls B -(even indirectly), and B never calls A (even indirectly), then their locals can +If there are two routines A and B, and A never calls B (even indirectly), and +B never calls A (even indirectly), then their non-static locals can be allocated at the same space. +This is more an impressive trick than a really useful feature, but still. +Impressive tricks are impressive. + +### Locals with explicit addresses + A local could also be given an explicit address. In this case, two locals in different routines could be given the same address, and as long as the condition in the above paragraph holds, that's okay. (If it doesn't, the analyzer should diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 197b68c..0313f73 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -183,9 +183,14 @@ class AnalysisContext(object): def assert_meaningful(self, *refs, **kwargs): exception_class = kwargs.get('exception_class', UnmeaningfulReadError) for ref in refs: - # statics are always meaningful - if self.symtab.has_static(self.routine.name, ref.name): - continue + if self.symtab.has_local(self.routine.name, ref.name): + if ref not in self._range: + message = ref.name + if kwargs.get('message'): + message += ' (%s)' % kwargs['message'] + raise exception_class(self.routine, message) + else: + continue if self.is_constant(ref): pass elif isinstance(ref, LocationRef): @@ -203,8 +208,8 @@ class AnalysisContext(object): def assert_writeable(self, *refs, **kwargs): exception_class = kwargs.get('exception_class', ForbiddenWriteError) for ref in refs: - # statics are always writeable - if self.symtab.has_static(self.routine.name, ref.name): + # locals are always writeable + if self.symtab.has_local(self.routine.name, ref.name): continue if ref not in self._writeable: message = ref.name @@ -384,8 +389,8 @@ class AnalysisContext(object): def max_range(self, ref): if isinstance(ref, ConstantRef): return (ref.value, ref.value) - elif self.symtab.has_static(self.routine.name, ref.name): - return self.symtab.fetch_static_type(self.routine.name, ref.name).max_range + elif self.symtab.has_local(self.routine.name, ref.name): + return self.symtab.fetch_local_type(self.routine.name, ref.name).max_range else: return self.symtab.fetch_global_type(ref.name).max_range @@ -401,8 +406,8 @@ class Analyzer(object): # - - - - helper methods - - - - def get_type_for_name(self, name): - if self.current_routine and self.symtab.has_static(self.current_routine.name, name): - return self.symtab.fetch_static_type(self.current_routine.name, 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): @@ -462,6 +467,14 @@ class Analyzer(object): self.current_routine = routine type_ = self.get_type_for_name(routine.name) 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) @@ -505,7 +518,7 @@ class Analyzer(object): # 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_static(routine.name, ref.name): + 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 diff --git a/src/sixtypical/ast.py b/src/sixtypical/ast.py index fc5f96f..cbbf19c 100644 --- a/src/sixtypical/ast.py +++ b/src/sixtypical/ast.py @@ -59,7 +59,7 @@ class Defn(AST): class Routine(AST): value_attrs = ('name', 'addr', 'initial',) - children_attrs = ('statics',) + children_attrs = ('locals',) child_attrs = ('block',) diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index e033cb1..9e3f382 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -34,7 +34,7 @@ class Compiler(object): self.symtab = symtab self.emitter = emitter self.routines = {} # routine.name -> Routine - self.routine_statics = {} # routine.name -> { static.name -> Label } + self.routine_locals = {} # routine.name -> { local.name -> Label } self.labels = {} # global.name -> Label ("global" includes routines) self.trampolines = {} # Location -> Label self.current_routine = None @@ -42,8 +42,8 @@ class Compiler(object): # - - - - helper methods - - - - def get_type_for_name(self, name): - if self.current_routine and self.symtab.has_static(self.current_routine.name, name): - return self.symtab.fetch_static_type(self.current_routine.name, 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): @@ -76,9 +76,9 @@ class Compiler(object): def get_label(self, name): if self.current_routine: - static_label = self.routine_statics.get(self.current_routine.name, {}).get(name) - if static_label: - return static_label + local_label = self.routine_locals.get(self.current_routine.name, {}).get(name) + if local_label: + return local_label return self.labels[name] def absolute_or_zero_page(self, label): @@ -107,16 +107,15 @@ class Compiler(object): label.set_addr(routine.addr) self.labels[routine.name] = label - if hasattr(routine, 'statics'): - self.current_routine = routine - static_labels = {} - for defn in routine.statics: - length = self.compute_length_of_defn(defn) - label = Label(defn.name, addr=defn.addr, length=length) - static_labels[defn.name] = label - declarations.append((defn, self.symtab.fetch_static_type(routine.name, defn.name), label)) - self.routine_statics[routine.name] = static_labels - self.current_routine = None + self.current_routine = routine + local_labels = {} + for defn in routine.locals: + length = self.compute_length_of_defn(defn) + label = Label(defn.name, addr=defn.addr, length=length) + local_labels[defn.name] = label + declarations.append((defn, self.symtab.fetch_local_type(routine.name, defn.name), label)) + self.routine_locals[routine.name] = local_labels + self.current_routine = None if compilation_roster is None: compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main'] diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 8a8cb29..23ec757 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -31,7 +31,7 @@ class ForwardReference(object): class SymbolTable(object): def __init__(self): self.symbols = {} # symbol name -> SymEntry - self.statics = {} # routine name -> (symbol name -> SymEntry) + self.locals = {} # routine name -> (symbol name -> SymEntry) self.typedefs = {} # type name -> Type AST self.consts = {} # const name -> ConstantRef @@ -41,25 +41,25 @@ class SymbolTable(object): self.symbols[name] = SymEntry(None, TYPE_BIT) def __str__(self): - return "Symbols: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts) + return "Symbols: {}\nLocals: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.locals, self.typedefs, self.consts) - def has_static(self, routine_name, name): - return name in self.statics.get(routine_name, {}) + def has_local(self, routine_name, name): + return name in self.locals.get(routine_name, {}) def fetch_global_type(self, name): return self.symbols[name].type_ - def fetch_static_type(self, routine_name, name): - return self.statics[routine_name][name].type_ + def fetch_local_type(self, routine_name, name): + return self.locals[routine_name][name].type_ def fetch_global_ref(self, name): if name in self.symbols: return LocationRef(name) return None - def fetch_static_ref(self, routine_name, name): - routine_statics = self.statics.get(routine_name, {}) - if name in routine_statics: + def fetch_local_ref(self, routine_name, name): + routine_locals = self.locals.get(routine_name, {}) + if name in routine_locals: return LocationRef(name) return None @@ -76,7 +76,7 @@ class Parser(object): def lookup(self, name, allow_forward=False, routine_name=None): model = self.symtab.fetch_global_ref(name) if model is None and routine_name: - model = self.symtab.fetch_static_ref(routine_name, name) + model = self.symtab.fetch_local_ref(routine_name, name) if model is None and allow_forward: return ForwardReference(name) if model is None: @@ -88,10 +88,12 @@ class Parser(object): self.syntax_error('Symbol "%s" already declared' % name) self.symtab.symbols[name] = SymEntry(ast_node, type_) - def declare_static(self, routine_name, name, ast_node, type_): + def declare_local(self, routine_name, name, ast_node, type_): + if self.symtab.fetch_local_ref(routine_name, name): + self.syntax_error('Symbol "%s" already declared locally' % name) if self.symtab.fetch_global_ref(name): - self.syntax_error('Symbol "%s" already declared' % name) - self.symtab.statics.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_) + self.syntax_error('Symbol "%s" already declared globally' % name) + self.symtab.locals.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_) # ---- symbol resolution @@ -306,17 +308,17 @@ class Parser(object): type_ = self.defn_type() if not isinstance(type_, RoutineType): self.syntax_error("Can only define a routine, not {}".format(repr(type_))) - statics = [] + locals_ = [] if self.scanner.consume('@'): self.scanner.check_type('integer literal') block = None addr = int(self.scanner.token) self.scanner.scan() else: - statics = self.statics() + locals_ = self.locals() block = self.block() addr = None - return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, statics=statics) + return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, locals=locals_) def labels(self): accum = [] @@ -370,13 +372,19 @@ class Parser(object): loc = IndexedRef(loc, offset, index) return loc - def statics(self): + def locals(self): defns = [] while self.scanner.consume('static'): type_, defn = self.defn() if defn.initial is None: self.syntax_error("Static definition {} must have initial value".format(defn)) - self.declare_static(self.current_routine_name, defn.name, defn, type_) + self.declare_local(self.current_routine_name, defn.name, defn, type_) + defns.append(defn) + while self.scanner.consume('local'): + type_, defn = self.defn() + if defn.initial is not None: + self.syntax_error("Local definition {} may not have initial value".format(defn)) + self.declare_local(self.current_routine_name, defn.name, defn, type_) defns.append(defn) return defns diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 040f48f..b06a3a8 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -4251,7 +4251,7 @@ The new style routine definitions support typedefs. | } = ok -### static ### +### locals ### When memory locations are defined static to a routine, they cannot be directly input, nor directly output; and since they are always initialized, @@ -4276,3 +4276,30 @@ they cannot be trashed. Thus, they really don't participate in the analysis. | call foo | } = ok + +When memory locations are defined local to a routine, but not static, +they cannot be directly input, nor directly output; but they are considered +uninitialized from the time the routine is called to until a value is stored +in them. + + | define main routine + | inputs x + | outputs x + | trashes z, n + | local byte t + | { + | st x, t + | inc t + | ld x, t + | } + = ok + + | define main routine + | outputs x + | trashes z, n + | local byte t + | { + | inc t + | ld x, t + | } + ? UnmeaningfulReadError: t diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index ea02c99..a40a99a 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -746,13 +746,14 @@ Only routines can be defined in the new style. | } ? SyntaxError -Memory locations can be defined static to a routine. +Memory locations can be defined local to a routine. | define foo routine | inputs x | outputs x | trashes z, n | static byte t : 0 + | local word w | { | st x, t | inc t @@ -762,13 +763,14 @@ Memory locations can be defined static to a routine. | define main routine | trashes a, x, z, n | static byte t : 0 + | local word w | { | ld x, t | call foo | } = ok -Static memory locations must always be given an initial value. +Local static memory locations must always be given an initial value. | define main routine | inputs x @@ -782,7 +784,21 @@ Static memory locations must always be given an initial value. | } ? SyntaxError -Name of a static cannot shadow an existing global or static. +Local dynamic memory locations may not be given an initial value. + + | define main routine + | inputs x + | outputs x + | trashes z, n + | local byte t : 10 + | { + | st x, t + | inc t + | ld x, t + | } + ? SyntaxError + +Name of a local cannot shadow an existing global or local. | byte t | @@ -790,7 +806,7 @@ Name of a static cannot shadow an existing global or static. | inputs x | outputs x | trashes z, n - | static byte t + | static byte t : 10 | { | st x, t | inc t @@ -802,11 +818,89 @@ Name of a static cannot shadow an existing global or static. | inputs x | outputs x | trashes z, n - | static byte t - | static byte t + | static byte t : 10 + | static byte t : 20 | { | st x, t | inc t | ld x, t | } ? SyntaxError + + | byte t + | + | define main routine + | inputs x + | outputs x + | trashes z, n + | local byte t + | { + | st x, t + | inc t + | ld x, t + | } + ? SyntaxError + + | define main routine + | inputs x + | outputs x + | trashes z, n + | local word w + | local word w + | { + | st x, t + | inc t + | ld x, t + | } + ? SyntaxError + +Since the names of locals are lexically local to a routine, they cannot +appear in the inputs, outputs, trashes list of the routine. + + | define main routine + | inputs t + | static byte t : 0 + | { + | inc t + | } + ? SyntaxError + + | define main routine + | outputs t + | static byte t : 0 + | { + | inc t + | } + ? SyntaxError + + | define main routine + | trashes t + | static byte t : 0 + | { + | inc t + | } + ? SyntaxError + + | define main routine + | inputs t + | local byte t + | { + | inc t + | } + ? SyntaxError + + | define main routine + | outputs t + | local byte t + | { + | inc t + | } + ? SyntaxError + + | define main routine + | trashes t + | local byte t + | { + | inc t + | } + ? SyntaxError