1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2025-02-19 20:30:45 +00:00

Implement local locations that aren't statically initialized.

This commit is contained in:
Chris Pressey 2019-05-13 12:32:18 +01:00
parent 81e28fa757
commit dd29b6fd4a
8 changed files with 209 additions and 64 deletions

View File

@ -1,6 +1,14 @@
History of SixtyPical 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 0.19
---- ----

20
TODO.md
View File

@ -53,21 +53,17 @@ at different times.
These can co-exist with general, non-specific-table-linked `pointer` variables. 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 If there are two routines A and B, and A never calls B (even indirectly), and
location which is not static. B never calls A (even indirectly), then their non-static locals can
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
be allocated at the same space. 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 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 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 in the above paragraph holds, that's okay. (If it doesn't, the analyzer should

View File

@ -183,9 +183,14 @@ class AnalysisContext(object):
def assert_meaningful(self, *refs, **kwargs): def assert_meaningful(self, *refs, **kwargs):
exception_class = kwargs.get('exception_class', UnmeaningfulReadError) exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
for ref in refs: for ref in refs:
# statics are always meaningful if self.symtab.has_local(self.routine.name, ref.name):
if self.symtab.has_static(self.routine.name, ref.name): if ref not in self._range:
continue message = ref.name
if kwargs.get('message'):
message += ' (%s)' % kwargs['message']
raise exception_class(self.routine, message)
else:
continue
if self.is_constant(ref): if self.is_constant(ref):
pass pass
elif isinstance(ref, LocationRef): elif isinstance(ref, LocationRef):
@ -203,8 +208,8 @@ class AnalysisContext(object):
def assert_writeable(self, *refs, **kwargs): def assert_writeable(self, *refs, **kwargs):
exception_class = kwargs.get('exception_class', ForbiddenWriteError) exception_class = kwargs.get('exception_class', ForbiddenWriteError)
for ref in refs: for ref in refs:
# statics are always writeable # locals are always writeable
if self.symtab.has_static(self.routine.name, ref.name): if self.symtab.has_local(self.routine.name, ref.name):
continue continue
if ref not in self._writeable: if ref not in self._writeable:
message = ref.name message = ref.name
@ -384,8 +389,8 @@ class AnalysisContext(object):
def max_range(self, ref): def max_range(self, ref):
if isinstance(ref, ConstantRef): if isinstance(ref, ConstantRef):
return (ref.value, ref.value) return (ref.value, ref.value)
elif self.symtab.has_static(self.routine.name, ref.name): elif self.symtab.has_local(self.routine.name, ref.name):
return self.symtab.fetch_static_type(self.routine.name, ref.name).max_range return self.symtab.fetch_local_type(self.routine.name, ref.name).max_range
else: else:
return self.symtab.fetch_global_type(ref.name).max_range return self.symtab.fetch_global_type(ref.name).max_range
@ -401,8 +406,8 @@ class Analyzer(object):
# - - - - helper methods - - - - # - - - - helper methods - - - -
def get_type_for_name(self, name): def get_type_for_name(self, name):
if self.current_routine and self.symtab.has_static(self.current_routine.name, name): if self.current_routine and self.symtab.has_local(self.current_routine.name, name):
return self.symtab.fetch_static_type(self.current_routine.name, name) return self.symtab.fetch_local_type(self.current_routine.name, name)
return self.symtab.fetch_global_type(name) return self.symtab.fetch_global_type(name)
def get_type(self, ref): def get_type(self, ref):
@ -462,6 +467,14 @@ class Analyzer(object):
self.current_routine = routine self.current_routine = routine
type_ = self.get_type_for_name(routine.name) type_ = self.get_type_for_name(routine.name)
context = AnalysisContext(self.symtab, routine, type_.inputs, type_.outputs, type_.trashes) 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.exit_contexts = []
self.analyze_block(routine.block, context) 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. # if something was touched, then it should have been declared to be writable.
for ref in context.each_touched(): 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) raise ForbiddenWriteError(routine, ref.name)
self.exit_contexts = None self.exit_contexts = None

View File

@ -59,7 +59,7 @@ class Defn(AST):
class Routine(AST): class Routine(AST):
value_attrs = ('name', 'addr', 'initial',) value_attrs = ('name', 'addr', 'initial',)
children_attrs = ('statics',) children_attrs = ('locals',)
child_attrs = ('block',) child_attrs = ('block',)

View File

@ -34,7 +34,7 @@ class Compiler(object):
self.symtab = symtab self.symtab = symtab
self.emitter = emitter self.emitter = emitter
self.routines = {} # routine.name -> Routine 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.labels = {} # global.name -> Label ("global" includes routines)
self.trampolines = {} # Location -> Label self.trampolines = {} # Location -> Label
self.current_routine = None self.current_routine = None
@ -42,8 +42,8 @@ class Compiler(object):
# - - - - helper methods - - - - # - - - - helper methods - - - -
def get_type_for_name(self, name): def get_type_for_name(self, name):
if self.current_routine and self.symtab.has_static(self.current_routine.name, name): if self.current_routine and self.symtab.has_local(self.current_routine.name, name):
return self.symtab.fetch_static_type(self.current_routine.name, name) return self.symtab.fetch_local_type(self.current_routine.name, name)
return self.symtab.fetch_global_type(name) return self.symtab.fetch_global_type(name)
def get_type(self, ref): def get_type(self, ref):
@ -76,9 +76,9 @@ class Compiler(object):
def get_label(self, name): def get_label(self, name):
if self.current_routine: if self.current_routine:
static_label = self.routine_statics.get(self.current_routine.name, {}).get(name) local_label = self.routine_locals.get(self.current_routine.name, {}).get(name)
if static_label: if local_label:
return static_label return local_label
return self.labels[name] return self.labels[name]
def absolute_or_zero_page(self, label): def absolute_or_zero_page(self, label):
@ -107,16 +107,15 @@ class Compiler(object):
label.set_addr(routine.addr) label.set_addr(routine.addr)
self.labels[routine.name] = label self.labels[routine.name] = label
if hasattr(routine, 'statics'): self.current_routine = routine
self.current_routine = routine local_labels = {}
static_labels = {} for defn in routine.locals:
for defn in routine.statics: length = self.compute_length_of_defn(defn)
length = self.compute_length_of_defn(defn) label = Label(defn.name, addr=defn.addr, length=length)
label = Label(defn.name, addr=defn.addr, length=length) local_labels[defn.name] = label
static_labels[defn.name] = label declarations.append((defn, self.symtab.fetch_local_type(routine.name, defn.name), label))
declarations.append((defn, self.symtab.fetch_static_type(routine.name, defn.name), label)) self.routine_locals[routine.name] = local_labels
self.routine_statics[routine.name] = static_labels self.current_routine = None
self.current_routine = None
if compilation_roster is None: if compilation_roster is None:
compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main'] compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']

View File

@ -31,7 +31,7 @@ class ForwardReference(object):
class SymbolTable(object): class SymbolTable(object):
def __init__(self): def __init__(self):
self.symbols = {} # symbol name -> SymEntry 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.typedefs = {} # type name -> Type AST
self.consts = {} # const name -> ConstantRef self.consts = {} # const name -> ConstantRef
@ -41,25 +41,25 @@ class SymbolTable(object):
self.symbols[name] = SymEntry(None, TYPE_BIT) self.symbols[name] = SymEntry(None, TYPE_BIT)
def __str__(self): 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): def has_local(self, routine_name, name):
return name in self.statics.get(routine_name, {}) return name in self.locals.get(routine_name, {})
def fetch_global_type(self, name): def fetch_global_type(self, name):
return self.symbols[name].type_ return self.symbols[name].type_
def fetch_static_type(self, routine_name, name): def fetch_local_type(self, routine_name, name):
return self.statics[routine_name][name].type_ return self.locals[routine_name][name].type_
def fetch_global_ref(self, name): def fetch_global_ref(self, name):
if name in self.symbols: if name in self.symbols:
return LocationRef(name) return LocationRef(name)
return None return None
def fetch_static_ref(self, routine_name, name): def fetch_local_ref(self, routine_name, name):
routine_statics = self.statics.get(routine_name, {}) routine_locals = self.locals.get(routine_name, {})
if name in routine_statics: if name in routine_locals:
return LocationRef(name) return LocationRef(name)
return None return None
@ -76,7 +76,7 @@ class Parser(object):
def lookup(self, name, allow_forward=False, routine_name=None): def lookup(self, name, allow_forward=False, routine_name=None):
model = self.symtab.fetch_global_ref(name) model = self.symtab.fetch_global_ref(name)
if model is None and routine_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: if model is None and allow_forward:
return ForwardReference(name) return ForwardReference(name)
if model is None: if model is None:
@ -88,10 +88,12 @@ class Parser(object):
self.syntax_error('Symbol "%s" already declared' % name) self.syntax_error('Symbol "%s" already declared' % name)
self.symtab.symbols[name] = SymEntry(ast_node, type_) 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): if self.symtab.fetch_global_ref(name):
self.syntax_error('Symbol "%s" already declared' % name) self.syntax_error('Symbol "%s" already declared globally' % name)
self.symtab.statics.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_) self.symtab.locals.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_)
# ---- symbol resolution # ---- symbol resolution
@ -306,17 +308,17 @@ class Parser(object):
type_ = self.defn_type() type_ = self.defn_type()
if not isinstance(type_, RoutineType): if not isinstance(type_, RoutineType):
self.syntax_error("Can only define a routine, not {}".format(repr(type_))) self.syntax_error("Can only define a routine, not {}".format(repr(type_)))
statics = [] locals_ = []
if self.scanner.consume('@'): if self.scanner.consume('@'):
self.scanner.check_type('integer literal') self.scanner.check_type('integer literal')
block = None block = None
addr = int(self.scanner.token) addr = int(self.scanner.token)
self.scanner.scan() self.scanner.scan()
else: else:
statics = self.statics() locals_ = self.locals()
block = self.block() block = self.block()
addr = None 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): def labels(self):
accum = [] accum = []
@ -370,13 +372,19 @@ class Parser(object):
loc = IndexedRef(loc, offset, index) loc = IndexedRef(loc, offset, index)
return loc return loc
def statics(self): def locals(self):
defns = [] defns = []
while self.scanner.consume('static'): while self.scanner.consume('static'):
type_, defn = self.defn() type_, defn = self.defn()
if defn.initial is None: if defn.initial is None:
self.syntax_error("Static definition {} must have initial value".format(defn)) 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) defns.append(defn)
return defns return defns

View File

@ -4251,7 +4251,7 @@ The new style routine definitions support typedefs.
| } | }
= ok = ok
### static ### ### locals ###
When memory locations are defined static to a routine, they cannot be When memory locations are defined static to a routine, they cannot be
directly input, nor directly output; and since they are always initialized, 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 | call foo
| } | }
= ok = 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

View File

@ -746,13 +746,14 @@ Only routines can be defined in the new style.
| } | }
? SyntaxError ? SyntaxError
Memory locations can be defined static to a routine. Memory locations can be defined local to a routine.
| define foo routine | define foo routine
| inputs x | inputs x
| outputs x | outputs x
| trashes z, n | trashes z, n
| static byte t : 0 | static byte t : 0
| local word w
| { | {
| st x, t | st x, t
| inc t | inc t
@ -762,13 +763,14 @@ Memory locations can be defined static to a routine.
| define main routine | define main routine
| trashes a, x, z, n | trashes a, x, z, n
| static byte t : 0 | static byte t : 0
| local word w
| { | {
| ld x, t | ld x, t
| call foo | call foo
| } | }
= ok = 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 | define main routine
| inputs x | inputs x
@ -782,7 +784,21 @@ Static memory locations must always be given an initial value.
| } | }
? SyntaxError ? 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 | byte t
| |
@ -790,7 +806,7 @@ Name of a static cannot shadow an existing global or static.
| inputs x | inputs x
| outputs x | outputs x
| trashes z, n | trashes z, n
| static byte t | static byte t : 10
| { | {
| st x, t | st x, t
| inc t | inc t
@ -802,11 +818,89 @@ Name of a static cannot shadow an existing global or static.
| inputs x | inputs x
| outputs x | outputs x
| trashes z, n | trashes z, n
| static byte t | static byte t : 10
| static byte t | static byte t : 20
| { | {
| st x, t | st x, t
| inc t | inc t
| ld x, t | ld x, t
| } | }
? SyntaxError ? 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